s3: VFS: Change SMB_VFS_MKNOD to use const struct smb_filename * instead of const...
[nivanova/samba-autobuild/.git] / source3 / modules / vfs_shadow_copy2.c
index 31f36a533f946847e0742754738697cef6275f15..1fb492ba71eb7aaf1bbbd8deaa121ba93a43d6f0 100644 (file)
@@ -78,6 +78,8 @@ struct shadow_copy2_private {
        char *shadow_cwd; /* Absolute $cwd path. */
        /* Absolute connectpath - can vary depending on $cwd. */
        char *shadow_connectpath;
+       /* malloc'ed realpath return. */
+       char *shadow_realpath;
 };
 
 static int shadow_copy2_get_shadow_copy_data(
@@ -442,7 +444,11 @@ static bool make_relative_path(const char *cwd, char *abs_path)
        if (memcmp(abs_path, cwd, cwd_len) != 0) {
                return false;
        }
-       if (abs_path[cwd_len] != '/' && abs_path[cwd_len] != '\0') {
+       /* The cwd_len != 1 case is for $cwd == '/' */
+       if (cwd_len != 1 &&
+           abs_path[cwd_len] != '/' &&
+           abs_path[cwd_len] != '\0')
+       {
                return false;
        }
        if (abs_path[cwd_len] == '/') {
@@ -452,11 +458,130 @@ static bool make_relative_path(const char *cwd, char *abs_path)
        return true;
 }
 
+static bool shadow_copy2_snapshot_to_gmt(vfs_handle_struct *handle,
+                                       const char *name,
+                                       char *gmt, size_t gmt_len);
+
+/*
+ * Check if an incoming filename is already a snapshot converted pathname.
+ *
+ * If so, it returns the pathname truncated at the snapshot point which
+ * will be used as the connectpath.
+ */
+
+static int check_for_converted_path(TALLOC_CTX *mem_ctx,
+                               struct vfs_handle_struct *handle,
+                               struct shadow_copy2_private *priv,
+                               char *abs_path,
+                               bool *ppath_already_converted,
+                               char **pconnectpath)
+{
+       size_t snapdirlen = 0;
+       char *p = strstr_m(abs_path, priv->config->snapdir);
+       char *q = NULL;
+       char *connect_path = NULL;
+       char snapshot[GMT_NAME_LEN+1];
+
+       *ppath_already_converted = false;
+
+       if (p == NULL) {
+               /* Must at least contain shadow:snapdir. */
+               return 0;
+       }
+
+       if (priv->config->snapdir[0] == '/' &&
+                       p != abs_path) {
+               /* Absolute shadow:snapdir must be at the start. */
+               return 0;
+       }
+
+       snapdirlen = strlen(priv->config->snapdir);
+       if (p[snapdirlen] != '/') {
+               /* shadow:snapdir must end as a separate component. */
+               return 0;
+       }
+
+       if (p > abs_path && p[-1] != '/') {
+               /* shadow:snapdir must start as a separate component. */
+               return 0;
+       }
+
+       p += snapdirlen;
+       p++; /* Move past the / */
+
+       /*
+        * Need to return up to the next path
+        * component after the time.
+        * This will be used as the connectpath.
+        */
+       q = strchr(p, '/');
+       if (q == NULL) {
+               /*
+                * No next path component.
+                * Use entire string.
+                */
+               connect_path = talloc_strdup(mem_ctx,
+                                       abs_path);
+       } else {
+               connect_path = talloc_strndup(mem_ctx,
+                                       abs_path,
+                                       q - abs_path);
+       }
+       if (connect_path == NULL) {
+               return ENOMEM;
+       }
+
+       /*
+        * Point p at the same offset in connect_path as
+        * it is in abs_path.
+        */
+
+       p = &connect_path[p - abs_path];
+
+       /*
+        * Now ensure there is a time string at p.
+        * The SMB-format @GMT-token string is returned
+        * in snapshot.
+        */
+
+       if (!shadow_copy2_snapshot_to_gmt(handle,
+                               p,
+                               snapshot,
+                               sizeof(snapshot))) {
+               TALLOC_FREE(connect_path);
+               return 0;
+       }
+
+       if (pconnectpath != NULL) {
+               *pconnectpath = connect_path;
+       }
+
+       *ppath_already_converted = true;
+
+       DBG_DEBUG("path |%s| is already converted. "
+               "connect path = |%s|\n",
+               abs_path,
+               connect_path);
+
+       return 0;
+}
+
 /**
- * Strip a snapshot component from a filename as
- * handed in via the smb layer.
- * Returns the parsed timestamp and the stripped filename.
+ * This function does two things.
+ *
+ * 1). Checks if an incoming filename is already a
+ * snapshot converted pathname.
+ *     If so, it returns the pathname truncated
+ *     at the snapshot point which will be used
+ *     as the connectpath, and then does an early return.
+ *
+ * 2). Checks if an incoming filename contains an
+ * SMB-layer @GMT- style timestamp.
+ *     If so, it strips the timestamp, and returns
+ *     both the timestamp and the stripped path
+ *     (making it cwd-relative).
  */
+
 static bool shadow_copy2_strip_snapshot_internal(TALLOC_CTX *mem_ctx,
                                        struct vfs_handle_struct *handle,
                                        const char *orig_name,
@@ -471,62 +596,72 @@ static bool shadow_copy2_strip_snapshot_internal(TALLOC_CTX *mem_ctx,
        char *stripped = NULL;
        size_t rest_len, dst_len;
        struct shadow_copy2_private *priv;
-       const char *snapdir;
-       ssize_t snapdirlen;
        ptrdiff_t len_before_gmt;
        const char *name = orig_name;
+       char *abs_path = NULL;
+       bool ret = true;
+       bool already_converted = false;
+       int err = 0;
 
        SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private,
                                return false);
 
        DEBUG(10, (__location__ ": enter path '%s'\n", name));
 
+       abs_path = make_path_absolute(mem_ctx, priv, name);
+       if (abs_path == NULL) {
+               ret = false;
+               goto out;
+       }
+       name = abs_path;
+
+       DEBUG(10, (__location__ ": abs path '%s'\n", name));
+
+       err = check_for_converted_path(mem_ctx,
+                                       handle,
+                                       priv,
+                                       abs_path,
+                                       &already_converted,
+                                       psnappath);
+       if (err != 0) {
+               /* error in conversion. */
+               ret = false;
+               goto out;
+       }
+
+       if (already_converted) {
+               goto out;
+       }
+
+       /*
+        * From here we're only looking to strip an
+        * SMB-layer @GMT- token.
+        */
+
        p = strstr_m(name, "@GMT-");
        if (p == NULL) {
                DEBUG(11, ("@GMT not found\n"));
-               goto no_snapshot;
+               goto out;
        }
        if ((p > name) && (p[-1] != '/')) {
                /* the GMT-token does not start a path-component */
                DEBUG(10, ("not at start, p=%p, name=%p, p[-1]=%d\n",
                           p, name, (int)p[-1]));
-               goto no_snapshot;
+               goto out;
        }
 
-       /*
-        * Figure out whether we got an already converted string. One
-        * case where this happens is in a smb2 create call with the
-        * mxac create blob set. We do the get_acl call on
-        * fsp->fsp_name, which is already converted. We are converted
-        * if we got a file name of the form ".snapshots/@GMT-",
-        * i.e. ".snapshots/" precedes "p".
-        */
-
-       snapdir = lp_parm_const_string(SNUM(handle->conn), "shadow", "snapdir",
-                                      ".snapshots");
-       snapdirlen = strlen(snapdir);
        len_before_gmt = p - name;
 
-       if ((len_before_gmt >= (snapdirlen + 1)) && (p[-1] == '/')) {
-               const char *parent_snapdir = p - (snapdirlen+1);
-
-               DEBUG(10, ("parent_snapdir = %s\n", parent_snapdir));
-
-               if (strncmp(parent_snapdir, snapdir, snapdirlen) == 0) {
-                       DEBUG(10, ("name=%s is already converted\n", name));
-                       goto no_snapshot;
-               }
-       }
        q = strptime(p, GMT_FORMAT, &tm);
        if (q == NULL) {
                DEBUG(10, ("strptime failed\n"));
-               goto no_snapshot;
+               goto out;
        }
        tm.tm_isdst = -1;
        timestamp = timegm(&tm);
        if (timestamp == (time_t)-1) {
                DEBUG(10, ("timestamp==-1\n"));
-               goto no_snapshot;
+               goto out;
        }
        if (q[0] == '\0') {
                /*
@@ -536,22 +671,35 @@ static bool shadow_copy2_strip_snapshot_internal(TALLOC_CTX *mem_ctx,
                 * with a path prefix.
                 */
                if (pstripped != NULL) {
-                       if (len_before_gmt > 0) {
+                       if (len_before_gmt > 1) {
                                /*
-                                * There is a slash before
-                                * the @GMT-. Remove it.
+                                * There is a path (and not only a slash)
+                                * before the @GMT-. Remove the trailing
+                                * slash character.
                                 */
                                len_before_gmt -= 1;
                        }
                        stripped = talloc_strndup(mem_ctx, name,
                                        len_before_gmt);
                        if (stripped == NULL) {
-                               return false;
+                               ret = false;
+                               goto out;
+                       }
+                       if (orig_name[0] != '/') {
+                               if (make_relative_path(priv->shadow_cwd,
+                                               stripped) == false) {
+                                       DEBUG(10, (__location__ ": path '%s' "
+                                               "doesn't start with cwd '%s'\n",
+                                               stripped, priv->shadow_cwd));
+                                               ret = false;
+                                       errno = ENOENT;
+                                       goto out;
+                               }
                        }
                        *pstripped = stripped;
                }
                *ptimestamp = timestamp;
-               return true;
+               goto out;
        }
        if (q[0] != '/') {
                /*
@@ -559,75 +707,18 @@ static bool shadow_copy2_strip_snapshot_internal(TALLOC_CTX *mem_ctx,
                 * component continues after the gmt-token.
                 */
                DEBUG(10, ("q[0] = %d\n", (int)q[0]));
-               goto no_snapshot;
+               goto out;
        }
        q += 1;
 
        rest_len = strlen(q);
        dst_len = len_before_gmt + rest_len;
 
-       if (priv->config->snapdirseverywhere) {
-               char *insert;
-               bool have_insert;
-               insert = shadow_copy2_insert_string(talloc_tos(), handle,
-                                                   timestamp);
-               if (insert == NULL) {
-                       errno = ENOMEM;
-                       return false;
-               }
-
-               DEBUG(10, (__location__ ": snapdirseverywhere mode.\n"
-                          "path '%s'.\n"
-                          "insert string '%s'\n", name, insert));
-
-               have_insert = (strstr(name, insert+1) != NULL);
-               DEBUG(10, ("have_insert=%d, name=%s, insert+1=%s\n",
-                          (int)have_insert, name, insert+1));
-               if (have_insert) {
-                       DEBUG(10, (__location__ ": insert string '%s' found in "
-                                  "path '%s' found in snapdirseverywhere mode "
-                                  "==> already converted\n", insert, name));
-                       TALLOC_FREE(insert);
-                       goto no_snapshot;
-               }
-               TALLOC_FREE(insert);
-       } else {
-               char *snapshot_path;
-               char *s;
-
-               snapshot_path = shadow_copy2_snapshot_path(talloc_tos(),
-                                                          handle,
-                                                          timestamp);
-               if (snapshot_path == NULL) {
-                       errno = ENOMEM;
-                       return false;
-               }
-
-               DEBUG(10, (__location__ " path: '%s'.\n"
-                          "snapshot path: '%s'\n", name, snapshot_path));
-
-               s = strstr(name, snapshot_path);
-               if (s == name) {
-                       /*
-                        * this starts with "snapshot_basepath/GMT-Token"
-                        * so it is already a converted absolute
-                        * path. Don't process further.
-                        */
-                       DEBUG(10, (__location__ ": path '%s' starts with "
-                                  "snapshot path '%s' (not in "
-                                  "snapdirseverywhere mode) ==> "
-                                  "already converted\n", name, snapshot_path));
-                       talloc_free(snapshot_path);
-                       goto no_snapshot;
-               }
-               talloc_free(snapshot_path);
-       }
-
        if (pstripped != NULL) {
                stripped = talloc_array(mem_ctx, char, dst_len+1);
                if (stripped == NULL) {
-                       errno = ENOMEM;
-                       return false;
+                       ret = false;
+                       goto out;
                }
                if (p > name) {
                        memcpy(stripped, name, len_before_gmt);
@@ -636,13 +727,25 @@ static bool shadow_copy2_strip_snapshot_internal(TALLOC_CTX *mem_ctx,
                        memcpy(stripped + len_before_gmt, q, rest_len);
                }
                stripped[dst_len] = '\0';
+               if (orig_name[0] != '/') {
+                       if (make_relative_path(priv->shadow_cwd,
+                                       stripped) == false) {
+                               DEBUG(10, (__location__ ": path '%s' "
+                                       "doesn't start with cwd '%s'\n",
+                                       stripped, priv->shadow_cwd));
+                               ret = false;
+                               errno = ENOENT;
+                               goto out;
+                       }
+               }
                *pstripped = stripped;
        }
        *ptimestamp = timestamp;
-       return true;
-no_snapshot:
-       *ptimestamp = 0;
-       return true;
+       ret = true;
+
+  out:
+       TALLOC_FREE(abs_path);
+       return ret;
 }
 
 static bool shadow_copy2_strip_snapshot(TALLOC_CTX *mem_ctx,
@@ -710,7 +813,8 @@ static char *shadow_copy2_do_convert(TALLOC_CTX *mem_ctx,
        char *insert = NULL;
        char *converted = NULL;
        size_t insertlen, connectlen = 0;
-       int i, saved_errno;
+       int saved_errno = 0;
+       int i;
        size_t min_offset;
        struct shadow_copy2_config *config;
        struct shadow_copy2_private *priv;
@@ -896,12 +1000,16 @@ static char *shadow_copy2_do_convert(TALLOC_CTX *mem_ctx,
                errno = ENOENT;
        }
 fail:
-       saved_errno = errno;
+       if (result == NULL) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(converted);
        TALLOC_FREE(insert);
        TALLOC_FREE(slashes);
        TALLOC_FREE(path);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return result;
 }
 
@@ -960,7 +1068,7 @@ static DIR *shadow_copy2_opendir(vfs_handle_struct *handle,
        time_t timestamp = 0;
        char *stripped = NULL;
        DIR *ret;
-       int saved_errno;
+       int saved_errno = 0;
        char *conv;
        struct smb_filename *conv_smb_fname = NULL;
 
@@ -989,10 +1097,14 @@ static DIR *shadow_copy2_opendir(vfs_handle_struct *handle,
                return NULL;
        }
        ret = SMB_VFS_NEXT_OPENDIR(handle, conv_smb_fname, mask, attr);
-       saved_errno = errno;
+       if (ret == NULL) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
        TALLOC_FREE(conv_smb_fname);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -1002,15 +1114,17 @@ static int shadow_copy2_rename(vfs_handle_struct *handle,
 {
        time_t timestamp_src = 0;
        time_t timestamp_dst = 0;
+       char *snappath_src = NULL;
+       char *snappath_dst = NULL;
 
-       if (!shadow_copy2_strip_snapshot(talloc_tos(), handle,
+       if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), handle,
                                         smb_fname_src->base_name,
-                                        &timestamp_src, NULL)) {
+                                        &timestamp_src, NULL, &snappath_src)) {
                return -1;
        }
-       if (!shadow_copy2_strip_snapshot(talloc_tos(), handle,
+       if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), handle,
                                         smb_fname_dst->base_name,
-                                        &timestamp_dst, NULL)) {
+                                        &timestamp_dst, NULL, &snappath_dst)) {
                return -1;
        }
        if (timestamp_src != 0) {
@@ -1021,6 +1135,17 @@ static int shadow_copy2_rename(vfs_handle_struct *handle,
                errno = EROFS;
                return -1;
        }
+       /*
+        * Don't allow rename on already converted paths.
+        */
+       if (snappath_src != NULL) {
+               errno = EXDEV;
+               return -1;
+       }
+       if (snappath_dst != NULL) {
+               errno = EROFS;
+               return -1;
+       }
        return SMB_VFS_NEXT_RENAME(handle, smb_fname_src, smb_fname_dst);
 }
 
@@ -1029,19 +1154,28 @@ static int shadow_copy2_symlink(vfs_handle_struct *handle,
 {
        time_t timestamp_old = 0;
        time_t timestamp_new = 0;
+       char *snappath_old = NULL;
+       char *snappath_new = NULL;
 
-       if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, oldname,
-                                        &timestamp_old, NULL)) {
+       if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), handle, oldname,
+                                        &timestamp_old, NULL, &snappath_old)) {
                return -1;
        }
-       if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, newname,
-                                        &timestamp_new, NULL)) {
+       if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), handle, newname,
+                                        &timestamp_new, NULL, &snappath_new)) {
                return -1;
        }
        if ((timestamp_old != 0) || (timestamp_new != 0)) {
                errno = EROFS;
                return -1;
        }
