selftest/Samba4: make use of get_cmd_env_vars() to setup all relevant env variables
[samba.git] / source3 / modules / vfs_syncops.c
index 562195cbda7abf39003e7c89505cba85d666c097..a0d9809329683a6c94866e54b5b6944d233f3b9e 100644 (file)
@@ -2,6 +2,7 @@
  * ensure meta data operations are performed synchronously
  *
  * Copyright (C) Andrew Tridgell     2007
+ * Copyright (C) Christian Ambach, 2010-2011
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,6 +20,9 @@
  */
 
 #include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "source3/smbd/dir.h"
 
 /*
 
 
   On those filesystems this module provides a way to perform those
   operations safely.  
- */
 
-/*
   most of the performance loss with this module is in fsync on close(). 
-  You can disable that with syncops:onclose = no
+  You can disable that with
+     syncops:onclose = no
+  that can be set either globally or per share.
+
+  On certain filesystems that only require the last data written to be
+  fsync()'ed, you can disable the metadata synchronization of this module with
+     syncops:onmeta = no
+  This option can be set either globally or per share.
+
+  you can also disable the module completely for a share with
+     syncops:disable = true
+
  */
-static bool sync_onclose;
+
+struct syncops_config_data {
+       bool onclose;
+       bool onmeta;
+       bool disable;
+};
 
 /*
   given a filename, find the parent directory
@@ -54,39 +72,50 @@ static char *parent_dir(TALLOC_CTX *mem_ctx, const char *name)
 /*
   fsync a directory by name
  */
-static void syncops_sync_directory(const char *dname)
+static void syncops_sync_directory(connection_struct *conn,
+                                  char *dname)
 {
-#ifdef O_DIRECTORY
-       int fd = open(dname, O_DIRECTORY|O_RDONLY);
-       if (fd != -1) {
-               fsync(fd);
-               close(fd);
-       }
-#else
-       DIR *d = opendir(dname);
-       if (d != NULL) {
-               fsync(dirfd(d));
-               closedir(d);
-       }
-#endif
+       struct smb_Dir *dir_hnd = NULL;
+       struct files_struct *dirfsp = NULL;
+       struct smb_filename smb_dname = { .base_name = dname };
+       NTSTATUS status;
+
+       status = OpenDir(talloc_tos(),
+                        conn,
+                        &smb_dname,
+                        "*",
+                        0,
+                        &dir_hnd);
+       if (!NT_STATUS_IS_OK(status)) {
+               errno = map_errno_from_nt_status(status);
+               return;
+       }
+
+       dirfsp = dir_hnd_fetch_fsp(dir_hnd);
+
+       smb_vfs_fsync_sync(dirfsp);
+
+       TALLOC_FREE(dir_hnd);
 }
 
 /*
   sync two meta data changes for 2 names
  */
-static void syncops_two_names(const char *name1, const char *name2)
+static void syncops_two_names(connection_struct *conn,
+                             const struct smb_filename *name1,
+                             const struct smb_filename *name2)
 {
        TALLOC_CTX *tmp_ctx = talloc_new(NULL);
        char *parent1, *parent2;
-       parent1 = parent_dir(tmp_ctx, name1);
-       parent2 = parent_dir(tmp_ctx, name2);
+       parent1 = parent_dir(tmp_ctx, name1->base_name);
+       parent2 = parent_dir(tmp_ctx, name2->base_name);
        if (!parent1 || !parent2) {
                talloc_free(tmp_ctx);
                return;
        }
-       syncops_sync_directory(parent1);
+       syncops_sync_directory(conn, parent1);
        if (strcmp(parent1, parent2) != 0) {
-               syncops_sync_directory(parent2);                
+               syncops_sync_directory(conn, parent2);
        }
        talloc_free(tmp_ctx);
 }
@@ -94,138 +123,296 @@ static void syncops_two_names(const char *name1, const char *name2)
 /*
   sync two meta data changes for 1 names
  */
