changeset 4007:c6f5c6753018

PSARC 2007/142 zfs rename -r 6479884 want 'zfs rename -r' to recursively rename snapshots
author mmusante
date Wed, 11 Apr 2007 09:10:08 -0700
parents ae098b2a8faa
children a95c00c4179a
files usr/src/cmd/zfs/zfs_main.c usr/src/lib/libzfs/common/libzfs.h usr/src/lib/libzfs/common/libzfs_dataset.c usr/src/uts/common/fs/zfs/dsl_dataset.c usr/src/uts/common/fs/zfs/sys/dmu.h usr/src/uts/common/fs/zfs/sys/dsl_dataset.h usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h usr/src/uts/common/fs/zfs/zfs_ctldir.c usr/src/uts/common/fs/zfs/zfs_ioctl.c
diffstat 9 files changed, 304 insertions(+), 59 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/cmd/zfs/zfs_main.c	Tue Apr 10 16:14:57 2007 -0700
+++ b/usr/src/cmd/zfs/zfs_main.c	Wed Apr 11 09:10:08 2007 -0700
@@ -196,7 +196,8 @@
 		"\treceive [-vnF] -d <filesystem>\n"));
 	case HELP_RENAME:
 		return (gettext("\trename <filesystem|volume|snapshot> "
-		    "<filesystem|volume|snapshot>\n"));
+		    "<filesystem|volume|snapshot>\n"
+		    "\trename -r <snapshot> <snapshot>"));
 	case HELP_ROLLBACK:
 		return (gettext("\trollback [-rRf] <snapshot>\n"));
 	case HELP_SEND:
@@ -1475,7 +1476,7 @@
 }
 
 /*
- * zfs rename <fs | snap | vol> <fs | snap | vol>
+ * zfs rename [-r] <fs | snap | vol> <fs | snap | vol>
  *
  * Renames the given dataset to another of the same type.
  */