+       /*
+        * Don't allow symlinks on already converted paths.
+        */
+       if ((snappath_old != NULL) || (snappath_new != NULL)) {
+               errno = EROFS;
+               return -1;
+       }
        return SMB_VFS_NEXT_SYMLINK(handle, oldname, newname);
 }
 
@@ -1050,19 +1184,28 @@ static int shadow_copy2_link(vfs_handle_struct *handle,
 {
        time_t timestamp_old = 0;
        time_t timestamp_new = 0;
+       char *snappath_old = NULL;
+       char *snappath_new = NULL;
 
-       if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, oldname,
-                                        &timestamp_old, NULL)) {
+       if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), handle, oldname,
+                                        &timestamp_old, NULL, &snappath_old)) {
                return -1;
        }
-       if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, newname,
-                                        &timestamp_new, NULL)) {
+       if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), handle, newname,
+                                        &timestamp_new, NULL, &snappath_new)) {
                return -1;
        }
        if ((timestamp_old != 0) || (timestamp_new != 0)) {
                errno = EROFS;
                return -1;
        }
+       /*
+        * Don't allow links on already converted paths.
+        */
+       if ((snappath_old != NULL) || (snappath_new != NULL)) {
+               errno = EROFS;
+               return -1;
+       }
        return SMB_VFS_NEXT_LINK(handle, oldname, newname);
 }
 