-static void syncops_name(const char *name)
+static void syncops_smb_fname(connection_struct *conn,
+                             const struct smb_filename *smb_fname)
 {
-       char *parent;
-       parent = parent_dir(NULL, name);
-       if (parent) {
-               syncops_sync_directory(parent);
-               talloc_free(parent);
+       char *parent = NULL;
+       if (smb_fname != NULL) {
+               parent = parent_dir(NULL, smb_fname->base_name);
+               if (parent != NULL) {
+                       syncops_sync_directory(conn, parent);
+                       talloc_free(parent);
+               }
        }
 }
 
+
 /*
-  sync two meta data changes for 1 names
+  renameat needs special handling, as we may need to fsync two directories
  */
-static void syncops_smb_fname(struct smb_filename *smb_fname)
+static int syncops_renameat(vfs_handle_struct *handle,
+                       files_struct *srcfsp,
+                       const struct smb_filename *smb_fname_src,
+                       files_struct *dstfsp,
+                       const struct smb_filename *smb_fname_dst)
 {
-       char *parent;
-       parent = parent_dir(NULL, smb_fname->base_name);
-       if (parent) {
-               syncops_sync_directory(parent);
-               talloc_free(parent);
-       }
-}
 
+       int ret;
+       struct smb_filename *full_fname_src = NULL;
+       struct smb_filename *full_fname_dst = NULL;
+       struct syncops_config_data *config;
 
-/*
-  rename needs special handling, as we may need to fsync two directories
- */
-static int syncops_rename(vfs_handle_struct *handle,
-                         const char *oldname, const char *newname)
-{
-       int ret = SMB_VFS_NEXT_RENAME(handle, oldname, newname);
-       if (ret == 0) {
-               syncops_two_names(oldname, newname);
+       SMB_VFS_HANDLE_GET_DATA(handle, config,
+                               struct syncops_config_data,
+                               return -1);
+
+       ret = SMB_VFS_NEXT_RENAMEAT(handle,
+                       srcfsp,
+                       smb_fname_src,
+                       dstfsp,
+                       smb_fname_dst);
+       if (ret == -1) {
+               return ret;
+       }
+       if (config->disable) {
+               return ret;
+       }
+       if (!config->onmeta) {
+               return ret;
+       }
+
+       full_fname_src = full_path_from_dirfsp_atname(talloc_tos(),
+                                                     srcfsp,
+                                                     smb_fname_src);
+       if (full_fname_src == NULL) {
+               errno = ENOMEM;
+               return ret;
+       }
+       full_fname_dst = full_path_from_dirfsp_atname(talloc_tos(),
+                                                     dstfsp,
+                                                     smb_fname_dst);
+       if (full_fname_dst == NULL) {
+               TALLOC_FREE(full_fname_src);
+               errno = ENOMEM;
+               return ret;
        }
+       syncops_two_names(handle->conn,
+                         full_fname_src,
+                         full_fname_dst);
+       TALLOC_FREE(full_fname_src);
+       TALLOC_FREE(full_fname_dst);
        return ret;
 }
 
-/* handle the rest with a macro */
-#define SYNCOPS_NEXT(op, fname, args) do {   \
-       int ret = SMB_VFS_NEXT_ ## op args; \
-       if (ret == 0 && fname) syncops_name(fname); \
-       return ret; \
-} while (0)
-
 #define SYNCOPS_NEXT_SMB_FNAME(op, fname, args) do {   \
-       int ret = SMB_VFS_NEXT_ ## op args; \
-       if (ret == 0 && fname) syncops_smb_fname(fname); \
+       int ret; \
+       struct smb_filename *full_fname = NULL; \
+       struct syncops_config_data *config; \
+       SMB_VFS_HANDLE_GET_DATA(handle, config, \
+                               struct syncops_config_data, \
+                               return -1); \
+       ret = SMB_VFS_NEXT_ ## op args; \
+       if (ret != 0) { \
+               return ret; \
+       } \
+       if (config->disable) { \
+               return ret; \
+       } \
+       if (!config->onmeta) { \
+               return ret; \
+       } \
+       full_fname = full_path_from_dirfsp_atname(talloc_tos(), \
+                               dirfsp, \
+                               smb_fname); \
+       if (full_fname == NULL) { \
+               return ret; \
+       } \
+       syncops_smb_fname(dirfsp->conn, full_fname); \
+       TALLOC_FREE(full_fname); \
        return ret; \
 } while (0)
 
-static int syncops_symlink(vfs_handle_struct *handle,
-                          const char *oldname, const char *newname)
+static int syncops_symlinkat(vfs_handle_struct *handle,
+                       const struct smb_filename *link_contents,
+                       struct files_struct *dirfsp,
+                       const struct smb_filename *smb_fname)
 {
-       SYNCOPS_NEXT(SYMLINK, newname, (handle, oldname, newname));
+       SYNCOPS_NEXT_SMB_FNAME(SYMLINKAT,
+                       smb_fname,
+                               (handle,
+                               link_contents,
+                               dirfsp,
+                               smb_fname));
 }
 
