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 file_id (*mapping_fn)(struct fileid_handle_data *data,
45 const SMB_STRUCT_STAT *sbuf);
46 char **fstype_deny_list;
47 char **fstype_allow_list;
48 char **mntdir_deny_list;
49 char **mntdir_allow_list;
50 unsigned num_mount_entries;
51 struct fileid_mount_entry *mount_entries;
54 struct fileid_nolock_inode *inodes;
58 /* check if a mount entry is allowed based on fstype and mount directory */
59 static bool fileid_mount_entry_allowed(struct fileid_handle_data *data,
63 char **fstype_deny = data->fstype_deny_list;
64 char **fstype_allow = data->fstype_allow_list;
65 char **mntdir_deny = data->mntdir_deny_list;
66 char **mntdir_allow = data->mntdir_allow_list;
68 if (fstype_deny != NULL) {
69 for (i = 0; fstype_deny[i] != NULL; i++) {
70 if (strcmp(m->mnt_type, fstype_deny[i]) == 0) {
75 if (fstype_allow != NULL) {
76 for (i = 0; fstype_allow[i] != NULL; i++) {
77 if (strcmp(m->mnt_type, fstype_allow[i]) == 0) {
81 if (fstype_allow[i] == NULL) {
85 if (mntdir_deny != NULL) {
86 for (i=0; mntdir_deny[i] != NULL; i++) {
87 if (strcmp(m->mnt_dir, mntdir_deny[i]) == 0) {
92 if (mntdir_allow != NULL) {
93 for (i=0; mntdir_allow[i] != NULL; i++) {
94 if (strcmp(m->mnt_dir, mntdir_allow[i]) == 0) {
98 if (mntdir_allow[i] == NULL) {
106 /* load all the mount entries from the mtab */
107 static void fileid_load_mount_entries(struct fileid_handle_data *data)
112 data->num_mount_entries = 0;
113 TALLOC_FREE(data->mount_entries);
115 f = setmntent("/etc/mtab", "r");
118 while ((m = getmntent(f))) {
121 struct fileid_mount_entry *cur;
124 allowed = fileid_mount_entry_allowed(data, m);
126 DBG_DEBUG("skipping mount entry %s\n", m->mnt_dir);
129 if (stat(m->mnt_dir, &st) != 0) continue;
130 if (statfs(m->mnt_dir, &sfs) != 0) continue;
132 if (strncmp(m->mnt_fsname, "/dev/", 5) == 0) {
136 data->mount_entries = talloc_realloc(data,
138 struct fileid_mount_entry,
139 data->num_mount_entries+1);
140 if (data->mount_entries == NULL) {
144 cur = &data->mount_entries[data->num_mount_entries];
145 cur->device = st.st_dev;
146 cur->mnt_fsname = talloc_strdup(data->mount_entries,
148 if (!cur->mnt_fsname) goto nomem;
149 cur->fsid = sfs.f_fsid;
150 cur->devid = (uint64_t)-1;
152 data->num_mount_entries++;
160 data->num_mount_entries = 0;
161 TALLOC_FREE(data->mount_entries);
166 /* find a mount entry given a dev_t */
167 static struct fileid_mount_entry *fileid_find_mount_entry(struct fileid_handle_data *data,
172 if (data->num_mount_entries == 0) {
173 fileid_load_mount_entries(data);
175 for (i=0;i<data->num_mount_entries;i++) {
176 if (data->mount_entries[i].device == dev) {
177 return &data->mount_entries[i];
180 /* 2nd pass after reloading */
181 fileid_load_mount_entries(data);
182 for (i=0;i<data->num_mount_entries;i++) {
183 if (data->mount_entries[i].device == dev) {
184 return &data->mount_entries[i];
191 /* a 64 bit hash, based on the one in tdb */
192 static uint64_t fileid_uint64_hash(const uint8_t *s, size_t len)
194 uint64_t value; /* Used to compute the hash value. */
195 uint32_t i; /* Used to cycle through random values. */
197 /* Set the initial value from the key size. */
198 for (value = 0x238F13AFLL * len, i=0; i < len; i++)
199 value = (value + (((uint64_t)s[i]) << (i*5 % 24)));
201 return (1103515243LL * value + 12345LL);
204 /* a device mapping using a fsname */
205 static uint64_t fileid_device_mapping_fsname(struct fileid_handle_data *data,
206 const SMB_STRUCT_STAT *sbuf)
208 struct fileid_mount_entry *m;
210 m = fileid_find_mount_entry(data, sbuf->st_ex_dev);
211 if (!m) return sbuf->st_ex_dev;
213 if (m->devid == (uint64_t)-1) {
214 m->devid = fileid_uint64_hash((const uint8_t *)m->mnt_fsname,
215 strlen(m->mnt_fsname));
221 static struct file_id fileid_mapping_fsname(struct fileid_handle_data *data,
222 const SMB_STRUCT_STAT *sbuf)
224 struct file_id id = { .inode = sbuf->st_ex_ino, };
226 id.devid = fileid_device_mapping_fsname(data, sbuf);
231 /* a device mapping using a hostname */
232 static uint64_t fileid_device_mapping_hostname(struct fileid_handle_data *data,
233 const SMB_STRUCT_STAT *sbuf)
235 char hostname[HOST_NAME_MAX+1];
236 char *devname = NULL;
241 rc = gethostname(hostname, HOST_NAME_MAX+1);
243 DBG_ERR("gethostname failed\n");
247 devname = talloc_asprintf(talloc_tos(), "%s%ju",
248 hostname, (uintmax_t)sbuf->st_ex_dev);
249 if (devname == NULL) {
250 DBG_ERR("talloc_asprintf failed\n");
253 devname_len = talloc_array_length(devname) - 1;
255 id = fileid_uint64_hash((uint8_t *)devname, devname_len);
257 TALLOC_FREE(devname);
262 static struct file_id fileid_mapping_hostname(struct fileid_handle_data *data,
263 const SMB_STRUCT_STAT *sbuf)
265 struct file_id id = { .inode = sbuf->st_ex_ino, };
267 id.devid = fileid_device_mapping_hostname(data, sbuf);
272 static bool fileid_is_nolock_inode(struct fileid_handle_data *data,
273 const SMB_STRUCT_STAT *sbuf)
278 * We could make this a binary search over an sorted array,
279 * but for now we keep things simple.
282 for (i=0; i < data->nolock.num_inodes; i++) {
283 if (data->nolock.inodes[i].ino != sbuf->st_ex_ino) {
287 if (data->nolock.inodes[i].dev == 0) {
289 * legacy "fileid:nolockinode"
290 * handling ignoring dev
295 if (data->nolock.inodes[i].dev != sbuf->st_ex_dev) {
305 static int fileid_add_nolock_inode(struct fileid_handle_data *data,
306 const SMB_STRUCT_STAT *sbuf)
308 bool exists = fileid_is_nolock_inode(data, sbuf);
309 struct fileid_nolock_inode *inodes = NULL;
315 inodes = talloc_realloc(data, data->nolock.inodes,
316 struct fileid_nolock_inode,
317 data->nolock.num_inodes + 1);
318 if (inodes == NULL) {
322 inodes[data->nolock.num_inodes] = (struct fileid_nolock_inode) {
323 .dev = sbuf->st_ex_dev,
324 .ino = sbuf->st_ex_ino,
326 data->nolock.inodes = inodes;
327 data->nolock.num_inodes += 1;
332 /* a device mapping using a fsname for files and hostname for dirs */
333 static struct file_id fileid_mapping_fsname_nodirs(
334 struct fileid_handle_data *data,
335 const SMB_STRUCT_STAT *sbuf)
337 if (S_ISDIR(sbuf->st_ex_mode)) {
338 return fileid_mapping_hostname(data, sbuf);
341 return fileid_mapping_fsname(data, sbuf);
344 static struct file_id fileid_mapping_fsname_norootdir(
345 struct fileid_handle_data *data,
346 const SMB_STRUCT_STAT *sbuf)
348 if (fileid_is_nolock_inode(data, sbuf)) {
349 return fileid_mapping_hostname(data, sbuf);
352 return fileid_mapping_fsname(data, sbuf);
355 static struct file_id fileid_mapping_fsname_norootdir_ext(
356 struct fileid_handle_data *data,
357 const SMB_STRUCT_STAT *sbuf)
359 if (fileid_is_nolock_inode(data, sbuf)) {
360 struct file_id id = fileid_mapping_hostname(data, sbuf);
365 return fileid_mapping_fsname(data, sbuf);
368 /* device mapping functions using a fsid */
369 static uint64_t fileid_device_mapping_fsid(struct fileid_handle_data *data,
370 const SMB_STRUCT_STAT *sbuf)
372 struct fileid_mount_entry *m;
374 m = fileid_find_mount_entry(data, sbuf->st_ex_dev);
375 if (!m) return sbuf->st_ex_dev;
377 if (m->devid == (uint64_t)-1) {
378 if (sizeof(fsid_t) > sizeof(uint64_t)) {
379 m->devid = fileid_uint64_hash((uint8_t *)&m->fsid,
395 static struct file_id fileid_mapping_fsid(struct fileid_handle_data *data,
396 const SMB_STRUCT_STAT *sbuf)
398 struct file_id id = { .inode = sbuf->st_ex_ino, };
400 id.devid = fileid_device_mapping_fsid(data, sbuf);
405 static int get_connectpath_ino(struct vfs_handle_struct *handle,
407 SMB_STRUCT_STAT *psbuf)
409 TALLOC_CTX *frame = talloc_stackframe();
410 struct smb_filename *fname = NULL;
411 const char *fullpath = NULL;
414 if (path[0] == '/') {
417 fullpath = talloc_asprintf(frame,
419 handle->conn->connectpath,
421 if (fullpath == NULL) {
422 DBG_ERR("talloc_asprintf() failed\n");
428 fname = synthetic_smb_fname(frame,
435 DBG_ERR("synthetic_smb_fname(%s) failed - %s\n",
436 fullpath, strerror(errno));
441 ret = SMB_VFS_NEXT_STAT(handle, fname);
443 DBG_ERR("stat failed for %s with %s\n",
444 fullpath, strerror(errno));
455 static int fileid_connect(struct vfs_handle_struct *handle,
456 const char *service, const char *user)
458 struct fileid_handle_data *data;
459 const char *algorithm;
460 const char **fstype_deny_list = NULL;
461 const char **fstype_allow_list = NULL;
462 const char **mntdir_deny_list = NULL;
463 const char **mntdir_allow_list = NULL;
465 bool rootdir_nolock = false;
467 int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
473 data = talloc_zero(handle->conn, struct fileid_handle_data);
476 SMB_VFS_NEXT_DISCONNECT(handle);
477 DEBUG(0, ("talloc_zero() failed\n"));
483 * "fileid:mapping" is only here as fallback for old setups
484 * "fileid:algorithm" is the option new setups should use
486 algorithm = lp_parm_const_string(SNUM(handle->conn),
489 algorithm = lp_parm_const_string(SNUM(handle->conn),
490 "fileid", "algorithm",
492 if (strcmp("fsname", algorithm) == 0) {
493 data->mapping_fn = fileid_mapping_fsname;
494 } else if (strcmp("fsname_nodirs", algorithm) == 0) {
495 data->mapping_fn = fileid_mapping_fsname_nodirs;
496 } else if (strcmp("fsid", algorithm) == 0) {
497 data->mapping_fn = fileid_mapping_fsid;
498 } else if (strcmp("hostname", algorithm) == 0) {
499 data->mapping_fn = fileid_mapping_hostname;
500 } else if (strcmp("fsname_norootdir", algorithm) == 0) {
501 data->mapping_fn = fileid_mapping_fsname_norootdir;
502 rootdir_nolock = true;
503 } else if (strcmp("fsname_norootdir_ext", algorithm) == 0) {
504 data->mapping_fn = fileid_mapping_fsname_norootdir_ext;
505 rootdir_nolock = true;
507 SMB_VFS_NEXT_DISCONNECT(handle);
508 DEBUG(0,("fileid_connect(): unknown algorithm[%s]\n", algorithm));
512 fstype_deny_list = lp_parm_string_list(SNUM(handle->conn), "fileid",
513 "fstype deny", NULL);
514 if (fstype_deny_list != NULL) {
515 data->fstype_deny_list = str_list_copy(data, fstype_deny_list);
516 if (data->fstype_deny_list == NULL) {
518 DBG_ERR("str_list_copy failed\n");
519 SMB_VFS_NEXT_DISCONNECT(handle);
525 fstype_allow_list = lp_parm_string_list(SNUM(handle->conn), "fileid",
526 "fstype allow", NULL);
527 if (fstype_allow_list != NULL) {
528 data->fstype_allow_list = str_list_copy(data, fstype_allow_list);
529 if (data->fstype_allow_list == NULL) {
531 DBG_ERR("str_list_copy failed\n");
532 SMB_VFS_NEXT_DISCONNECT(handle);
538 mntdir_deny_list = lp_parm_string_list(SNUM(handle->conn), "fileid",
539 "mntdir deny", NULL);
540 if (mntdir_deny_list != NULL) {
541 data->mntdir_deny_list = str_list_copy(data, mntdir_deny_list);
542 if (data->mntdir_deny_list == NULL) {
544 DBG_ERR("str_list_copy failed\n");
545 SMB_VFS_NEXT_DISCONNECT(handle);
551 mntdir_allow_list = lp_parm_string_list(SNUM(handle->conn), "fileid",
552 "mntdir allow", NULL);
553 if (mntdir_allow_list != NULL) {
554 data->mntdir_allow_list = str_list_copy(data, mntdir_allow_list);
555 if (data->mntdir_allow_list == NULL) {
557 DBG_ERR("str_list_copy failed\n");
558 SMB_VFS_NEXT_DISCONNECT(handle);
564 nolockinode = lp_parm_ulong(SNUM(handle->conn), "fileid", "nolockinode", 0);
565 if (nolockinode != 0) {
566 SMB_STRUCT_STAT tmpsbuf = { .st_ex_ino = nolockinode, };
568 ret = fileid_add_nolock_inode(data, &tmpsbuf);
571 SMB_VFS_NEXT_DISCONNECT(handle);
577 if (rootdir_nolock) {
578 SMB_STRUCT_STAT rootdirsbuf;
580 ret = get_connectpath_ino(handle, ".", &rootdirsbuf);
583 SMB_VFS_NEXT_DISCONNECT(handle);
588 ret = fileid_add_nolock_inode(data, &rootdirsbuf);
591 SMB_VFS_NEXT_DISCONNECT(handle);
597 SMB_VFS_HANDLE_SET_DATA(handle, data, NULL,
598 struct fileid_handle_data,
601 DBG_DEBUG("connect to service[%s] with algorithm[%s] nolock.inodes %zu\n",
602 service, algorithm, data->nolock.num_inodes);
607 static void fileid_disconnect(struct vfs_handle_struct *handle)
609 const struct loadparm_substitution *lp_sub =
610 loadparm_s3_global_substitution();
612 DEBUG(10,("fileid_disconnect() connect to service[%s].\n",
613 lp_servicename(talloc_tos(), lp_sub, SNUM(handle->conn))));
615 SMB_VFS_NEXT_DISCONNECT(handle);
618 static struct file_id fileid_file_id_create(struct vfs_handle_struct *handle,
619 const SMB_STRUCT_STAT *sbuf)
621 struct fileid_handle_data *data;
622 struct file_id id = { .inode = 0, };
624 SMB_VFS_HANDLE_GET_DATA(handle, data,
625 struct fileid_handle_data,
628 id = data->mapping_fn(data, sbuf);
630 DBG_DEBUG("Returning dev [%jx] inode [%jx] extid [%jx]\n",
631 (uintmax_t)id.devid, (uintmax_t)id.inode, (uintmax_t)id.extid);
636 static struct vfs_fn_pointers vfs_fileid_fns = {
637 .connect_fn = fileid_connect,
638 .disconnect_fn = fileid_disconnect,
639 .file_id_create_fn = fileid_file_id_create
643 NTSTATUS vfs_fileid_init(TALLOC_CTX *ctx)
647 ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "fileid",
649 if (!NT_STATUS_IS_OK(ret)) {
653 vfs_fileid_debug_level = debug_add_class("fileid");
654 if (vfs_fileid_debug_level == -1) {
655 vfs_fileid_debug_level = DBGC_VFS;
656 DEBUG(0, ("vfs_fileid: Couldn't register custom debugging class!\n"));
658 DEBUG(10, ("vfs_fileid: Debug class number of 'fileid': %d\n", vfs_fileid_debug_level));