Shadow Copy with ZFS

Ed Plese ed at edplese.com
Wed Feb 7 03:45:15 GMT 2007


I've been working on a VFS module for Samba that allows the shadow copy
functionality to work better with Solaris and specifically ZFS snapshots.
The current shadow_copy VFS module works but has the following
limitations:

 * Each ZFS snapshot in .zfs/snapshot must be symlinked to the root of
   the share which complicates snapshot creation and deletion.
 * Snapshots are not sorted by date in the Microsoft Shadow Copy Client
   GUI.  The existing shadow_copy module shares this problem when using
   certain filesystems.

The attached patch against 3.0.23d creates a new shadow_copy_zfs module
based off the shadow_copy module that fixes the two above issues.

The limitations of the patch are:

 * Snapshots must be named in a particular fashion similar to the
   original shadow_copy module.  Take a snapshot like:

   # zfs snapshot pool/fs at GMT-`date -u +%Y.%m.%d-%H.%M.%S`

 * The path of the Samba share must be at the root of a ZFS filesystem.

Example usage in smb.conf is:

[testshare]
  comment = Shadow Copy Share
  writable = yes
  path = /pool/fs

  vfs objects = shadow_copy_zfs
  shadow_copy_zfs: sort = desc

Is there any desire to have something like this merged into Samba?  If so,
would it be better to go about adding this functionality by creating a
more generalized module with customizable snapshot paths instead of a
ZFS-specific module?


Thanks,

Ed Plese
-------------- next part --------------
diff -Nur samba-3.0.23d/source/Makefile.in samba-3.0.23d-zfs/source/Makefile.in
--- samba-3.0.23d/source/Makefile.in	2006-11-14 08:42:15.000000000 -0600
+++ samba-3.0.23d-zfs/source/Makefile.in	2006-12-12 11:40:22.000000000 -0600
@@ -372,6 +372,7 @@
 VFS_CAP_OBJ = modules/vfs_cap.o
 VFS_EXPAND_MSDFS_OBJ = modules/vfs_expand_msdfs.o
 VFS_SHADOW_COPY_OBJ = modules/vfs_shadow_copy.o
+VFS_SHADOW_COPY_ZFS_OBJ = modules/vfs_shadow_copy_zfs.o
 VFS_AFSACL_OBJ = modules/vfs_afsacl.o
 VFS_CATIA_OBJ = modules/vfs_catia.o
 
@@ -1352,6 +1353,11 @@
 	@$(SHLD) $(LDSHFLAGS) -o $@ $(VFS_SHADOW_COPY_OBJ:.o=. at PICSUFFIX@) \
 		@SONAMEFLAG@`basename $@`
 
+bin/shadow_copy_zfs. at SHLIBEXT@: $(VFS_SHADOW_COPY_ZFS_OBJ:.o=. at PICSUFFIX@)
+	@echo "Building plugin $@"
+	@$(SHLD) $(LDSHFLAGS) -o $@ $(VFS_SHADOW_COPY_ZFS_OBJ:.o=. at PICSUFFIX@) \
+		@SONAMEFLAG@`basename $@`
+
 bin/cap. at SHLIBEXT@: $(VFS_CAP_OBJ:.o=. at PICSUFFIX@)
 	@echo "Building plugin $@"
 	@$(SHLD) $(LDSHFLAGS) -o $@ $(VFS_CAP_OBJ:.o=. at PICSUFFIX@) \
diff -Nur samba-3.0.23d/source/configure samba-3.0.23d-zfs/source/configure
--- samba-3.0.23d/source/configure	2006-11-15 09:14:25.000000000 -0600
+++ samba-3.0.23d-zfs/source/configure	2006-12-12 11:40:22.000000000 -0600
@@ -4787,7 +4787,7 @@
 
 default_static_modules="pdb_smbpasswd pdb_tdbsam rpc_lsa rpc_samr rpc_reg rpc_lsa_ds rpc_wks rpc_svcctl rpc_ntsvcs rpc_net rpc_netdfs rpc_srv rpc_spoolss rpc_eventlog auth_sam auth_unix auth_winbind auth_server auth_domain auth_builtin"
 