@@ -1484,38 +1485,57 @@
 zfs_do_rename(int argc, char **argv)
 {
 	zfs_handle_t *zhp;
+	int c;
 	int ret;
+	int recurse = 0;
 
 	/* check options */
-	if (argc > 1 && argv[1][0] == '-') {
-		(void) fprintf(stderr, gettext("invalid option '%c'\n"),
-		    argv[1][1]);
-		usage(B_FALSE);
+	while ((c = getopt(argc, argv, "r")) != -1) {
+		switch (c) {
+		case 'r':
+			recurse = 1;
+			break;
+		case '?':
+		default:
+			(void) fprintf(stderr, gettext("invalid option '%c'\n"),
+			    optopt);
+			usage(B_FALSE);
+		}
 	}
 
+	argc -= optind;
+	argv += optind;
+
 	/* check number of arguments */
-	if (argc < 2) {
+	if (argc < 1) {
 		(void) fprintf(stderr, gettext("missing source dataset "
 		    "argument\n"));
 		usage(B_FALSE);
 	}
-	if (argc < 3) {
+	if (argc < 2) {
 		(void) fprintf(stderr, gettext("missing target dataset "
 		    "argument\n"));
 		usage(B_FALSE);
 	}
-	if (argc > 3) {
+	if (argc > 2) {
 		(void) fprintf(stderr, gettext("too many arguments\n"));
 		usage(B_FALSE);
 	}
 
-	if ((zhp = zfs_open(g_zfs, argv[1], ZFS_TYPE_ANY)) == NULL)
+	if (recurse && strchr(argv[0], '@') == 0) {
+		(void) fprintf(stderr, gettext("source dataset for recursive "
+		    "rename must be a snapshot\n"));
+		usage(B_FALSE);
+	}
+
+	if ((zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_ANY)) == NULL)
 		return (1);
 
-	ret = (zfs_rename(zhp, argv[2]) != 0);
+	ret = (zfs_rename(zhp, argv[1], recurse) != 0);
 
 	if (!ret)
-		zpool_log_history(g_zfs, argc, argv, argv[2], B_FALSE, B_FALSE);
+		zpool_log_history(g_zfs, argc + optind, argv - optind, argv[1],
+		    B_FALSE, B_FALSE);
 
 	zfs_close(zhp);
 	return (ret);
--- a/usr/src/lib/libzfs/common/libzfs.h	Tue Apr 10 16:14:57 2007 -0700
+++ b/usr/src/lib/libzfs/common/libzfs.h	Wed Apr 11 09:10:08 2007 -0700
@@ -335,7 +335,7 @@
 extern int zfs_clone(zfs_handle_t *, const char *, nvlist_t *);
 extern int zfs_snapshot(libzfs_handle_t *, const char *, boolean_t);
 extern int zfs_rollback(zfs_handle_t *, zfs_handle_t *, int);
-extern int zfs_rename(zfs_handle_t *, const char *);
+extern int zfs_rename(zfs_handle_t *, const char *, int);
 extern int zfs_send(zfs_handle_t *, const char *, int);
 extern int zfs_receive(libzfs_handle_t *, const char *, int, int, int,
     boolean_t, int);
--- a/usr/src/lib/libzfs/common/libzfs_dataset.c	Tue Apr 10 16:14:57 2007 -0700
+++ b/usr/src/lib/libzfs/common/libzfs_dataset.c	Wed Apr 11 09:10:08 2007 -0700
@@ -51,6 +51,8 @@
 #include "zfs_prop.h"
 #include "libzfs_impl.h"
 
+static int zvol_create_link_common(libzfs_handle_t *, const char *, int);
+
 /*
  * Given a single type (not a mask of types), return the type in a human
  * readable form.
@@ -2512,10 +2514,15 @@
 	return (ret);
 }
 
+struct createdata {
+	const char *cd_snapname;
+	int cd_ifexists;
+};
+
 static int
 zfs_create_link_cb(zfs_handle_t *zhp, void *arg)
 {
-	char *snapname = arg;
+	struct createdata *cd = arg;
 	int ret;
 
 	if (zhp->zfs_type == ZFS_TYPE_VOLUME) {
@@ -2523,8 +2530,9 @@
 
 		(void) strlcpy(name, zhp->zfs_name, sizeof (name));
 		(void) strlcat(name, "@", sizeof (name));
-		(void) strlcat(name, snapname, sizeof (name));
-		(void) zvol_create_link(zhp->zfs_hdl, name);
+		(void) strlcat(name, cd->cd_snapname, sizeof (name));
+		(void) zvol_create_link_common(zhp->zfs_hdl, name,
+		    cd->cd_ifexists);
 		/*
 		 * NB: this is simply a best-effort.  We don't want to
 		 * return an error, because then we wouldn't visit all
@@ -2532,7 +2540,7 @@
 		 */
 	}
 
-	ret = zfs_iter_filesystems(zhp, zfs_create_link_cb, snapname);
+	ret = zfs_iter_filesystems(zhp, zfs_create_link_cb, cd);
 
 	zfs_close(zhp);
 
@@ -2584,8 +2592,11 @@
 	(void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
 	    "cannot create snapshot '%s@%s'"), zc.zc_name, zc.zc_value);
 	if (ret == 0 && recursive) {
-		(void) zfs_iter_filesystems(zhp,
-		    zfs_create_link_cb, (char *)delim+1);
+		struct createdata cd;
+
+		cd.cd_snapname = delim + 1;
+		cd.cd_ifexists = B_FALSE;
+		(void) zfs_iter_filesystems(zhp, zfs_create_link_cb, &cd);
 	}
 	if (ret == 0 && zhp->zfs_type == ZFS_TYPE_VOLUME) {
 		ret = zvol_create_link(zhp->zfs_hdl, path);
@@ -3181,12 +3192,14 @@
  * Renames the given dataset.
  */
 int
-zfs_rename(zfs_handle_t *zhp, const char *target)
+zfs_rename(zfs_handle_t *zhp, const char *target, int recursive)
 {
 	int ret;
 	zfs_cmd_t zc = { 0 };
 	char *delim;
-	prop_changelist_t *cl;
+	prop_changelist_t *cl = NULL;
+	zfs_handle_t *zhrp = NULL;
+	char *parentname = NULL;
 	char parent[ZFS_MAXNAMELEN];
 	libzfs_handle_t *hdl = zhp->zfs_hdl;
 	char errbuf[1024];
@@ -3234,6 +3247,12 @@
 		if (!zfs_validate_name(hdl, target, zhp->zfs_type))
 			return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
 	} else {
+		if (recursive) {
+			zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+			    "recursive rename must be a snapshot"));
+			return (zfs_error(hdl, EZFS_BADTYPE, errbuf));
+		}
+
 		if (!zfs_validate_name(hdl, target, zhp->zfs_type))
 			return (zfs_error(hdl, EZFS_INVALIDNAME, errbuf));
 		uint64_t unused;
@@ -3273,20 +3292,42 @@
 		return (zfs_error(hdl, EZFS_ZONED, errbuf));
 	}
 
-	if ((cl = changelist_gather(zhp, ZFS_PROP_NAME, 0)) == NULL)
-		return (-1);
-
-	if (changelist_haszonedchild(cl)) {
-		zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
-		    "child dataset with inherited mountpoint is used "
-		    "in a non-global zone"));
-		(void) zfs_error(hdl, EZFS_ZONED, errbuf);
-		goto error;
+	if (recursive) {
+		struct destroydata dd;
+
+		parentname = strdup(zhp->zfs_name);
+		delim = strchr(parentname, '@');
+		*delim = '\0';
+		zhrp = zfs_open(zhp->zfs_hdl, parentname, ZFS_TYPE_ANY);
+		if (zhrp == NULL) {
+			return (-1);
+		}
+
+		dd.snapname = delim + 1;
+		dd.gotone = B_FALSE;
+		dd.closezhp = B_FALSE;
+
+		/* We remove any zvol links prior to renaming them */
+		ret = zfs_iter_filesystems(zhrp, zfs_remove_link_cb, &dd);
+		if (ret) {
+			goto error;
+		}
+	} else {
+		if ((cl = changelist_gather(zhp, ZFS_PROP_NAME, 0)) == NULL)
+			return (-1);
+
+		if (changelist_haszonedchild(cl)) {
+			zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+			    "child dataset with inherited mountpoint is used "
+			    "in a non-global zone"));
+			(void) zfs_error(hdl, EZFS_ZONED, errbuf);
+			goto error;
+		}
+
+		if ((ret = changelist_prefix(cl)) != 0)
+			goto error;
 	}
 