-static int syncops_link(vfs_handle_struct *handle,
-                        const char *oldname, const char *newname)
+static int syncops_linkat(vfs_handle_struct *handle,
+                       files_struct *srcfsp,
+                       const struct smb_filename *old_smb_fname,
+                       files_struct *dstfsp,
+                       const struct smb_filename *new_smb_fname,
+                       int flags)
 {
-       SYNCOPS_NEXT(LINK, newname, (handle, oldname, newname));
-}
+       int ret;
+       struct syncops_config_data *config;
+       struct smb_filename *old_full_fname = NULL;
+       struct smb_filename *new_full_fname = NULL;
 
-static int syncops_open(vfs_handle_struct *handle,
-                       struct smb_filename *smb_fname, files_struct *fsp,
-                       int flags, mode_t mode)
-{
-       SYNCOPS_NEXT_SMB_FNAME(OPEN, (flags&O_CREAT?smb_fname:NULL),
-                              (handle, smb_fname, fsp, flags, mode));
+       SMB_VFS_HANDLE_GET_DATA(handle, config,
+                               struct syncops_config_data,
+                               return -1);
+
+       ret = SMB_VFS_NEXT_LINKAT(handle,
+                       srcfsp,
+                       old_smb_fname,
+                       dstfsp,
+                       new_smb_fname,
+                       flags);
+
+       if (ret == -1) {
+               return ret;
+       }
+       if (config->disable) {
+               return ret;
+       }
+       if (!config->onmeta) {
+               return ret;
+       }
+
+       old_full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+                                                     srcfsp,
+                                                     old_smb_fname);
+       if (old_full_fname == NULL) {
+               return ret;
+       }
+       new_full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+                                                     dstfsp,
+                                                     new_smb_fname);
+       if (new_full_fname == NULL) {
+               TALLOC_FREE(old_full_fname);
+               return ret;
+       }
+       syncops_two_names(handle->conn,
+                         old_full_fname,
+                         new_full_fname);
+       TALLOC_FREE(old_full_fname);
+       TALLOC_FREE(new_full_fname);
+       return ret;
 }
 
-static int syncops_unlink(vfs_handle_struct *handle, const char *fname)
+static int syncops_openat(struct vfs_handle_struct *handle,
+                         const struct files_struct *dirfsp,
+                         const struct smb_filename *smb_fname,
+                         struct files_struct *fsp,
+                         const struct vfs_open_how *how)
 {
-        SYNCOPS_NEXT(UNLINK, fname, (handle, fname));
+       SYNCOPS_NEXT_SMB_FNAME(OPENAT, (how->flags & O_CREAT ? smb_fname : NULL),
+                              (handle, dirfsp, smb_fname, fsp, how));
 }
 
-static int syncops_mknod(vfs_handle_struct *handle,
-                        const char *fname, mode_t mode, SMB_DEV_T dev)
+static int syncops_unlinkat(vfs_handle_struct *handle,
+                       files_struct *dirfsp,
+                       const struct smb_filename *smb_fname,
+                       int flags)
 {
-        SYNCOPS_NEXT(MKNOD, fname, (handle, fname, mode, dev));
+        SYNCOPS_NEXT_SMB_FNAME(UNLINKAT,
+                       smb_fname,
+                               (handle,
+                               dirfsp,
+                               smb_fname,
+                               flags));
 }
 
-static int syncops_mkdir(vfs_handle_struct *handle,  const char *fname, mode_t mode)
+static int syncops_mknodat(vfs_handle_struct *handle,
+                       files_struct *dirfsp,
+                       const struct smb_filename *smb_fname,
+                       mode_t mode,
+                       SMB_DEV_T dev)
 {
-        SYNCOPS_NEXT(MKDIR, fname, (handle, fname, mode));
+        SYNCOPS_NEXT_SMB_FNAME(MKNODAT,
+                       smb_fname,
+                               (handle,
+                               dirfsp,
+                               smb_fname,
+                               mode,
+                               dev));
 }
 