@@ -1072,7 +1215,8 @@ static int shadow_copy2_stat(vfs_handle_struct *handle,
        time_t timestamp = 0;
        char *stripped = NULL;
        char *tmp;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle,
                                         smb_fname->base_name,
@@ -1094,7 +1238,9 @@ static int shadow_copy2_stat(vfs_handle_struct *handle,
        }
 
        ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
 
        TALLOC_FREE(smb_fname->base_name);
        smb_fname->base_name = tmp;
@@ -1102,7 +1248,9 @@ static int shadow_copy2_stat(vfs_handle_struct *handle,
        if (ret == 0) {
                convert_sbuf(handle, smb_fname->base_name, &smb_fname->st);
        }
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -1112,7 +1260,8 @@ static int shadow_copy2_lstat(vfs_handle_struct *handle,
        time_t timestamp = 0;
        char *stripped = NULL;
        char *tmp;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle,
                                         smb_fname->base_name,
@@ -1134,7 +1283,9 @@ static int shadow_copy2_lstat(vfs_handle_struct *handle,
        }
 
        ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
 
        TALLOC_FREE(smb_fname->base_name);
        smb_fname->base_name = tmp;
@@ -1142,7 +1293,9 @@ static int shadow_copy2_lstat(vfs_handle_struct *handle,
        if (ret == 0) {
                convert_sbuf(handle, smb_fname->base_name, &smb_fname->st);
        }
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -1174,7 +1327,8 @@ static int shadow_copy2_open(vfs_handle_struct *handle,
        time_t timestamp = 0;
        char *stripped = NULL;
        char *tmp;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle,
                                         smb_fname->base_name,
@@ -1196,12 +1350,16 @@ static int shadow_copy2_open(vfs_handle_struct *handle,
        }
 
        ret = SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
 
        TALLOC_FREE(smb_fname->base_name);
        smb_fname->base_name = tmp;
 
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -1210,7 +1368,8 @@ static int shadow_copy2_unlink(vfs_handle_struct *handle,
 {
        time_t timestamp = 0;
        char *stripped = NULL;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
        struct smb_filename *conv;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle,
@@ -1233,9 +1392,13 @@ static int shadow_copy2_unlink(vfs_handle_struct *handle,
                return -1;
        }
        ret = SMB_VFS_NEXT_UNLINK(handle, conv);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -1245,7 +1408,8 @@ static int shadow_copy2_chmod(vfs_handle_struct *handle,
 {
        time_t timestamp = 0;
        char *stripped = NULL;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
        char *conv = NULL;
        struct smb_filename *conv_smb_fname;
 
@@ -1277,10 +1441,14 @@ static int shadow_copy2_chmod(vfs_handle_struct *handle,
        }
 
        ret = SMB_VFS_NEXT_CHMOD(handle, conv_smb_fname, mode);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
        TALLOC_FREE(conv_smb_fname);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -1291,7 +1459,8 @@ static int shadow_copy2_chown(vfs_handle_struct *handle,
 {
        time_t timestamp = 0;
        char *stripped = NULL;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
        char *conv = NULL;
        struct smb_filename *conv_smb_fname = NULL;
 
@@ -1321,10 +1490,14 @@ static int shadow_copy2_chown(vfs_handle_struct *handle,
                return -1;
        }
        ret = SMB_VFS_NEXT_CHOWN(handle, conv_smb_fname, uid, gid);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
        TALLOC_FREE(conv_smb_fname);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -1416,7 +1589,8 @@ static int shadow_copy2_ntimes(vfs_handle_struct *handle,
 {
        time_t timestamp = 0;
        char *stripped = NULL;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
        struct smb_filename *conv;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle,
@@ -1439,9 +1613,13 @@ static int shadow_copy2_ntimes(vfs_handle_struct *handle,
                return -1;
        }
        ret = SMB_VFS_NEXT_NTIMES(handle, conv, ft);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -1450,7 +1628,8 @@ static int shadow_copy2_readlink(vfs_handle_struct *handle,
 {
        time_t timestamp = 0;
        char *stripped = NULL;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
        char *conv;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname,
@@ -1466,36 +1645,54 @@ static int shadow_copy2_readlink(vfs_handle_struct *handle,
                return -1;
        }
        ret = SMB_VFS_NEXT_READLINK(handle, conv, buf, bufsiz);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
 static int shadow_copy2_mknod(vfs_handle_struct *handle,
-                             const char *fname, mode_t mode, SMB_DEV_T dev)
+                               const struct smb_filename *smb_fname,
+                               mode_t mode,
+                               SMB_DEV_T dev)
 {
        time_t timestamp = 0;
        char *stripped = NULL;
-       int ret, saved_errno;
-       char *conv;
+       int saved_errno = 0;
+       int ret;
+       struct smb_filename *conv = NULL;
 
-       if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname,
+       if (!shadow_copy2_strip_snapshot(talloc_tos(), handle,
+                                        smb_fname->base_name,
                                         &timestamp, &stripped)) {
                return -1;
        }
        if (timestamp == 0) {
-               return SMB_VFS_NEXT_MKNOD(handle, fname, mode, dev);
+               return SMB_VFS_NEXT_MKNOD(handle, smb_fname, mode, dev);
        }
-       conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp);
-       TALLOC_FREE(stripped);
+       conv = cp_smb_filename(talloc_tos(), smb_fname);
        if (conv == NULL) {
+               errno = ENOMEM;
+               return -1;
+       }
+       conv->base_name = shadow_copy2_convert(
+               conv, handle, stripped, timestamp);
+       TALLOC_FREE(stripped);
+       if (conv->base_name == NULL) {
                return -1;
        }
        ret = SMB_VFS_NEXT_MKNOD(handle, conv, mode, dev);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -1506,7 +1703,7 @@ static char *shadow_copy2_realpath(vfs_handle_struct *handle,
        char *stripped = NULL;
        char *tmp = NULL;
        char *result = NULL;
-       int saved_errno;
+       int saved_errno = 0;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname,
                                         &timestamp, &stripped)) {
@@ -1524,10 +1721,14 @@ static char *shadow_copy2_realpath(vfs_handle_struct *handle,
        result = SMB_VFS_NEXT_REALPATH(handle, tmp);
 
 done:
-       saved_errno = errno;
+       if (result == NULL) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(tmp);
        TALLOC_FREE(stripped);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return result;
 }
 
@@ -1686,6 +1887,9 @@ static bool shadow_copy2_snapshot_to_gmt(vfs_handle_struct *handle,
                }
                /* Extract the prefix */
                tmp = strstr(tmpstr, priv->config->delimiter);
+               if (tmp == NULL) {
+                       goto done;
+               }
                *tmp = '\0';
 
                /* Parse regex */
@@ -2018,7 +2222,8 @@ static int shadow_copy2_mkdir(vfs_handle_struct *handle,
 {
        time_t timestamp = 0;
        char *stripped = NULL;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
        char *conv;
        struct smb_filename *conv_smb_fname = NULL;
 
@@ -2047,10 +2252,14 @@ static int shadow_copy2_mkdir(vfs_handle_struct *handle,
                return -1;
        }
        ret = SMB_VFS_NEXT_MKDIR(handle, conv_smb_fname, mode);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
        TALLOC_FREE(conv_smb_fname);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -2059,7 +2268,8 @@ static int shadow_copy2_rmdir(vfs_handle_struct *handle,
 {
        time_t timestamp = 0;
        char *stripped = NULL;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
        char *conv;
        struct smb_filename *conv_smb_fname = NULL;
 
@@ -2088,10 +2298,14 @@ static int shadow_copy2_rmdir(vfs_handle_struct *handle,
                return -1;
        }
        ret = SMB_VFS_NEXT_RMDIR(handle, conv_smb_fname);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv_smb_fname);
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -2100,7 +2314,8 @@ static int shadow_copy2_chflags(vfs_handle_struct *handle, const char *fname,
 {
        time_t timestamp = 0;
        char *stripped = NULL;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
        char *conv;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname,
@@ -2116,28 +2331,38 @@ static int shadow_copy2_chflags(vfs_handle_struct *handle, const char *fname,
                return -1;
        }
        ret = SMB_VFS_NEXT_CHFLAGS(handle, conv, flags);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
 static ssize_t shadow_copy2_getxattr(vfs_handle_struct *handle,
-                                    const char *fname, const char *aname,
-                                    void *value, size_t size)
+                               const struct smb_filename *smb_fname,
+                               const char *aname,
+                               void *value,
+                               size_t size)
 {
        time_t timestamp = 0;
        char *stripped = NULL;
        ssize_t ret;
-       int saved_errno;
+       int saved_errno = 0;
        char *conv;
+       struct smb_filename *conv_smb_fname = NULL;
 
-       if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname,
-                                        &timestamp, &stripped)) {
+       if (!shadow_copy2_strip_snapshot(talloc_tos(),
+                               handle,
+                               smb_fname->base_name,
+                               &timestamp,
+                               &stripped)) {
                return -1;
        }
        if (timestamp == 0) {
-               return SMB_VFS_NEXT_GETXATTR(handle, fname, aname, value,
+               return SMB_VFS_NEXT_GETXATTR(handle, smb_fname, aname, value,
                                             size);
        }
        conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp);
@@ -2145,97 +2370,170 @@ static ssize_t shadow_copy2_getxattr(vfs_handle_struct *handle,
        if (conv == NULL) {
                return -1;
        }
-       ret = SMB_VFS_NEXT_GETXATTR(handle, conv, aname, value, size);
-       saved_errno = errno;
+
+       conv_smb_fname = synthetic_smb_fname(talloc_tos(),
+                                       conv,
+                                       NULL,
+                                       NULL,
+                                       smb_fname->flags);
+       if (conv_smb_fname == NULL) {
+               TALLOC_FREE(conv);
+               return -1;
+       }
+
+       ret = SMB_VFS_NEXT_GETXATTR(handle, conv_smb_fname, aname, value, size);
+       if (ret == -1) {
+               saved_errno = errno;
+       }
+       TALLOC_FREE(conv_smb_fname);
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
 static ssize_t shadow_copy2_listxattr(struct vfs_handle_struct *handle,
-                                     const char *fname,
+                                     const struct smb_filename *smb_fname,
                                      char *list, size_t size)
 {
        time_t timestamp = 0;
        char *stripped = NULL;
        ssize_t ret;
-       int saved_errno;
+       int saved_errno = 0;
        char *conv;
+       struct smb_filename *conv_smb_fname = NULL;
 
-       if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname,
-                                        &timestamp, &stripped)) {
+       if (!shadow_copy2_strip_snapshot(talloc_tos(),
+                               handle,
+                               smb_fname->base_name,
+                               &timestamp,
+                               &stripped)) {
                return -1;
        }
        if (timestamp == 0) {
-               return SMB_VFS_NEXT_LISTXATTR(handle, fname, list, size);
+               return SMB_VFS_NEXT_LISTXATTR(handle, smb_fname, list, size);
        }
        conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp);
        TALLOC_FREE(stripped);
        if (conv == NULL) {
                return -1;
        }
-       ret = SMB_VFS_NEXT_LISTXATTR(handle, conv, list, size);
-       saved_errno = errno;
+       conv_smb_fname = synthetic_smb_fname(talloc_tos(),
+                                       conv,
+                                       NULL,
+                                       NULL,
+                                       smb_fname->flags);
+       if (conv_smb_fname == NULL) {
+               TALLOC_FREE(conv);
+               return -1;
+       }
+       ret = SMB_VFS_NEXT_LISTXATTR(handle, conv_smb_fname, list, size);
+       if (ret == -1) {
+               saved_errno = errno;
+       }
+       TALLOC_FREE(conv_smb_fname);
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
 static int shadow_copy2_removexattr(vfs_handle_struct *handle,
-                                   const char *fname, const char *aname)
+                               const struct smb_filename *smb_fname,
+                               const char *aname)
 {
        time_t timestamp = 0;
        char *stripped = NULL;
-       int ret, saved_errno;
+       int saved_errno = 0;
+       int ret;
        char *conv;
+       struct smb_filename *conv_smb_fname = NULL;
 
-       if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname,
-                                        &timestamp, &stripped)) {
+       if (!shadow_copy2_strip_snapshot(talloc_tos(),
+                               handle,
+                               smb_fname->base_name,
+                               &timestamp,
+                               &stripped)) {
                return -1;
        }
        if (timestamp == 0) {
-               return SMB_VFS_NEXT_REMOVEXATTR(handle, fname, aname);
+               return SMB_VFS_NEXT_REMOVEXATTR(handle, smb_fname, aname);
        }
        conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp);
        TALLOC_FREE(stripped);
        if (conv == NULL) {
                return -1;
        }
-       ret = SMB_VFS_NEXT_REMOVEXATTR(handle, conv, aname);
-       saved_errno = errno;
+       conv_smb_fname = synthetic_smb_fname(talloc_tos(),
+                                       conv,
+                                       NULL,
+                                       NULL,
+                                       smb_fname->flags);
+       if (conv_smb_fname == NULL) {
+               TALLOC_FREE(conv);
+               return -1;
+       }
+       ret = SMB_VFS_NEXT_REMOVEXATTR(handle, conv_smb_fname, aname);
+       if (ret == -1) {
+               saved_errno = errno;
+       }
+       TALLOC_FREE(conv_smb_fname);
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
 static int shadow_copy2_setxattr(struct vfs_handle_struct *handle,
-                                const char *fname,
+                                const struct smb_filename *smb_fname,
                                 const char *aname, const void *value,
                                 size_t size, int flags)
 {
        time_t timestamp = 0;
        char *stripped = NULL;
        ssize_t ret;
-       int saved_errno;
+       int saved_errno = 0;
        char *conv;
+       struct smb_filename *conv_smb_fname = NULL;
 
-       if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname,
-                                        &timestamp, &stripped)) {
+       if (!shadow_copy2_strip_snapshot(talloc_tos(),
+                               handle,
+                               smb_fname->base_name,
+                               &timestamp,
+                               &stripped)) {
                return -1;
        }
        if (timestamp == 0) {
-               return SMB_VFS_NEXT_SETXATTR(handle, fname, aname, value, size,
-                                            flags);
+               return SMB_VFS_NEXT_SETXATTR(handle, smb_fname,
+                                       aname, value, size, flags);
        }
        conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp);
        TALLOC_FREE(stripped);
        if (conv == NULL) {
                return -1;
        }
-       ret = SMB_VFS_NEXT_SETXATTR(handle, conv, aname, value, size, flags);
-       saved_errno = errno;
+       conv_smb_fname = synthetic_smb_fname(talloc_tos(),
+                                       conv,
+                                       NULL,
+                                       NULL,
+                                       smb_fname->flags);
+       if (conv_smb_fname == NULL) {
+               TALLOC_FREE(conv);
+               return -1;
+       }
+       ret = SMB_VFS_NEXT_SETXATTR(handle, conv_smb_fname,
+                               aname, value, size, flags);
+       if (ret == -1) {
+               saved_errno = errno;
+       }
+       TALLOC_FREE(conv_smb_fname);
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -2246,7 +2544,7 @@ static int shadow_copy2_chmod_acl(vfs_handle_struct *handle,
        time_t timestamp = 0;
        char *stripped = NULL;
        ssize_t ret;
-       int saved_errno;
+       int saved_errno = 0;
        char *conv = NULL;
        struct smb_filename *conv_smb_fname = NULL;
 
@@ -2276,10 +2574,14 @@ static int shadow_copy2_chmod_acl(vfs_handle_struct *handle,
                return -1;
        }
        ret = SMB_VFS_NEXT_CHMOD_ACL(handle, conv_smb_fname, mode);
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
        TALLOC_FREE(conv_smb_fname);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -2292,7 +2594,7 @@ static int shadow_copy2_get_real_filename(struct vfs_handle_struct *handle,
        time_t timestamp = 0;
        char *stripped = NULL;
        ssize_t ret;
-       int saved_errno;
+       int saved_errno = 0;
        char *conv;
 
        DEBUG(10, ("shadow_copy2_get_real_filename called for path=[%s], "
@@ -2319,9 +2621,13 @@ static int shadow_copy2_get_real_filename(struct vfs_handle_struct *handle,
        ret = SMB_VFS_NEXT_GET_REAL_FILENAME(handle, conv, name,
                                             mem_ctx, found_name);
        DEBUG(10, ("NEXT_REAL_FILE_NAME returned %d\n", (int)ret));
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return ret;
 }
 
@@ -2333,7 +2639,7 @@ static const char *shadow_copy2_connectpath(struct vfs_handle_struct *handle,
        char *tmp = NULL;
        char *result = NULL;
        char *parent_dir = NULL;
-       int saved_errno;
+       int saved_errno = 0;
        size_t rootpath_len = 0;
        struct shadow_copy2_private *priv = NULL;
 
@@ -2398,14 +2704,25 @@ static const char *shadow_copy2_connectpath(struct vfs_handle_struct *handle,
                goto done;
        }
 
+       /*
+        * SMB_VFS_NEXT_REALPATH returns a malloc'ed string.
+        * Don't leak memory.
+        */
+       SAFE_FREE(priv->shadow_realpath);
+       priv->shadow_realpath = result;
+
        DBG_DEBUG("connect path is [%s]\n", result);
 
 done:
-       saved_errno = errno;
+       if (result == NULL) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(tmp);
        TALLOC_FREE(stripped);
        TALLOC_FREE(parent_dir);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
        return result;
 }
 
@@ -2416,7 +2733,7 @@ static uint64_t shadow_copy2_disk_free(vfs_handle_struct *handle,
        time_t timestamp = 0;
        char *stripped = NULL;
        ssize_t ret;
-       int saved_errno;
+       int saved_errno = 0;
        char *conv;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, path,
@@ -2436,9 +2753,13 @@ static uint64_t shadow_copy2_disk_free(vfs_handle_struct *handle,
 
        ret = SMB_VFS_NEXT_DISK_FREE(handle, conv, bsize, dfree, dsize);
 
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
 
        return ret;
 }
@@ -2450,7 +2771,7 @@ static int shadow_copy2_get_quota(vfs_handle_struct *handle, const char *path,
        time_t timestamp = 0;
        char *stripped = NULL;
        int ret;
-       int saved_errno;
+       int saved_errno = 0;
        char *conv;
 
        if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, path, &timestamp,
@@ -2469,13 +2790,23 @@ static int shadow_copy2_get_quota(vfs_handle_struct *handle, const char *path,
 
        ret = SMB_VFS_NEXT_GET_QUOTA(handle, conv, qtype, id, dq);
 
-       saved_errno = errno;
+       if (ret == -1) {
+               saved_errno = errno;
+       }
        TALLOC_FREE(conv);
-       errno = saved_errno;
+       if (saved_errno != 0) {
+               errno = saved_errno;
+       }
 
        return ret;
 }
 
+static int shadow_copy2_private_destructor(struct shadow_copy2_private *priv)
+{
+       SAFE_FREE(priv->shadow_realpath);
+       return 0;
+}
+
 static int shadow_copy2_connect(struct vfs_handle_struct *handle,
                                const char *service, const char *user)
 {
@@ -2507,6 +2838,8 @@ static int shadow_copy2_connect(struct vfs_handle_struct *handle,
                return -1;
        }
 
+       talloc_set_destructor(priv, shadow_copy2_private_destructor);
+
        priv->snaps = talloc_zero(priv, struct shadow_copy2_snaplist_info);
        if (priv->snaps == NULL) {
                DBG_ERR("talloc_zero() failed\n");
@@ -2533,6 +2866,15 @@ static int shadow_copy2_connect(struct vfs_handle_struct *handle,
                return -1;
        }
 
+       /* config->gmt_format must not contain a path separator. */
+       if (strchr(config->gmt_format, '/') != NULL) {
+               DEBUG(0, ("shadow:format %s must not contain a /"
+                       "character. Unable to initialize module.\n",
+                       config->gmt_format));
+               errno = EINVAL;
+               return -1;
+       }
+
        config->use_sscanf = lp_parm_bool(SNUM(handle->conn),
                                          "shadow", "sscanf", false);
 
@@ -2833,8 +3175,8 @@ static struct vfs_fn_pointers vfs_shadow_copy2_fns = {
        .connectpath_fn = shadow_copy2_connectpath,
 };
 
-NTSTATUS vfs_shadow_copy2_init(void);
-NTSTATUS vfs_shadow_copy2_init(void)
+NTSTATUS vfs_shadow_copy2_init(TALLOC_CTX *);
+NTSTATUS vfs_shadow_copy2_init(TALLOC_CTX *ctx)
 {
        return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
                                "shadow_copy2", &vfs_shadow_copy2_fns);