-	if ((ret = changelist_prefix(cl)) != 0)
-		goto error;
-
 	if (ZFS_IS_VOLUME(zhp))
 		zc.zc_objset_type = DMU_OST_ZVOL;
 	else
@@ -3295,22 +3336,65 @@
 	(void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
 	(void) strlcpy(zc.zc_value, target, sizeof (zc.zc_value));
 
+	zc.zc_cookie = recursive;
+
 	if ((ret = ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_RENAME, &zc)) != 0) {
-		(void) zfs_standard_error(zhp->zfs_hdl, errno, errbuf);
+		/*
+		 * if it was recursive, the one that actually failed will
+		 * be in zc.zc_name
+		 */
+		(void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+		    "cannot rename to '%s'"), zc.zc_name);
+
+		if (recursive && errno == EEXIST) {
+			zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+			    "a child dataset already has a snapshot "
+			    "with the new name"));
+			(void) zfs_error(hdl, EZFS_CROSSTARGET, errbuf);
+		} else {
+			(void) zfs_standard_error(zhp->zfs_hdl, errno, errbuf);
+		}
 
 		/*
 		 * On failure, we still want to remount any filesystems that
 		 * were previously mounted, so we don't alter the system state.
 		 */
-		(void) changelist_postfix(cl);
+		if (recursive) {
+			struct createdata cd;
+
+			/* only create links for datasets that had existed */
+			cd.cd_snapname = delim + 1;
+			cd.cd_ifexists = B_TRUE;
+			(void) zfs_iter_filesystems(zhrp, zfs_create_link_cb,
+			    &cd);
+		} else {
+			(void) changelist_postfix(cl);
+		}
 	} else {
-		changelist_rename(cl, zfs_get_name(zhp), target);
-
-		ret = changelist_postfix(cl);
+		if (recursive) {
+			struct createdata cd;
+
+			/* only create links for datasets that had existed */
+			cd.cd_snapname = strchr(target, '@') + 1;
+			cd.cd_ifexists = B_TRUE;
+			ret = zfs_iter_filesystems(zhrp, zfs_create_link_cb,
+			    &cd);
+		} else {
+			changelist_rename(cl, zfs_get_name(zhp), target);
+			ret = changelist_postfix(cl);
+		}
 	}
 
 error:
-	changelist_free(cl);
+	if (parentname) {
+		free(parentname);
+	}
+	if (zhrp) {
+		zfs_close(zhrp);
+	}
+	if (cl) {
+		changelist_free(cl);
+	}
 	return (ret);
 }
 
