s3: smbd: Make extract_snapshot_token() a wrapper for extract_snapshot_token_internal().
[Samba.git] / source3 / modules / vfs_fileid.c
blob2c67946efb3fdb52dbffd08581c9d70c96c8293f
1 /*
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/>.
22 #include "includes.h"
23 #include "smbd/smbd.h"
24 #include "system/filesys.h"
26 static int vfs_fileid_debug_level = DBGC_VFS;
28 #undef DBGC_CLASS
29 #define DBGC_CLASS vfs_fileid_debug_level
31 struct fileid_mount_entry {
32 SMB_DEV_T device;
33 const char *mnt_fsname;
34 fsid_t fsid;
35 uint64_t devid;
38 struct fileid_nolock_inode {
39 dev_t dev;
40 ino_t ino;
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;
53 struct {
54 bool force_all_inodes;
55 bool force_all_dirs;
56 uint64_t extid;
57 size_t num_inodes;
58 struct fileid_nolock_inode *inodes;
59 } nolock;
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,
64 struct mntent *m)
66 int i;
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) {
75 return false;
79 if (fstype_allow != NULL) {
80 for (i = 0; fstype_allow[i] != NULL; i++) {
81 if (strcmp(m->mnt_type, fstype_allow[i]) == 0) {
82 break;
85 if (fstype_allow[i] == NULL) {
86 return false;
89 if (mntdir_deny != NULL) {
90 for (i=0; mntdir_deny[i] != NULL; i++) {
91 if (strcmp(m->mnt_dir, mntdir_deny[i]) == 0) {
92 return false;
96 if (mntdir_allow != NULL) {
97 for (i=0; mntdir_allow[i] != NULL; i++) {
98 if (strcmp(m->mnt_dir, mntdir_allow[i]) == 0) {
99 break;
102 if (mntdir_allow[i] == NULL) {
103 return false;
106 return true;
110 /* load all the mount entries from the mtab */
111 static void fileid_load_mount_entries(struct fileid_handle_data *data)
113 FILE *f;
114 struct mntent *m;
116 data->num_mount_entries = 0;
117 TALLOC_FREE(data->mount_entries);
119 f = setmntent("/etc/mtab", "r");
120 if (!f) return;
122 while ((m = getmntent(f))) {
123 struct stat st;
124 struct statfs sfs;
125 struct fileid_mount_entry *cur;
126 bool allowed;
128 allowed = fileid_mount_entry_allowed(data, m);
129 if (!allowed) {
130 DBG_DEBUG("skipping mount entry %s\n", m->mnt_dir);
131 continue;
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) {
137 m->mnt_fsname += 5;
140 data->mount_entries = talloc_realloc(data,
141 data->mount_entries,
142 struct fileid_mount_entry,
143 data->num_mount_entries+1);
144 if (data->mount_entries == NULL) {
145 goto nomem;
148 cur = &data->mount_entries[data->num_mount_entries];
149 cur->device = st.st_dev;
150 cur->mnt_fsname = talloc_strdup(data->mount_entries,
151 m->mnt_fsname);
152 if (!cur->mnt_fsname) goto nomem;
153 cur->fsid = sfs.f_fsid;
154 cur->devid = (uint64_t)-1;
156 data->num_mount_entries++;
158 endmntent(f);
159 return;
161 nomem:
162 if (f) endmntent(f);
164 data->num_mount_entries = 0;
165 TALLOC_FREE(data->mount_entries);
167 return;
170 /* find a mount entry given a dev_t */
171 static struct fileid_mount_entry *fileid_find_mount_entry(struct fileid_handle_data *data,
172 SMB_DEV_T dev)
174 unsigned i;
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];
191 return NULL;
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));
222 return m->devid;
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);
232 return id;
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;
241 uint64_t id;
242 size_t devname_len;
243 int rc;
245 rc = gethostname(hostname, HOST_NAME_MAX+1);
246 if (rc != 0) {
247 DBG_ERR("gethostname failed\n");
248 return UINT64_MAX;
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");
255 return UINT64_MAX;
257 devname_len = talloc_array_length(devname) - 1;
259 id = fileid_uint64_hash((uint8_t *)devname, devname_len);
261 TALLOC_FREE(devname);
263 return id;
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);
273 return id;
276 static bool fileid_is_nolock_inode(struct fileid_handle_data *data,
277 const SMB_STRUCT_STAT *sbuf)
279 size_t i;
281 if (data->nolock.force_all_inodes) {
282 return true;
285 if (S_ISDIR(sbuf->st_ex_mode) && data->nolock.force_all_dirs) {
286 return true;
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) {
296 continue;
299 if (data->nolock.inodes[i].dev == 0) {
301 * legacy "fileid:nolockinode"
302 * handling ignoring dev
304 return true;
307 if (data->nolock.inodes[i].dev != sbuf->st_ex_dev) {
308 continue;
311 return true;
314 return false;
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;
323 if (exists) {
324 return 0;
327 inodes = talloc_realloc(data, data->nolock.inodes,
328 struct fileid_nolock_inode,
329 data->nolock.num_inodes + 1);
330 if (inodes == NULL) {
331 return -1;
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;
341 return 0;
344 static uint64_t fileid_mapping_nolock_extid(uint64_t max_slots)
346 char buf[8+4+HOST_NAME_MAX+1] = { 0, };
347 uint64_t slot = 0;
348 uint64_t id;
349 int rc;
351 if (max_slots > 1) {
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);
359 if (rc != 0) {
360 DBG_ERR("gethostname failed\n");
361 return UINT64_MAX;
364 id = fileid_uint64_hash((uint8_t *)buf, ARRAY_SIZE(buf));
366 return id;
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,
381 sizeof(m->fsid));
382 } else {
383 union {
384 uint64_t ret;
385 fsid_t fsid;
386 } u;
387 ZERO_STRUCT(u);
388 u.fsid = m->fsid;
389 m->devid = u.ret;
393 return m->devid;
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);
403 return id;
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,
413 const char *path,
414 SMB_STRUCT_STAT *psbuf)
416 TALLOC_CTX *frame = talloc_stackframe();
417 struct smb_filename *fname = NULL;
418 const char *fullpath = NULL;
419 int ret;
421 if (path[0] == '/') {
422 fullpath = path;
423 } else {
424 fullpath = talloc_asprintf(frame,
425 "%s/%s",
426 handle->conn->connectpath,
427 path);
428 if (fullpath == NULL) {
429 DBG_ERR("talloc_asprintf() failed\n");
430 TALLOC_FREE(frame);
431 return -1;
435 fname = synthetic_smb_fname(frame,
436 fullpath,
437 NULL,
438 NULL,
441 if (fname == NULL) {
442 DBG_ERR("synthetic_smb_fname(%s) failed - %s\n",
443 fullpath, strerror(errno));
444 TALLOC_FREE(frame);
445 return -1;
448 ret = SMB_VFS_NEXT_STAT(handle, fname);
449 if (ret != 0) {
450 DBG_ERR("stat failed for %s with %s\n",
451 fullpath, strerror(errno));
452 TALLOC_FREE(frame);
453 return -1;
455 *psbuf = fname->st;
457 TALLOC_FREE(frame);
459 return 0;
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;
471 ino_t nolockinode;
472 uint64_t max_slots = 0;
473 bool rootdir_nolock = false;
474 const char **nolock_paths = NULL;
475 size_t i;
476 int saved_errno;
477 int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
479 if (ret < 0) {
480 return ret;
483 data = talloc_zero(handle, struct fileid_handle_data);
484 if (!data) {
485 saved_errno = errno;
486 SMB_VFS_NEXT_DISCONNECT(handle);
487 DEBUG(0, ("talloc_zero() failed\n"));
488 errno = saved_errno;
489 return -1;
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),
498 "fileid", "mapping",
499 "fsname");
500 algorithm = lp_parm_const_string(SNUM(handle->conn),
501 "fileid", "algorithm",
502 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;
522 } else {
523 SMB_VFS_NEXT_DISCONNECT(handle);
524 DEBUG(0,("fileid_connect(): unknown algorithm[%s]\n", algorithm));
525 return -1;
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) {
533 saved_errno = errno;
534 DBG_ERR("str_list_copy failed\n");
535 SMB_VFS_NEXT_DISCONNECT(handle);
536 errno = saved_errno;
537 return -1;
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) {
546 saved_errno = errno;
547 DBG_ERR("str_list_copy failed\n");
548 SMB_VFS_NEXT_DISCONNECT(handle);
549 errno = saved_errno;
550 return -1;
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) {
559 saved_errno = errno;
560 DBG_ERR("str_list_copy failed\n");
561 SMB_VFS_NEXT_DISCONNECT(handle);
562 errno = saved_errno;
563 return -1;
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) {
572 saved_errno = errno;
573 DBG_ERR("str_list_copy failed\n");
574 SMB_VFS_NEXT_DISCONNECT(handle);
575 errno = saved_errno;
576 return -1;
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",
589 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);
599 if (ret != 0) {
600 saved_errno = errno;
601 SMB_VFS_NEXT_DISCONNECT(handle);
602 errno = saved_errno;
603 return -1;
607 if (rootdir_nolock) {
608 SMB_STRUCT_STAT rootdirsbuf;
610 ret = get_connectpath_ino(handle, ".", &rootdirsbuf);
611 if (ret != 0) {
612 saved_errno = errno;
613 SMB_VFS_NEXT_DISCONNECT(handle);
614 errno = saved_errno;
615 return -1;
618 ret = fileid_add_nolock_inode(data, &rootdirsbuf);
619 if (ret != 0) {
620 saved_errno = errno;
621 SMB_VFS_NEXT_DISCONNECT(handle);
622 errno = saved_errno;
623 return -1;
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",
634 i, nolock_paths[i]);
635 continue;
637 if (ret != 0) {
638 saved_errno = errno;
639 SMB_VFS_NEXT_DISCONNECT(handle);
640 errno = saved_errno;
641 return -1;
644 ret = fileid_add_nolock_inode(data, &tmpsbuf);
645 if (ret != 0) {
646 saved_errno = errno;
647 SMB_VFS_NEXT_DISCONNECT(handle);
648 errno = saved_errno;
649 return -1;
651 DBG_DEBUG("Adding nolock_paths[%zu]='%s'\n",
652 i, nolock_paths[i]);
655 SMB_VFS_HANDLE_SET_DATA(handle, data, NULL,
656 struct fileid_handle_data,
657 return -1);
659 DBG_DEBUG("connect to service[%s] with algorithm[%s] nolock.inodes %zu\n",
660 service, algorithm, data->nolock.num_inodes);
662 return 0;
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,
684 return id);
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);
694 return id;
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
703 static_decl_vfs;
704 NTSTATUS vfs_fileid_init(TALLOC_CTX *ctx)
706 NTSTATUS ret;
708 ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "fileid",
709 &vfs_fileid_fns);
710 if (!NT_STATUS_IS_OK(ret)) {
711 return 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"));
718 } else {
719 DEBUG(10, ("vfs_fileid: Debug class number of 'fileid': %d\n", vfs_fileid_debug_level));
722 return ret;