vfs:glusterfs_fuse: ensure fileids are constant across nodes
[samba.git] / source3 / modules / vfs_glusterfs_fuse.c
1 /*
2    Unix SMB/CIFS implementation.
3
4    Copyright (c) 2019 Guenther Deschner <gd@samba.org>
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "includes.h"
21 #include "smbd/smbd.h"
22 #include "system/filesys.h"
23
24 #define GLUSTER_NAME_MAX 255
25
26 static int vfs_gluster_fuse_get_real_filename(struct vfs_handle_struct *handle,
27                                               const char *path,
28                                               const char *name,
29                                               TALLOC_CTX *mem_ctx,
30                                               char **_found_name)
31 {
32         int ret;
33         char key_buf[GLUSTER_NAME_MAX + 64];
34         char val_buf[GLUSTER_NAME_MAX + 1];
35         char *found_name = NULL;
36
37         if (strlen(name) >= GLUSTER_NAME_MAX) {
38                 errno = ENAMETOOLONG;
39                 return -1;
40         }
41
42         snprintf(key_buf, GLUSTER_NAME_MAX + 64,
43                  "glusterfs.get_real_filename:%s", name);
44
45         ret = getxattr(path, key_buf, val_buf, GLUSTER_NAME_MAX + 1);
46         if (ret == -1) {
47                 if (errno == ENOATTR) {
48                         errno = ENOENT;
49                 }
50                 return -1;
51         }
52
53         found_name = talloc_strdup(mem_ctx, val_buf);
54         if (found_name == NULL) {
55                 errno = ENOMEM;
56                 return -1;
57         }
58         *_found_name = found_name;
59         return 0;
60 }
61
62 struct device_mapping_entry {
63         SMB_DEV_T device;       /* the local device, for reference */
64         uint64_t mapped_device; /* the mapped device */
65 };
66
67 struct vfs_glusterfs_fuse_handle_data {
68         unsigned num_mapped_devices;
69         struct device_mapping_entry *mapped_devices;
70 };
71
72 /* a 64 bit hash, based on the one in tdb, copied from vfs_fileied */
73 static uint64_t vfs_glusterfs_fuse_uint64_hash(const uint8_t *s, size_t len)
74 {
75         uint64_t value; /* Used to compute the hash value.  */
76         uint32_t i;     /* Used to cycle through random values. */
77
78         /* Set the initial value from the key size. */
79         for (value = 0x238F13AFLL * len, i=0; i < len; i++)
80                 value = (value + (((uint64_t)s[i]) << (i*5 % 24)));
81
82         return (1103515243LL * value + 12345LL);
83 }
84
85 static void vfs_glusterfs_fuse_load_devices(
86                 struct vfs_glusterfs_fuse_handle_data *data)
87 {
88         FILE *f;
89         struct mntent *m;
90
91         data->num_mapped_devices = 0;
92         TALLOC_FREE(data->mapped_devices);
93
94         f = setmntent("/etc/mtab", "r");
95         if (!f) {
96                 return;
97         }
98
99         while ((m = getmntent(f))) {
100                 struct stat st;
101                 char *p;
102                 uint64_t mapped_device;
103
104                 if (stat(m->mnt_dir, &st) != 0) {
105                         /* TODO: log? */
106                         continue;
107                 }
108
109                 /* strip the host part off of the fsname */
110                 p = strrchr(m->mnt_fsname, ':');
111                 if (p == NULL) {
112                         p = m->mnt_fsname;
113                 } else {
114                         /* TODO: consider the case of '' ? */
115                         p++;
116                 }
117
118                 mapped_device = vfs_glusterfs_fuse_uint64_hash(
119                                                 (const uint8_t *)p,
120                                                 strlen(p));
121
122                 data->mapped_devices = talloc_realloc(data,
123                                                 data->mapped_devices,
124                                                 struct device_mapping_entry,
125                                                 data->num_mapped_devices + 1);
126                 if (data->mapped_devices == NULL) {
127                         goto nomem;
128                 }
129
130                 data->mapped_devices[data->num_mapped_devices].device =
131                                                                 st.st_dev;
132                 data->mapped_devices[data->num_mapped_devices].mapped_device =
133                                                                 mapped_device;
134
135                 data->num_mapped_devices++;
136         }
137
138         endmntent(f);
139         return;
140
141 nomem:
142         data->num_mapped_devices = 0;
143         TALLOC_FREE(data->mapped_devices);
144
145         endmntent(f);
146         return;
147 }
148
149 static int vfs_glusterfs_fuse_map_device_cached(
150                                 struct vfs_glusterfs_fuse_handle_data *data,
151                                 SMB_DEV_T device,
152                                 uint64_t *mapped_device)
153 {
154         unsigned i;
155
156         for (i = 0; i < data->num_mapped_devices; i++) {
157                 if (data->mapped_devices[i].device == device) {
158                         *mapped_device = data->mapped_devices[i].mapped_device;
159                         return 0;
160                 }
161         }
162
163         return -1;
164 }
165
166 static int vfs_glusterfs_fuse_map_device(
167                                 struct vfs_glusterfs_fuse_handle_data *data,
168                                 SMB_DEV_T device,
169                                 uint64_t *mapped_device)
170 {
171         int ret;
172
173         ret = vfs_glusterfs_fuse_map_device_cached(data, device, mapped_device);
174         if (ret == 0) {
175                 return 0;
176         }
177
178         vfs_glusterfs_fuse_load_devices(data);
179
180         ret = vfs_glusterfs_fuse_map_device_cached(data, device, mapped_device);
181
182         return ret;
183 }
184
185 static struct file_id vfs_glusterfs_fuse_file_id_create(
186                         struct vfs_handle_struct *handle,
187                         const SMB_STRUCT_STAT *sbuf)
188 {
189         struct vfs_glusterfs_fuse_handle_data *data;
190         struct file_id id;
191         uint64_t mapped_device;
192         int ret;
193
194         ZERO_STRUCT(id);
195
196         id = SMB_VFS_NEXT_FILE_ID_CREATE(handle, sbuf);
197
198         SMB_VFS_HANDLE_GET_DATA(handle, data,
199                                 struct vfs_glusterfs_fuse_handle_data,
200                                 return id);
201
202         ret = vfs_glusterfs_fuse_map_device(data, sbuf->st_ex_dev,
203                                             &mapped_device);
204         if (ret == 0) {
205                 id.devid = mapped_device;
206         } else {
207                 DBG_WARNING("Failed to map device [%jx], falling back to "
208                             "standard file_id [%jx]",
209                             (uintmax_t)sbuf->st_ex_dev,
210                             (uintmax_t)id.devid);
211         }
212
213         DBG_DEBUG("Returning dev [%jx] inode [%jx]\n",
214                   (uintmax_t)id.devid, (uintmax_t)id.inode);
215
216         return id;
217 }
218
219 static int vfs_glusterfs_fuse_connect(struct vfs_handle_struct *handle,
220                                       const char *service, const char *user)
221 {
222         struct vfs_glusterfs_fuse_handle_data *data;
223         int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
224
225         if (ret < 0) {
226                 return ret;
227         }
228
229         data = talloc_zero(handle->conn, struct vfs_glusterfs_fuse_handle_data);
230         if (data == NULL) {
231                 DBG_ERR("talloc_zero() failed.\n");
232                 SMB_VFS_NEXT_DISCONNECT(handle);
233                 return -1;
234         }
235
236         /*
237          * Fill the cache in the tree connect, so that the first file/dir access
238          * has chances of being fast...
239          */
240         vfs_glusterfs_fuse_load_devices(data);
241
242         SMB_VFS_HANDLE_SET_DATA(handle, data, NULL,
243                                 struct vfs_glusterfs_fuse_handle_data,
244                                 return -1);
245
246         DBG_DEBUG("vfs_glusterfs_fuse_connect(): connected to service[%s]\n",
247                   service);
248
249         return 0;
250 }
251
252 struct vfs_fn_pointers glusterfs_fuse_fns = {
253
254         .connect_fn = vfs_glusterfs_fuse_connect,
255         .get_real_filename_fn = vfs_gluster_fuse_get_real_filename,
256         .file_id_create_fn = vfs_glusterfs_fuse_file_id_create,
257 };
258
259 static_decl_vfs;
260 NTSTATUS vfs_glusterfs_fuse_init(TALLOC_CTX *ctx)
261 {
262         return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
263                                 "glusterfs_fuse", &glusterfs_fuse_fns);
264 }