2 * VFS module to alter the algorithm to calculate
3 * the struct file_id used as key for the share mode
4 * and byte range locking db's.
6 * Copyright (C) 2007, Stefan Metzmacher
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, see <http://www.gnu.org/licenses/>.
23 #include "smbd/smbd.h"
24 #include "system/filesys.h"
26 static int vfs_fileid_debug_level = DBGC_VFS;
29 #define DBGC_CLASS vfs_fileid_debug_level
31 struct fileid_mount_entry {
33 const char *mnt_fsname;
38 struct fileid_nolock_inode {
43 struct fileid_handle_data {
44 struct vfs_handle_struct *handle;
45 struct file_id (*mapping_fn)(struct fileid_handle_data *data,
46 const SMB_STRUCT_STAT *sbuf);
47 char **fstype_deny_list;
48 char **fstype_allow_list;
49 char **mntdir_deny_list;
50 char **mntdir_allow_list;
51 unsigned num_mount_entries;
52 struct fileid_mount_entry *mount_entries;
56 struct fileid_nolock_inode *inodes;
60 /* check if a mount entry is allowed based on fstype and mount directory */
61 static bool fileid_mount_entry_allowed(struct fileid_handle_data *data,
65 char **fstype_deny = data->fstype_deny_list;
66 char **fstype_allow = data->fstype_allow_list;
67 char **mntdir_deny = data->mntdir_deny_list;
68 char **mntdir_allow = data->mntdir_allow_list;
70 if (fstype_deny != NULL) {
71 for (i = 0; fstype_deny[i] != NULL; i++) {
72 if (strcmp(m->mnt_type, fstype_deny[i]) == 0) {
77 if (fstype_allow != NULL) {
78 for (i = 0; fstype_allow[i] != NULL; i++) {
79 if (strcmp(m->mnt_type, fstype_allow[i]) == 0) {
83 if (fstype_allow[i] == NULL) {
87 if (mntdir_deny != NULL) {
88 for (i=0; mntdir_deny[i] != NULL; i++) {
89 if (strcmp(m->mnt_dir, mntdir_deny[i]) == 0) {
94 if (mntdir_allow != NULL) {
95 for (i=0; mntdir_allow[i] != NULL; i++) {
96 if (strcmp(m->mnt_dir, mntdir_allow[i]) == 0) {
100 if (mntdir_allow[i] == NULL) {
108 /* load all the mount entries from the mtab */
109 static void fileid_load_mount_entries(struct fileid_handle_data *data)
114 data->num_mount_entries = 0;
115 TALLOC_FREE(data->mount_entries);
117 f = setmntent("/etc/mtab", "r");
120 while ((m = getmntent(f))) {
123 struct fileid_mount_entry *cur;
126 allowed = fileid_mount_entry_allowed(data, m);
128 DBG_DEBUG("skipping mount entry %s\n", m->mnt_dir);
131 if (stat(m->mnt_dir, &st) != 0) continue;
132 if (statfs(m->mnt_dir, &sfs) != 0) continue;
134 if (strncmp(m->mnt_fsname, "/dev/", 5) == 0) {
138 data->mount_entries = talloc_realloc(data,
140 struct fileid_mount_entry,
141 data->num_mount_entries+1);
142 if (data->mount_entries == NULL) {
146 cur = &data->mount_entries[data->num_mount_entries];
147 cur->device = st.st_dev;
148 cur->mnt_fsname = talloc_strdup(data->mount_entries,
150 if (!cur->mnt_fsname) goto nomem;
151 cur->fsid = sfs.f_fsid;
152 cur->devid = (uint64_t)-1;
154 data->num_mount_entries++;
162 data->num_mount_entries = 0;
163 TALLOC_FREE(data->mount_entries);
168 /* find a mount entry given a dev_t */
169 static struct fileid_mount_entry *fileid_find_mount_entry(struct fileid_handle_data *data,
174 if (data->num_mount_entries == 0) {
175 fileid_load_mount_entries(data);
177 for (i=0;i<data->num_mount_entries;i++) {
178 if (data->mount_entries[i].device == dev) {
179 return &data->mount_entries[i];
182 /* 2nd pass after reloading */
183 fileid_load_mount_entries(data);
184 for (i=0;i<data->num_mount_entries;i++) {
185 if (data->mount_entries[i].device == dev) {
186 return &data->mount_entries[i];
193 /* a 64 bit hash, based on the one in tdb */
194 static uint64_t fileid_uint64_hash(const uint8_t *s, size_t len)
196 uint64_t value; /* Used to compute the hash value. */
197 uint32_t i; /* Used to cycle through random values. */
199 /* Set the initial value from the key size. */
200 for (value = 0x238F13AFLL * len, i=0; i < len; i++)
201 value = (value + (((uint64_t)s[i]) << (i*5 % 24)));
203 return (1103515243LL * value + 12345LL);
206 /* a device mapping using a fsname */
207 static uint64_t fileid_device_mapping_fsname(struct fileid_handle_data *data,
208 const SMB_STRUCT_STAT *sbuf)
210 struct fileid_mount_entry *m;
212 m = fileid_find_mount_entry(data, sbuf->st_ex_dev);
213 if (!m) return sbuf->st_ex_dev;
215 if (m->devid == (uint64_t)-1) {
216 m->devid = fileid_uint64_hash((const uint8_t *)m->mnt_fsname,
217 strlen(m->mnt_fsname));
223 static struct file_id fileid_mapping_fsname(struct fileid_handle_data *data,
224 const SMB_STRUCT_STAT *sbuf)
226 struct file_id id = { .inode = sbuf->st_ex_ino, };
228 id.devid = fileid_device_mapping_fsname(data, sbuf);
233 /* a device mapping using a hostname */
234 static uint64_t fileid_device_mapping_hostname(struct fileid_handle_data *data,
235 const SMB_STRUCT_STAT *sbuf)
237 char hostname[HOST_NAME_MAX+1];
238 char *devname = NULL;
243 rc = gethostname(hostname, HOST_NAME_MAX+1);
245 DBG_ERR("gethostname failed\n");
249 devname = talloc_asprintf(talloc_tos(), "%s%ju",
250 hostname, (uintmax_t)sbuf->st_ex_dev);
251 if (devname == NULL) {
252 DBG_ERR("talloc_asprintf failed\n");
255 devname_len = talloc_array_length(devname) - 1;
257 id = fileid_uint64_hash((uint8_t *)devname, devname_len);
259 TALLOC_FREE(devname);
264 static struct file_id fileid_mapping_hostname(struct fileid_handle_data *data,
265 const SMB_STRUCT_STAT *sbuf)
267 struct file_id id = { .inode = sbuf->st_ex_ino, };
269 id.devid = fileid_device_mapping_hostname(data, sbuf);
274 static bool fileid_is_nolock_inode(struct fileid_handle_data *data,
275 const SMB_STRUCT_STAT *sbuf)
280 * We could make this a binary search over an sorted array,
281 * but for now we keep things simple.
284 for (i=0; i < data->nolock.num_inodes; i++) {
285 if (data->nolock.inodes[i].ino != sbuf->st_ex_ino) {
289 if (data->nolock.inodes[i].dev == 0) {
291 * legacy "fileid:nolockinode"
292 * handling ignoring dev
297 if (data->nolock.inodes[i].dev != sbuf->st_ex_dev) {
307 static int fileid_add_nolock_inode(struct fileid_handle_data *data,
308 const SMB_STRUCT_STAT *sbuf)
310 bool exists = fileid_is_nolock_inode(data, sbuf);
311 struct fileid_nolock_inode *inodes = NULL;
317 inodes = talloc_realloc(data, data->nolock.inodes,
318 struct fileid_nolock_inode,
319 data->nolock.num_inodes + 1);
320 if (inodes == NULL) {
324 inodes[data->nolock.num_inodes] = (struct fileid_nolock_inode) {
325 .dev = sbuf->st_ex_dev,
326 .ino = sbuf->st_ex_ino,
328 data->nolock.inodes = inodes;
329 data->nolock.num_inodes += 1;
334 static uint64_t fileid_mapping_nolock_extid(uint64_t max_slots)
336 char buf[8+4+HOST_NAME_MAX+1] = { 0, };
342 slot = getpid() % max_slots;
345 PUSH_LE_U64(buf, 0, slot);
346 PUSH_LE_U32(buf, 8, get_my_vnn());
348 rc = gethostname(&buf[12], HOST_NAME_MAX+1);
350 DBG_ERR("gethostname failed\n");
354 id = fileid_uint64_hash((uint8_t *)buf, ARRAY_SIZE(buf));
359 /* a device mapping using a fsname for files and hostname for dirs */
360 static struct file_id fileid_mapping_fsname_nodirs(
361 struct fileid_handle_data *data,
362 const SMB_STRUCT_STAT *sbuf)
364 if (S_ISDIR(sbuf->st_ex_mode)) {
365 return fileid_mapping_hostname(data, sbuf);
368 return fileid_mapping_fsname(data, sbuf);
371 /* device mapping functions using a fsid */
372 static uint64_t fileid_device_mapping_fsid(struct fileid_handle_data *data,
373 const SMB_STRUCT_STAT *sbuf)
375 struct fileid_mount_entry *m;
377 m = fileid_find_mount_entry(data, sbuf->st_ex_dev);
378 if (!m) return sbuf->st_ex_dev;
380 if (m->devid == (uint64_t)-1) {
381 if (sizeof(fsid_t) > sizeof(uint64_t)) {
382 m->devid = fileid_uint64_hash((uint8_t *)&m->fsid,
398 static struct file_id fileid_mapping_fsid(struct fileid_handle_data *data,
399 const SMB_STRUCT_STAT *sbuf)
401 struct file_id id = { .inode = sbuf->st_ex_ino, };
403 id.devid = fileid_device_mapping_fsid(data, sbuf);
408 static struct file_id fileid_mapping_next_module(struct fileid_handle_data *data,
409 const SMB_STRUCT_STAT *sbuf)
411 return SMB_VFS_NEXT_FILE_ID_CREATE(data->handle, sbuf);
414 static int get_connectpath_ino(struct vfs_handle_struct *handle,
416 SMB_STRUCT_STAT *psbuf)
418 TALLOC_CTX *frame = talloc_stackframe();
419 struct smb_filename *fname = NULL;
420 const char *fullpath = NULL;
423 if (path[0] == '/') {
426 fullpath = talloc_asprintf(frame,
428 handle->conn->connectpath,
430 if (fullpath == NULL) {
431 DBG_ERR("talloc_asprintf() failed\n");
437 fname = synthetic_smb_fname(frame,
444 DBG_ERR("synthetic_smb_fname(%s) failed - %s\n",
445 fullpath, strerror(errno));
450 ret = SMB_VFS_NEXT_STAT(handle, fname);
452 DBG_ERR("stat failed for %s with %s\n",
453 fullpath, strerror(errno));
464 static int fileid_connect(struct vfs_handle_struct *handle,
465 const char *service, const char *user)
467 struct fileid_handle_data *data;
468 const char *algorithm;
469 const char **fstype_deny_list = NULL;
470 const char **fstype_allow_list = NULL;
471 const char **mntdir_deny_list = NULL;
472 const char **mntdir_allow_list = NULL;
474 uint64_t max_slots = 0;
475 bool rootdir_nolock = false;
477 int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
483 data = talloc_zero(handle, struct fileid_handle_data);
486 SMB_VFS_NEXT_DISCONNECT(handle);
487 DEBUG(0, ("talloc_zero() failed\n"));
491 data->handle = handle;
494 * "fileid:mapping" is only here as fallback for old setups
495 * "fileid:algorithm" is the option new setups should use
497 algorithm = lp_parm_const_string(SNUM(handle->conn),
500 algorithm = lp_parm_const_string(SNUM(handle->conn),
501 "fileid", "algorithm",
503 if (strcmp("fsname", algorithm) == 0) {
504 data->mapping_fn = fileid_mapping_fsname;
505 } else if (strcmp("fsname_nodirs", algorithm) == 0) {
506 data->mapping_fn = fileid_mapping_fsname_nodirs;
507 } else if (strcmp("fsid", algorithm) == 0) {
508 data->mapping_fn = fileid_mapping_fsid;
509 } else if (strcmp("hostname", algorithm) == 0) {
510 data->mapping_fn = fileid_mapping_hostname;
511 } else if (strcmp("fsname_norootdir", algorithm) == 0) {
512 data->mapping_fn = fileid_mapping_fsname;
513 rootdir_nolock = true;
514 } else if (strcmp("fsname_norootdir_ext", algorithm) == 0) {
515 data->mapping_fn = fileid_mapping_fsname;
516 rootdir_nolock = true;
517 max_slots = UINT64_MAX;
518 } else if (strcmp("next_module", algorithm) == 0) {
519 data->mapping_fn = fileid_mapping_next_module;
521 SMB_VFS_NEXT_DISCONNECT(handle);
522 DEBUG(0,("fileid_connect(): unknown algorithm[%s]\n", algorithm));
526 fstype_deny_list = lp_parm_string_list(SNUM(handle->conn), "fileid",
527 "fstype deny", NULL);
528 if (fstype_deny_list != NULL) {
529 data->fstype_deny_list = str_list_copy(data, fstype_deny_list);
530 if (data->fstype_deny_list == NULL) {
532 DBG_ERR("str_list_copy failed\n");
533 SMB_VFS_NEXT_DISCONNECT(handle);
539 fstype_allow_list = lp_parm_string_list(SNUM(handle->conn), "fileid",
540 "fstype allow", NULL);
541 if (fstype_allow_list != NULL) {
542 data->fstype_allow_list = str_list_copy(data, fstype_allow_list);
543 if (data->fstype_allow_list == NULL) {
545 DBG_ERR("str_list_copy failed\n");
546 SMB_VFS_NEXT_DISCONNECT(handle);
552 mntdir_deny_list = lp_parm_string_list(SNUM(handle->conn), "fileid",
553 "mntdir deny", NULL);
554 if (mntdir_deny_list != NULL) {
555 data->mntdir_deny_list = str_list_copy(data, mntdir_deny_list);
556 if (data->mntdir_deny_list == NULL) {
558 DBG_ERR("str_list_copy failed\n");
559 SMB_VFS_NEXT_DISCONNECT(handle);
565 mntdir_allow_list = lp_parm_string_list(SNUM(handle->conn), "fileid",
566 "mntdir allow", NULL);
567 if (mntdir_allow_list != NULL) {
568 data->mntdir_allow_list = str_list_copy(data, mntdir_allow_list);
569 if (data->mntdir_allow_list == NULL) {
571 DBG_ERR("str_list_copy failed\n");
572 SMB_VFS_NEXT_DISCONNECT(handle);
578 max_slots = MAX(max_slots, 1);
580 data->nolock.extid = fileid_mapping_nolock_extid(max_slots);
582 nolockinode = lp_parm_ulong(SNUM(handle->conn), "fileid", "nolockinode", 0);
583 if (nolockinode != 0) {
584 SMB_STRUCT_STAT tmpsbuf = { .st_ex_ino = nolockinode, };
586 ret = fileid_add_nolock_inode(data, &tmpsbuf);
589 SMB_VFS_NEXT_DISCONNECT(handle);
595 if (rootdir_nolock) {
596 SMB_STRUCT_STAT rootdirsbuf;
598 ret = get_connectpath_ino(handle, ".", &rootdirsbuf);
601 SMB_VFS_NEXT_DISCONNECT(handle);
606 ret = fileid_add_nolock_inode(data, &rootdirsbuf);
609 SMB_VFS_NEXT_DISCONNECT(handle);
615 SMB_VFS_HANDLE_SET_DATA(handle, data, NULL,
616 struct fileid_handle_data,
619 DBG_DEBUG("connect to service[%s] with algorithm[%s] nolock.inodes %zu\n",
620 service, algorithm, data->nolock.num_inodes);
625 static void fileid_disconnect(struct vfs_handle_struct *handle)
627 const struct loadparm_substitution *lp_sub =
628 loadparm_s3_global_substitution();
630 DEBUG(10,("fileid_disconnect() connect to service[%s].\n",
631 lp_servicename(talloc_tos(), lp_sub, SNUM(handle->conn))));
633 SMB_VFS_NEXT_DISCONNECT(handle);
636 static struct file_id fileid_file_id_create(struct vfs_handle_struct *handle,
637 const SMB_STRUCT_STAT *sbuf)
639 struct fileid_handle_data *data;
640 struct file_id id = { .inode = 0, };
642 SMB_VFS_HANDLE_GET_DATA(handle, data,
643 struct fileid_handle_data,
646 id = data->mapping_fn(data, sbuf);
647 if (id.extid == 0 && fileid_is_nolock_inode(data, sbuf)) {
648 id.extid = data->nolock.extid;
651 DBG_DEBUG("Returning dev [%jx] inode [%jx] extid [%jx]\n",
652 (uintmax_t)id.devid, (uintmax_t)id.inode, (uintmax_t)id.extid);
657 static struct vfs_fn_pointers vfs_fileid_fns = {
658 .connect_fn = fileid_connect,
659 .disconnect_fn = fileid_disconnect,
660 .file_id_create_fn = fileid_file_id_create
664 NTSTATUS vfs_fileid_init(TALLOC_CTX *ctx)
668 ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "fileid",
670 if (!NT_STATUS_IS_OK(ret)) {
674 vfs_fileid_debug_level = debug_add_class("fileid");
675 if (vfs_fileid_debug_level == -1) {
676 vfs_fileid_debug_level = DBGC_VFS;
677 DEBUG(0, ("vfs_fileid: Couldn't register custom debugging class!\n"));
679 DEBUG(10, ("vfs_fileid: Debug class number of 'fileid': %d\n", vfs_fileid_debug_level));