-static int syncops_rmdir(vfs_handle_struct *handle,  const char *fname)
+static int syncops_mkdirat(vfs_handle_struct *handle,
+                       struct files_struct *dirfsp,
+                       const struct smb_filename *smb_fname,
+                       mode_t mode)
 {
-        SYNCOPS_NEXT(RMDIR, fname, (handle, fname));
+        SYNCOPS_NEXT_SMB_FNAME(MKDIRAT,
+                       full_fname,
+                               (handle,
+                               dirfsp,
+                               smb_fname,
+                               mode));
 }
 
 /* close needs to be handled specially */
 static int syncops_close(vfs_handle_struct *handle, files_struct *fsp)
 {
-       if (fsp->can_write && sync_onclose) {
+       struct syncops_config_data *config;
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config,
+                               struct syncops_config_data,
+                               return -1);
+
+       if (fsp->fsp_flags.can_write && config->onclose) {
                /* ideally we'd only do this if we have written some
                 data, but there is no flag for that in fsp yet. */
-               fsync(fsp->fh->fd);
+               fsync(fsp_get_io_fd(fsp));
        }
        return SMB_VFS_NEXT_CLOSE(handle, fsp);
 }
 
+static int syncops_connect(struct vfs_handle_struct *handle, const char *service,
+                          const char *user)
+{
 
-/* VFS operations structure */
+       struct syncops_config_data *config;
+       int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+       if (ret < 0) {
+               return ret;
+       }
 
-static vfs_op_tuple syncops_ops[] = {
-       /* directory operations */
-        {SMB_VFS_OP(syncops_mkdir),       SMB_VFS_OP_MKDIR,       SMB_VFS_LAYER_TRANSPARENT},
-        {SMB_VFS_OP(syncops_rmdir),       SMB_VFS_OP_RMDIR,       SMB_VFS_LAYER_TRANSPARENT},
+       config = talloc_zero(handle->conn, struct syncops_config_data);
+       if (!config) {
+               SMB_VFS_NEXT_DISCONNECT(handle);
+               DEBUG(0, ("talloc_zero() failed\n"));
+               return -1;
+       }
+
+       config->onclose = lp_parm_bool(SNUM(handle->conn), "syncops",
+                                       "onclose", true);
+
+       config->onmeta = lp_parm_bool(SNUM(handle->conn), "syncops",
+                                       "onmeta", true);
+
+       config->disable = lp_parm_bool(SNUM(handle->conn), "syncops",
+                                       "disable", false);
 
-        /* File operations */
-        {SMB_VFS_OP(syncops_open),       SMB_VFS_OP_OPEN,     SMB_VFS_LAYER_TRANSPARENT},
-        {SMB_VFS_OP(syncops_rename),     SMB_VFS_OP_RENAME,   SMB_VFS_LAYER_TRANSPARENT},
-        {SMB_VFS_OP(syncops_unlink),     SMB_VFS_OP_UNLINK,   SMB_VFS_LAYER_TRANSPARENT},
-        {SMB_VFS_OP(syncops_symlink),    SMB_VFS_OP_SYMLINK,  SMB_VFS_LAYER_TRANSPARENT},
-        {SMB_VFS_OP(syncops_link),       SMB_VFS_OP_LINK,     SMB_VFS_LAYER_TRANSPARENT},
-        {SMB_VFS_OP(syncops_mknod),      SMB_VFS_OP_MKNOD,    SMB_VFS_LAYER_TRANSPARENT},
-       {SMB_VFS_OP(syncops_close),      SMB_VFS_OP_CLOSE,    SMB_VFS_LAYER_TRANSPARENT},
+       SMB_VFS_HANDLE_SET_DATA(handle, config,
+                               NULL, struct syncops_config_data,
+                               return -1);
+
+       return 0;
+
+}
 
-       {SMB_VFS_OP(NULL), SMB_VFS_OP_NOOP, SMB_VFS_LAYER_NOOP}
+static struct vfs_fn_pointers vfs_syncops_fns = {
+       .connect_fn = syncops_connect,
+       .mkdirat_fn = syncops_mkdirat,
+       .openat_fn = syncops_openat,
+       .renameat_fn = syncops_renameat,
+       .unlinkat_fn = syncops_unlinkat,
+       .symlinkat_fn = syncops_symlinkat,
+       .linkat_fn = syncops_linkat,
+       .mknodat_fn = syncops_mknodat,
+       .close_fn = syncops_close,
 };
 
-NTSTATUS vfs_syncops_init(void)
+static_decl_vfs;
+NTSTATUS vfs_syncops_init(TALLOC_CTX *ctx)
 {
        NTSTATUS ret;
 
-       ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "syncops", syncops_ops);
+       ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "syncops",
+                              &vfs_syncops_fns);
 
        if (!NT_STATUS_IS_OK(ret))
                return ret;
 
-       sync_onclose = lp_parm_bool(-1, "syncops", "onclose", true);
-       
        return ret;
 }