-default_shared_modules="vfs_recycle vfs_audit vfs_extd_audit vfs_full_audit vfs_netatalk vfs_fake_perms vfs_default_quota vfs_readonly vfs_cap vfs_expand_msdfs vfs_shadow_copy charset_CP850 charset_CP437 auth_script"
+default_shared_modules="vfs_recycle vfs_audit vfs_extd_audit vfs_full_audit vfs_netatalk vfs_fake_perms vfs_default_quota vfs_readonly vfs_cap vfs_expand_msdfs vfs_shadow_copy vfs_shadow_copy_zfs charset_CP850 charset_CP437 auth_script"
 
 if test "x$developer" = xyes; then
    default_static_modules="$default_static_modules rpc_echo"
@@ -50459,6 +50459,43 @@
 	fi
 
 
+	echo "$as_me:$LINENO: checking how to build vfs_shadow_copy_zfs" >&5
+echo $ECHO_N "checking how to build vfs_shadow_copy_zfs... $ECHO_C" >&6
+	if test "$MODULE_vfs_shadow_copy_zfs"; then
+		DEST=$MODULE_vfs_shadow_copy_zfs
+	elif test "$MODULE_vfs" -a "$MODULE_DEFAULT_vfs_shadow_copy_zfs"; then
+		DEST=$MODULE_vfs
+	else
+		DEST=$MODULE_DEFAULT_vfs_shadow_copy_zfs
+	fi
+
+	if test x"$DEST" = xSHARED; then
+
+cat >>confdefs.h <<\_ACEOF
+#define vfs_shadow_copy_zfs_init init_module
+_ACEOF
+
+		VFS_MODULES="$VFS_MODULES "bin/shadow_copy_zfs.$SHLIBEXT""
+		echo "$as_me:$LINENO: result: shared" >&5
+echo "${ECHO_T}shared" >&6
+
+		string_shared_modules="$string_shared_modules vfs_shadow_copy_zfs"
+	elif test x"$DEST" = xSTATIC; then
+		init_static_modules_vfs="$init_static_modules_vfs  vfs_shadow_copy_zfs_init();"
+ 		decl_static_modules_vfs="$decl_static_modules_vfs extern NTSTATUS vfs_shadow_copy_zfs_init(void);"
+		string_static_modules="$string_static_modules vfs_shadow_copy_zfs"
+		VFS_STATIC="$VFS_STATIC \$(VFS_SHADOW_COPY_ZFS_OBJ)"
+
+
+		echo "$as_me:$LINENO: result: static" >&5
+echo "${ECHO_T}static" >&6
+	else
+	    string_ignored_modules="$string_ignored_modules vfs_shadow_copy_zfs"
+		echo "$as_me:$LINENO: result: not" >&5
+echo "${ECHO_T}not" >&6
+	fi
+
+
 	echo "$as_me:$LINENO: checking how to build vfs_afsacl" >&5
 echo $ECHO_N "checking how to build vfs_afsacl... $ECHO_C" >&6
 	if test "$MODULE_vfs_afsacl"; then
diff -Nur samba-3.0.23d/source/configure.in samba-3.0.23d-zfs/source/configure.in
--- samba-3.0.23d/source/configure.in	2006-11-14 08:42:15.000000000 -0600
+++ samba-3.0.23d-zfs/source/configure.in	2006-12-12 11:40:22.000000000 -0600
@@ -581,7 +581,7 @@
 default_static_modules="pdb_smbpasswd pdb_tdbsam rpc_lsa rpc_samr rpc_reg rpc_lsa_ds rpc_wks rpc_svcctl rpc_ntsvcs rpc_net rpc_netdfs rpc_srv rpc_spoolss rpc_eventlog auth_sam auth_unix auth_winbind auth_server auth_domain auth_builtin"
 
 dnl These are preferably build shared, and static if dlopen() is not available
