[PATCH] Shadow Copy Improvements
Ed Plese
ed at edplese.com
Thu Apr 26 17:44:27 GMT 2007
Attached is a set of 3 patches that add fixes and improvements to the
existing shadow_copy VFS module. I had previously sent a patch for a new
shadow_copy_zfs module with customizations specific to ZFS on Solaris but
these patches instead add generic improvements that should work with most
snapshot methods.
I'd like give a big thanks to Alison Winters for contributing many of the
ideas for this patch as well as code and testing.
The defaults for all of the new smb.conf parameters are set to values
that preserve backwards compatibility with the current module.
The patches are:
1-dirent-fix.patch
This fixes the module to work on systems that define struct dirent with
d_name[1]. Solaris is an example of such a system and it causes the
share to appear to be completely empty.
2-sort.patch
With the existing shadow_copy module the shadow copies are displayed in
the Windows GUI in the order the server obtains them from readdir(3).
On some systems and filesystems readdir(3) does not return files in any
particular order which leads to the list in the GUI being unsorted and
difficult to use. This patch allows the list to be sorted based on
a "sort" parameter specified for the module. Allowed values are "asc"
or "desc". When not specified the current unsorted behavior is maintained.
3-paths.patch
This patch allows for various components of the snapshot paths to be
easily customized. Currently this must be done by creating scripts that
will create the appropriate symbolic links every time a snapshot is taken
and consequently clean up all of the symbolic links whenever a snapshot
is deleted. With this patch the path components are specified in the
smb.conf file using the new parameters "path", "subpath", "format", and
"localtime". The defaults for all of the new parameters maintain the
current behavior of the module.
path - Specifies the path to the directory that contains the snapshots.
format - This is the format of the snapshot names in str[fp]time notation
except that $ characters are used in place of % characters.
subpath - The subdirectory under the snapshot that contains all of the
files for this shadow copy.
localtime (boolean) - Treat the snapshot names as being in localtime
instead of the default of GMT.
These probably aren't the clearest explanations of the parameters but the
examples below should help to clear up any confusion.
Some limitations include:
* shadow copy does not work correctly when mapping a drive to a
subdirectory of a share
* snapshot names are limited to expressions that can be expressed with the
str[fp]time(3) functions and variable substitutions in smb.conf
Example uses:
Use with @GMT- directories or symbolic links in the share:
[homes]
public = no
writable = yes
printable = no
vfs object = shadow_copy
A single large filesystem mounted at /home that contains all of the home
directories. The snapshots reside in /snapshots/home.
[homes]
path = /home/%U
public = no
writable = yes
printable = no
vfs object = shadow_copy
shadow_copy: path = /snapshots/home
shadow_copy: subpath = %U
shadow_copy: format = $Y.$m.$d-$H.$M.$S
shadow_copy: sort = desc
shadow_copy: localtime = yes
A separate ZFS filesystem for each home directory.
[homes]
path = /home/%U
public = no
writable = yes
printable = no
vfs object = shadow_copy
shadow_copy: path = /home/%U/.zfs/snapshot
shadow_copy: format = $Y.$m.$d-$H.$M.$S
shadow_copy: sort = desc
shadow_copy: localtime = yes
Comments and feedback are welcome!
Thanks,
Ed Plese
-------------- next part --------------
=== modified file 'source/modules/vfs_shadow_copy.c'
--- source/modules/vfs_shadow_copy.c 2006-12-21 13:52:11 +0000
+++ source/modules/vfs_shadow_copy.c 2007-04-16 01:45:15 +0000
@@ -58,8 +58,8 @@
typedef struct {
int pos;
- int num;
- SMB_STRUCT_DIRENT *dirs;
+ int length;
+ unsigned char *dirs;
} shadow_copy_Dir;
static BOOL shadow_copy_match_name(const char *name)
@@ -106,13 +106,14 @@
DEBUG(10,("shadow_copy_opendir: not hide [%s]\n",d->d_name));
- dirp->dirs = SMB_REALLOC_ARRAY(dirp->dirs,SMB_STRUCT_DIRENT, dirp->num+1);
+ dirp->dirs = SMB_REALLOC(dirp->dirs, dirp->length + d->d_reclen);
if (!dirp->dirs) {
DEBUG(0,("shadow_copy_opendir: Out of memory\n"));
break;
}
- dirp->dirs[dirp->num++] = *d;
+ memcpy(dirp->dirs + dirp->length, d, d->d_reclen);
+ dirp->length += d->d_reclen;
}
SMB_VFS_NEXT_CLOSEDIR(handle,p);
@@ -121,10 +122,13 @@
static SMB_STRUCT_DIRENT *shadow_copy_readdir(vfs_handle_struct *handle, SMB_STRUCT_DIR *_dirp)
{
+ SMB_STRUCT_DIRENT *d;
shadow_copy_Dir *dirp = (shadow_copy_Dir *)_dirp;
- if (dirp->pos < dirp->num) {
- return &(dirp->dirs[dirp->pos++]);
+ if (dirp->pos < dirp->length) {
+ d = (SMB_STRUCT_DIRENT *)(dirp->dirs + dirp->pos);
+ dirp->pos += d->d_reclen;
+ return d;
}
return NULL;
@@ -134,7 +138,7 @@
{
shadow_copy_Dir *dirp = (shadow_copy_Dir *)_dirp;
- if (offset < dirp->num) {
+ if (offset < dirp->length) {
dirp->pos = offset ;
}
}
-------------- next part --------------
=== modified file 'source/modules/vfs_shadow_copy.c'
--- source/modules/vfs_shadow_copy.c 2007-04-16 01:45:15 +0000
+++ source/modules/vfs_shadow_copy.c 2007-04-26 15:45:09 +0000
@@ -56,12 +56,50 @@
#define SHADOW_COPY_PREFIX "@GMT-"
#define SHADOW_COPY_SAMPLE "@GMT-2004.02.18-15.44.00"
+#define SHADOW_COPY_DEFAULT_SORT ""
+
typedef struct {
int pos;
int length;
unsigned char *dirs;
} shadow_copy_Dir;
+static int shadow_copy_label_cmp_asc(const void *x, const void *y)
+{
+ return strncmp((char *)x, (char *)y, sizeof(SHADOW_COPY_LABEL));
+}
+
+static int shadow_copy_label_cmp_desc(const void *x, const void *y)
+{
+ return -strncmp((char *)x, (char *)y, sizeof(SHADOW_COPY_LABEL));
+}
+
+static void shadow_copy_sort_data(vfs_handle_struct *handle, SHADOW_COPY_DATA *shadow_copy_data)
+{
+ const char *tmp_str = NULL;
+
+ if (shadow_copy_data && shadow_copy_data->num_volumes > 0 &&
+ shadow_copy_data->labels) {
+
+ tmp_str = lp_parm_const_string(SNUM(handle->conn), "shadow_copy", "sort",SHADOW_COPY_DEFAULT_SORT);
+
+ if (strcmp(tmp_str, "asc") == 0) {
+ qsort(shadow_copy_data->labels,
+ shadow_copy_data->num_volumes,
+ sizeof(SHADOW_COPY_LABEL),
+ shadow_copy_label_cmp_asc);
+
+ } else if (strcmp(tmp_str, "desc") == 0) {
+ qsort(shadow_copy_data->labels,
+ shadow_copy_data->num_volumes,
+ sizeof(SHADOW_COPY_LABEL),
+ shadow_copy_label_cmp_desc);
+ }
+ }
+
+ return;
+}
+
static BOOL shadow_copy_match_name(const char *name)
{
if (strncmp(SHADOW_COPY_PREFIX,name, sizeof(SHADOW_COPY_PREFIX)-1)==0 &&
@@ -213,6 +251,8 @@
shadow_copy_data->labels = tlabels;
}
+ shadow_copy_sort_data(handle, shadow_copy_data);
+
SMB_VFS_NEXT_CLOSEDIR(handle,p);
return 0;
}
-------------- next part --------------
=== modified file 'source/modules/vfs_shadow_copy.c'
--- source/modules/vfs_shadow_copy.c 2007-04-26 15:45:09 +0000
+++ source/modules/vfs_shadow_copy.c 2007-04-26 15:46:05 +0000
@@ -46,8 +46,52 @@
Note: Files must differ to be displayed via Windows Explorer!
Directories are always displayed...
+
+ In addition it is possible to specify additional parameters
+ that eliminate the need to utilize the @GMT- directories
+ or symbolic links.
+
+ path
+ Specifies the path to the directory that contains the
+ snapshots.
+
+ format
+ Specifies the format of the snapshot names in str[fp]time
+ notation except that $ characters are used in place of %
+ characters.
+
+ subpath
+ Specifies the subdirectory under the snapshot that contains
+ all of the files for this shadow copy.
+
+ localtime (boolean)
+ Treat the snapshot names as being in localtime instead of
+ the default of GMT.
+
+ sort
+ Sorts the shadow copies, specified as "asc" or "desc".
+ The default is to leave them unsorted.
+
+ Below is example usage for a single large filesystem mounted
+ at /home that contains all of the home directories. The
+ snapshots reside in /snapshots/home.
+
+ [homes]
+ path = /home/%U
+ public = no
+ writable = yes
+ printable = no
+ vfs object = shadow_copy
+ shadow_copy: path = /snapshots/home
+ shadow_copy: subpath = %U
+ shadow_copy: format = $Y.$m.$d-$H.$M.$S
+ shadow_copy: sort = desc
+ shadow_copy: localtime = yes
+
*/
+extern userdom_struct current_user_info;
+
static int vfs_shadow_copy_debug_level = DBGC_VFS;
#undef DBGC_CLASS
@@ -56,6 +100,11 @@
#define SHADOW_COPY_PREFIX "@GMT-"
#define SHADOW_COPY_SAMPLE "@GMT-2004.02.18-15.44.00"
+#define SHADOW_COPY_GMT_FORMAT "@GMT-%Y.%m.%d-%H.%M.%S"
+#define SHADOW_COPY_DEFAULT_FORMAT "@GMT-$Y.$m.$d-$H.$M.$S"
+#define SHADOW_COPY_DEFAULT_PATH "."
+#define SHADOW_COPY_DEFAULT_SUBPATH "."
+#define SHADOW_COPY_DEFAULT_LOCALTIME (False)
#define SHADOW_COPY_DEFAULT_SORT ""
typedef struct {
@@ -110,13 +159,146 @@
return False;
}
+static BOOL shadow_copy_snapshot_to_gmt(vfs_handle_struct *handle, const char *name, char *converted, int converted_length)
+{
+ char *cstr = NULL;
+ struct tm timestamp;
+ time_t timestamp_t;
+
+ char *fmt = alloc_sub_basic(get_current_username(), current_user_info.domain,
+ lp_parm_const_string(SNUM(handle->conn),
+ "shadow_copy", "format", SHADOW_COPY_DEFAULT_FORMAT));
+ char *tmp;
+
+ if (fmt == NULL) {
+ DEBUG(0, ("shadow_copy_snapshot_to_gmt: alloc_sub_basic failed for format\n"));
+ return False;
+ }
+
+ /* replace $ in the parameter with % */
+ for (tmp = fmt; *tmp != '\0'; tmp++) {
+ if (*tmp == '$') {
+ *tmp = '%';
+ }
+ }
+
+ memset(×tamp, 0, sizeof(timestamp));
+ cstr = strptime(name, fmt, ×tamp);
+
+ if (cstr == NULL) {
+ DEBUG(10, ("shadow_copy_snapshot_to_gmt: no match %s: %s\n", fmt, name));
+ SAFE_FREE(fmt);
+ return False;
+ }
+
+ DEBUG(10, ("shadow_copy_snapshot_to_gmt: match %s: %s\n", fmt, name));
+ if (lp_parm_bool(SNUM(handle->conn), "shadow_copy", "localtime", SHADOW_COPY_DEFAULT_LOCALTIME)) {
+ timestamp.tm_isdst = -1;
+ timestamp_t = mktime(×tamp);
+ gmtime_r(×tamp_t, ×tamp);
+ }
+ strftime(converted, converted_length, SHADOW_COPY_GMT_FORMAT, ×tamp);
+ SAFE_FREE(fmt);
+ return True;
+}
+
+static BOOL shadow_copy_file_to_snapshot_path(vfs_handle_struct *handle, const char *path, char *converted, int converted_length)
+{
+ /* all conversions start with the initial portion of the path matching
+ * @GMT-xxxxxxx
+ *
+ * take the full string and pass it along to strptime */
+
+ char *cstr = NULL;
+ struct tm timestamp;
+ time_t timestamp_t;
+ char snapname[MAXPATHLEN];
+ char *snappath = NULL;
+ char *subpath = NULL;
+
+ char *fmt = alloc_sub_basic(get_current_username(), current_user_info.domain,
+ lp_parm_const_string(SNUM(handle->conn),
+ "shadow_copy", "format", SHADOW_COPY_DEFAULT_FORMAT));
+ char *tmp;
+
+ if (fmt == NULL) {
+ DEBUG(0, ("shadow_copy_file_to_snapshot_path: alloc_sub_basic failed for format\n"));
+ return False;
+ }
+
+ /* replace $ in the parameter with % */
+ for (tmp = fmt; *tmp != '\0'; tmp++) {
+ if (*tmp == '$') {
+ *tmp = '%';
+ }
+ }
+
+ memset(×tamp, 0, sizeof(timestamp));
+ cstr = strptime(path, SHADOW_COPY_GMT_FORMAT, ×tamp);
+
+ if (cstr == NULL) {
+ /* string doesn't match the required SHADOW_COPY_GMT_FORMAT so just
+ * return the original path */
+
+ strncpy(converted, path, converted_length);
+ SAFE_FREE(fmt);
+ return False;
+ }
+
+ /* cstr is the remaining portion of the path after the @GMT-xxx */
+
+ if (lp_parm_bool(SNUM(handle->conn), "shadow_copy", "localtime", SHADOW_COPY_DEFAULT_LOCALTIME)) {
+ timestamp_t = timegm(×tamp);
+ localtime_r(×tamp_t, ×tamp);
+ }
+ strftime(snapname, MAXPATHLEN, fmt, ×tamp);
+
+ snappath = alloc_sub_basic(get_current_username(), current_user_info.domain,
+ lp_parm_const_string(SNUM(handle->conn),
+ "shadow_copy", "path", SHADOW_COPY_DEFAULT_PATH));
+ if (snappath == NULL) {
+ DEBUG(0, ("shadow_copy_file_to_snapshot_path: alloc_sub_basic failed for path\n"));
+ SAFE_FREE(fmt);
+ return False;
+ }
+
+ subpath = alloc_sub_basic(get_current_username(), current_user_info.domain,
+ lp_parm_const_string(SNUM(handle->conn),
+ "shadow_copy", "subpath", SHADOW_COPY_DEFAULT_SUBPATH));
+ if (subpath == NULL) {
+ DEBUG(0, ("shadow_copy_file_to_snapshot_path: alloc_sub_basic failed for subpath\n"));
+ SAFE_FREE(fmt);
+ SAFE_FREE(snappath);
+ return False;
+ }
+
+ /* path/snap/subpath/filepath */
+ snprintf(converted, converted_length, "%s/%s/%s/%s",
+ snappath,
+ snapname,
+ subpath,
+ cstr);
+
+ SAFE_FREE(fmt);
+ SAFE_FREE(snappath);
+ SAFE_FREE(subpath);
+ return True;
+}
+
static SMB_STRUCT_DIR *shadow_copy_opendir(vfs_handle_struct *handle, const char *fname, const char *mask, uint32 attr)
{
shadow_copy_Dir *dirp;
- SMB_STRUCT_DIR *p = SMB_VFS_NEXT_OPENDIR(handle,fname,mask,attr);
+ SMB_STRUCT_DIR *p = NULL;
+
+ char newpath[MAXPATHLEN];
+ shadow_copy_file_to_snapshot_path(handle, fname, newpath, MAXPATHLEN);
+
+ DEBUG(10,("shadow_copy_opendir: [%s] -> [%s]\n",fname,newpath));
+
+ p = SMB_VFS_NEXT_OPENDIR(handle,newpath,mask,attr);
if (!p) {
- DEBUG(0,("shadow_copy_opendir: SMB_VFS_NEXT_OPENDIR() failed for [%s]\n",fname));
+ DEBUG(0,("shadow_copy_opendir: SMB_VFS_NEXT_OPENDIR() failed for [%s]\n",newpath));
return NULL;
}
@@ -203,15 +385,49 @@
return 0;
}
+static int shadow_copy_stat(vfs_handle_struct *handle, const char *fname, SMB_STRUCT_STAT *sbuf)
+{
+ char newpath[MAXPATHLEN];
+
+ shadow_copy_file_to_snapshot_path(handle, fname, newpath, MAXPATHLEN);
+ DEBUG(10,("shadow_copy_stat: [%s] -> [%s]\n",fname,newpath));
+
+ return SMB_VFS_NEXT_STAT(handle, newpath, sbuf);
+}
+
+static int shadow_copy_open(vfs_handle_struct *handle, const char *fname, files_struct *fsp, int flags, mode_t mode)
+{
+ char newpath[MAXPATHLEN];
+
+ shadow_copy_file_to_snapshot_path(handle, fname, newpath, MAXPATHLEN);
+ DEBUG(10,("shadow_copy_open: [%s] -> [%s]\n",fname,newpath));
+
+ return SMB_VFS_NEXT_OPEN(handle, newpath, fsp, flags, mode);
+}
+
static int shadow_copy_get_shadow_copy_data(vfs_handle_struct *handle, files_struct *fsp, SHADOW_COPY_DATA *shadow_copy_data, BOOL labels)
{
- SMB_STRUCT_DIR *p = SMB_VFS_NEXT_OPENDIR(handle,fsp->conn->connectpath,NULL,0);
+ SMB_STRUCT_DIR *p = NULL;
+ char snapname[MAXPATHLEN];
+ char *path = alloc_sub_basic(get_current_username(), current_user_info.domain,
+ lp_parm_const_string(SNUM(handle->conn),
+ "shadow_copy", "path", fsp->conn->connectpath));
+
+ if (path == NULL) {
+ DEBUG(0, ("shadow_copy_get_shadow_copy_data: alloc_sub_basic failed for path\n"));
+ return -1;
+ }
+
+ DEBUG(10,("shadow_copy_get_shadow_copy_data: [%s]\n",path));
+
+ p = SMB_VFS_NEXT_OPENDIR(handle,path,NULL,0);
shadow_copy_data->num_volumes = 0;
shadow_copy_data->labels = NULL;
if (!p) {
- DEBUG(0,("shadow_copy_get_shadow_copy_data: SMB_VFS_NEXT_OPENDIR() failed for [%s]\n",fsp->conn->connectpath));
+ DEBUG(0,("shadow_copy_get_shadow_copy_data: SMB_VFS_NEXT_OPENDIR() failed for [%s]\n",path));
+ SAFE_FREE(path);
return -1;
}
@@ -225,8 +441,8 @@
}
/* */
- if (!shadow_copy_match_name(d->d_name)) {
- DEBUG(10,("shadow_copy_get_shadow_copy_data: ignore [%s]\n",d->d_name));
+ if (!shadow_copy_snapshot_to_gmt(handle, d->d_name, snapname, MAXPATHLEN)) {
+ DEBUG(6,("shadow_copy_get_shadow_copy_data: ignore [%s]\n",d->d_name));
continue;
}
@@ -243,10 +459,11 @@
if (tlabels == NULL) {
DEBUG(0,("shadow_copy_get_shadow_copy_data: Out of memory\n"));
SMB_VFS_NEXT_CLOSEDIR(handle,p);
+ SAFE_FREE(path);
return -1;
}
- snprintf(tlabels[shadow_copy_data->num_volumes++], sizeof(*tlabels), "%s",d->d_name);
+ snprintf(tlabels[shadow_copy_data->num_volumes++], sizeof(*tlabels), "%s",snapname);
shadow_copy_data->labels = tlabels;
}
@@ -254,6 +471,7 @@
shadow_copy_sort_data(handle, shadow_copy_data);
SMB_VFS_NEXT_CLOSEDIR(handle,p);
+ SAFE_FREE(path);
return 0;
}
@@ -267,6 +485,9 @@
{SMB_VFS_OP(shadow_copy_rewinddir), SMB_VFS_OP_REWINDDIR, SMB_VFS_LAYER_TRANSPARENT},
{SMB_VFS_OP(shadow_copy_closedir), SMB_VFS_OP_CLOSEDIR, SMB_VFS_LAYER_TRANSPARENT},
+ {SMB_VFS_OP(shadow_copy_stat), SMB_VFS_OP_STAT, SMB_VFS_LAYER_TRANSPARENT},
+ {SMB_VFS_OP(shadow_copy_open), SMB_VFS_OP_OPEN, SMB_VFS_LAYER_TRANSPARENT},
+
{SMB_VFS_OP(shadow_copy_get_shadow_copy_data), SMB_VFS_OP_GET_SHADOW_COPY_DATA,SMB_VFS_LAYER_OPAQUE},
{SMB_VFS_OP(NULL), SMB_VFS_OP_NOOP, SMB_VFS_LAYER_NOOP}
More information about the samba-technical
mailing list