@@ -3321,6 +3405,12 @@
 int
 zvol_create_link(libzfs_handle_t *hdl, const char *dataset)
 {
+	return (zvol_create_link_common(hdl, dataset, B_FALSE));
+}
+
+static int
+zvol_create_link_common(libzfs_handle_t *hdl, const char *dataset, int ifexists)
+{
 	zfs_cmd_t zc = { 0 };
 	di_devlink_handle_t dhdl;
 
@@ -3339,6 +3429,18 @@
 			 */
 			return (0);
 
+		case ENOENT:
+			/*
+			 * Dataset does not exist in the kernel.  If we
+			 * don't care (see zfs_rename), then ignore the
+			 * error quietly.
+			 */
+			if (ifexists) {
+				return (0);
+			}
+
+			/* FALLTHROUGH */
+
 		default:
 			return (zfs_standard_error_fmt(hdl, errno,
 			    dgettext(TEXT_DOMAIN, "cannot create device links "
--- a/usr/src/uts/common/fs/zfs/dsl_dataset.c	Tue Apr 10 16:14:57 2007 -0700
+++ b/usr/src/uts/common/fs/zfs/dsl_dataset.c	Wed Apr 11 09:10:08 2007 -0700
@@ -37,6 +37,7 @@
 #include <sys/zap.h>
 #include <sys/unique.h>
 #include <sys/zfs_context.h>
+#include <sys/zfs_ioctl.h>
 
 static dsl_checkfunc_t dsl_dataset_destroy_begin_check;
 static dsl_syncfunc_t dsl_dataset_destroy_begin_sync;
@@ -639,7 +640,6 @@
 struct destroyarg {
 	dsl_sync_task_group_t *dstg;
 	char *snapname;
-	void *tag;
 	char *failed;
 };
 
@@ -655,7 +655,7 @@
 	(void) strcat(name, da->snapname);
 	err = dsl_dataset_open(name,
 	    DS_MODE_EXCLUSIVE | DS_MODE_READONLY | DS_MODE_INCONSISTENT,
-	    da->tag, &ds);
+	    da->dstg, &ds);
 	cp = strchr(name, '@');
 	*cp = '\0';
 	if (err == ENOENT)
@@ -666,7 +666,7 @@
 	}
 
 	dsl_sync_task_create(da->dstg, dsl_dataset_destroy_check,
-	    dsl_dataset_destroy_sync, ds, da->tag, 0);
+	    dsl_dataset_destroy_sync, ds, da->dstg, 0);
 	return (0);
 }
 
@@ -695,7 +695,6 @@
 		return (err);
 	da.dstg = dsl_sync_task_group_create(spa_get_dsl(spa));
 	da.snapname = snapname;
-	da.tag = FTAG;
 	da.failed = fsname;
 
 	err = dmu_objset_find(fsname,
@@ -717,7 +716,7 @@
 		 * closed the ds
 		 */
 		if (err)
-			dsl_dataset_close(ds, DS_MODE_EXCLUSIVE, FTAG);
+			dsl_dataset_close(ds, DS_MODE_EXCLUSIVE, da.dstg);
 	}
 
 	dsl_sync_task_group_destroy(da.dstg);
@@ -1546,6 +1545,11 @@
 		err = EEXIST;
 	else if (err == ENOENT)
 		err = 0;
+
+	/* dataset name + 1 for the "@" + the new snapshot name must fit */
+	if (dsl_dir_namelen(ds->ds_dir) + 1 + strlen(newsnapname) >= MAXNAMELEN)
+		err = ENAMETOOLONG;
+
 	return (err);
 }
 
@@ -1578,9 +1582,115 @@
 	dsl_dataset_close(hds, DS_MODE_NONE, FTAG);
 }
 
