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;
54 bool force_all_inodes;
58 struct fileid_nolock_inode *inodes;
62 /* check if a mount entry is allowed based on fstype and mount directory */
63 static bool fileid_mount_entry_allowed(struct fileid_handle_data *data,
67 char **fstype_deny = data->fstype_deny_list;
68 char **fstype_allow = data->fstype_allow_list;
69 char **mntdir_deny = data->mntdir_deny_list;
70 char **mntdir_allow = data->mntdir_allow_list;
72 if (fstype_deny != NULL) {
73 for (i = 0; fstype_deny[i] != NULL; i++) {
74 if (strcmp(m->mnt_type, fstype_deny[i]) == 0) {
79 if (fstype_allow != NULL) {
80 for (i = 0; fstype_allow[i] != NULL; i++) {
81 if (strcmp(m->mnt_type, fstype_allow[i]) == 0) {
85 if (fstype_allow[i] == NULL) {
89 if (mntdir_deny != NULL) {
90 for (i=0; mntdir_deny[i] != NULL; i++) {
91 if (strcmp(m->mnt_dir, mntdir_deny[i]) == 0) {
96 if (mntdir_allow != NULL) {
97 for (i=0; mntdir_allow[i] != NULL; i++) {
98 if (strcmp(m->mnt_dir, mntdir_allow[i]) == 0) {
102 if (mntdir_allow[i] == NULL) {
110 /* load all the mount entries from the mtab */
111 static void fileid_load_mount_entries(struct fileid_handle_data *data)
116 data->num_mount_entries = 0;
117 TALLOC_FREE(data->mount_entries);
119 f = setmntent("/etc/mtab", "r");
122 while ((m = getmntent(f))) {
125 struct fileid_mount_entry *cur;
128 allowed = fileid_mount_entry_allowed(data, m);
130 DBG_DEBUG("skipping mount entry %s\n", m->mnt_dir);
133 if (stat(m->mnt_dir, &st) != 0) continue;
134 if (statfs(m->mnt_dir, &sfs) != 0) continue;
136 if (strncmp(m->mnt_fsname, "/dev/", 5) == 0) {
140 data->mount_entries = talloc_realloc(data,
142 struct fileid_mount_entry,
143 data->num_mount_entries+1);
144 if (data->mount_entries == NULL) {
148 cur = &data->mount_entries[data->num_mount_entries];
149 cur->device = st.st_dev;
150 cur->mnt_fsname = talloc_strdup(data->mount_entries,
152 if (!cur->mnt_fsname) goto nomem;
153 cur->fsid = sfs.f_fsid;
154 cur->devid = (uint64_t)-1;
156 data->num_mount_entries++;
164 data->num_mount_entries = 0;
165 TALLOC_FREE(data->mount_entries);
170 /* find a mount entry given a dev_t */
171 static struct fileid_mount_entry *fileid_find_mount_entry(struct fileid_handle_data *data,
176 if (data->num_mount_entries == 0) {
177 fileid_load_mount_entries(data);
179 for (i=0;i<data->num_mount_entries;i++) {
180 if (data->mount_entries[i].device == dev) {
181 return &data->mount_entries[i];
184 /* 2nd pass after reloading */
185 fileid_load_mount_entries(data);
186 for (i=0;i<data->num_mount_entries;i++) {
187 if (data->mount_entries[i].device == dev) {
188 return &data->mount_entries[i];
195 /* a 64 bit hash, based on the one in tdb */
196 static uint64_t fileid_uint64_hash(const uint8_t *s, size_t len)
198 uint64_t value; /* Used to compute the hash value. */
199 uint32_t i; /* Used to cycle through random values. */
201 /* Set the initial value from the key size. */
202 for (value = 0x238F13AFLL * len, i=0; i < len; i++)
203 value = (value + (((uint64_t)s[i]) << (i*5 % 24)));
205 return (1103515243LL * value + 12345LL);
208 /* a device mapping using a fsname */
209 static uint64_t fileid_device_mapping_fsname(struct fileid_handle_data *data,
210 const SMB_STRUCT_STAT *sbuf)
212 struct fileid_mount_entry *m;
214 m = fileid_find_mount_entry(data, sbuf->st_ex_dev);
215 if (!m) return sbuf->st_ex_dev;
217 if (m->devid == (uint64_t)-1) {
218 m->devid = fileid_uint64_hash((const uint8_t *)m->mnt_fsname,
219 strlen(m->mnt_fsname));
225 static struct file_id fileid_mapping_fsname(struct fileid_handle_data *data,
226 const SMB_STRUCT_STAT *sbuf)
228 struct file_id id = { .inode = sbuf->st_ex_ino, };
230 id.devid = fileid_device_mapping_fsname(data, sbuf);
235 /* a device mapping using a hostname */
236 static uint64_t fileid_device_mapping_hostname(struct fileid_handle_data *data,
237 const SMB_STRUCT_STAT *sbuf)
239 char hostname[HOST_NAME_MAX+1];
240 char *devname = NULL;
245 rc = gethostname(hostname, HOST_NAME_MAX+1);
247 DBG_ERR("gethostname failed\n");
251 devname = talloc_asprintf(talloc_tos(), "%s%ju",
252 hostname, (uintmax_t)sbuf->st_ex_dev);
253 if (devname == NULL) {
254 DBG_ERR("talloc_asprintf failed\n");
257 devname_len = talloc_array_length(devname) - 1;
259 id = fileid_uint64_hash((uint8_t *)devname, devname_len);
261 TALLOC_FREE(devname);
266 static struct file_id fileid_mapping_hostname(struct fileid_handle_data *data,
267 const SMB_STRUCT_STAT *sbuf)
269 struct file_id id = { .inode = sbuf->st_ex_ino, };
271 id.devid = fileid_device_mapping_hostname(data, sbuf);
276 static bool fileid_is_nolock_inode(struct fileid_handle_data *data,
277 const SMB_STRUCT_STAT *sbuf)
281 if (data->nolock.force_all_inodes) {
285 if (S_ISDIR(sbuf->st_ex_mode) && data->nolock.force_all_dirs) {
290 * We could make this a binary search over an sorted array,
291 * but for now we keep things simple.
294 for (i=0; i < data->nolock.num_inodes; i++) {
295 if (data->nolock.inodes[i].ino != sbuf->st_ex_ino) {
299 if (data->nolock.inodes[i].dev == 0) {
301 * legacy "fileid:nolockinode"
302 * handling ignoring dev
307 if (data->nolock.inodes[i].dev != sbuf->st_ex_dev) {
317 static int fileid_add_nolock_inode(struct fileid_handle_data *data,
318 const SMB_STRUCT_STAT *sbuf)
320 bool exists = fileid_is_nolock_inode(data, sbuf);
321 struct fileid_nolock_inode *inodes = NULL;
327 inodes = talloc_realloc(data, data->nolock.inodes,
328 struct fileid_nolock_inode,
329 data->nolock.num_inodes + 1);
330 if (inodes == NULL) {
334 inodes[data->nolock.num_inodes] = (struct fileid_nolock_inode) {
335 .dev = sbuf->st_ex_dev,
336 .ino = sbuf->st_ex_ino,
338 data->nolock.inodes = inodes;
339 data->nolock.num_inodes += 1;
344 static uint64_t fileid_mapping_nolock_extid(uint64_t max_slots)
346 char buf[8+4+HOST_NAME_MAX+1] = { 0, };
352 slot = getpid() % max_slots;
355 PUSH_LE_U64(buf, 0, slot);
356 PUSH_LE_U32(buf, 8, get_my_vnn());
358 rc = gethostname(&buf[12], HOST_NAME_MAX+1);
360 DBG_ERR("gethostname failed\n");
364 id = fileid_uint64_hash((uint8_t *)buf, ARRAY_SIZE(buf));
369 /* device mapping functions using a fsid */
370 static uint64_t fileid_device_mapping_fsid(struct fileid_handle_data *data,
371 const SMB_STRUCT_STAT *sbuf)
373 struct fileid_mount_entry *m;
375 m = fileid_find_mount_entry(data, sbuf->st_ex_dev);
376 if (!m) return sbuf->st_ex_dev;
378 if (m->devid == (uint64_t)-1) {
379 if (sizeof(fsid_t) > sizeof(uint64_t)) {
380 m->devid = fileid_uint64_hash((uint8_t *)&m->fsid,
396 static struct file_id fileid_mapping_fsid(struct fileid_handle_data *data,
397 const SMB_STRUCT_STAT *sbuf)
399 struct file_id id = { .inode = sbuf->st_ex_ino, };
401 id.devid = fileid_device_mapping_fsid(data, sbuf);
406 static struct file_id fileid_mapping_next_module(struct fileid_handle_data *data,
407 const SMB_STRUCT_STAT *sbuf)
409 return SMB_VFS_NEXT_FILE_ID_CREATE(data->handle, sbuf);
412 static int get_connectpath_ino(struct vfs_handle_struct *handle,
414 SMB_STRUCT_STAT *psbuf)
416 TALLOC_CTX *frame = talloc_stackframe();
417 struct smb_filename *fname = NULL;
418 const char *fullpath = NULL;
421 if (path[0] == '/') {
424 fullpath = talloc_asprintf(frame,
426 handle->conn->connectpath,
428 if (fullpath == NULL) {
429 DBG_ERR("talloc_asprintf() failed\n");
435 fname = synthetic_smb_fname(frame,
442 DBG_ERR("synthetic_smb_fname(%s) failed - %s\n",
443 fullpath, strerror(errno));
448 ret = SMB_VFS_NEXT_STAT(handle, fname);
450 DBG_ERR("stat failed for %s with %s\n",
451 fullpath, strerror(errno));
462 static int fileid_connect(struct vfs_handle_struct *handle,
463 const char *service, const char *user)
465 struct fileid_handle_data *data;
466 const char *algorithm;
467 const char **fstype_deny_list = NULL;
468 const char **fstype_allow_list = NULL;
469 const char **mntdir_deny_list = NULL;
470 const char **mntdir_allow_list = NULL;
472 uint64_t max_slots = 0;
473 bool rootdir_nolock = false;
474 const char **nolock_paths = NULL;
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;
507 data->nolock.force_all_dirs = true;
508 } else if (strcmp("fsid", algorithm) == 0) {
509 data->mapping_fn = fileid_mapping_fsid;
510 } else if (strcmp("hostname", algorithm) == 0) {
511 data->mapping_fn = fileid_mapping_hostname;
512 data->nolock.force_all_inodes = true;
513 } else if (strcmp("fsname_norootdir", algorithm) == 0) {
514 data->mapping_fn = fileid_mapping_fsname;
515 rootdir_nolock = true;
516 } else if (strcmp("fsname_norootdir_ext", algorithm) == 0) {
517 data->mapping_fn = fileid_mapping_fsname;
518 rootdir_nolock = true;
519 max_slots = UINT64_MAX;
520 } else if (strcmp("next_module", algorithm) == 0) {
521 data->mapping_fn = fileid_mapping_next_module;
523 SMB_VFS_NEXT_DISCONNECT(handle);
524 DEBUG(0,("fileid_connect(): unknown algorithm[%s]\n", algorithm));
528 fstype_deny_list = lp_parm_string_list(SNUM(handle->conn), "fileid",
529 "fstype deny", NULL);
530 if (fstype_deny_list != NULL) {
531 data->fstype_deny_list = str_list_copy(data, fstype_deny_list);
532 if (data->fstype_deny_list == NULL) {
534 DBG_ERR("str_list_copy failed\n");
535 SMB_VFS_NEXT_DISCONNECT(handle);
541 fstype_allow_list = lp_parm_string_list(SNUM(handle->conn), "fileid",
542 "fstype allow", NULL);
543 if (fstype_allow_list != NULL) {
544 data->fstype_allow_list = str_list_copy(data, fstype_allow_list);
545 if (data->fstype_allow_list == NULL) {
547 DBG_ERR("str_list_copy failed\n");
548 SMB_VFS_NEXT_DISCONNECT(handle);
554 mntdir_deny_list = lp_parm_string_list(SNUM(handle->conn), "fileid",
555 "mntdir deny", NULL);
556 if (mntdir_deny_list != NULL) {
557 data->mntdir_deny_list = str_list_copy(data, mntdir_deny_list);
558 if (data->mntdir_deny_list == NULL) {
560 DBG_ERR("str_list_copy failed\n");
561 SMB_VFS_NEXT_DISCONNECT(handle);
567 mntdir_allow_list = lp_parm_string_list(SNUM(handle->conn), "fileid",
568 "mntdir allow", NULL);
569 if (mntdir_allow_list != NULL) {
570 data->mntdir_allow_list = str_list_copy(data, mntdir_allow_list);
571 if (data->mntdir_allow_list == NULL) {
573 DBG_ERR("str_list_copy failed\n");
574 SMB_VFS_NEXT_DISCONNECT(handle);
580 data->nolock.force_all_inodes = lp_parm_bool(SNUM(handle->conn),
581 "fileid", "nolock_all_inodes",
582 data->nolock.force_all_inodes);
583 data->nolock.force_all_dirs = lp_parm_bool(SNUM(handle->conn),
584 "fileid", "nolock_all_dirs",
585 data->nolock.force_all_dirs);
587 max_slots = lp_parm_ulonglong(SNUM(handle->conn),
588 "fileid", "nolock_max_slots",
590 max_slots = MAX(max_slots, 1);
592 data->nolock.extid = fileid_mapping_nolock_extid(max_slots);
594 nolockinode = lp_parm_ulong(SNUM(handle->conn), "fileid", "nolockinode", 0);
595 if (nolockinode != 0) {
596 SMB_STRUCT_STAT tmpsbuf = { .st_ex_ino = nolockinode, };
598 ret = fileid_add_nolock_inode(data, &tmpsbuf);
601 SMB_VFS_NEXT_DISCONNECT(handle);
607 if (rootdir_nolock) {
608 SMB_STRUCT_STAT rootdirsbuf;
610 ret = get_connectpath_ino(handle, ".", &rootdirsbuf);
613 SMB_VFS_NEXT_DISCONNECT(handle);
618 ret = fileid_add_nolock_inode(data, &rootdirsbuf);
621 SMB_VFS_NEXT_DISCONNECT(handle);
627 nolock_paths = lp_parm_string_list(SNUM(handle->conn), "fileid", "nolock_paths", NULL);
628 for (i = 0; nolock_paths != NULL && nolock_paths[i] != NULL; i++) {
629 SMB_STRUCT_STAT tmpsbuf;
631 ret = get_connectpath_ino(handle, nolock_paths[i], &tmpsbuf);
632 if (ret == -1 && errno == ENOENT) {
633 DBG_ERR("ignoring non existing nolock_paths[%zu]='%s'\n",
639 SMB_VFS_NEXT_DISCONNECT(handle);
644 ret = fileid_add_nolock_inode(data, &tmpsbuf);
647 SMB_VFS_NEXT_DISCONNECT(handle);
651 DBG_DEBUG("Adding nolock_paths[%zu]='%s'\n",
655 SMB_VFS_HANDLE_SET_DATA(handle, data, NULL,
656 struct fileid_handle_data,
659 DBG_DEBUG("connect to service[%s] with algorithm[%s] nolock.inodes %zu\n",
660 service, algorithm, data->nolock.num_inodes);
665 static void fileid_disconnect(struct vfs_handle_struct *handle)
667 const struct loadparm_substitution *lp_sub =
668 loadparm_s3_global_substitution();
670 DEBUG(10,("fileid_disconnect() connect to service[%s].\n",
671 lp_servicename(talloc_tos(), lp_sub, SNUM(handle->conn))));
673 SMB_VFS_NEXT_DISCONNECT(handle);
676 static struct file_id fileid_file_id_create(struct vfs_handle_struct *handle,
677 const SMB_STRUCT_STAT *sbuf)
679 struct fileid_handle_data *data;
680 struct file_id id = { .inode = 0, };
682 SMB_VFS_HANDLE_GET_DATA(handle, data,
683 struct fileid_handle_data,
686 id = data->mapping_fn(data, sbuf);
687 if (id.extid == 0 && fileid_is_nolock_inode(data, sbuf)) {
688 id.extid = data->nolock.extid;
691 DBG_DEBUG("Returning dev [%jx] inode [%jx] extid [%jx]\n",
692 (uintmax_t)id.devid, (uintmax_t)id.inode, (uintmax_t)id.extid);
697 static struct vfs_fn_pointers vfs_fileid_fns = {
698 .connect_fn = fileid_connect,
699 .disconnect_fn = fileid_disconnect,
700 .file_id_create_fn = fileid_file_id_create
704 NTSTATUS vfs_fileid_init(TALLOC_CTX *ctx)
708 ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "fileid",
710 if (!NT_STATUS_IS_OK(ret)) {
714 vfs_fileid_debug_level = debug_add_class("fileid");
715 if (vfs_fileid_debug_level == -1) {
716 vfs_fileid_debug_level = DBGC_VFS;
717 DEBUG(0, ("vfs_fileid: Couldn't register custom debugging class!\n"));
719 DEBUG(10, ("vfs_fileid: Debug class number of 'fileid': %d\n", vfs_fileid_debug_level));