X-Git-Url: http://git.samba.org/?a=blobdiff_plain;f=source3%2Fmodules%2Fvfs_shadow_copy2.c;h=93bca9c3da16908aab8f02c554edbcc7498df14d;hb=e8e9e7f2fe33d1e158202df4eb91d3738c9eb266;hp=5efae3b0edcf6b392de9e027dc4a110b0d0ca4cb;hpb=033185e2a1b2892fe8dc74a18a38e5e13e08cb22;p=obnox%2Fsamba%2Fsamba-obnox.git diff --git a/source3/modules/vfs_shadow_copy2.c b/source3/modules/vfs_shadow_copy2.c index 5efae3b0edc..93bca9c3da1 100644 --- a/source3/modules/vfs_shadow_copy2.c +++ b/source3/modules/vfs_shadow_copy2.c @@ -1,196 +1,407 @@ -/* - * implementation of an Shadow Copy module - version 2 +/* + * shadow_copy2: a shadow copy module (second implementation) * - * Copyright (C) Andrew Tridgell 2007 + * Copyright (C) Andrew Tridgell 2007 (portions taken from shadow_copy2) + * Copyright (C) Ed Plese 2009 + * Copyright (C) Volker Lendecke 2011 + * Copyright (C) Christian Ambach 2011 + * Copyright (C) Michael Adam 2013 * * 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 * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#include "includes.h" - /* + * This is a second implemetation of a shadow copy module for exposing + * file system snapshots to windows clients as shadow copies. + * + * See the manual page for documentation. + */ - This is a 2nd implemetation of a shadow copy module for exposing - snapshots to windows clients as shadow copies. This version has the - following features: - - 1) you don't need to populate your shares with symlinks to the - snapshots. This can be very important when you have thousands of - shares, or use [homes] +#include "includes.h" +#include "smbd/smbd.h" +#include "system/filesys.h" +#include "include/ntioctl.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; + char *rel_connectpath; /* share root, relative to the basedir */ + char *snapshot_basepath; /* the absolute version of snapdir */ +}; - 2) the inode number of the files is altered so it is different - from the original. This allows the 'restore' button to work - without a sharing violation +static bool shadow_copy2_find_slashes(TALLOC_CTX *mem_ctx, const char *str, + size_t **poffsets, + unsigned *pnum_offsets) +{ + unsigned num_offsets; + size_t *offsets; + const char *p; - Module options: + num_offsets = 0; - shadow:snapdir = + p = str; + while ((p = strchr(p, '/')) != NULL) { + num_offsets += 1; + p += 1; + } - This is the directory containing the @GMT-* snapshot directories. If it is an absolute - path it is used as-is. If it is a relative path, then it is taken relative to the mount - point of the filesystem that the root of this share is on + offsets = talloc_array(mem_ctx, size_t, num_offsets); + if (offsets == NULL) { + return false; + } - shadow:basedir = + p = str; + num_offsets = 0; + while ((p = strchr(p, '/')) != NULL) { + offsets[num_offsets] = p-str; + num_offsets += 1; + p += 1; + } - This is an optional parameter that specifies the directory that - the snapshots are relative to. It defaults to the filesystem - mount point + *poffsets = offsets; + *pnum_offsets = num_offsets; + return true; +} - shadow:fixinodes = yes/no +/** + * Given a timestamp, build the posix level GMT-tag string + * based on the configurable format. + */ +static size_t shadow_copy2_posix_gmt_string(struct vfs_handle_struct *handle, + time_t snapshot, + char *snaptime_string, + size_t len) +{ + struct tm snap_tm; + size_t snaptime_len; + struct shadow_copy2_config *config; + + SMB_VFS_HANDLE_GET_DATA(handle, config, struct shadow_copy2_config, + return 0); + + if (config->use_sscanf) { + snaptime_len = snprintf(snaptime_string, + len, + config->gmt_format, + (unsigned long)snapshot); + if (snaptime_len <= 0) { + DEBUG(10, ("snprintf failed\n")); + return snaptime_len; + } + } else { + if (config->use_localtime) { + if (localtime_r(&snapshot, &snap_tm) == 0) { + DEBUG(10, ("gmtime_r failed\n")); + return -1; + } + } else { + if (gmtime_r(&snapshot, &snap_tm) == 0) { + DEBUG(10, ("gmtime_r failed\n")); + return -1; + } + } + snaptime_len = strftime(snaptime_string, + len, + config->gmt_format, + &snap_tm); + if (snaptime_len == 0) { + DEBUG(10, ("strftime failed\n")); + return 0; + } + } - If you enable shadow:fixinodes then this module will modify the - apparent inode number of files in the snapshot directories using - a hash of the files path. This is needed for snapshot systems - where the snapshots have the same device:inode number as the - original files (such as happens with GPFS snapshots). If you - don't set this option then the 'restore' button in the shadow - copy UI will fail with a sharing violation. + return snaptime_len; +} - Note that the directory names in the snapshot directory must take the form - @GMT-YYYY.MM.DD-HH.MM.SS - - The following command would generate a correctly formatted directory name: - date -u +@GMT-%Y.%m.%d-%H.%M.%S - +/** + * Given a timestamp, 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. + * + * In the case of a parallel snapdir (specified with an + * absolute path), this is the inital portion of the + * local path of any snapshot file. The complete path is + * obtained by appending the portion of the file's path + * below the share root's mountpoint. */ +static char *shadow_copy2_insert_string(TALLOC_CTX *mem_ctx, + struct vfs_handle_struct *handle, + time_t snapshot) +{ + fstring snaptime_string; + size_t snaptime_len = 0; + char *result = NULL; + struct shadow_copy2_config *config; + + SMB_VFS_HANDLE_GET_DATA(handle, config, struct shadow_copy2_config, + return NULL); + + snaptime_len = shadow_copy2_posix_gmt_string(handle, + snapshot, + snaptime_string, + sizeof(snaptime_string)); + if (snaptime_len <= 0) { + return NULL; + } -static int vfs_shadow_copy2_debug_level = DBGC_VFS; - -#undef DBGC_CLASS -#define DBGC_CLASS vfs_shadow_copy2_debug_level + if (config->snapdir_absolute) { + result = talloc_asprintf(mem_ctx, "%s/%s", + config->snapdir, snaptime_string); + } else { + result = talloc_asprintf(mem_ctx, "/%s/%s", + config->snapdir, snaptime_string); + } + if (result == NULL) { + DEBUG(1, (__location__ " talloc_asprintf failed\n")); + } -#define GMT_NAME_LEN 24 /* length of a @GMT- name */ + return result; +} -/* - make very sure it is one of our special names +/** + * Build the posix snapshot path for the connection + * at the given timestamp, i.e. the absolute posix path + * that contains the snapshot for this file system. + * + * This only applies to classical case, i.e. not + * to the "snapdirseverywhere" mode. */ -static inline bool shadow_copy2_match_name(const char *name) +static char *shadow_copy2_snapshot_path(TALLOC_CTX *mem_ctx, + struct vfs_handle_struct *handle, + time_t snapshot) { - unsigned year, month, day, hr, min, sec; - if (name[0] != '@') return False; - if (strncmp(name, "@GMT-", 5) != 0) return False; - if (sscanf(name, "@GMT-%04u.%02u.%02u-%02u.%02u.%02u", &year, &month, - &day, &hr, &min, &sec) != 6) { - return False; + fstring snaptime_string; + size_t snaptime_len = 0; + char *result = NULL; + struct shadow_copy2_config *config; + + SMB_VFS_HANDLE_GET_DATA(handle, config, struct shadow_copy2_config, + return NULL); + + snaptime_len = shadow_copy2_posix_gmt_string(handle, + snapshot, + snaptime_string, + sizeof(snaptime_string)); + if (snaptime_len <= 0) { + return NULL; } - if (name[24] != 0 && name[24] != '/') { - return False; + + result = talloc_asprintf(mem_ctx, "%s/%s", + config->snapshot_basepath, snaptime_string); + if (result == NULL) { + DEBUG(1, (__location__ " talloc_asprintf failed\n")); } - return True; + + return result; } -/* - convert a name to the shadow directory +/** + * Strip a snapshot component from a 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, + time_t *ptimestamp, + char **pstripped) +{ + struct tm tm; + time_t timestamp; + const char *p; + char *q; + char *stripped; + size_t rest_len, dst_len; + struct shadow_copy2_config *config; + const char *snapdir; + ssize_t snapdirlen; + ptrdiff_t len_before_gmt; -#define _SHADOW2_NEXT(op, args, rtype, eret, extra) do { \ - const char *name = fname; \ - if (shadow_copy2_match_name(fname)) { \ - char *name2; \ - rtype ret; \ - name2 = convert_shadow2_name(handle, fname); \ - if (name2 == NULL) { \ - errno = EINVAL; \ - return eret; \ - } \ - name = name2; \ - ret = SMB_VFS_NEXT_ ## op args; \ - talloc_free(name2); \ - if (ret != eret) extra; \ - return ret; \ - } else { \ - return SMB_VFS_NEXT_ ## op args; \ - } \ -} while (0) - -#define _SHADOW2_NEXT_SMB_FNAME(op, args, rtype, eret, extra) do { \ - if (shadow_copy2_match_name(smb_fname->base_name)) { \ - char *name2; \ - char *smb_base_name_tmp = NULL; \ - rtype ret; \ - name2 = convert_shadow2_name(handle, smb_fname->base_name); \ - if (name2 == NULL) { \ - errno = EINVAL; \ - return eret; \ - } \ - smb_base_name_tmp = smb_fname->base_name; \ - smb_fname->base_name = name2; \ - ret = SMB_VFS_NEXT_ ## op args; \ - smb_fname->base_name = smb_base_name_tmp; \ - talloc_free(name2); \ - if (ret != eret) extra; \ - return ret; \ - } else { \ - return SMB_VFS_NEXT_ ## op args; \ - } \ -} while (0) + SMB_VFS_HANDLE_GET_DATA(handle, config, struct shadow_copy2_config, + return false); -/* - convert a name to the shadow directory: NTSTATUS-specific handling - */ + DEBUG(10, (__location__ ": enter path '%s'\n", name)); + + p = strstr_m(name, "@GMT-"); + if (p == NULL) { + DEBUG(11, ("@GMT not found\n")); + goto no_snapshot; + } + 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; + } -#define _SHADOW2_NTSTATUS_NEXT(op, args, eret, extra) do { \ - const char *name = fname; \ - if (shadow_copy2_match_name(fname)) { \ - char *name2; \ - NTSTATUS ret; \ - name2 = convert_shadow2_name(handle, fname); \ - if (name2 == NULL) { \ - errno = EINVAL; \ - return eret; \ - } \ - name = name2; \ - ret = SMB_VFS_NEXT_ ## op args; \ - talloc_free(name2); \ - if (!NT_STATUS_EQUAL(ret, eret)) extra; \ - return ret; \ - } else { \ - return SMB_VFS_NEXT_ ## op args; \ - } \ -} while (0) - -#define SHADOW2_NTSTATUS_NEXT(op, args, eret) _SHADOW2_NTSTATUS_NEXT(op, args, eret, ) - -#define SHADOW2_NEXT(op, args, rtype, eret) _SHADOW2_NEXT(op, args, rtype, eret, ) - -#define SHADOW2_NEXT_SMB_FNAME(op, args, rtype, eret) _SHADOW2_NEXT_SMB_FNAME(op, args, rtype, eret, ) - -#define SHADOW2_NEXT2(op, args) do { \ - if (shadow_copy2_match_name(oldname) || shadow_copy2_match_name(newname)) { \ - errno = EROFS; \ - return -1; \ - } else { \ - return SMB_VFS_NEXT_ ## op args; \ - } \ -} while (0) - -#define SHADOW2_NEXT2_SMB_FNAME(op, args) do { \ - if (shadow_copy2_match_name(smb_fname_src->base_name) || \ - shadow_copy2_match_name(smb_fname_dst->base_name)) { \ - errno = EROFS; \ - return -1; \ - } else { \ - return SMB_VFS_NEXT_ ## op args; \ - } \ -} while (0) + /* + * 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; -/* - find the mount point of a filesystem - */ -static char *find_mount_point(TALLOC_CTX *mem_ctx, vfs_handle_struct *handle) + 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; + } + tm.tm_isdst = -1; + timestamp = timegm(&tm); + if (timestamp == (time_t)-1) { + DEBUG(10, ("timestamp==-1\n")); + goto no_snapshot; + } + if (q[0] == '\0') { + /* + * The name consists of only the GMT token or the GMT + * token is at the end of the path. XP seems to send + * @GMT- at the end under certain circumstances even + * with a path prefix. + */ + if (pstripped != NULL) { + stripped = talloc_strndup(mem_ctx, name, p - name); + if (stripped == NULL) { + return false; + } + *pstripped = stripped; + } + *ptimestamp = timestamp; + return true; + } + if (q[0] != '/') { + /* + * It is not a complete path component, i.e. the path + * component continues after the gmt-token. + */ + DEBUG(10, ("q[0] = %d\n", (int)q[0])); + goto no_snapshot; + } + q += 1; + + rest_len = strlen(q); + dst_len = (p-name) + rest_len; + + if (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; + } + if (p > name) { + memcpy(stripped, name, p-name); + } + if (rest_len > 0) { + memcpy(stripped + (p-name), q, rest_len); + } + stripped[dst_len] = '\0'; + *pstripped = stripped; + } + *ptimestamp = timestamp; + return true; +no_snapshot: + *ptimestamp = 0; + return true; +} + +static char *shadow_copy2_find_mount_point(TALLOC_CTX *mem_ctx, + vfs_handle_struct *handle) { char *path = talloc_strdup(mem_ctx, handle->conn->connectpath); dev_t dev; @@ -216,124 +427,216 @@ static char *find_mount_point(TALLOC_CTX *mem_ctx, vfs_handle_struct *handle) } } - return path; + return path; } -/* - work out the location of the snapshot for this share +/** + * 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 const char *shadow_copy2_find_snapdir(TALLOC_CTX *mem_ctx, vfs_handle_struct *handle) +static char *shadow_copy2_convert(TALLOC_CTX *mem_ctx, + struct vfs_handle_struct *handle, + const char *name, time_t timestamp) { - const char *snapdir; - char *mount_point; - const char *ret; + struct smb_filename converted_fname; + char *result = NULL; + size_t *slashes = NULL; + unsigned num_slashes; + char *path = NULL; + size_t pathlen; + char *insert = NULL; + char *converted = NULL; + 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); + + DEBUG(10, ("converting '%s'\n", name)); + + if (!config->snapdirseverywhere) { + int ret; + char *snapshot_path; + + snapshot_path = shadow_copy2_snapshot_path(talloc_tos(), + handle, + timestamp); + if (snapshot_path == NULL) { + goto fail; + } - snapdir = lp_parm_const_string(SNUM(handle->conn), "shadow", "snapdir", NULL); - if (snapdir == NULL) { - return NULL; + if (config->rel_connectpath == NULL) { + converted = talloc_asprintf(mem_ctx, "%s/%s", + snapshot_path, name); + } else { + converted = talloc_asprintf(mem_ctx, "%s/%s/%s", + snapshot_path, + config->rel_connectpath, + name); + } + if (converted == NULL) { + goto fail; + } + + ZERO_STRUCT(converted_fname); + converted_fname.base_name = converted; + + ret = SMB_VFS_NEXT_LSTAT(handle, &converted_fname); + DEBUG(10, ("Trying[not snapdirseverywhere] %s: %d (%s)\n", + converted, + ret, ret == 0 ? "ok" : strerror(errno))); + if (ret == 0) { + DEBUG(10, ("Found %s\n", converted)); + result = converted; + converted = NULL; + goto fail; + } else { + errno = ENOENT; + goto fail; + } + /* never reached ... */ } - /* if its an absolute path, we're done */ - if (*snapdir == '/') { - return snapdir; + + if (name[0] == 0) { + path = talloc_strdup(mem_ctx, handle->conn->connectpath); + } else { + path = talloc_asprintf( + mem_ctx, "%s/%s", handle->conn->connectpath, name); + } + if (path == NULL) { + errno = ENOMEM; + goto fail; } + pathlen = talloc_get_size(path)-1; - /* other its relative to the filesystem mount point */ - mount_point = find_mount_point(mem_ctx, handle); - if (mount_point == NULL) { - return NULL; + if (!shadow_copy2_find_slashes(talloc_tos(), path, + &slashes, &num_slashes)) { + goto fail; } - ret = talloc_asprintf(mem_ctx, "%s/%s", mount_point, snapdir); - talloc_free(mount_point); - return ret; -} + insert = shadow_copy2_insert_string(talloc_tos(), handle, timestamp); + if (insert == NULL) { + goto fail; + } + insertlen = talloc_get_size(insert)-1; + + /* + * Note: We deliberatly don't expensively initialize the + * array with talloc_zero here: Putting zero into + * converted[pathlen+insertlen] below is sufficient, because + * in the following for loop, the insert string is inserted + * at various slash places. So the memory up to position + * pathlen+insertlen will always be initialized when the + * converted string is used. + */ + converted = talloc_array(mem_ctx, char, pathlen + insertlen + 1); + if (converted == NULL) { + goto fail; + } -/* - work out the location of the base directory for snapshots of this share - */ -static const char *shadow_copy2_find_basedir(TALLOC_CTX *mem_ctx, vfs_handle_struct *handle) -{ - const char *basedir = lp_parm_const_string(SNUM(handle->conn), "shadow", "basedir", NULL); + if (path[pathlen-1] != '/') { + /* + * Append a fake slash to find the snapshot root + */ + size_t *tmp; + tmp = talloc_realloc(talloc_tos(), slashes, + size_t, num_slashes+1); + if (tmp == NULL) { + goto fail; + } + slashes = tmp; + slashes[num_slashes] = pathlen; + num_slashes += 1; + } - /* other its the filesystem mount point */ - if (basedir == NULL) { - basedir = find_mount_point(mem_ctx, handle); + min_offset = 0; + + if (!config->crossmountpoints) { + min_offset = strlen(config->mount_point); } - return basedir; -} + memcpy(converted, path, pathlen+1); + converted[pathlen+insertlen] = '\0'; -/* - convert a filename from a share relative path, to a path in the - snapshot directory - */ -static char *convert_shadow2_name(vfs_handle_struct *handle, const char *fname) -{ - TALLOC_CTX *tmp_ctx = talloc_new(handle->data); - const char *snapdir, *relpath, *baseoffset, *basedir; - size_t baselen; - char *ret; + ZERO_STRUCT(converted_fname); + converted_fname.base_name = converted; - snapdir = shadow_copy2_find_snapdir(tmp_ctx, handle); - if (snapdir == NULL) { - DEBUG(2,("no snapdir found for share at %s\n", handle->conn->connectpath)); - talloc_free(tmp_ctx); - return NULL; - } + for (i = num_slashes-1; i>=0; i--) { + int ret; + size_t offset; - basedir = shadow_copy2_find_basedir(tmp_ctx, handle); - if (basedir == NULL) { - DEBUG(2,("no basedir found for share at %s\n", handle->conn->connectpath)); - talloc_free(tmp_ctx); - return NULL; - } + offset = slashes[i]; - relpath = fname + GMT_NAME_LEN; - baselen = strlen(basedir); - baseoffset = handle->conn->connectpath + baselen; + if (offset < min_offset) { + errno = ENOENT; + goto fail; + } - /* some sanity checks */ - if (strncmp(basedir, handle->conn->connectpath, baselen) != 0 || - (handle->conn->connectpath[baselen] != 0 && handle->conn->connectpath[baselen] != '/')) { - DEBUG(0,("convert_shadow2_name: basedir %s is not a parent of %s\n", - basedir, handle->conn->connectpath)); - talloc_free(tmp_ctx); - return NULL; - } + memcpy(converted+offset, insert, insertlen); - if (*relpath == '/') relpath++; - if (*baseoffset == '/') baseoffset++; + offset += insertlen; + memcpy(converted+offset, path + slashes[i], + pathlen - slashes[i]); - ret = talloc_asprintf(handle->data, "%s/%.*s/%s/%s", - snapdir, - GMT_NAME_LEN, fname, - baseoffset, - relpath); - DEBUG(6,("convert_shadow2_name: '%s' -> '%s'\n", fname, ret)); - talloc_free(tmp_ctx); - return ret; -} + ret = SMB_VFS_NEXT_LSTAT(handle, &converted_fname); + DEBUG(10, ("Trying[snapdirseverywhere] %s: %d (%s)\n", + converted, + ret, ret == 0 ? "ok" : strerror(errno))); + if (ret == 0) { + /* success */ + break; + } + if (errno == ENOTDIR) { + /* + * This is a valid condition: We appended the + * .snaphots/@GMT.. to a file name. Just try + * with the upper levels. + */ + continue; + } + if (errno != ENOENT) { + /* Other problem than "not found" */ + goto fail; + } + } -/* - simple string hash - */ -static uint32 string_hash(const char *s) -{ - uint32 n = 0; - while (*s) { - n = ((n << 5) + n) ^ (uint32)(*s++); - } - return n; + if (i >= 0) { + /* + * Found something + */ + DEBUG(10, ("Found %s\n", converted)); + result = converted; + converted = NULL; + } else { + errno = ENOENT; + } +fail: + saved_errno = errno; + TALLOC_FREE(converted); + TALLOC_FREE(insert); + TALLOC_FREE(slashes); + TALLOC_FREE(path); + errno = saved_errno; + return result; } /* modify a sbuf return to ensure that inodes in the shadow directory are different from those in the main directory */ -static void convert_sbuf(vfs_handle_struct *handle, const char *fname, SMB_STRUCT_STAT *sbuf) +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 @@ -344,7 +647,11 @@ static void convert_sbuf(vfs_handle_struct *handle, const char *fname, SMB_STRUC number collision, but I can't see a better approach without significant VFS changes */ - uint32_t shash = string_hash(fname) & 0xFF000000; + TDB_DATA key = { .dptr = discard_const_p(uint8_t, fname), + .dsize = strlen(fname) }; + uint32_t shash; + + shash = tdb_jenkins_hash(&key) & 0xFF000000; if (shash == 0) { shash = 1; } @@ -352,268 +659,716 @@ static void convert_sbuf(vfs_handle_struct *handle, const char *fname, SMB_STRUC } } +static DIR *shadow_copy2_opendir(vfs_handle_struct *handle, + const char *fname, + const char *mask, + uint32_t attr) +{ + time_t timestamp; + char *stripped; + DIR *ret; + int saved_errno; + char *conv; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname, + ×tamp, &stripped)) { + return NULL; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_OPENDIR(handle, fname, mask, attr); + } + conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); + if (conv == NULL) { + return NULL; + } + ret = SMB_VFS_NEXT_OPENDIR(handle, conv, mask, attr); + saved_errno = errno; + TALLOC_FREE(conv); + errno = saved_errno; + return ret; +} + static int shadow_copy2_rename(vfs_handle_struct *handle, const struct smb_filename *smb_fname_src, const struct smb_filename *smb_fname_dst) { - SHADOW2_NEXT2_SMB_FNAME(RENAME, - (handle, smb_fname_src, smb_fname_dst)); + time_t timestamp_src, timestamp_dst; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, + smb_fname_src->base_name, + ×tamp_src, NULL)) { + return -1; + } + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, + smb_fname_dst->base_name, + ×tamp_dst, NULL)) { + return -1; + } + if (timestamp_src != 0) { + errno = EXDEV; + return -1; + } + if (timestamp_dst != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_RENAME(handle, smb_fname_src, smb_fname_dst); } static int shadow_copy2_symlink(vfs_handle_struct *handle, const char *oldname, const char *newname) { - SHADOW2_NEXT2(SYMLINK, (handle, oldname, newname)); -} + time_t timestamp_old, timestamp_new; -static int shadow_copy2_link(vfs_handle_struct *handle, - const char *oldname, const char *newname) -{ - SHADOW2_NEXT2(LINK, (handle, oldname, newname)); + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, oldname, + ×tamp_old, NULL)) { + return -1; + } + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, newname, + ×tamp_new, NULL)) { + return -1; + } + if ((timestamp_old != 0) || (timestamp_new != 0)) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_SYMLINK(handle, oldname, newname); } -static int shadow_copy2_open(vfs_handle_struct *handle, - struct smb_filename *smb_fname, files_struct *fsp, - int flags, mode_t mode) +static int shadow_copy2_link(vfs_handle_struct *handle, + const char *oldname, const char *newname) { - SHADOW2_NEXT_SMB_FNAME(OPEN, - (handle, smb_fname, fsp, flags, mode), - int, -1); -} + time_t timestamp_old, timestamp_new; -static SMB_STRUCT_DIR *shadow_copy2_opendir(vfs_handle_struct *handle, - const char *fname, const char *mask, uint32 attr) -{ - SHADOW2_NEXT(OPENDIR, (handle, name, mask, attr), SMB_STRUCT_DIR *, NULL); + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, oldname, + ×tamp_old, NULL)) { + return -1; + } + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, newname, + ×tamp_new, NULL)) { + return -1; + } + if ((timestamp_old != 0) || (timestamp_new != 0)) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_LINK(handle, oldname, newname); } static int shadow_copy2_stat(vfs_handle_struct *handle, struct smb_filename *smb_fname) { - _SHADOW2_NEXT_SMB_FNAME(STAT, (handle, smb_fname), int, -1, - convert_sbuf(handle, smb_fname->base_name, - &smb_fname->st)); -} - -static int shadow_copy2_lstat(vfs_handle_struct *handle, - struct smb_filename *smb_fname) -{ - _SHADOW2_NEXT_SMB_FNAME(LSTAT, (handle, smb_fname), int, -1, - convert_sbuf(handle, smb_fname->base_name, - &smb_fname->st)); -} + time_t timestamp; + char *stripped, *tmp; + int ret, saved_errno; -static int shadow_copy2_fstat(vfs_handle_struct *handle, files_struct *fsp, SMB_STRUCT_STAT *sbuf) -{ - int ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); - if (ret == 0 && shadow_copy2_match_name(fsp->fsp_name->base_name)) { - convert_sbuf(handle, fsp->fsp_name->base_name, sbuf); + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, + smb_fname->base_name, + ×tamp, &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_STAT(handle, smb_fname); } - return ret; -} -static int shadow_copy2_unlink(vfs_handle_struct *handle, - const struct smb_filename *smb_fname_in) -{ - struct smb_filename *smb_fname = NULL; - NTSTATUS status; + tmp = smb_fname->base_name; + smb_fname->base_name = shadow_copy2_convert( + talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); - status = copy_smb_filename(talloc_tos(), smb_fname_in, &smb_fname); - if (!NT_STATUS_IS_OK(status)) { - errno = map_errno_from_nt_status(status); + if (smb_fname->base_name == NULL) { + smb_fname->base_name = tmp; return -1; } - SHADOW2_NEXT_SMB_FNAME(UNLINK, (handle, smb_fname), int, -1); -} + ret = SMB_VFS_NEXT_STAT(handle, smb_fname); + saved_errno = errno; -static int shadow_copy2_chmod(vfs_handle_struct *handle, - const char *fname, mode_t mode) -{ - SHADOW2_NEXT(CHMOD, (handle, name, mode), int, -1); -} + TALLOC_FREE(smb_fname->base_name); + smb_fname->base_name = tmp; -static int shadow_copy2_chown(vfs_handle_struct *handle, - const char *fname, uid_t uid, gid_t gid) -{ - SHADOW2_NEXT(CHOWN, (handle, name, uid, gid), int, -1); + if (ret == 0) { + convert_sbuf(handle, smb_fname->base_name, &smb_fname->st); + } + errno = saved_errno; + return ret; } -static int shadow_copy2_chdir(vfs_handle_struct *handle, - const char *fname) +static int shadow_copy2_lstat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) { - SHADOW2_NEXT(CHDIR, (handle, name), int, -1); -} + time_t timestamp; + char *stripped, *tmp; + int ret, saved_errno; -static int shadow_copy2_ntimes(vfs_handle_struct *handle, - const struct smb_filename *smb_fname_in, - struct smb_file_time *ft) -{ - struct smb_filename *smb_fname = NULL; - NTSTATUS status; + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, + smb_fname->base_name, + ×tamp, &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_LSTAT(handle, smb_fname); + } - status = copy_smb_filename(talloc_tos(), smb_fname_in, &smb_fname); - if (!NT_STATUS_IS_OK(status)) { - errno = map_errno_from_nt_status(status); + tmp = smb_fname->base_name; + smb_fname->base_name = shadow_copy2_convert( + talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); + + if (smb_fname->base_name == NULL) { + smb_fname->base_name = tmp; return -1; } - SHADOW2_NEXT_SMB_FNAME(NTIMES, (handle, smb_fname, ft), int, -1); -} + ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + saved_errno = errno; -static int shadow_copy2_readlink(vfs_handle_struct *handle, - const char *fname, char *buf, size_t bufsiz) -{ - SHADOW2_NEXT(READLINK, (handle, name, buf, bufsiz), int, -1); -} + TALLOC_FREE(smb_fname->base_name); + smb_fname->base_name = tmp; -static int shadow_copy2_mknod(vfs_handle_struct *handle, - const char *fname, mode_t mode, SMB_DEV_T dev) -{ - SHADOW2_NEXT(MKNOD, (handle, name, mode, dev), int, -1); + if (ret == 0) { + convert_sbuf(handle, smb_fname->base_name, &smb_fname->st); + } + errno = saved_errno; + return ret; } -static char *shadow_copy2_realpath(vfs_handle_struct *handle, - const char *fname, char *resolved_path) +static int shadow_copy2_fstat(vfs_handle_struct *handle, files_struct *fsp, + SMB_STRUCT_STAT *sbuf) { - SHADOW2_NEXT(REALPATH, (handle, name, resolved_path), char *, NULL); + time_t timestamp; + int ret; + + ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); + if (ret == -1) { + return ret; + } + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, + fsp->fsp_name->base_name, + ×tamp, NULL)) { + return 0; + } + if (timestamp != 0) { + convert_sbuf(handle, fsp->fsp_name->base_name, sbuf); + } + return 0; } -static const char *shadow_copy2_connectpath(struct vfs_handle_struct *handle, - const char *fname) +static int shadow_copy2_open(vfs_handle_struct *handle, + struct smb_filename *smb_fname, files_struct *fsp, + int flags, mode_t mode) { - TALLOC_CTX *tmp_ctx = talloc_stackframe(); - const char *snapdir, *baseoffset, *basedir; - size_t baselen; - char *ret; + time_t timestamp; + char *stripped, *tmp; + int ret, saved_errno; - if (!shadow_copy2_match_name(fname)) { - return handle->conn->connectpath; - } - - snapdir = shadow_copy2_find_snapdir(tmp_ctx, handle); - if (snapdir == NULL) { - DEBUG(2,("no snapdir found for share at %s\n", - handle->conn->connectpath)); - TALLOC_FREE(tmp_ctx); - return NULL; + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, + smb_fname->base_name, + ×tamp, &stripped)) { + return -1; } - - basedir = shadow_copy2_find_basedir(tmp_ctx, handle); - if (basedir == NULL) { - DEBUG(2,("no basedir found for share at %s\n", - handle->conn->connectpath)); - TALLOC_FREE(tmp_ctx); - return NULL; + if (timestamp == 0) { + return SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode); } - baselen = strlen(basedir); - baseoffset = handle->conn->connectpath + baselen; + tmp = smb_fname->base_name; + smb_fname->base_name = shadow_copy2_convert( + talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); - /* some sanity checks */ - if (strncmp(basedir, handle->conn->connectpath, baselen) != 0 || - (handle->conn->connectpath[baselen] != 0 - && handle->conn->connectpath[baselen] != '/')) { - DEBUG(0,("shadow_copy2_connectpath: basedir %s is not a " - "parent of %s\n", basedir, - handle->conn->connectpath)); - TALLOC_FREE(tmp_ctx); - return NULL; + if (smb_fname->base_name == NULL) { + smb_fname->base_name = tmp; + return -1; } - if (*baseoffset == '/') baseoffset++; + ret = SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode); + saved_errno = errno; - ret = talloc_asprintf(talloc_tos(), "%s/%.*s/%s", - snapdir, - GMT_NAME_LEN, fname, - baseoffset); - DEBUG(6,("shadow_copy2_connectpath: '%s' -> '%s'\n", fname, ret)); - TALLOC_FREE(tmp_ctx); + TALLOC_FREE(smb_fname->base_name); + smb_fname->base_name = tmp; + + errno = saved_errno; return ret; } -static NTSTATUS shadow_copy2_get_nt_acl(vfs_handle_struct *handle, - const char *fname, uint32 security_info, - struct security_descriptor **ppdesc) +static int shadow_copy2_unlink(vfs_handle_struct *handle, + const struct smb_filename *smb_fname) { - SHADOW2_NTSTATUS_NEXT(GET_NT_ACL, (handle, name, security_info, ppdesc), NT_STATUS_ACCESS_DENIED); + time_t timestamp; + char *stripped; + int ret, saved_errno; + struct smb_filename *conv; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, + smb_fname->base_name, + ×tamp, &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_UNLINK(handle, smb_fname); + } + 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_UNLINK(handle, conv); + saved_errno = errno; + TALLOC_FREE(conv); + errno = saved_errno; + return ret; } -static int shadow_copy2_mkdir(vfs_handle_struct *handle, const char *fname, mode_t mode) +static int shadow_copy2_chmod(vfs_handle_struct *handle, const char *fname, + mode_t mode) { - SHADOW2_NEXT(MKDIR, (handle, name, mode), int, -1); + time_t timestamp; + char *stripped; + int ret, saved_errno; + char *conv; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname, + ×tamp, &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_CHMOD(handle, fname, mode); + } + conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); + if (conv == NULL) { + return -1; + } + ret = SMB_VFS_NEXT_CHMOD(handle, conv, mode); + saved_errno = errno; + TALLOC_FREE(conv); + errno = saved_errno; + return ret; } -static int shadow_copy2_rmdir(vfs_handle_struct *handle, const char *fname) +static int shadow_copy2_chown(vfs_handle_struct *handle, const char *fname, + uid_t uid, gid_t gid) { - SHADOW2_NEXT(RMDIR, (handle, name), int, -1); -} + time_t timestamp; + char *stripped; + int ret, saved_errno; + char *conv; -static int shadow_copy2_chflags(vfs_handle_struct *handle, const char *fname, - unsigned int flags) + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname, + ×tamp, &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_CHOWN(handle, fname, uid, gid); + } + conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); + if (conv == NULL) { + return -1; + } + ret = SMB_VFS_NEXT_CHOWN(handle, conv, uid, gid); + saved_errno = errno; + TALLOC_FREE(conv); + errno = saved_errno; + return ret; +} + +static int shadow_copy2_chdir(vfs_handle_struct *handle, + const char *fname) { - SHADOW2_NEXT(CHFLAGS, (handle, name, flags), int, -1); + time_t timestamp; + char *stripped; + int ret, saved_errno; + char *conv; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname, + ×tamp, &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_CHDIR(handle, fname); + } + conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); + if (conv == NULL) { + return -1; + } + ret = SMB_VFS_NEXT_CHDIR(handle, conv); + saved_errno = errno; + TALLOC_FREE(conv); + 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) +static int shadow_copy2_ntimes(vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + struct smb_file_time *ft) { - SHADOW2_NEXT(GETXATTR, (handle, name, aname, value, size), ssize_t, -1); + time_t timestamp; + char *stripped; + int ret, saved_errno; + struct smb_filename *conv; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, + smb_fname->base_name, + ×tamp, &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_NTIMES(handle, smb_fname, ft); + } + 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_NTIMES(handle, conv, ft); + saved_errno = errno; + TALLOC_FREE(conv); + errno = saved_errno; + return ret; } -static ssize_t shadow_copy2_lgetxattr(vfs_handle_struct *handle, - const char *fname, const char *aname, void *value, size_t size) +static int shadow_copy2_readlink(vfs_handle_struct *handle, + const char *fname, char *buf, size_t bufsiz) { - SHADOW2_NEXT(LGETXATTR, (handle, name, aname, value, size), ssize_t, -1); + time_t timestamp; + char *stripped; + int ret, saved_errno; + char *conv; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname, + ×tamp, &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_READLINK(handle, fname, buf, bufsiz); + } + conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); + if (conv == NULL) { + return -1; + } + ret = SMB_VFS_NEXT_READLINK(handle, conv, buf, bufsiz); + saved_errno = errno; + TALLOC_FREE(conv); + errno = saved_errno; + return ret; } -static ssize_t shadow_copy2_listxattr(struct vfs_handle_struct *handle, const char *fname, - char *list, size_t size) +static int shadow_copy2_mknod(vfs_handle_struct *handle, + const char *fname, mode_t mode, SMB_DEV_T dev) +{ + time_t timestamp; + char *stripped; + int ret, saved_errno; + char *conv; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname, + ×tamp, &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_MKNOD(handle, fname, mode, dev); + } + conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); + if (conv == NULL) { + return -1; + } + ret = SMB_VFS_NEXT_MKNOD(handle, conv, mode, dev); + saved_errno = errno; + TALLOC_FREE(conv); + errno = saved_errno; + return ret; +} + +static char *shadow_copy2_realpath(vfs_handle_struct *handle, + const char *fname) +{ + time_t timestamp; + char *stripped = NULL; + char *tmp = NULL; + char *result = NULL; + char *inserted = NULL; + char *inserted_to, *inserted_end; + int saved_errno; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname, + ×tamp, &stripped)) { + goto done; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_REALPATH(handle, fname); + } + + tmp = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp); + if (tmp == NULL) { + goto done; + } + + result = SMB_VFS_NEXT_REALPATH(handle, tmp); + if (result == NULL) { + goto done; + } + + /* + * Take away what we've inserted. This removes the @GMT-thingy + * completely, but will give a path under the share root. + */ + inserted = shadow_copy2_insert_string(talloc_tos(), handle, timestamp); + if (inserted == NULL) { + goto done; + } + inserted_to = strstr_m(result, inserted); + if (inserted_to == NULL) { + DEBUG(2, ("SMB_VFS_NEXT_REALPATH removed %s\n", inserted)); + goto done; + } + inserted_end = inserted_to + talloc_get_size(inserted) - 1; + memmove(inserted_to, inserted_end, strlen(inserted_end)+1); + +done: + saved_errno = errno; + TALLOC_FREE(inserted); + TALLOC_FREE(tmp); + TALLOC_FREE(stripped); + errno = saved_errno; + 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) { - SHADOW2_NEXT(LISTXATTR, (handle, name, list, size), ssize_t, -1); + 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, config->snapdir); + if (smb_fname.base_name == NULL) { + return NULL; + } + + ret = SMB_VFS_NEXT_STAT(handle, &smb_fname); + if ((ret == 0) && (S_ISDIR(smb_fname.st.st_ex_mode))) { + return smb_fname.base_name; + } + TALLOC_FREE(smb_fname.base_name); + return NULL; +} + +static bool check_access_snapdir(struct vfs_handle_struct *handle, + const char *path) +{ + struct smb_filename smb_fname; + int ret; + NTSTATUS status; + + ZERO_STRUCT(smb_fname); + smb_fname.base_name = talloc_asprintf(talloc_tos(), + "%s", + path); + if (smb_fname.base_name == NULL) { + return false; + } + + ret = SMB_VFS_NEXT_STAT(handle, &smb_fname); + if (ret != 0 || !S_ISDIR(smb_fname.st.st_ex_mode)) { + TALLOC_FREE(smb_fname.base_name); + return false; + } + + status = smbd_check_access_rights(handle->conn, + &smb_fname, + false, + SEC_DIR_LIST); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("user does not have list permission " + "on snapdir %s\n", + smb_fname.base_name)); + TALLOC_FREE(smb_fname.base_name); + return false; + } + TALLOC_FREE(smb_fname.base_name); + return true; } -static int shadow_copy2_removexattr(struct vfs_handle_struct *handle, const char *fname, - const char *aname) +/** + * Find the snapshot directory (if any) for the given + * filename (which is relative to the share). + */ +static const char *shadow_copy2_find_snapdir(TALLOC_CTX *mem_ctx, + struct vfs_handle_struct *handle, + struct smb_filename *smb_fname) { - SHADOW2_NEXT(REMOVEXATTR, (handle, name, aname), int, -1); + char *path, *p; + const char *snapdir; + struct shadow_copy2_config *config; + + SMB_VFS_HANDLE_GET_DATA(handle, config, struct shadow_copy2_config, + return NULL); + + /* + * If the non-snapdisrseverywhere mode, we should not search! + */ + if (!config->snapdirseverywhere) { + return config->snapshot_basepath; + } + + path = talloc_asprintf(mem_ctx, "%s/%s", + handle->conn->connectpath, + smb_fname->base_name); + if (path == NULL) { + return NULL; + } + + snapdir = have_snapdir(handle, path); + if (snapdir != NULL) { + TALLOC_FREE(path); + return snapdir; + } + + while ((p = strrchr(path, '/')) && (p > path)) { + + p[0] = '\0'; + + snapdir = have_snapdir(handle, path); + if (snapdir != NULL) { + TALLOC_FREE(path); + return snapdir; + } + } + TALLOC_FREE(path); + return NULL; } -static int shadow_copy2_lremovexattr(struct vfs_handle_struct *handle, const char *fname, - const char *aname) +static bool shadow_copy2_snapshot_to_gmt(vfs_handle_struct *handle, + const char *name, + char *gmt, size_t gmt_len) { - SHADOW2_NEXT(LREMOVEXATTR, (handle, name, aname), int, -1); + struct tm timestamp; + 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 = config->gmt_format; + + ZERO_STRUCT(timestamp); + if (config->use_sscanf) { + if (sscanf(name, fmt, ×tamp_long) != 1) { + DEBUG(10, ("shadow_copy2_snapshot_to_gmt: " + "no sscanf match %s: %s\n", + fmt, name)); + return false; + } + timestamp_t = timestamp_long; + gmtime_r(×tamp_t, ×tamp); + } else { + if (strptime(name, fmt, ×tamp) == NULL) { + 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)); + + if (config->use_localtime) { + timestamp.tm_isdst = -1; + timestamp_t = mktime(×tamp); + gmtime_r(×tamp_t, ×tamp); + } + } + + strftime(gmt, gmt_len, GMT_FORMAT, ×tamp); + return true; } -static int shadow_copy2_setxattr(struct vfs_handle_struct *handle, const char *fname, - const char *aname, const void *value, size_t size, int flags) +static int shadow_copy2_label_cmp_asc(const void *x, const void *y) { - SHADOW2_NEXT(SETXATTR, (handle, name, aname, value, size, flags), int, -1); + return strncmp((const char *)x, (const char *)y, sizeof(SHADOW_COPY_LABEL)); } -static int shadow_copy2_lsetxattr(struct vfs_handle_struct *handle, const char *fname, - const char *aname, const void *value, size_t size, int flags) +static int shadow_copy2_label_cmp_desc(const void *x, const void *y) { - SHADOW2_NEXT(LSETXATTR, (handle, name, aname, value, size, flags), int, -1); + return -strncmp((const char *)x, (const char *)y, sizeof(SHADOW_COPY_LABEL)); } -static int shadow_copy2_chmod_acl(vfs_handle_struct *handle, - const char *fname, mode_t mode) +/* + sort the shadow copy data in ascending or descending order + */ +static void shadow_copy2_sort_data(vfs_handle_struct *handle, + struct shadow_copy_data *shadow_copy2_data) { - SHADOW2_NEXT(CHMOD_ACL, (handle, name, mode), int, -1); + 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 = config->sort_order; + if (sort == NULL) { + return; + } + + if (strcmp(sort, "asc") == 0) { + cmpfunc = shadow_copy2_label_cmp_asc; + } else if (strcmp(sort, "desc") == 0) { + cmpfunc = shadow_copy2_label_cmp_desc; + } else { + return; + } + + if (shadow_copy2_data && shadow_copy2_data->num_volumes > 0 && + shadow_copy2_data->labels) + { + TYPESAFE_QSORT(shadow_copy2_data->labels, + shadow_copy2_data->num_volumes, + cmpfunc); + } } -static int shadow_copy2_get_shadow_copy2_data(vfs_handle_struct *handle, - files_struct *fsp, - SHADOW_COPY_DATA *shadow_copy2_data, - bool labels) +static int shadow_copy2_get_shadow_copy_data( + vfs_handle_struct *handle, files_struct *fsp, + struct shadow_copy_data *shadow_copy2_data, + bool labels) { - SMB_STRUCT_DIR *p; + DIR *p; const char *snapdir; - SMB_STRUCT_DIRENT *d; - TALLOC_CTX *tmp_ctx = talloc_new(handle->data); + struct dirent *d; + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + bool ret; - snapdir = shadow_copy2_find_snapdir(tmp_ctx, handle); + snapdir = shadow_copy2_find_snapdir(tmp_ctx, handle, fsp->fsp_name); if (snapdir == NULL) { DEBUG(0,("shadow:snapdir not found for %s in get_shadow_copy_data\n", handle->conn->connectpath)); @@ -621,6 +1376,13 @@ static int shadow_copy2_get_shadow_copy2_data(vfs_handle_struct *handle, talloc_free(tmp_ctx); return -1; } + ret = check_access_snapdir(handle, snapdir); + if (!ret) { + DEBUG(0,("access denied on listing snapdir %s\n", snapdir)); + errno = EACCES; + talloc_free(tmp_ctx); + return -1; + } p = SMB_VFS_NEXT_OPENDIR(handle, snapdir, NULL, 0); @@ -632,18 +1394,27 @@ static int shadow_copy2_get_shadow_copy2_data(vfs_handle_struct *handle, return -1; } - talloc_free(tmp_ctx); - shadow_copy2_data->num_volumes = 0; shadow_copy2_data->labels = NULL; while ((d = SMB_VFS_NEXT_READDIR(handle, p, NULL))) { + char snapshot[GMT_NAME_LEN+1]; SHADOW_COPY_LABEL *tlabels; - /* ignore names not of the right form in the snapshot directory */ - if (!shadow_copy2_match_name(d->d_name)) { + /* + * ignore names not of the right form in the snapshot + * directory + */ + if (!shadow_copy2_snapshot_to_gmt( + handle, d->d_name, + snapshot, sizeof(snapshot))) { + + DEBUG(6, ("shadow_copy2_get_shadow_copy_data: " + "ignoring %s\n", d->d_name)); continue; } + DEBUG(6,("shadow_copy2_get_shadow_copy_data: %s -> %s\n", + d->d_name, snapshot)); if (!labels) { /* the caller doesn't want the labels */ @@ -651,77 +1422,668 @@ static int shadow_copy2_get_shadow_copy2_data(vfs_handle_struct *handle, continue; } - tlabels = talloc_realloc(shadow_copy2_data->mem_ctx, + tlabels = talloc_realloc(shadow_copy2_data, shadow_copy2_data->labels, - SHADOW_COPY_LABEL, shadow_copy2_data->num_volumes+1); + SHADOW_COPY_LABEL, + shadow_copy2_data->num_volumes+1); if (tlabels == NULL) { DEBUG(0,("shadow_copy2: out of memory\n")); SMB_VFS_NEXT_CLOSEDIR(handle, p); + talloc_free(tmp_ctx); return -1; } - strlcpy(tlabels[shadow_copy2_data->num_volumes], d->d_name, sizeof(*tlabels)); + strlcpy(tlabels[shadow_copy2_data->num_volumes], snapshot, + sizeof(*tlabels)); + shadow_copy2_data->num_volumes++; shadow_copy2_data->labels = tlabels; } SMB_VFS_NEXT_CLOSEDIR(handle,p); + + shadow_copy2_sort_data(handle, shadow_copy2_data); + + talloc_free(tmp_ctx); return 0; } -static struct vfs_fn_pointers vfs_shadow_copy2_fns = { - .opendir = shadow_copy2_opendir, - .mkdir = shadow_copy2_mkdir, - .rmdir = shadow_copy2_rmdir, - .chflags = shadow_copy2_chflags, - .getxattr = shadow_copy2_getxattr, - .lgetxattr = shadow_copy2_lgetxattr, - .listxattr = shadow_copy2_listxattr, - .removexattr = shadow_copy2_removexattr, - .lremovexattr = shadow_copy2_lremovexattr, - .setxattr = shadow_copy2_setxattr, - .lsetxattr = shadow_copy2_lsetxattr, - .open = shadow_copy2_open, - .rename = shadow_copy2_rename, - .stat = shadow_copy2_stat, - .lstat = shadow_copy2_lstat, - .fstat = shadow_copy2_fstat, - .unlink = shadow_copy2_unlink, - .chmod = shadow_copy2_chmod, - .chown = shadow_copy2_chown, - .chdir = shadow_copy2_chdir, - .ntimes = shadow_copy2_ntimes, - .symlink = shadow_copy2_symlink, - .vfs_readlink = shadow_copy2_readlink, - .link = shadow_copy2_link, - .mknod = shadow_copy2_mknod, - .realpath = shadow_copy2_realpath, - .connectpath = shadow_copy2_connectpath, - .get_nt_acl = shadow_copy2_get_nt_acl, - .chmod_acl = shadow_copy2_chmod_acl, - .get_shadow_copy_data = shadow_copy2_get_shadow_copy2_data, -}; +static NTSTATUS shadow_copy2_fget_nt_acl(vfs_handle_struct *handle, + struct files_struct *fsp, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc) +{ + time_t timestamp; + char *stripped; + NTSTATUS status; + char *conv; -NTSTATUS vfs_shadow_copy2_init(void); -NTSTATUS vfs_shadow_copy2_init(void) + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, + fsp->fsp_name->base_name, + ×tamp, &stripped)) { + return map_nt_error_from_unix(errno); + } + 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); + TALLOC_FREE(stripped); + if (conv == NULL) { + return map_nt_error_from_unix(errno); + } + status = SMB_VFS_NEXT_GET_NT_ACL(handle, conv, security_info, + mem_ctx, ppdesc); + TALLOC_FREE(conv); + return status; +} + +static NTSTATUS shadow_copy2_get_nt_acl(vfs_handle_struct *handle, + const char *fname, + uint32_t security_info, + TALLOC_CTX *mem_ctx, + struct security_descriptor **ppdesc) +{ + time_t timestamp; + char *stripped; + NTSTATUS status; + char *conv; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname, + ×tamp, &stripped)) { + return map_nt_error_from_unix(errno); + } + if (timestamp == 0) { + return SMB_VFS_NEXT_GET_NT_ACL(handle, fname, security_info, + 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, + mem_ctx, ppdesc); + TALLOC_FREE(conv); + return status; +} + +static int shadow_copy2_mkdir(vfs_handle_struct *handle, + const char *fname, mode_t mode) +{ + time_t timestamp; + char *stripped; + int ret, saved_errno; + char *conv; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname, + ×tamp, &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_MKDIR(handle, fname, mode); + } + conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); + if (conv == NULL) { + return -1; + } + ret = SMB_VFS_NEXT_MKDIR(handle, conv, mode); + saved_errno = errno; + TALLOC_FREE(conv); + errno = saved_errno; + return ret; +} + +static int shadow_copy2_rmdir(vfs_handle_struct *handle, const char *fname) +{ + time_t timestamp; + char *stripped; + int ret, saved_errno; + char *conv; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname, + ×tamp, &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_RMDIR(handle, fname); + } + conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); + if (conv == NULL) { + return -1; + } + ret = SMB_VFS_NEXT_RMDIR(handle, conv); + saved_errno = errno; + TALLOC_FREE(conv); + errno = saved_errno; + return ret; +} + +static int shadow_copy2_chflags(vfs_handle_struct *handle, const char *fname, + unsigned int flags) +{ + time_t timestamp; + char *stripped; + int ret, saved_errno; + char *conv; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname, + ×tamp, &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_CHFLAGS(handle, fname, flags); + } + conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); + if (conv == NULL) { + return -1; + } + ret = SMB_VFS_NEXT_CHFLAGS(handle, conv, flags); + saved_errno = errno; + TALLOC_FREE(conv); + 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) +{ + time_t timestamp; + char *stripped; + ssize_t ret; + int saved_errno; + char *conv; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname, + ×tamp, &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_GETXATTR(handle, fname, aname, value, + size); + } + conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); + if (conv == NULL) { + return -1; + } + ret = SMB_VFS_NEXT_GETXATTR(handle, conv, aname, value, size); + saved_errno = errno; + TALLOC_FREE(conv); + errno = saved_errno; + return ret; +} + +static ssize_t shadow_copy2_listxattr(struct vfs_handle_struct *handle, + const char *fname, + char *list, size_t size) +{ + time_t timestamp; + char *stripped; + ssize_t ret; + int saved_errno; + char *conv; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname, + ×tamp, &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_LISTXATTR(handle, 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; + TALLOC_FREE(conv); + errno = saved_errno; + return ret; +} + +static int shadow_copy2_removexattr(vfs_handle_struct *handle, + const char *fname, const char *aname) +{ + time_t timestamp; + char *stripped; + int ret, saved_errno; + char *conv; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname, + ×tamp, &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_REMOVEXATTR(handle, 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; + TALLOC_FREE(conv); + errno = saved_errno; + return ret; +} + +static int shadow_copy2_setxattr(struct vfs_handle_struct *handle, + const char *fname, + const char *aname, const void *value, + size_t size, int flags) +{ + time_t timestamp; + char *stripped; + ssize_t ret; + int saved_errno; + char *conv; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname, + ×tamp, &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_SETXATTR(handle, 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; + TALLOC_FREE(conv); + errno = saved_errno; + return ret; +} + +static int shadow_copy2_chmod_acl(vfs_handle_struct *handle, + const char *fname, mode_t mode) +{ + time_t timestamp; + char *stripped; + ssize_t ret; + int saved_errno; + char *conv; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, fname, + ×tamp, &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_CHMOD_ACL(handle, fname, mode); + } + conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); + if (conv == NULL) { + return -1; + } + ret = SMB_VFS_NEXT_CHMOD_ACL(handle, conv, mode); + saved_errno = errno; + TALLOC_FREE(conv); + errno = saved_errno; + return ret; +} + +static int shadow_copy2_get_real_filename(struct vfs_handle_struct *handle, + const char *path, + const char *name, + TALLOC_CTX *mem_ctx, + char **found_name) +{ + time_t timestamp; + char *stripped; + ssize_t ret; + int saved_errno; + char *conv; + + DEBUG(10, ("shadow_copy2_get_real_filename called for path=[%s], " + "name=[%s]\n", path, name)); + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, path, + ×tamp, &stripped)) { + DEBUG(10, ("shadow_copy2_strip_snapshot failed\n")); + return -1; + } + if (timestamp == 0) { + DEBUG(10, ("timestamp == 0\n")); + return SMB_VFS_NEXT_GET_REAL_FILENAME(handle, path, name, + mem_ctx, found_name); + } + conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); + if (conv == NULL) { + DEBUG(10, ("shadow_copy2_convert failed\n")); + return -1; + } + DEBUG(10, ("Calling NEXT_GET_REAL_FILE_NAME for conv=[%s], " + "name=[%s]\n", conv, name)); + 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; + TALLOC_FREE(conv); + errno = saved_errno; + return ret; +} + +static uint64_t shadow_copy2_disk_free(vfs_handle_struct *handle, + const char *path, uint64_t *bsize, + uint64_t *dfree, uint64_t *dsize) +{ + time_t timestamp; + char *stripped; + ssize_t ret; + int saved_errno; + char *conv; + + if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, path, + ×tamp, &stripped)) { + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_DISK_FREE(handle, path, + bsize, dfree, dsize); + } + + conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp); + TALLOC_FREE(stripped); + if (conv == NULL) { + return -1; + } + + ret = SMB_VFS_NEXT_DISK_FREE(handle, conv, bsize, dfree, dsize); + + saved_errno = errno; + TALLOC_FREE(conv); + errno = saved_errno; + + return ret; +} + +static int shadow_copy2_connect(struct vfs_handle_struct *handle, + const char *service, const char *user) { - NTSTATUS ret; + 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; - ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "shadow_copy2", - &vfs_shadow_copy2_fns); + DEBUG(10, (__location__ ": cnum[%u], connectpath[%s]\n", + (unsigned)handle->conn->cnum, + handle->conn->connectpath)); - if (!NT_STATUS_IS_OK(ret)) + 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) { + DBG_WARNING("Warning: the share root (%s) is " + "not a subdirectory of the " + "specified mountpoint (%s). " + "Ignoring provided value.\n", + handle->conn->connectpath, + mount_point); + mount_point = NULL; + } + } + } - vfs_shadow_copy2_debug_level = debug_add_class("shadow_copy2"); - if (vfs_shadow_copy2_debug_level == -1) { - vfs_shadow_copy2_debug_level = DBGC_VFS; - DEBUG(0, ("%s: Couldn't register custom debugging class!\n", - "vfs_shadow_copy2_init")); + 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 { - DEBUG(10, ("%s: Debug class number of '%s': %d\n", - "vfs_shadow_copy2_init","shadow_copy2",vfs_shadow_copy2_debug_level)); + config->mount_point = shadow_copy2_find_mount_point(config, + handle); + if (config->mount_point == NULL) { + DBG_WARNING("shadow_copy2_find_mount_point " + "of the share root '%s' failed: %s\n", + handle->conn->connectpath, strerror(errno)); + return -1; + } } - return ret; + 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 (strlen(config->basedir) != strlen(handle->conn->connectpath)) { + config->rel_connectpath = talloc_strdup(config, + handle->conn->connectpath + strlen(config->basedir)); + if (config->rel_connectpath == 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; + } + + config->snapshot_basepath = config->snapdir; + } else { + config->snapshot_basepath = talloc_asprintf(config, "%s/%s", + config->mount_point, config->snapdir); + if (config->snapshot_basepath == NULL) { + DEBUG(0, ("talloc_asprintf() failed\n")); + errno = ENOMEM; + return -1; + } + } + + DEBUG(10, ("shadow_copy2_connect: configuration:\n" + " share root: '%s'\n" + " basedir: '%s'\n" + " mountpoint: '%s'\n" + " rel share root: '%s'\n" + " snapdir: '%s'\n" + " snapshot base path: '%s'\n" + " format: '%s'\n" + " use sscanf: %s\n" + " snapdirs everywhere: %s\n" + " cross mountpoints: %s\n" + " fix inodes: %s\n" + " sort order: %s\n" + "", + handle->conn->connectpath, + config->basedir, + config->mount_point, + config->rel_connectpath, + config->snapdir, + config->snapshot_basepath, + config->gmt_format, + config->use_sscanf ? "yes" : "no", + config->snapdirseverywhere ? "yes" : "no", + config->crossmountpoints ? "yes" : "no", + config->fixinodes ? "yes" : "no", + config->sort_order + )); + + + 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, + .disk_free_fn = shadow_copy2_disk_free, + .rename_fn = shadow_copy2_rename, + .link_fn = shadow_copy2_link, + .symlink_fn = shadow_copy2_symlink, + .stat_fn = shadow_copy2_stat, + .lstat_fn = shadow_copy2_lstat, + .fstat_fn = shadow_copy2_fstat, + .open_fn = shadow_copy2_open, + .unlink_fn = shadow_copy2_unlink, + .chmod_fn = shadow_copy2_chmod, + .chown_fn = shadow_copy2_chown, + .chdir_fn = shadow_copy2_chdir, + .ntimes_fn = shadow_copy2_ntimes, + .readlink_fn = shadow_copy2_readlink, + .mknod_fn = shadow_copy2_mknod, + .realpath_fn = shadow_copy2_realpath, + .get_nt_acl_fn = shadow_copy2_get_nt_acl, + .fget_nt_acl_fn = shadow_copy2_fget_nt_acl, + .get_shadow_copy_data_fn = shadow_copy2_get_shadow_copy_data, + .mkdir_fn = shadow_copy2_mkdir, + .rmdir_fn = shadow_copy2_rmdir, + .getxattr_fn = shadow_copy2_getxattr, + .listxattr_fn = shadow_copy2_listxattr, + .removexattr_fn = shadow_copy2_removexattr, + .setxattr_fn = shadow_copy2_setxattr, + .chmod_acl_fn = shadow_copy2_chmod_acl, + .chflags_fn = shadow_copy2_chflags, + .get_real_filename_fn = shadow_copy2_get_real_filename, +}; + +NTSTATUS vfs_shadow_copy2_init(void); +NTSTATUS vfs_shadow_copy2_init(void) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "shadow_copy2", &vfs_shadow_copy2_fns); }