Introduce "struct stat_ex" as a replacement for SMB_STRUCT_STAT
[samba.git] / source3 / modules / vfs_fileid.c
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.
5  *
6  * Copyright (C) 2007, Stefan Metzmacher
7  *
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.
12  *
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.
17  *
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/>.
20  */
21
22 #include "includes.h"
23
24 static int vfs_fileid_debug_level = DBGC_VFS;
25
26 #undef DBGC_CLASS
27 #define DBGC_CLASS vfs_fileid_debug_level
28
29 struct fileid_mount_entry {
30         SMB_DEV_T device;
31         const char *mnt_fsname;
32         fsid_t fsid;
33         uint64_t devid;
34 };
35
36 struct fileid_handle_data {
37         uint64_t (*device_mapping_fn)(struct fileid_handle_data *data,
38                                       SMB_DEV_T dev);
39         unsigned num_mount_entries;
40         struct fileid_mount_entry *mount_entries;
41 };
42
43 /* load all the mount entries from the mtab */
44 static void fileid_load_mount_entries(struct fileid_handle_data *data)
45 {
46         FILE *f;
47         struct mntent *m;
48
49         data->num_mount_entries = 0;
50         TALLOC_FREE(data->mount_entries);
51
52         f = setmntent("/etc/mtab", "r");
53         if (!f) return;
54
55         while ((m = getmntent(f))) {
56                 struct stat st;
57                 struct statfs sfs;
58                 struct fileid_mount_entry *cur;
59
60                 if (stat(m->mnt_dir, &st) != 0) continue;
61                 if (statfs(m->mnt_dir, &sfs) != 0) continue;
62
63                 if (strncmp(m->mnt_fsname, "/dev/", 5) == 0) {
64                         m->mnt_fsname += 5;
65                 }
66
67                 data->mount_entries = TALLOC_REALLOC_ARRAY(data,
68                                                            data->mount_entries,
69                                                            struct fileid_mount_entry,
70                                                            data->num_mount_entries+1);
71                 if (data->mount_entries == NULL) {
72                         goto nomem;
73                 }
74
75                 cur = &data->mount_entries[data->num_mount_entries];
76                 cur->device     = st.st_dev;
77                 cur->mnt_fsname = talloc_strdup(data->mount_entries,
78                                                 m->mnt_fsname);
79                 if (!cur->mnt_fsname) goto nomem;
80                 cur->fsid       = sfs.f_fsid;
81                 cur->devid      = (uint64_t)-1;
82
83                 data->num_mount_entries++;
84         }
85         endmntent(f);
86         return;
87         
88 nomem:
89         if (f) endmntent(f);
90
91         data->num_mount_entries = 0;
92         TALLOC_FREE(data->mount_entries);
93
94         return;
95 }
96
97 /* find a mount entry given a dev_t */
98 static struct fileid_mount_entry *fileid_find_mount_entry(struct fileid_handle_data *data,
99                                                           SMB_DEV_T dev)
100 {
101         int i;
102
103         if (data->num_mount_entries == 0) {
104                 fileid_load_mount_entries(data);
105         }
106         for (i=0;i<data->num_mount_entries;i++) {
107                 if (data->mount_entries[i].device == dev) {
108                         return &data->mount_entries[i];
109                 }
110         }
111         /* 2nd pass after reloading */
112         fileid_load_mount_entries(data);
113         for (i=0;i<data->num_mount_entries;i++) {
114                 if (data->mount_entries[i].device == dev) {
115                         return &data->mount_entries[i];
116                 }
117         }       
118         return NULL;
119 }
120
121
122 /* a 64 bit hash, based on the one in tdb */
123 static uint64_t fileid_uint64_hash(const uint8_t *s, size_t len)
124 {
125         uint64_t value; /* Used to compute the hash value.  */
126         uint32_t i;     /* Used to cycle through random values. */
127
128         /* Set the initial value from the key size. */
129         for (value = 0x238F13AFLL * len, i=0; i < len; i++)
130                 value = (value + (s[i] << (i*5 % 24)));
131
132         return (1103515243LL * value + 12345LL);
133 }
134
135 /* a device mapping using a fsname */
136 static uint64_t fileid_device_mapping_fsname(struct fileid_handle_data *data,
137                                              SMB_DEV_T dev)
138 {
139         struct fileid_mount_entry *m;
140
141         m = fileid_find_mount_entry(data, dev);
142         if (!m) return dev;
143
144         if (m->devid == (uint64_t)-1) {
145                 m->devid = fileid_uint64_hash((uint8_t *)m->mnt_fsname,
146                                               strlen(m->mnt_fsname));
147         }
148
149         return m->devid;
150 }
151
152 /* device mapping functions using a fsid */
153 static uint64_t fileid_device_mapping_fsid(struct fileid_handle_data *data,
154                                            SMB_DEV_T dev)
155 {
156         struct fileid_mount_entry *m;
157
158         m = fileid_find_mount_entry(data, dev);
159         if (!m) return dev;
160
161         if (m->devid == (uint64_t)-1) {
162                 if (sizeof(fsid_t) > sizeof(uint64_t)) {
163                         m->devid = fileid_uint64_hash((uint8_t *)&m->fsid,
164                                                       sizeof(m->fsid));
165                 } else {
166                         union {
167                                 uint64_t ret;
168                                 fsid_t fsid;
169                         } u;
170                         ZERO_STRUCT(u);
171                         u.fsid = m->fsid;
172                         m->devid = u.ret;
173                 }
174         }
175
176         return m->devid;
177 }
178
179 static int fileid_connect(struct vfs_handle_struct *handle,
180                           const char *service, const char *user)
181 {
182         struct fileid_handle_data *data;
183         const char *algorithm;
184
185         data = talloc_zero(handle->conn, struct fileid_handle_data);
186         if (!data) {
187                 DEBUG(0, ("talloc_zero() failed\n"));
188                 return -1;
189         }
190
191         /*
192          * "fileid:mapping" is only here as fallback for old setups
193          * "fileid:algorithm" is the option new setups should use
194          */
195         algorithm = lp_parm_const_string(SNUM(handle->conn),
196                                          "fileid", "mapping",
197                                          "fsname");
198         algorithm = lp_parm_const_string(SNUM(handle->conn),
199                                          "fileid", "algorithm",
200                                          algorithm);
201         if (strcmp("fsname", algorithm) == 0) {
202                 data->device_mapping_fn = fileid_device_mapping_fsname;
203         } else if (strcmp("fsid", algorithm) == 0) {
204                 data->device_mapping_fn = fileid_device_mapping_fsid;
205         } else {
206                 DEBUG(0,("fileid_connect(): unknown algorithm[%s]\n", algorithm));
207                 return -1;
208         }
209
210         SMB_VFS_HANDLE_SET_DATA(handle, data, NULL,
211                                 struct fileid_handle_data,
212                                 return -1);
213
214         DEBUG(10, ("fileid_connect(): connect to service[%s] with algorithm[%s]\n",
215                 service, algorithm));
216
217         return SMB_VFS_NEXT_CONNECT(handle, service, user);
218 }
219
220 static void fileid_disconnect(struct vfs_handle_struct *handle)
221 {
222         DEBUG(10,("fileid_disconnect() connect to service[%s].\n",
223                 lp_servicename(SNUM(handle->conn))));
224
225         SMB_VFS_NEXT_DISCONNECT(handle);
226 }
227
228 static struct file_id fileid_file_id_create(struct vfs_handle_struct *handle,
229                                             const SMB_STRUCT_STAT *sbuf)
230 {
231         struct fileid_handle_data *data;
232         struct file_id id;
233
234         ZERO_STRUCT(id);
235
236         SMB_VFS_HANDLE_GET_DATA(handle, data,
237                                 struct fileid_handle_data,
238                                 return id);
239
240         id.devid        = data->device_mapping_fn(data, sbuf->st_ex_dev);
241         id.inode        = sbuf->st_ex_ino;
242
243         return id;
244 }
245
246 static vfs_op_tuple fileid_ops[] = {
247
248         /* Disk operations */
249         {
250                 SMB_VFS_OP(fileid_connect),
251                 SMB_VFS_OP_CONNECT,
252                 SMB_VFS_LAYER_TRANSPARENT
253         },
254         {
255                 SMB_VFS_OP(fileid_disconnect),
256                 SMB_VFS_OP_DISCONNECT,
257                 SMB_VFS_LAYER_TRANSPARENT
258         },
259
260         /* File operations */
261         {
262                 SMB_VFS_OP(fileid_file_id_create),
263                 SMB_VFS_OP_FILE_ID_CREATE,
264                 SMB_VFS_LAYER_OPAQUE
265         },
266
267         /* End marker */
268         {
269                 SMB_VFS_OP(NULL),
270                 SMB_VFS_OP_NOOP,
271                 SMB_VFS_LAYER_NOOP
272         }
273 };
274
275 NTSTATUS vfs_fileid_init(void);
276 NTSTATUS vfs_fileid_init(void)
277 {
278         NTSTATUS ret;
279
280         ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "fileid", fileid_ops);
281         if (!NT_STATUS_IS_OK(ret)) {
282                 return ret;
283         }
284
285         vfs_fileid_debug_level = debug_add_class("fileid");
286         if (vfs_fileid_debug_level == -1) {
287                 vfs_fileid_debug_level = DBGC_VFS;
288                 DEBUG(0, ("vfs_fileid: Couldn't register custom debugging class!\n"));
289         } else {
290                 DEBUG(10, ("vfs_fileid: Debug class number of 'fileid': %d\n", vfs_fileid_debug_level));
291         }
292
293         return ret;
294 }