-default_shared_modules="vfs_recycle vfs_audit vfs_extd_audit vfs_full_audit vfs_netatalk vfs_fake_perms vfs_default_quota vfs_readonly vfs_cap vfs_expand_msdfs vfs_shadow_copy charset_CP850 charset_CP437 auth_script"
+default_shared_modules="vfs_recycle vfs_audit vfs_extd_audit vfs_full_audit vfs_netatalk vfs_fake_perms vfs_default_quota vfs_readonly vfs_cap vfs_expand_msdfs vfs_shadow_copy vfs_shadow_copy_zfs charset_CP850 charset_CP437 auth_script"
 
 if test "x$developer" = xyes; then
    default_static_modules="$default_static_modules rpc_echo"
@@ -5596,6 +5596,7 @@
 SMB_MODULE(vfs_cap, \$(VFS_CAP_OBJ), "bin/cap.$SHLIBEXT", VFS)
 SMB_MODULE(vfs_expand_msdfs, \$(VFS_EXPAND_MSDFS_OBJ), "bin/expand_msdfs.$SHLIBEXT", VFS)
 SMB_MODULE(vfs_shadow_copy, \$(VFS_SHADOW_COPY_OBJ), "bin/shadow_copy.$SHLIBEXT", VFS)
+SMB_MODULE(vfs_shadow_copy_zfs, \$(VFS_SHADOW_COPY_ZFS_OBJ), "bin/shadow_copy_zfs.$SHLIBEXT", VFS)
 SMB_MODULE(vfs_afsacl, \$(VFS_AFSACL_OBJ), "bin/afsacl.$SHLIBEXT", VFS)
 SMB_MODULE(vfs_catia, \$(VFS_CATIA_OBJ), "bin/catia.$SHLIBEXT", VFS)
 SMB_SUBSYSTEM(VFS,smbd/vfs.o)
diff -Nur samba-3.0.23d/source/include/config.h.in samba-3.0.23d-zfs/source/include/config.h.in
--- samba-3.0.23d/source/include/config.h.in	2006-11-15 09:14:22.000000000 -0600
+++ samba-3.0.23d-zfs/source/include/config.h.in	2006-12-12 11:40:22.000000000 -0600
@@ -2485,5 +2485,8 @@
 /* Whether to build vfs_shadow_copy as shared module */
 #undef vfs_shadow_copy_init
 
+/* Whether to build vfs_shadow_copy_zfs as shared module */
+#undef vfs_shadow_copy_zfs_init
+
 /* Define to `unsigned short' if <sys/types.h> does not define. */
 #undef wchar_t
