[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(&timestamp, 0, sizeof(timestamp));
+	cstr = strptime(name, fmt, &timestamp);
+
+	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(&timestamp);
+		gmtime_r(&timestamp_t, &timestamp);
+	}
+	strftime(converted, converted_length, SHADOW_COPY_GMT_FORMAT, &timestamp);
+	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(&timestamp, 0, sizeof(timestamp));
+	cstr = strptime(path, SHADOW_COPY_GMT_FORMAT, &timestamp);
+
+	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(&timestamp);
+		localtime_r(&timestamp_t, &timestamp);
+	}
+	strftime(snapname, MAXPATHLEN, fmt, &timestamp);
+
+	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