shadow_copy2: introduce "shadow:mountpoint" option
[mat/samba.git] / source3 / modules / vfs_shadow_copy2.c
index 932f93f693bdf467ed0f4fdfed46442f4dd5dd43..afe2ff4638d16720fdfff0673e3348acb9f7fbf4 100644 (file)
 #include <ccan/hash/hash.h>
 #include "util_tdb.h"
 
+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;
+       char *basedir;
+       char *mount_point;
+};
+
 static bool shadow_copy2_find_slashes(TALLOC_CTX *mem_ctx, const char *str,
                                      size_t **poffsets,
                                      unsigned *pnum_offsets)
@@ -141,27 +155,34 @@ 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 snaptime_string;
        size_t snaptime_len;
+       struct shadow_copy2_config *config;
 
-       fmt = lp_parm_const_string(SNUM(handle->conn), "shadow",
-                                  "format", GMT_FORMAT);
+       SMB_VFS_HANDLE_GET_DATA(handle, config, struct shadow_copy2_config,
+                               return NULL);
 
-       if (lp_parm_bool(SNUM(handle->conn), "shadow", "sscanf", false)) {
-               snaptime_len = snprintf(snaptime_string, sizeof(snaptime_string), fmt,
-                                  (unsigned long)snapshot);
+       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 {
-               if (lp_parm_bool(SNUM(handle->conn), "shadow", "localtime", false)) {
+               if (config->use_localtime) {
                        if (localtime_r(&snapshot, &snap_tm) == 0) {
                                DEBUG(10, ("gmtime_r failed\n"));
                                return NULL;
@@ -172,18 +193,17 @@ static char *shadow_copy2_insert_string(TALLOC_CTX *mem_ctx,
                                return NULL;
                        }
                }
-               snaptime_len = strftime(snaptime_string, sizeof(snaptime_string), fmt,
-                                  &snap_tm);
+               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"),
-                              snaptime_string);
+                              config->snapdir, snaptime_string);
 }
 
 /**
@@ -203,6 +223,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) {
@@ -239,8 +263,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,
@@ -309,6 +332,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)
@@ -324,6 +352,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);
@@ -366,8 +398,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(),
@@ -451,7 +482,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
@@ -974,12 +1010,14 @@ static char *have_snapdir(struct vfs_handle_struct *handle,
 {
        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;
        }
@@ -992,12 +1030,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,
@@ -1034,12 +1080,15 @@ 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",
@@ -1058,7 +1107,7 @@ static bool shadow_copy2_snapshot_to_gmt(vfs_handle_struct *handle,
                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);
@@ -1087,9 +1136,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;
+
+       SMB_VFS_HANDLE_GET_DATA(handle, config, struct shadow_copy2_config,
+                               return);
 
-       sort = lp_parm_const_string(SNUM(handle->conn), "shadow",
-                                   "sort", "desc");
+       sort = config->sort_order;
        if (sort == NULL) {
                return;
        }
@@ -1522,8 +1574,193 @@ 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;
+       const char *basedir;
+       const char *mount_point;
+
+       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;
+       }
+
+       mount_point = lp_parm_const_string(SNUM(handle->conn),
+                                          "shadow", "mountpoint", NULL);
+       if (mount_point != NULL) {
+               if (mount_point[0] != '/') {
+                       DEBUG(1, (__location__ " Warning: 'mountpoint' is "
+                                 "relative ('%s'), but it has to be an "
+                                 "absolute path. Ignoring provided value.\n",
+                                 mount_point));
+                       mount_point = NULL;
+               } else {
+                       char *p;
+                       p = strstr(handle->conn->connectpath, mount_point);
+                       if (p != handle->conn->connectpath) {
+                               DEBUG(1, ("Warning: mount_point (%s) is not a "
+                                         "subdirectory of the share root "
+                                         "(%s). Ignoring provided value.\n",
+                                         mount_point,
+                                         handle->conn->connectpath));
+                               mount_point = NULL;
+                       }
+               }
+       }
+
+       if (mount_point != NULL) {
+               config->mount_point = talloc_strdup(config, mount_point);
+               if (config->mount_point == NULL) {
+                       DEBUG(0, (__location__ " talloc_strdup() failed\n"));
+                       return -1;
+               }
+       } else {
+               config->mount_point = shadow_copy2_find_mount_point(config,
+                                                                   handle);
+               if (config->mount_point == NULL) {
+                       DEBUG(0, (__location__ ": shadow_copy2_find_mount_point"
+                                 " failed: %s\n", strerror(errno)));
+                       return -1;
+               }
+       }
+
+       basedir = lp_parm_const_string(SNUM(handle->conn),
+                                      "shadow", "basedir", NULL);
+
+       if (basedir != NULL) {
+               if (basedir[0] != '/') {
+                       DEBUG(1, (__location__ " Warning: 'basedir' is "
+                                 "relative ('%s'), but it has to be an "
+                                 "absolute path. Disabling basedir.\n",
+                                 basedir));
+               } else {
+                       char *p;
+                       p = strstr(basedir, config->mount_point);
+                       if (p != basedir) {
+                               DEBUG(1, ("Warning: basedir (%s) is not a "
+                                         "subdirectory of the share root's "
+                                         "mount point (%s). "
+                                         "Disabling basedir\n",
+                                         basedir, config->mount_point));
+                       } else {
+                               config->basedir = talloc_strdup(config,
+                                                               basedir);
+                               if (config->basedir == NULL) {
+                                       DEBUG(0, ("talloc_strdup() failed\n"));
+                                       errno = ENOMEM;
+                                       return -1;
+                               }
+                       }
+               }
+       }
+
+       if (config->snapdirseverywhere && config->basedir != NULL) {
+               DEBUG(1, (__location__ " Warning: 'basedir' is incompatible "
+                         "with 'snapdirseverywhere'. Disabling basedir.\n"));
+               TALLOC_FREE(config->basedir);
+       }
+
+       if (config->crossmountpoints && config->basedir != NULL) {
+               DEBUG(1, (__location__ " Warning: 'basedir' is incompatible "
+                         "with 'crossmountpoints'. Disabling basedir.\n"));
+               TALLOC_FREE(config->basedir);
+       }
+
+       if (config->basedir == NULL) {
+               config->basedir = config->mount_point;
+       }
+
+       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,