+struct renamearg {
+	dsl_sync_task_group_t *dstg;
+	char failed[MAXPATHLEN];
+	char *oldsnap;
+	char *newsnap;
+};
+
+static int
+dsl_snapshot_rename_one(char *name, void *arg)
+{
+	struct renamearg *ra = arg;
+	dsl_dataset_t *ds = NULL;
+	objset_t *os;
+	char *cp;
+	int err;
+
+	cp = name + strlen(name);
+	*cp = '@';
+	(void) strcpy(cp + 1, ra->oldsnap);
+	err = dsl_dataset_open(name, DS_MODE_READONLY | DS_MODE_STANDARD,
+	    ra->dstg, &ds);
+	if (err == ENOENT) {
+		*cp = '\0';
+		return (0);
+	}
+	if (err) {
+		(void) strcpy(ra->failed, name);
+		*cp = '\0';
+		dsl_dataset_close(ds, DS_MODE_STANDARD, ra->dstg);
+		return (err);
+	}
+
+#ifdef _KERNEL
+	/* for all filesystems undergoing rename, we'll need to unmount it */
+	(void) zfs_unmount_snap(name, NULL);
+#endif
+
+	*cp = '\0';
+
+	dsl_sync_task_create(ra->dstg, dsl_dataset_snapshot_rename_check,
+	    dsl_dataset_snapshot_rename_sync, ds, ra->newsnap, 0);
+
+	return (0);
+}
+
+static int
+dsl_recursive_rename(char *oldname, const char *newname)
+{
+	int err;
+	struct renamearg *ra;
+	dsl_sync_task_t *dst;
+	spa_t *spa;
+	char *cp, *fsname = spa_strdup(oldname);
+	int len = strlen(oldname);
+
+	/* truncate the snapshot name to get the fsname */
+	cp = strchr(fsname, '@');
+	*cp = '\0';
+
+	cp = strchr(fsname, '/');
+	if (cp) {
+		*cp = '\0';
+		err = spa_open(fsname, &spa, FTAG);
+		*cp = '/';
+	} else {
+		err = spa_open(fsname, &spa, FTAG);
+	}
+	if (err) {
+		kmem_free(fsname, len + 1);
+		return (err);
+	}
+	ra = kmem_alloc(sizeof (struct renamearg), KM_SLEEP);
+	ra->dstg = dsl_sync_task_group_create(spa_get_dsl(spa));
+
+	ra->oldsnap = strchr(oldname, '@') + 1;
+	ra->newsnap = strchr(newname, '@') + 1;
+	*ra->failed = '\0';
+
+	err = dmu_objset_find(fsname, dsl_snapshot_rename_one, ra,
+	    DS_FIND_CHILDREN);
+	kmem_free(fsname, len + 1);
+
+	if (err == 0) {
+		err = dsl_sync_task_group_wait(ra->dstg);
+	}
+
+	for (dst = list_head(&ra->dstg->dstg_tasks); dst;
+	    dst = list_next(&ra->dstg->dstg_tasks, dst)) {
+		dsl_dataset_t *ds = dst->dst_arg1;
+		if (dst->dst_err) {
+			dsl_dir_name(ds->ds_dir, ra->failed);
+			strcat(ra->failed, "@");
+			strcat(ra->failed, ra->newsnap);
+		}
+		dsl_dataset_close(ds, DS_MODE_STANDARD, ra->dstg);
+	}
+
+	(void) strcpy(oldname, ra->failed);
+
+	dsl_sync_task_group_destroy(ra->dstg);
+	kmem_free(ra, sizeof (struct renamearg));
+	spa_close(spa, FTAG);
+	return (err);
+}
+
 #pragma weak dmu_objset_rename = dsl_dataset_rename
 int
-dsl_dataset_rename(const char *oldname, const char *newname)
+dsl_dataset_rename(char *oldname, const char *newname,
+    boolean_t recursive)
 {
 	dsl_dir_t *dd;
 	dsl_dataset_t *ds;
@@ -1611,16 +1721,20 @@
 	if (strncmp(oldname, newname, tail - newname) != 0)
 		return (EXDEV);
 
-	err = dsl_dataset_open(oldname,
-	    DS_MODE_READONLY | DS_MODE_STANDARD, FTAG, &ds);
-	if (err)
-		return (err);
+	if (recursive) {
+		err = dsl_recursive_rename(oldname, newname);
+	} else {
+		err = dsl_dataset_open(oldname,
+		    DS_MODE_READONLY | DS_MODE_STANDARD, FTAG, &ds);
+		if (err)
+			return (err);
 
-	err = dsl_sync_task_do(ds->ds_dir->dd_pool,
-	    dsl_dataset_snapshot_rename_check,
-	    dsl_dataset_snapshot_rename_sync, ds, (char *)tail, 1);
+		err = dsl_sync_task_do(ds->ds_dir->dd_pool,
+		    dsl_dataset_snapshot_rename_check,
+		    dsl_dataset_snapshot_rename_sync, ds, (char *)tail, 1);
 
-	dsl_dataset_close(ds, DS_MODE_STANDARD, FTAG);
+		dsl_dataset_close(ds, DS_MODE_STANDARD, FTAG);
+	}
 
 	return (err);
 }
