s3: VFS: syncops: Remove direct system calls and use OpenDir()/smb_vfs_fsync_sync...
[samba.git] / source3 / modules / vfs_syncops.c
1 /* 
2  * ensure meta data operations are performed synchronously
3  *
4  * Copyright (C) Andrew Tridgell     2007
5  * Copyright (C) Christian Ambach, 2010-2011
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *  
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *  
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  */
21
22 #include "includes.h"
23 #include "system/filesys.h"
24 #include "smbd/smbd.h"
25
26 /*
27
28   Some filesystems (even some journaled filesystems) require that a
29   fsync() be performed on many meta data operations to ensure that the
30   operation is guaranteed to remain in the filesystem after a power
31   failure. This is particularly important for some cluster filesystems
32   which are participating in a node failover system with clustered
33   Samba
34
35   On those filesystems this module provides a way to perform those
36   operations safely.  
37
38   most of the performance loss with this module is in fsync on close(). 
39   You can disable that with
40      syncops:onclose = no
41   that can be set either globally or per share.
42
43   On certain filesystems that only require the last data written to be
44   fsync()'ed, you can disable the metadata synchronization of this module with
45      syncops:onmeta = no
46   This option can be set either globally or per share.
47
48   you can also disable the module completely for a share with
49      syncops:disable = true
50
51  */
52
53 struct syncops_config_data {
54         bool onclose;
55         bool onmeta;
56         bool disable;
57 };
58
59 /*
60   given a filename, find the parent directory
61  */
62 static char *parent_dir(TALLOC_CTX *mem_ctx, const char *name)
63 {
64         const char *p = strrchr(name, '/');
65         if (p == NULL) {
66                 return talloc_strdup(mem_ctx, ".");
67         }
68         return talloc_strndup(mem_ctx, name, (p+1) - name);
69 }
70
71 /*
72   fsync a directory by name
73  */
74 static void syncops_sync_directory(connection_struct *conn,
75                                    char *dname)
76 {
77         struct smb_Dir *dir_hnd = NULL;
78         struct files_struct *dirfsp = NULL;
79         struct smb_filename smb_dname = { .base_name = dname };
80
81         dir_hnd = OpenDir(talloc_tos(),
82                           conn,
83                           &smb_dname,
84                           "*",
85                           0);
86         if (dir_hnd == NULL) {
87                 return;
88         }
89
90         dirfsp = dir_hnd_fetch_fsp(dir_hnd);
91
92         smb_vfs_fsync_sync(dirfsp);
93
94         TALLOC_FREE(dir_hnd);
95 }
96
97 /*
98   sync two meta data changes for 2 names
99  */
100 static void syncops_two_names(connection_struct *conn,
101                               const struct smb_filename *name1,
102                               const struct smb_filename *name2)
103 {
104         TALLOC_CTX *tmp_ctx = talloc_new(NULL);
105         char *parent1, *parent2;
106         parent1 = parent_dir(tmp_ctx, name1->base_name);
107         parent2 = parent_dir(tmp_ctx, name2->base_name);
108         if (!parent1 || !parent2) {
109                 talloc_free(tmp_ctx);
110                 return;
111         }
112         syncops_sync_directory(conn, parent1);
113         if (strcmp(parent1, parent2) != 0) {
114                 syncops_sync_directory(conn, parent2);
115         }
116         talloc_free(tmp_ctx);
117 }
118
119 /*
120   sync two meta data changes for 1 names
121  */
122 static void syncops_smb_fname(connection_struct *conn,
123                               const struct smb_filename *smb_fname)
124 {
125         char *parent = NULL;
126         if (smb_fname != NULL) {
127                 parent = parent_dir(NULL, smb_fname->base_name);
128                 if (parent != NULL) {
129                         syncops_sync_directory(conn, parent);
130                         talloc_free(parent);
131                 }
132         }
133 }
134
135
136 /*
137   renameat needs special handling, as we may need to fsync two directories
138  */
139 static int syncops_renameat(vfs_handle_struct *handle,
140                         files_struct *srcfsp,
141                         const struct smb_filename *smb_fname_src,
142                         files_struct *dstfsp,
143                         const struct smb_filename *smb_fname_dst)
144 {
145
146         int ret;
147         struct syncops_config_data *config;
148
149         SMB_VFS_HANDLE_GET_DATA(handle, config,
150                                 struct syncops_config_data,
151                                 return -1);
152
153         ret = SMB_VFS_NEXT_RENAMEAT(handle,
154                         srcfsp,
155                         smb_fname_src,
156                         dstfsp,
157                         smb_fname_dst);
158         if (ret == 0 && config->onmeta && !config->disable) {
159                 syncops_two_names(handle->conn,
160                                   smb_fname_src,
161                                   smb_fname_dst);
162         }
163         return ret;
164 }
165
166 #define SYNCOPS_NEXT_SMB_FNAME(op, fname, args) do {   \
167         int ret; \
168         struct smb_filename *full_fname = NULL; \
169         struct syncops_config_data *config; \
170         SMB_VFS_HANDLE_GET_DATA(handle, config, \
171                                 struct syncops_config_data, \
172                                 return -1); \
173         full_fname = full_path_from_dirfsp_atname(talloc_tos(), \
174                                 dirfsp, \
175                                 smb_fname); \
176         if (full_fname == NULL) { \
177                 return -1; \
178         } \
179         ret = SMB_VFS_NEXT_ ## op args; \
180         if (ret == 0 \
181         && config->onmeta && !config->disable \
182         && fname) syncops_smb_fname(dirfsp->conn, full_fname); \
183         TALLOC_FREE(full_fname); \
184         return ret; \
185 } while (0)
186
187 static int syncops_symlinkat(vfs_handle_struct *handle,
188                         const struct smb_filename *link_contents,
189                         struct files_struct *dirfsp,
190                         const struct smb_filename *smb_fname)
191 {
192         SYNCOPS_NEXT_SMB_FNAME(SYMLINKAT,
193                         smb_fname,
194                                 (handle,
195                                 link_contents,
196                                 dirfsp,
197                                 smb_fname));
198 }
199
200 static int syncops_linkat(vfs_handle_struct *handle,
201                         files_struct *srcfsp,
202                         const struct smb_filename *old_smb_fname,
203                         files_struct *dstfsp,
204                         const struct smb_filename *new_smb_fname,
205                         int flags)
206 {
207         int ret;
208         struct syncops_config_data *config;
209         struct smb_filename *old_full_fname = NULL;
210         struct smb_filename *new_full_fname = NULL;
211
212         SMB_VFS_HANDLE_GET_DATA(handle, config,
213                                 struct syncops_config_data,
214                                 return -1);
215
216         old_full_fname = full_path_from_dirfsp_atname(talloc_tos(),
217                                 srcfsp,
218                                 old_smb_fname);
219         if (old_full_fname == NULL) {
220                 return -1;
221         }
222         new_full_fname = full_path_from_dirfsp_atname(talloc_tos(),
223                                 dstfsp,
224                                 new_smb_fname);
225         if (new_full_fname == NULL) {
226                 TALLOC_FREE(old_full_fname);
227                 return -1;
228         }
229         ret = SMB_VFS_NEXT_LINKAT(handle,
230                         srcfsp,
231                         old_smb_fname,
232                         dstfsp,
233                         new_smb_fname,
234                         flags);
235
236         if (ret == 0 && config->onmeta && !config->disable) {
237                 syncops_two_names(handle->conn,
238                                   old_full_fname,
239                                   new_full_fname);
240         }
241         TALLOC_FREE(old_full_fname);
242         TALLOC_FREE(new_full_fname);
243         return ret;
244 }
245
246 static int syncops_openat(struct vfs_handle_struct *handle,
247                           const struct files_struct *dirfsp,
248                           const struct smb_filename *smb_fname,
249                           struct files_struct *fsp,
250                           int flags,
251                           mode_t mode)
252 {
253         SYNCOPS_NEXT_SMB_FNAME(OPENAT, (flags & O_CREAT ? smb_fname : NULL),
254                                (handle, dirfsp, smb_fname, fsp, flags, mode));
255 }
256
257 static int syncops_unlinkat(vfs_handle_struct *handle,
258                         files_struct *dirfsp,
259                         const struct smb_filename *smb_fname,
260                         int flags)
261 {
262         SYNCOPS_NEXT_SMB_FNAME(UNLINKAT,
263                         smb_fname,
264                                 (handle,
265                                 dirfsp,
266                                 smb_fname,
267                                 flags));
268 }
269
270 static int syncops_mknodat(vfs_handle_struct *handle,
271                         files_struct *dirfsp,
272                         const struct smb_filename *smb_fname,
273                         mode_t mode,
274                         SMB_DEV_T dev)
275 {
276         SYNCOPS_NEXT_SMB_FNAME(MKNODAT,
277                         smb_fname,
278                                 (handle,
279                                 dirfsp,
280                                 smb_fname,
281                                 mode,
282                                 dev));
283 }
284
285 static int syncops_mkdirat(vfs_handle_struct *handle,
286                         struct files_struct *dirfsp,
287                         const struct smb_filename *smb_fname,
288                         mode_t mode)
289 {
290         SYNCOPS_NEXT_SMB_FNAME(MKDIRAT,
291                         full_fname,
292                                 (handle,
293                                 dirfsp,
294                                 smb_fname,
295                                 mode));
296 }
297
298 /* close needs to be handled specially */
299 static int syncops_close(vfs_handle_struct *handle, files_struct *fsp)
300 {
301         struct syncops_config_data *config;
302
303         SMB_VFS_HANDLE_GET_DATA(handle, config,
304                                 struct syncops_config_data,
305                                 return -1);
306
307         if (fsp->fsp_flags.can_write && config->onclose) {
308                 /* ideally we'd only do this if we have written some
309                  data, but there is no flag for that in fsp yet. */
310                 fsync(fsp_get_io_fd(fsp));
311         }
312         return SMB_VFS_NEXT_CLOSE(handle, fsp);
313 }
314
315 static int syncops_connect(struct vfs_handle_struct *handle, const char *service,
316                            const char *user)
317 {
318
319         struct syncops_config_data *config;
320         int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
321         if (ret < 0) {
322                 return ret;
323         }
324
325         config = talloc_zero(handle->conn, struct syncops_config_data);
326         if (!config) {
327                 SMB_VFS_NEXT_DISCONNECT(handle);
328                 DEBUG(0, ("talloc_zero() failed\n"));
329                 return -1;
330         }
331
332         config->onclose = lp_parm_bool(SNUM(handle->conn), "syncops",
333                                         "onclose", true);
334
335         config->onmeta = lp_parm_bool(SNUM(handle->conn), "syncops",
336                                         "onmeta", true);
337
338         config->disable = lp_parm_bool(SNUM(handle->conn), "syncops",
339                                         "disable", false);
340
341         SMB_VFS_HANDLE_SET_DATA(handle, config,
342                                 NULL, struct syncops_config_data,
343                                 return -1);
344
345         return 0;
346
347 }
348
349 static struct vfs_fn_pointers vfs_syncops_fns = {
350         .connect_fn = syncops_connect,
351         .mkdirat_fn = syncops_mkdirat,
352         .openat_fn = syncops_openat,
353         .renameat_fn = syncops_renameat,
354         .unlinkat_fn = syncops_unlinkat,
355         .symlinkat_fn = syncops_symlinkat,
356         .linkat_fn = syncops_linkat,
357         .mknodat_fn = syncops_mknodat,
358         .close_fn = syncops_close,
359 };
360
361 static_decl_vfs;
362 NTSTATUS vfs_syncops_init(TALLOC_CTX *ctx)
363 {
364         NTSTATUS ret;
365
366         ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "syncops",
367                                &vfs_syncops_fns);
368
369         if (!NT_STATUS_IS_OK(ret))
370                 return ret;
371
372         return ret;
373 }