shadow_copy2: disable "snapdir:crossmountpoints" if the snapdir is absolute.
[mat/samba.git] / source3 / modules / vfs_shadow_copy2.c
index 227453d27794303dd7538dc3846e691fdc44ff5d..adcc855320d95fe8f569484335de8fe7e41cd79a 100644 (file)
 #include <ccan/hash/hash.h>
 #include "util_tdb.h"
 
-#define GMT_NAME_LEN 24 /* length of a @GMT- name */
-#define GMT_FORMAT "@GMT-%Y.%m.%d-%H.%M.%S"
+struct shadow_copy2_config {
+       char *gmt_format;
+       bool use_sscanf;
+       bool use_localtime;
+       char *snapdir;
+       bool snapdirseverywhere;
+       bool crossmountpoints;
+       bool fixinodes;
+       char *sort_order;
+       bool snapdir_absolute;
+};
 
 static bool shadow_copy2_find_slashes(TALLOC_CTX *mem_ctx, const char *str,
                                      size_t **poffsets,
@@ -144,44 +153,62 @@ static bool shadow_copy2_find_slashes(TALLOC_CTX *mem_ctx, const char *str,
        return true;
 }
 
+/**
+ * Given a timstamp, build the string to insert into a path
+ * as a path component for creating the local path to the
+ * snapshot at the given timestamp of the input path.
+ */
 static char *shadow_copy2_insert_string(TALLOC_CTX *mem_ctx,
                                        struct vfs_handle_struct *handle,
                                        time_t snapshot)
 {
-       const char *fmt;
        struct tm snap_tm;
-       fstring gmt;
-       size_t gmt_len;
-
-       if (localtime_r(&snapshot, &snap_tm) == 0) {
-               DEBUG(10, ("gmtime_r failed\n"));
-               return NULL;
-       }
-       fmt = lp_parm_const_string(SNUM(handle->conn), "shadow",
-                                  "format", GMT_FORMAT);
-
-       if (lp_parm_bool(SNUM(handle->conn), "shadow", "sscanf", false)) {
-               gmt_len = snprintf(gmt, sizeof(gmt), fmt,
-                                  (unsigned long)snapshot);
-               if (gmt_len == 0) {
+       fstring snaptime_string;
+       size_t snaptime_len;
+       struct shadow_copy2_config *config;
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config, struct shadow_copy2_config,
+                               return NULL);
+
+       if (config->use_sscanf) {
+               snaptime_len = snprintf(snaptime_string,
+                                       sizeof(snaptime_string),
+                                       config->gmt_format,
+                                       (unsigned long)snapshot);
+               if (snaptime_len <= 0) {
                        DEBUG(10, ("snprintf failed\n"));
                        return NULL;
                }
        } else {
-               gmt_len = strftime(gmt, sizeof(gmt), fmt,
-                                  &snap_tm);
-               if (gmt_len == 0) {
+               if (config->use_localtime) {
+                       if (localtime_r(&snapshot, &snap_tm) == 0) {
+                               DEBUG(10, ("gmtime_r failed\n"));
+                               return NULL;
+                       }
+               } else {
+                       if (gmtime_r(&snapshot, &snap_tm) == 0) {
+                               DEBUG(10, ("gmtime_r failed\n"));
+                               return NULL;
+                       }
+               }
+               snaptime_len = strftime(snaptime_string,
+                                       sizeof(snaptime_string),
+                                       config->gmt_format,
+                                       &snap_tm);
+               if (snaptime_len == 0) {
                        DEBUG(10, ("strftime failed\n"));
                        return NULL;
                }
        }
        return talloc_asprintf(mem_ctx, "/%s/%s",
-                              lp_parm_const_string(
-                                      SNUM(handle->conn), "shadow", "snapdir",
-                                      ".snapshots"),
-                              gmt);
+                              config->snapdir, snaptime_string);
 }
 
+/**
+ * Strip a snapshot component from an filename as
+ * handed in via the smb layer.
+ * Returns the parsed timestamp and the stripped filename.
+ */
 static bool shadow_copy2_strip_snapshot(TALLOC_CTX *mem_ctx,
                                        struct vfs_handle_struct *handle,
                                        const char *name,
@@ -194,6 +221,10 @@ static bool shadow_copy2_strip_snapshot(TALLOC_CTX *mem_ctx,
        char *q;
        char *stripped;
        size_t rest_len, dst_len;
+       struct shadow_copy2_config *config;
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config, struct shadow_copy2_config,
+                               return false);
 
        p = strstr_m(name, "@GMT-");
        if (p == NULL) {
@@ -207,7 +238,7 @@ static bool shadow_copy2_strip_snapshot(TALLOC_CTX *mem_ctx,
                goto no_snapshot;
        }
        tm.tm_isdst = -1;
-       timestamp = mktime(&tm);
+       timestamp = timegm(&tm);
        if (timestamp == (time_t)-1) {
                goto no_snapshot;
        }
@@ -230,8 +261,7 @@ static bool shadow_copy2_strip_snapshot(TALLOC_CTX *mem_ctx,
        rest_len = strlen(q);
        dst_len = (p-name) + rest_len;
 
-       if (lp_parm_bool(SNUM(handle->conn), "shadow", "snapdirseverywhere",
-                        false)) {
+       if (config->snapdirseverywhere) {
                char *insert;
                bool have_insert;
                insert = shadow_copy2_insert_string(talloc_tos(), handle,
@@ -300,6 +330,11 @@ static char *shadow_copy2_find_mount_point(TALLOC_CTX *mem_ctx,
        return path;
 }
 
+/**
+ * Convert from a name as handed in via the SMB layer
+ * and a timestamp into the local path of the snapshot
+ * of the provided file at the provided time.
+ */
 static char *shadow_copy2_convert(TALLOC_CTX *mem_ctx,
                                  struct vfs_handle_struct *handle,
                                  const char *name, time_t timestamp)
@@ -315,6 +350,10 @@ static char *shadow_copy2_convert(TALLOC_CTX *mem_ctx,
        size_t insertlen;
        int i, saved_errno;
        size_t min_offset;
+       struct shadow_copy2_config *config;
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config, struct shadow_copy2_config,
+                               return NULL);
 
        path = talloc_asprintf(mem_ctx, "%s/%s", handle->conn->connectpath,
                               name);
@@ -357,8 +396,7 @@ static char *shadow_copy2_convert(TALLOC_CTX *mem_ctx,
 
        min_offset = 0;
 
-       if (!lp_parm_bool(SNUM(handle->conn), "shadow", "crossmountpoints",
-                         false)) {
+       if (!config->crossmountpoints) {
                char *mount_point;
 
                mount_point = shadow_copy2_find_mount_point(talloc_tos(),
@@ -442,7 +480,12 @@ fail:
 static void convert_sbuf(vfs_handle_struct *handle, const char *fname,
                         SMB_STRUCT_STAT *sbuf)
 {
-       if (lp_parm_bool(SNUM(handle->conn), "shadow", "fixinodes", False)) {
+       struct shadow_copy2_config *config;
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config, struct shadow_copy2_config,
+                               return);
+
+       if (config->fixinodes) {
                /* some snapshot systems, like GPFS, return the name
                   device:inode for the snapshot files as the current
                   files. That breaks the 'restore' button in the shadow copy
@@ -703,7 +746,6 @@ static int shadow_copy2_unlink(vfs_handle_struct *handle,
        char *stripped;
        int ret, saved_errno;
        struct smb_filename *conv;
-       NTSTATUS status;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle,
                                         smb_fname->base_name,
@@ -713,8 +755,8 @@ static int shadow_copy2_unlink(vfs_handle_struct *handle,
        if (timestamp == 0) {
                return SMB_VFS_NEXT_UNLINK(handle, smb_fname);
        }
-       status = copy_smb_filename(talloc_tos(), smb_fname, &conv);
-       if (!NT_STATUS_IS_OK(status)) {
+       conv = cp_smb_filename(talloc_tos(), smb_fname);
+       if (conv == NULL) {
                errno = ENOMEM;
                return -1;
        }
@@ -820,7 +862,6 @@ static int shadow_copy2_ntimes(vfs_handle_struct *handle,
        char *stripped;
        int ret, saved_errno;
        struct smb_filename *conv;
-       NTSTATUS status;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle,
                                         smb_fname->base_name,
@@ -830,8 +871,8 @@ static int shadow_copy2_ntimes(vfs_handle_struct *handle,
        if (timestamp == 0) {
                return SMB_VFS_NEXT_NTIMES(handle, smb_fname, ft);
        }
-       status = copy_smb_filename(talloc_tos(), smb_fname, &conv);
-       if (!NT_STATUS_IS_OK(status)) {
+       conv = cp_smb_filename(talloc_tos(), smb_fname);
+       if (conv == NULL) {
                errno = ENOMEM;
                return -1;
        }
@@ -956,17 +997,25 @@ done:
        return result;
 }
 
+/**
+ * Check whether a given directory contains a
+ * snapshot directory as direct subdirectory.
+ * If yes, return the path of the snapshot-subdir,
+ * otherwise return NULL.
+ */
 static char *have_snapdir(struct vfs_handle_struct *handle,
                          const char *path)
 {
        struct smb_filename smb_fname;
        int ret;
+       struct shadow_copy2_config *config;
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config, struct shadow_copy2_config,
+                               return NULL);
 
        ZERO_STRUCT(smb_fname);
-       smb_fname.base_name = talloc_asprintf(
-               talloc_tos(), "%s/%s", path,
-               lp_parm_const_string(SNUM(handle->conn), "shadow", "snapdir",
-                                    ".snapshots"));
+       smb_fname.base_name = talloc_asprintf(talloc_tos(), "%s/%s",
+                                             path, config->snapdir);
        if (smb_fname.base_name == NULL) {
                return NULL;
        }
@@ -979,12 +1028,20 @@ static char *have_snapdir(struct vfs_handle_struct *handle,
        return NULL;
 }
 
+/**
+ * Find the snapshot directory (if any) for the given
+ * filename (which is relative to the share).
+ */
 static char *shadow_copy2_find_snapdir(TALLOC_CTX *mem_ctx,
                                       struct vfs_handle_struct *handle,
                                       struct smb_filename *smb_fname)
 {
        char *path, *p;
        char *snapdir;
+       struct shadow_copy2_config *config;
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config, struct shadow_copy2_config,
+                               return NULL);
 
        path = talloc_asprintf(mem_ctx, "%s/%s",
                               handle->conn->connectpath,
@@ -1021,14 +1078,18 @@ static bool shadow_copy2_snapshot_to_gmt(vfs_handle_struct *handle,
        time_t timestamp_t;
        unsigned long int timestamp_long;
        const char *fmt;
+       struct shadow_copy2_config *config;
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config, struct shadow_copy2_config,
+                               return NULL);
 
-       fmt = lp_parm_const_string(SNUM(handle->conn), "shadow",
-                                  "format", GMT_FORMAT);
+       fmt = config->gmt_format;
 
        ZERO_STRUCT(timestamp);
-       if (lp_parm_bool(SNUM(handle->conn), "shadow", "sscanf", false)) {
+       if (config->use_sscanf) {
                if (sscanf(name, fmt, &timestamp_long) != 1) {
-                       DEBUG(10, ("shadow_copy2_snapshot_to_gmt: no sscanf match %s: %s\n",
+                       DEBUG(10, ("shadow_copy2_snapshot_to_gmt: "
+                                  "no sscanf match %s: %s\n",
                                   fmt, name));
                        return false;
                }
@@ -1036,13 +1097,15 @@ static bool shadow_copy2_snapshot_to_gmt(vfs_handle_struct *handle,
                gmtime_r(&timestamp_t, &timestamp);
        } else {
                if (strptime(name, fmt, &timestamp) == NULL) {
-                       DEBUG(10, ("shadow_copy2_snapshot_to_gmt: no match %s: %s\n",
+                       DEBUG(10, ("shadow_copy2_snapshot_to_gmt: "
+                                  "no match %s: %s\n",
                                   fmt, name));
                        return false;
                }
-               DEBUG(10, ("shadow_copy2_snapshot_to_gmt: match %s: %s\n", fmt, name));
+               DEBUG(10, ("shadow_copy2_snapshot_to_gmt: match %s: %s\n",
+                          fmt, name));
                
-               if (lp_parm_bool(SNUM(handle->conn), "shadow", "localtime", false)) {
+               if (config->use_localtime) {
                        timestamp.tm_isdst = -1;
                        timestamp_t = mktime(&timestamp);
                        gmtime_r(&timestamp_t, &timestamp);
@@ -1071,9 +1134,12 @@ static void shadow_copy2_sort_data(vfs_handle_struct *handle,
 {
        int (*cmpfunc)(const void *, const void *);
        const char *sort;
+       struct shadow_copy2_config *config;
 
-       sort = lp_parm_const_string(SNUM(handle->conn), "shadow",
-                                   "sort", "desc");
+       SMB_VFS_HANDLE_GET_DATA(handle, config, struct shadow_copy2_config,
+                               return);
+
+       sort = config->sort_order;
        if (sort == NULL) {
                return;
        }
@@ -1181,6 +1247,7 @@ static int shadow_copy2_get_shadow_copy_data(
 static NTSTATUS shadow_copy2_fget_nt_acl(vfs_handle_struct *handle,
                                        struct files_struct *fsp,
                                        uint32 security_info,
+                                        TALLOC_CTX *mem_ctx,
                                        struct security_descriptor **ppdesc)
 {
        time_t timestamp;
@@ -1195,6 +1262,7 @@ static NTSTATUS shadow_copy2_fget_nt_acl(vfs_handle_struct *handle,
        }
        if (timestamp == 0) {
                return SMB_VFS_NEXT_FGET_NT_ACL(handle, fsp, security_info,
+                                               mem_ctx,
                                                ppdesc);
        }
        conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp);
@@ -1202,7 +1270,8 @@ static NTSTATUS shadow_copy2_fget_nt_acl(vfs_handle_struct *handle,
        if (conv == NULL) {
                return map_nt_error_from_unix(errno);
        }
-       status = SMB_VFS_NEXT_GET_NT_ACL(handle, conv, security_info, ppdesc);
+       status = SMB_VFS_NEXT_GET_NT_ACL(handle, conv, security_info,
+                                        mem_ctx, ppdesc);
        TALLOC_FREE(conv);
        return status;
 }
@@ -1210,6 +1279,7 @@ static NTSTATUS shadow_copy2_fget_nt_acl(vfs_handle_struct *handle,
 static NTSTATUS shadow_copy2_get_nt_acl(vfs_handle_struct *handle,
                                        const char *fname,
                                        uint32 security_info,
+                                       TALLOC_CTX *mem_ctx,
                                        struct security_descriptor **ppdesc)
 {
        time_t timestamp;
@@ -1223,14 +1293,15 @@ static NTSTATUS shadow_copy2_get_nt_acl(vfs_handle_struct *handle,
        }
        if (timestamp == 0) {
                return SMB_VFS_NEXT_GET_NT_ACL(handle, fname, security_info,
-                                              ppdesc);
+                                              mem_ctx, ppdesc);
        }
        conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp);
        TALLOC_FREE(stripped);
        if (conv == NULL) {
                return map_nt_error_from_unix(errno);
        }
-       status = SMB_VFS_NEXT_GET_NT_ACL(handle, conv, security_info, ppdesc);
+       status = SMB_VFS_NEXT_GET_NT_ACL(handle, conv, security_info,
+                                        mem_ctx, ppdesc);
        TALLOC_FREE(conv);
        return status;
 }
@@ -1501,8 +1572,106 @@ static int shadow_copy2_get_real_filename(struct vfs_handle_struct *handle,
        return ret;
 }
 
+static int shadow_copy2_connect(struct vfs_handle_struct *handle,
+                               const char *service, const char *user)
+{
+       struct shadow_copy2_config *config;
+       int ret;
+       const char *snapdir;
+       const char *gmt_format;
+       const char *sort_order;
+
+       DEBUG(10, (__location__ ": cnum[%u], connectpath[%s]\n",
+                  (unsigned)handle->conn->cnum,
+                  handle->conn->connectpath));
+
+       ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+       if (ret < 0) {
+               return ret;
+       }
+
+       config = talloc_zero(handle->conn, struct shadow_copy2_config);
+       if (config == NULL) {
+               DEBUG(0, ("talloc_zero() failed\n"));
+               errno = ENOMEM;
+               return -1;
+       }
+
+       gmt_format = lp_parm_const_string(SNUM(handle->conn),
+                                         "shadow", "format",
+                                         GMT_FORMAT);
+       config->gmt_format = talloc_strdup(config, gmt_format);
+       if (config->gmt_format == NULL) {
+               DEBUG(0, ("talloc_strdup() failed\n"));
+               errno = ENOMEM;
+               return -1;
+       }
+
+       config->use_sscanf = lp_parm_bool(SNUM(handle->conn),
+                                         "shadow", "sscanf", false);
+
+       config->use_localtime = lp_parm_bool(SNUM(handle->conn),
+                                            "shadow", "localtime",
+                                            false);
+
+       snapdir = lp_parm_const_string(SNUM(handle->conn),
+                                      "shadow", "snapdir",
+                                      ".snapshots");
+       config->snapdir = talloc_strdup(config, snapdir);
+       if (config->snapdir == NULL) {
+               DEBUG(0, ("talloc_strdup() failed\n"));
+               errno = ENOMEM;
+               return -1;
+       }
+
+       config->snapdirseverywhere = lp_parm_bool(SNUM(handle->conn),
+                                                 "shadow",
+                                                 "snapdirseverywhere",
+                                                 false);
+
+       config->crossmountpoints = lp_parm_bool(SNUM(handle->conn),
+                                               "shadow", "crossmountpoints",
+                                               false);
+
+       config->fixinodes = lp_parm_bool(SNUM(handle->conn),
+                                        "shadow", "fixinodes",
+                                        false);
+
+       sort_order = lp_parm_const_string(SNUM(handle->conn),
+                                         "shadow", "sort", "desc");
+       config->sort_order = talloc_strdup(config, sort_order);
+       if (config->sort_order == NULL) {
+               DEBUG(0, ("talloc_strdup() failed\n"));
+               errno = ENOMEM;
+               return -1;
+       }
+
+       if (config->snapdir[0] == '/') {
+               config->snapdir_absolute = true;
+               if (config->snapdirseverywhere == true) {
+                       DEBUG(1, (__location__ " Warning: An absolute snapdir "
+                                 "is incompatible with 'snapdirseverywhere', "
+                                 "setting 'snapdirseverywhere' to false.\n"));
+                       config->snapdirseverywhere = false;
+               }
+
+               if (config->crossmountpoints == true) {
+                       DEBUG(1, (__location__ " Warning: 'crossmountpoints' "
+                                 "is not supported with an absolute snapdir. "
+                                 "Disabling it.\n"));
+                       config->crossmountpoints = false;
+               }
+       }
+
+       SMB_VFS_HANDLE_SET_DATA(handle, config,
+                               NULL, struct shadow_copy2_config,
+                               return -1);
+
+       return 0;
+}
 
 static struct vfs_fn_pointers vfs_shadow_copy2_fns = {
+       .connect_fn = shadow_copy2_connect,
        .opendir_fn = shadow_copy2_opendir,
        .rename_fn = shadow_copy2_rename,
        .link_fn = shadow_copy2_link,