diff -Nur samba-3.0.23d/source/modules/vfs_shadow_copy_zfs.c samba-3.0.23d-zfs/source/modules/vfs_shadow_copy_zfs.c
--- samba-3.0.23d/source/modules/vfs_shadow_copy_zfs.c	1969-12-31 18:00:00.000000000 -0600
+++ samba-3.0.23d-zfs/source/modules/vfs_shadow_copy_zfs.c	2006-12-15 08:07:07.000000000 -0600
@@ -0,0 +1,252 @@
+/* 
+ * implementation of an Shadow Copy module for use with ZFS snapshots
+ *
+ * Copyright (C) Stefan Metzmacher	2003-2004
+ *
+ * Modifications for use with ZFS added by Ed Plese <ed at edplese.com>.
+ *
+ * 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 module is based off of the original shadow_copy module with
+ * changes to search for the snapshot directories in a subdirectory of the
+ * share rather than the root of the share.  This allows ZFS snapshots
+ * to be used as shadow copies without the need to create symbolic links
+ * to them.
+ *
+ * ZFS snapshots must be created with names similar in form to the original.
+ *
+ * Creating a snapshot is as simple as:
+ *
+ *   # zfs snapshot pool/fs at GMT-`date -u +%Y.%m.%d-%H.%M.%S`
+ *
+ * Note: Files must differ to be displayed via Windows Explorer!
+ * Directories are always displayed...    
+*/
+
+static int vfs_shadow_copy_zfs_debug_level = DBGC_VFS;
+
+#undef DBGC_CLASS
+#define DBGC_CLASS vfs_shadow_copy_zfs_debug_level
+
+#define SHADOW_COPY_ZFS_PREFIX "@GMT-"
+
+static int shadow_copy_zfs_label_cmp_asc(const void *x, const void *y)
+{
+	return strncmp((char *)x, (char *)y, sizeof(SHADOW_COPY_LABEL));
+}
+
+static int shadow_copy_zfs_label_cmp_desc(const void *x, const void *y)
+{
+	return -strncmp((char *)x, (char *)y, sizeof(SHADOW_COPY_LABEL));
+}
+
+static void shadow_copy_zfs_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_zfs", "sort", "");
+
+		if (strcmp(tmp_str, "asc") == 0) {
+			qsort(shadow_copy_data->labels,
+				shadow_copy_data->num_volumes,
+				sizeof(SHADOW_COPY_LABEL),
+				shadow_copy_zfs_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_zfs_label_cmp_desc);
+		}
+	}
+ 
+	return;
+}
+
+static BOOL shadow_copy_zfs_match_snapshot_name(const char *name)
+{
+	/* ignore the leading @ in SHADOW_COPY_ZFS_PREFIX */
+	if (strncmp(SHADOW_COPY_ZFS_PREFIX+1, name, sizeof(SHADOW_COPY_ZFS_PREFIX)-2) == 0) {
+		return True;
+	}
+
+	return False;
+}
+
+static BOOL shadow_copy_zfs_match_file_name(const char *name)
+{
+	if (strncmp(SHADOW_COPY_ZFS_PREFIX, name, sizeof(SHADOW_COPY_ZFS_PREFIX)-1) == 0) {
+		return True;
+	}
+
+	return False;
+}
+
+static SMB_STRUCT_DIR *shadow_copy_zfs_opendir(vfs_handle_struct *handle, connection_struct *conn, const char *fname, const char *mask, uint32 attr)
+{
+	char modpath[FILENAME_MAX];
+
+	if (shadow_copy_zfs_match_file_name(fname)) {
+		snprintf(modpath, FILENAME_MAX, ".zfs/snapshot/%s", &fname[1]);
+	} else {
+		snprintf(modpath, FILENAME_MAX, "%s", fname);
+	}
+
+	DEBUG(10, ("shadow_copy_zfs_opendir: [%s] -> [%s]\n", fname, modpath));
+
+	SMB_STRUCT_DIR *p = SMB_VFS_NEXT_OPENDIR(handle,conn,modpath,mask,attr);
+
+	if (!p) {
+		DEBUG(2, ("shadow_copy_zfs_opendir: SMB_VFS_NEXT_OPENDIR() failed for [%s]\n", modpath));
+		return NULL;
+	}
+
+	return p;
+}
+
+static int shadow_copy_zfs_get_shadow_copy_data(vfs_handle_struct *handle, files_struct *fsp, SHADOW_COPY_DATA *shadow_copy_data, BOOL labels)
+{
+	char modpath[FILENAME_MAX];
+
+	snprintf(modpath, FILENAME_MAX, "%s/.zfs/snapshot", fsp->conn->connectpath);
+	DEBUG(10, ("shadow_copy_zfs_get_shadow_copy_data: [%s] -> [%s]\n",
+		fsp->conn->connectpath, modpath));
+	SMB_STRUCT_DIR *p = SMB_VFS_NEXT_OPENDIR(handle, fsp->conn, modpath,
+		NULL, 0);
+
+	shadow_copy_data->num_volumes = 0;
+	shadow_copy_data->labels = NULL;
+
+	if (!p) {
+		DEBUG(2, ("shadow_copy_zfs_get_shadow_copy_data: SMB_VFS_NEXT_OPENDIR() failed for [%s]\n", modpath));
+		return -1;
+	}
+
+	while (True) {
+		SHADOW_COPY_LABEL *tlabels;
+		SMB_STRUCT_DIRENT *d;
+
+		d = SMB_VFS_NEXT_READDIR(handle, fsp->conn, p);
+		if (d == NULL) {
+			break;
+		}
+
+		if (!shadow_copy_zfs_match_snapshot_name(d->d_name)) {
+			DEBUG(10, ("shadow_copy_zfs_get_shadow_copy_data: ignore [%s]\n", d->d_name));
+			continue;
+		}
+
+		DEBUG(10, ("shadow_copy_zfs_get_shadow_copy_data: not ignore [%s]\n", d->d_name));
+
+		if (!labels) {
+			shadow_copy_data->num_volumes++;
+			continue;
+		}
+
+		tlabels = (SHADOW_COPY_LABEL *)TALLOC_REALLOC(shadow_copy_data->mem_ctx,
+									shadow_copy_data->labels,
+									(shadow_copy_data->num_volumes+1)*sizeof(SHADOW_COPY_LABEL));
+		if (tlabels == NULL) {
+			DEBUG(2, ("shadow_copy_zfs_get_shadow_copy_data: Out of memory\n"));
+			SMB_VFS_NEXT_CLOSEDIR(handle, fsp->conn, p);
+			return -1;
+		}
+
+		snprintf(tlabels[shadow_copy_data->num_volumes++], sizeof(*tlabels), "@%s", d->d_name);
+
+		shadow_copy_data->labels = tlabels;
+	}
+
+	shadow_copy_zfs_sort_data(handle, shadow_copy_data);
+
+	SMB_VFS_NEXT_CLOSEDIR(handle, fsp->conn, p);
+	return 0;
+}
+
+static int shadow_copy_zfs_stat(vfs_handle_struct *handle, connection_struct *conn, const char *fname, SMB_STRUCT_STAT *sbuf)
+{
+	char modpath[FILENAME_MAX];
+
+	if (shadow_copy_zfs_match_file_name(fname)) {
+		snprintf(modpath, FILENAME_MAX, ".zfs/snapshot/%s", &fname[1]);
+	} else {
+		snprintf(modpath, FILENAME_MAX, "%s", fname);
+	}
+
+	DEBUG(10, ("shadow_copy_zfs_stat: [%s] -> [%s]\n", fname, modpath));
+
+	return SMB_VFS_NEXT_STAT(handle, conn, modpath, sbuf);
+}
+
+static int shadow_copy_zfs_open(vfs_handle_struct *handle, connection_struct *conn, const char *fname, int flags, mode_t mode)
+{
+	char modpath[FILENAME_MAX];
+
+	if (shadow_copy_zfs_match_file_name(fname)) {
+		snprintf(modpath, FILENAME_MAX, ".zfs/snapshot/%s", &fname[1]);
+	} else {
+		snprintf(modpath, FILENAME_MAX, "%s", fname);
+	}
+
+	DEBUG(10, ("shadow_copy_zfs_open: [%s] -> [%s]\n", fname, modpath));
+
+	return SMB_VFS_NEXT_OPEN(handle, conn, modpath, flags, mode);
+}
+
+/* VFS operations structure */
+
+static vfs_op_tuple shadow_copy_zfs_ops[] = {
+	{SMB_VFS_OP(shadow_copy_zfs_opendir),
+		SMB_VFS_OP_OPENDIR,		SMB_VFS_LAYER_TRANSPARENT},
+	{SMB_VFS_OP(shadow_copy_zfs_stat),
+		SMB_VFS_OP_STAT,		SMB_VFS_LAYER_TRANSPARENT},
+	{SMB_VFS_OP(shadow_copy_zfs_open),
+		SMB_VFS_OP_OPEN,		SMB_VFS_LAYER_TRANSPARENT},
+	{SMB_VFS_OP(shadow_copy_zfs_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}
+};
+
+NTSTATUS vfs_shadow_copy_zfs_init(void)
+{
+	NTSTATUS ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+		"shadow_copy_zfs", shadow_copy_zfs_ops);
+
+	if (!NT_STATUS_IS_OK(ret))
+		return ret;
+
+	vfs_shadow_copy_zfs_debug_level = debug_add_class("shadow_copy_zfs");
+	if (vfs_shadow_copy_zfs_debug_level == -1) {
+		vfs_shadow_copy_zfs_debug_level = DBGC_VFS;
+		DEBUG(2, ("%s: Couldn't register custom debugging class!\n",
+			"vfs_shadow_copy_zfs_init"));
+	} else {
+		DEBUG(10, ("%s: Debug class number of '%s': %d\n", 
+			"vfs_shadow_copy_zfs_init", "shadow_copy_zfs",
+			vfs_shadow_copy_zfs_debug_level));
+	}
+
+	return ret;
+}


More information about the samba-technical mailing list