--- a/usr/src/uts/common/fs/zfs/sys/dmu.h	Tue Apr 10 16:14:57 2007 -0700
+++ b/usr/src/uts/common/fs/zfs/sys/dmu.h	Wed Apr 11 09:10:08 2007 -0700
@@ -164,7 +164,8 @@
 int dmu_snapshots_destroy(char *fsname, char *snapname);
 int dmu_objset_rollback(const char *name);
 int dmu_objset_snapshot(char *fsname, char *snapname, boolean_t recursive);
-int dmu_objset_rename(const char *name, const char *newname);
+int dmu_objset_rename(const char *name, const char *newname,
+    boolean_t recursive);
 int dmu_objset_find(char *name, int func(char *, void *), void *arg,
     int flags);
 void dmu_objset_byteswap(void *buf, size_t size);
--- a/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h	Tue Apr 10 16:14:57 2007 -0700
+++ b/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h	Wed Apr 11 09:10:08 2007 -0700
@@ -132,7 +132,7 @@
 dsl_checkfunc_t dsl_dataset_snapshot_check;
 dsl_syncfunc_t dsl_dataset_snapshot_sync;
 int dsl_dataset_rollback(dsl_dataset_t *ds);
-int dsl_dataset_rename(const char *name, const char *newname);
+int dsl_dataset_rename(char *name, const char *newname, boolean_t recursive);
 int dsl_dataset_promote(const char *name);
 
 void *dsl_dataset_set_user_ptr(dsl_dataset_t *ds,
--- a/usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h	Tue Apr 10 16:14:57 2007 -0700
+++ b/usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h	Wed Apr 11 09:10:08 2007 -0700
@@ -151,6 +151,7 @@
 
 extern int zfs_secpolicy_write(const char *dataset, cred_t *cr);
 extern int zfs_busy(void);
+extern int zfs_unmount_snap(char *, void *);
 
 #endif	/* _KERNEL */
 
--- a/usr/src/uts/common/fs/zfs/zfs_ctldir.c	Tue Apr 10 16:14:57 2007 -0700
+++ b/usr/src/uts/common/fs/zfs/zfs_ctldir.c	Wed Apr 11 09:10:08 2007 -0700
@@ -539,7 +539,7 @@
 		return (ENOENT);
 	}
 
-	err = dmu_objset_rename(from, to);
+	err = dmu_objset_rename(from, to, B_FALSE);
 	if (err == 0)
 		zfsctl_rename_snap(sdp, sep, tnm);
 
--- a/usr/src/uts/common/fs/zfs/zfs_ioctl.c	Tue Apr 10 16:14:57 2007 -0700
+++ b/usr/src/uts/common/fs/zfs/zfs_ioctl.c	Wed Apr 11 09:10:08 2007 -0700
@@ -1333,7 +1333,7 @@
 	    zc->zc_value, zc->zc_cookie));
 }
 
-static int
+int
 zfs_unmount_snap(char *name, void *arg)
 {
 	char *snapname = arg;
@@ -1408,18 +1408,25 @@
 static int
 zfs_ioc_rename(zfs_cmd_t *zc)
 {
+	int recursive = zc->zc_cookie & 1;
+
 	zc->zc_value[sizeof (zc->zc_value) - 1] = '\0';
 	if (dataset_namecheck(zc->zc_value, NULL, NULL) != 0)
 		return (EINVAL);
 
-	if (strchr(zc->zc_name, '@') != NULL &&
+	/*
+	 * Unmount snapshot unless we're doing a recursive rename,
+	 * in which case the dataset code figures out which snapshots
+	 * to unmount.
+	 */
+	if (!recursive && strchr(zc->zc_name, '@') != NULL &&
 	    zc->zc_objset_type == DMU_OST_ZFS) {
 		int err = zfs_unmount_snap(zc->zc_name, NULL);
 		if (err)
 			return (err);
 	}
 
-	return (dmu_objset_rename(zc->zc_name, zc->zc_value));
+	return (dmu_objset_rename(zc->zc_name, zc->zc_value, recursive));
 }
 
 static int