6833815 scheduled snapshots deleted per snapshot policy can lead to replication failures
authorChris Kirby <chris.kirby@sun.com>
Wed, 19 Aug 2009 11:15:14 -0600
changeset 10342 108f0058f837
parent 10341 cb88cd4df985
child 10343 8e7e92586904
6833815 scheduled snapshots deleted per snapshot policy can lead to replication failures
usr/src/cmd/zfs/zfs_main.c
usr/src/lib/libzfs/common/libzfs.h
usr/src/lib/libzfs/common/libzfs_dataset.c
usr/src/lib/libzfs/common/libzfs_sendrecv.c
usr/src/lib/libzfs/common/libzfs_util.c
usr/src/lib/libzfs/common/mapfile-vers
usr/src/uts/common/fs/zfs/dsl_dataset.c
usr/src/uts/common/fs/zfs/dsl_pool.c
usr/src/uts/common/fs/zfs/spa.c
usr/src/uts/common/fs/zfs/spa_errlog.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/dsl_pool.h
usr/src/uts/common/fs/zfs/sys/spa.h
usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h
usr/src/uts/common/fs/zfs/zfs_ioctl.c
usr/src/uts/common/fs/zfs/zfs_vfsops.c
--- a/usr/src/cmd/zfs/zfs_main.c	Tue Aug 18 20:06:58 2009 -0700
+++ b/usr/src/cmd/zfs/zfs_main.c	Wed Aug 19 11:15:14 2009 -0600
@@ -2681,15 +2681,19 @@
 	int i;
 	const char *tag;
 	boolean_t recursive = B_FALSE;
+	boolean_t temphold = B_FALSE;
+	const char *opts = holding ? "rt" : "r";
 	int c;
-	int (*func)(zfs_handle_t *, const char *, const char *, boolean_t);
 
 	/* check options */
-	while ((c = getopt(argc, argv, "r")) != -1) {
+	while ((c = getopt(argc, argv, opts)) != -1) {
 		switch (c) {
 		case 'r':
 			recursive = B_TRUE;
 			break;
+		case 't':
+			temphold = B_TRUE;
+			break;
 		case '?':
 			(void) fprintf(stderr, gettext("invalid option '%c'\n"),
 			    optopt);
@@ -2708,16 +2712,10 @@
 	--argc;
 	++argv;
 
-	if (holding) {
-		if (tag[0] == '.') {
-			/* tags starting with '.' are reserved for libzfs */
-			(void) fprintf(stderr,
-			    gettext("tag may not start with '.'\n"));
-			usage(B_FALSE);
-		}
-		func = zfs_hold;
-	} else {
-		func = zfs_release;
+	if (holding && tag[0] == '.') {
+		/* tags starting with '.' are reserved for libzfs */
+		(void) fprintf(stderr, gettext("tag may not start with '.'\n"));
+		usage(B_FALSE);
 	}
 
 	for (i = 0; i < argc; ++i) {
@@ -2742,8 +2740,14 @@
 			++errors;
 			continue;
 		}
-		if (func(zhp, delim+1, tag, recursive) != 0)
-			++errors;
+		if (holding) {
+			if (zfs_hold(zhp, delim+1, tag, recursive,
+			    temphold) != 0)
+				++errors;
+		} else {
+			if (zfs_release(zhp, delim+1, tag, recursive) != 0)
+				++errors;
+		}
 		zfs_close(zhp);
 	}
 
@@ -2751,9 +2755,10 @@
 }
 
 /*
- * zfs hold [-r] <tag> <snap> ...
+ * zfs hold [-r] [-t] <tag> <snap> ...
  *
  * 	-r	Recursively hold
+ *	-t	Temporary hold (hidden option)
  *
  * Apply a user-hold with the given tag to the list of snapshots.
  */
--- a/usr/src/lib/libzfs/common/libzfs.h	Tue Aug 18 20:06:58 2009 -0700
+++ b/usr/src/lib/libzfs/common/libzfs.h	Wed Aug 19 11:15:14 2009 -0600
@@ -119,6 +119,7 @@
 	EZFS_UNPLAYED_LOGS,	/* log device has unplayed logs */
 	EZFS_REFTAG_RELE,	/* snapshot release: tag not found */
 	EZFS_REFTAG_HOLD,	/* snapshot hold: tag already exists */
+	EZFS_TAGTOOLONG,	/* snapshot hold/rele: tag too long */
 	EZFS_UNKNOWN
 };
 
@@ -450,6 +451,7 @@
 extern int zfs_iter_dependents(zfs_handle_t *, boolean_t, zfs_iter_f, void *);
 extern int zfs_iter_filesystems(zfs_handle_t *, zfs_iter_f, void *);
 extern int zfs_iter_snapshots(zfs_handle_t *, zfs_iter_f, void *);
+extern int zfs_iter_snapshots_sorted(zfs_handle_t *, zfs_iter_f, void *);
 
 /*
  * Functions to create and destroy datasets.
@@ -466,8 +468,13 @@
 extern int zfs_send(zfs_handle_t *, const char *, const char *,
     boolean_t, boolean_t, boolean_t, boolean_t, int);
 extern int zfs_promote(zfs_handle_t *);
-extern int zfs_hold(zfs_handle_t *, const char *, const char *, boolean_t);
+extern int zfs_hold(zfs_handle_t *, const char *, const char *, boolean_t,
+    boolean_t);
+extern int zfs_hold_range(zfs_handle_t *, const char *, const char *,
+    const char *, boolean_t);
 extern int zfs_release(zfs_handle_t *, const char *, const char *, boolean_t);
+extern int zfs_release_range(zfs_handle_t *, const char *, const char *,
+    const char *);
 
 typedef int (*zfs_userspace_cb_t)(void *arg, const char *domain,
     uid_t rid, uint64_t space);
--- a/usr/src/lib/libzfs/common/libzfs_dataset.c	Tue Aug 18 20:06:58 2009 -0700
+++ b/usr/src/lib/libzfs/common/libzfs_dataset.c	Wed Aug 19 11:15:14 2009 -0600
@@ -4034,15 +4034,18 @@
 
 int
 zfs_hold(zfs_handle_t *zhp, const char *snapname, const char *tag,
-    boolean_t recursive)
+    boolean_t recursive, boolean_t temphold)
 {
 	zfs_cmd_t zc = { 0 };
 	libzfs_handle_t *hdl = zhp->zfs_hdl;
 
 	(void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
 	(void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value));
-	(void) strlcpy(zc.zc_string, tag, sizeof (zc.zc_string));
+	if (strlcpy(zc.zc_string, tag, sizeof (zc.zc_string))
+	    >= sizeof (zc.zc_string))
+		return (zfs_error(hdl, EZFS_TAGTOOLONG, tag));
 	zc.zc_cookie = recursive;
+	zc.zc_temphold = temphold;
 
 	if (zfs_ioctl(hdl, ZFS_IOC_HOLD, &zc) != 0) {
 		char errbuf[ZFS_MAXNAMELEN+32];
@@ -4070,6 +4073,86 @@
 	return (0);
 }
 
+struct hold_range_arg {
+	zfs_handle_t	*origin;
+	const char	*fromsnap;
+	const char	*tosnap;
+	char		lastsnapheld[ZFS_MAXNAMELEN];
+	const char	*tag;
+	boolean_t	temphold;
+	boolean_t	seento;
+	boolean_t	seenfrom;
+	boolean_t	holding;
+};
+
+static int
+zfs_hold_range_one(zfs_handle_t *zhp, void *arg)
+{
+	struct hold_range_arg *hra = arg;
+	const char *thissnap;
+	int error;
+
+	thissnap = strchr(zfs_get_name(zhp), '@') + 1;
+
+	if (hra->fromsnap && !hra->seenfrom &&
+	    strcmp(hra->fromsnap, thissnap) == 0)
+		hra->seenfrom = B_TRUE;
+
+	/* snap is older or newer than the desired range, ignore it */
+	if (hra->seento || !hra->seenfrom) {
+		zfs_close(zhp);
+		return (0);
+	}
+
+	if (hra->holding) {
+		error = zfs_hold(hra->origin, thissnap, hra->tag, B_FALSE,
+		    hra->temphold);
+		if (error == 0) {
+			(void) strlcpy(hra->lastsnapheld, zfs_get_name(zhp),
+			    sizeof (hra->lastsnapheld));
+		}
+	} else {
+		error = zfs_release(hra->origin, thissnap, hra->tag, B_FALSE);
+	}
+
+	if (!hra->seento && strcmp(hra->tosnap, thissnap) == 0)
+		hra->seento = B_TRUE;
+
+	zfs_close(zhp);
+	return (error);
+}
+
+/*
+ * Add a user hold on the set of snapshots starting with fromsnap up to
+ * and including tosnap. If we're unable to to acquire a particular hold,
+ * undo any holds up to that point.
+ */
+int
+zfs_hold_range(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
+    const char *tag, boolean_t temphold)
+{
+	struct hold_range_arg arg = { 0 };
+	int error;
+
+	arg.origin = zhp;
+	arg.fromsnap = fromsnap;
+	arg.tosnap = tosnap;
+	arg.tag = tag;
+	arg.temphold = temphold;
+	arg.holding = B_TRUE;
+
+	error = zfs_iter_snapshots_sorted(zhp, zfs_hold_range_one, &arg);
+
+	/*
+	 * Make sure we either hold the entire range or none.
+	 */
+	if (error && arg.lastsnapheld[0] != '\0') {
+		(void) zfs_release_range(zhp, fromsnap,
+		    (const char *)arg.lastsnapheld, tag);
+	}
+	return (error);
+}
+
 int
 zfs_release(zfs_handle_t *zhp, const char *snapname, const char *tag,
     boolean_t recursive)
@@ -4079,7 +4162,9 @@
 
 	(void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
 	(void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value));
-	(void) strlcpy(zc.zc_string, tag, sizeof (zc.zc_string));
+	if (strlcpy(zc.zc_string, tag, sizeof (zc.zc_string))
+	    >= sizeof (zc.zc_string))
+		return (zfs_error(hdl, EZFS_TAGTOOLONG, tag));
 	zc.zc_cookie = recursive;
 
 	if (zfs_ioctl(hdl, ZFS_IOC_RELEASE, &zc) != 0) {
@@ -4107,3 +4192,21 @@
 
 	return (0);
 }
+
+/*
+ * Release a user hold from the set of snapshots starting with fromsnap
+ * up to and including tosnap.
+ */
+int
+zfs_release_range(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
+    const char *tag)
+{
+	struct hold_range_arg arg = { 0 };
+
+	arg.origin = zhp;
+	arg.fromsnap = fromsnap;
+	arg.tosnap = tosnap;
+	arg.tag = tag;
+
+	return (zfs_iter_snapshots_sorted(zhp, zfs_hold_range_one, &arg));
+}
--- a/usr/src/lib/libzfs/common/libzfs_sendrecv.c	Tue Aug 18 20:06:58 2009 -0700
+++ b/usr/src/lib/libzfs/common/libzfs_sendrecv.c	Wed Aug 19 11:15:14 2009 -0600
@@ -415,7 +415,7 @@
 		return (0);
 }
 
-static int
+int
 zfs_iter_snapshots_sorted(zfs_handle_t *zhp, zfs_iter_f callback, void *data)
 {
 	int ret = 0;
@@ -724,6 +724,8 @@
 	int err;
 	nvlist_t *fss = NULL;
 	avl_tree_t *fsavl = NULL;
+	char holdtag[128];
+	static uint64_t holdseq;
 
 	(void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
 	    "cannot send '%s'"), zhp->zfs_name);
@@ -742,6 +744,12 @@
 
 		assert(fromsnap || doall);
 
+		(void) snprintf(holdtag, sizeof (holdtag), ".send-%d-%llu",
+		    getpid(), (u_longlong_t)holdseq);
+		++holdseq;
+		err = zfs_hold_range(zhp, fromsnap, tosnap, holdtag, B_TRUE);
+		if (err)
+			return (err);
 		if (replicate) {
 			nvlist_t *hdrnv;
 
@@ -754,8 +762,11 @@
 
 			err = gather_nvlist(zhp->zfs_hdl, zhp->zfs_name,
 			    fromsnap, tosnap, &fss, &fsavl);
-			if (err)
+			if (err) {
+				(void) zfs_release_range(zhp, fromsnap, tosnap,
+				    holdtag);
 				return (err);
+			}
 			VERIFY(0 == nvlist_add_nvlist(hdrnv, "fss", fss));
 			err = nvlist_pack(hdrnv, &packbuf, &buflen,
 			    NV_ENCODE_XDR, 0);
@@ -763,6 +774,8 @@
 			if (err) {
 				fsavl_destroy(fsavl);
 				nvlist_free(fss);
+				(void) zfs_release_range(zhp, fromsnap, tosnap,
+				    holdtag);
 				return (zfs_standard_error(zhp->zfs_hdl,
 				    err, errbuf));
 			}
@@ -788,6 +801,8 @@
 		if (err == -1) {
 			fsavl_destroy(fsavl);
 			nvlist_free(fss);
+			(void) zfs_release_range(zhp, fromsnap, tosnap,
+			    holdtag);
 			return (zfs_standard_error(zhp->zfs_hdl,
 			    errno, errbuf));
 		}
@@ -801,6 +816,8 @@
 			if (err == -1) {
 				fsavl_destroy(fsavl);
 				nvlist_free(fss);
+				(void) zfs_release_range(zhp, fromsnap, tosnap,
+				    holdtag);
 				return (zfs_standard_error(zhp->zfs_hdl,
 				    errno, errbuf));
 			}
@@ -833,6 +850,7 @@
 			return (zfs_standard_error(zhp->zfs_hdl,
 			    errno, errbuf));
 		}
+		(void) zfs_release_range(zhp, fromsnap, tosnap, holdtag);
 	}
 
 	return (err || sdd.err);
--- a/usr/src/lib/libzfs/common/libzfs_util.c	Tue Aug 18 20:06:58 2009 -0700
+++ b/usr/src/lib/libzfs/common/libzfs_util.c	Wed Aug 19 11:15:14 2009 -0600
@@ -218,6 +218,8 @@
 	case EZFS_REFTAG_HOLD:
 		return (dgettext(TEXT_DOMAIN, "tag already exists on this "
 		    "dataset"));
+	case EZFS_TAGTOOLONG:
+		return (dgettext(TEXT_DOMAIN, "tag too long"));
 	case EZFS_UNKNOWN:
 		return (dgettext(TEXT_DOMAIN, "unknown error"));
 	default:
--- a/usr/src/lib/libzfs/common/mapfile-vers	Tue Aug 18 20:06:58 2009 -0700
+++ b/usr/src/lib/libzfs/common/mapfile-vers	Wed Aug 19 11:15:14 2009 -0600
@@ -62,6 +62,7 @@
 	zfs_get_user_props;
 	zfs_get_type;
 	zfs_hold;
+	zfs_hold_range;
 	zfs_iscsi_perm_check;
 	zfs_is_mounted;
 	zfs_is_shared;
@@ -73,6 +74,7 @@
 	zfs_iter_filesystems;
 	zfs_iter_root;
 	zfs_iter_snapshots;
+	zfs_iter_snapshots_sorted;
 	zfs_mount;
 	zfs_name_to_prop;
 	zfs_name_valid;
@@ -107,6 +109,7 @@
 	zfs_receive;
 	zfs_refresh_properties;
 	zfs_release;
+	zfs_release_range;
 	zfs_rename;
 	zfs_rollback;
 	zfs_send;
--- a/usr/src/uts/common/fs/zfs/dsl_dataset.c	Tue Aug 18 20:06:58 2009 -0700
+++ b/usr/src/uts/common/fs/zfs/dsl_dataset.c	Wed Aug 19 11:15:14 2009 -0600
@@ -1000,6 +1000,11 @@
 			return (error);
 		dsda->rm_origin = origin;
 		dsl_dataset_make_exclusive(origin, tag);
+
+		if (origin->ds_objset != NULL) {
+			dmu_objset_evict(origin->ds_objset);
+			origin->ds_objset = NULL;
+		}
 	}
 
 	return (0);
@@ -1721,6 +1726,11 @@
 		ASSERT3U(err, ==, 0);
 		ASSERT(!DS_UNIQUE_IS_ACCURATE(ds) ||
 		    ds->ds_phys->ds_unique_bytes == 0);
+
+		if (ds->ds_prev != NULL) {
+			dsl_dataset_rele(ds->ds_prev, ds);
+			ds->ds_prev = ds_prev = NULL;
+		}
 	}
 
 	err = zio_wait(zio);
@@ -1782,19 +1792,9 @@
 		/*
 		 * Remove the origin of the clone we just destroyed.
 		 */
-		dsl_dataset_t *origin = ds->ds_prev;
 		struct dsl_ds_destroyarg ndsda = {0};
 
-		ASSERT3P(origin, ==, dsda->rm_origin);
-		if (origin->ds_objset) {
-			dmu_objset_evict(origin->ds_objset);
-			origin->ds_objset = NULL;
-		}
-
-		dsl_dataset_rele(ds->ds_prev, ds);
-		ds->ds_prev = NULL;
-
-		ndsda.ds = origin;
+		ndsda.ds = dsda->rm_origin;
 		dsl_dataset_destroy_sync(&ndsda, tag, cr, tx);
 	}
 }
@@ -3210,11 +3210,22 @@
 	return (err);
 }
 
+struct dsl_ds_holdarg {
+	dsl_sync_task_group_t *dstg;
+	char *htag;
+	char *snapname;
+	boolean_t recursive;
+	boolean_t gotone;
+	boolean_t temphold;
+	char failed[MAXPATHLEN];
+};
+
 static int
 dsl_dataset_user_hold_check(void *arg1, void *arg2, dmu_tx_t *tx)
 {
 	dsl_dataset_t *ds = arg1;
-	char *htag = arg2;
+	struct dsl_ds_holdarg *ha = arg2;
+	char *htag = ha->htag;
 	objset_t *mos = ds->ds_dir->dd_pool->dp_meta_objset;
 	int error = 0;
 
@@ -3224,9 +3235,6 @@
 	if (!dsl_dataset_is_snapshot(ds))
 		return (EINVAL);
 
-	if (strlen(htag) >= ZAP_MAXNAMELEN)
-		return (ENAMETOOLONG);
-
 	/* tags must be unique */
 	mutex_enter(&ds->ds_lock);
 	if (ds->ds_phys->ds_userrefs_obj) {
@@ -3246,8 +3254,10 @@
 dsl_dataset_user_hold_sync(void *arg1, void *arg2, cred_t *cr, dmu_tx_t *tx)
 {
 	dsl_dataset_t *ds = arg1;
-	char *htag = arg2;
-	objset_t *mos = ds->ds_dir->dd_pool->dp_meta_objset;
+	struct dsl_ds_holdarg *ha = arg2;
+	char *htag = ha->htag;
+	dsl_pool_t *dp = ds->ds_dir->dd_pool;
+	objset_t *mos = dp->dp_meta_objset;
 	time_t now = gethrestime_sec();
 	uint64_t zapobj;
 
@@ -3268,20 +3278,16 @@
 
 	VERIFY(0 == zap_add(mos, zapobj, htag, 8, 1, &now, tx));
 
+	if (ha->temphold) {
+		VERIFY(0 == dsl_pool_user_hold(dp, ds->ds_object,
+		    htag, &now, tx));
+	}
+
 	spa_history_internal_log(LOG_DS_USER_HOLD,
-	    ds->ds_dir->dd_pool->dp_spa, tx, cr, "<%s> dataset = %llu",
-	    htag, ds->ds_object);
+	    dp->dp_spa, tx, cr, "<%s> temp = %d dataset = %llu", htag,
+	    (int)ha->temphold, ds->ds_object);
 }
 
-struct dsl_ds_holdarg {
-	dsl_sync_task_group_t *dstg;
-	char *htag;
-	char *snapname;
-	boolean_t recursive;
-	boolean_t gotone;
-	char failed[MAXPATHLEN];
-};
-
 static int
 dsl_dataset_user_hold_one(char *dsname, void *arg)
 {
@@ -3297,7 +3303,7 @@
 	if (error == 0) {
 		ha->gotone = B_TRUE;
 		dsl_sync_task_create(ha->dstg, dsl_dataset_user_hold_check,
-		    dsl_dataset_user_hold_sync, ds, ha->htag, 0);
+		    dsl_dataset_user_hold_sync, ds, ha, 0);
 	} else if (error == ENOENT && ha->recursive) {
 		error = 0;
 	} else {
@@ -3308,7 +3314,7 @@
 
 int
 dsl_dataset_user_hold(char *dsname, char *snapname, char *htag,
-    boolean_t recursive)
+    boolean_t recursive, boolean_t temphold)
 {
 	struct dsl_ds_holdarg *ha;
 	dsl_sync_task_t *dst;
@@ -3329,6 +3335,7 @@
 	ha->htag = htag;
 	ha->snapname = snapname;
 	ha->recursive = recursive;
+	ha->temphold = temphold;
 	if (recursive) {
 		error = dmu_objset_find(dsname, dsl_dataset_user_hold_one,
 		    ha, DS_FIND_CHILDREN);
@@ -3446,16 +3453,19 @@
 {
 	struct dsl_ds_releasearg *ra = arg1;
 	dsl_dataset_t *ds = ra->ds;
-	spa_t *spa = ds->ds_dir->dd_pool->dp_spa;
-	objset_t *mos = ds->ds_dir->dd_pool->dp_meta_objset;
+	dsl_pool_t *dp = ds->ds_dir->dd_pool;
+	objset_t *mos = dp->dp_meta_objset;
 	uint64_t zapobj;
 	uint64_t dsobj = ds->ds_object;
 	uint64_t refs;
+	int error;
 
 	mutex_enter(&ds->ds_lock);
 	ds->ds_userrefs--;
 	refs = ds->ds_userrefs;
 	mutex_exit(&ds->ds_lock);
+	error = dsl_pool_user_release(dp, ds->ds_object, ra->htag, tx);
+	VERIFY(error == 0 || error == ENOENT);
 	zapobj = ds->ds_phys->ds_userrefs_obj;
 	VERIFY(0 == zap_remove(mos, zapobj, ra->htag, tx));
 	if (ds->ds_userrefs == 0 && ds->ds_phys->ds_num_children == 1 &&
@@ -3470,7 +3480,7 @@
 	}
 
 	spa_history_internal_log(LOG_DS_USER_RELEASE,
-	    spa, tx, cr, "<%s> %lld dataset = %llu",
+	    dp->dp_spa, tx, cr, "<%s> %lld dataset = %llu",
 	    ra->htag, (longlong_t)refs, dsobj);
 }
 
@@ -3486,9 +3496,6 @@
 	boolean_t own = B_FALSE;
 	boolean_t might_destroy;
 
-	if (strlen(ha->htag) >= ZAP_MAXNAMELEN)
-		return (ENAMETOOLONG);
-
 	/* alloc a buffer to hold dsname@snapname, plus the terminating NULL */
 	name = kmem_asprintf("%s@%s", dsname, ha->snapname);
 	error = dsl_dataset_hold(name, dtag, &ds);
@@ -3601,6 +3608,34 @@
 	return (error);
 }
 
+/*
+ * Called at spa_load time to release a stale temporary user hold.
+ */
+int
+dsl_dataset_user_release_tmp(dsl_pool_t *dp, uint64_t dsobj, char *htag)
+{
+	dsl_dataset_t *ds;
+	char *snap;
+	char *name;
+	int namelen;
+	int error;
+
+	rw_enter(&dp->dp_config_rwlock, RW_READER);
+	error = dsl_dataset_hold_obj(dp, dsobj, FTAG, &ds);
+	rw_exit(&dp->dp_config_rwlock);
+	if (error)
+		return (error);
+	namelen = dsl_dataset_namelen(ds)+1;
+	name = kmem_alloc(namelen, KM_SLEEP);
+	dsl_dataset_name(ds, name);
+	dsl_dataset_rele(ds, FTAG);
+
+	snap = strchr(name, '@');
+	*snap = '\0';
+	++snap;
+	return (dsl_dataset_user_release(name, snap, htag, B_FALSE));
+}
+
 int
 dsl_dataset_get_holds(const char *dsname, nvlist_t **nvp)
 {
--- a/usr/src/uts/common/fs/zfs/dsl_pool.c	Tue Aug 18 20:06:58 2009 -0700
+++ b/usr/src/uts/common/fs/zfs/dsl_pool.c	Wed Aug 19 11:15:14 2009 -0600
@@ -142,6 +142,14 @@
 			goto out;
 	}
 
+	err = zap_lookup(dp->dp_meta_objset, DMU_POOL_DIRECTORY_OBJECT,
+	    DMU_POOL_TMP_USERREFS, sizeof (uint64_t), 1,
+	    &dp->dp_tmp_userrefs_obj);
+	if (err == ENOENT)
+		err = 0;
+	if (err)
+		goto out;
+
 	/* get scrub status */
 	err = zap_lookup(dp->dp_meta_objset, DMU_POOL_DIRECTORY_OBJECT,
 	    DMU_POOL_SCRUB_FUNC, sizeof (uint32_t), 1,
@@ -649,3 +657,108 @@
 {
 	return (dp->dp_vnrele_taskq);
 }
+
+/*
+ * Walk through the pool-wide zap object of temporary snapshot user holds
+ * and release them.
+ */
+void
+dsl_pool_clean_tmp_userrefs(dsl_pool_t *dp)
+{
+	zap_attribute_t za;
+	zap_cursor_t zc;
+	objset_t *mos = dp->dp_meta_objset;
+	uint64_t zapobj = dp->dp_tmp_userrefs_obj;
+
+	if (zapobj == 0)
+		return;
+	ASSERT(spa_version(dp->dp_spa) >= SPA_VERSION_USERREFS);
+
+	for (zap_cursor_init(&zc, mos, zapobj);
+	    zap_cursor_retrieve(&zc, &za) == 0;
+	    zap_cursor_advance(&zc)) {
+		char *htag;
+		uint64_t dsobj;
+
+		htag = strchr(za.za_name, '-');
+		*htag = '\0';
+		++htag;
+		dsobj = strtonum(za.za_name, NULL);
+		(void) dsl_dataset_user_release_tmp(dp, dsobj, htag);
+	}
+	zap_cursor_fini(&zc);
+}
+
+/*
+ * Create the pool-wide zap object for storing temporary snapshot holds.
+ */
+void
+dsl_pool_user_hold_create_obj(dsl_pool_t *dp, dmu_tx_t *tx)
+{
+	objset_t *mos = dp->dp_meta_objset;
+
+	ASSERT(dp->dp_tmp_userrefs_obj == 0);
+	ASSERT(dmu_tx_is_syncing(tx));
+
+	dp->dp_tmp_userrefs_obj = zap_create(mos, DMU_OT_USERREFS,
+	    DMU_OT_NONE, 0, tx);
+
+	VERIFY(zap_add(mos, DMU_POOL_DIRECTORY_OBJECT, DMU_POOL_TMP_USERREFS,
+	    sizeof (uint64_t), 1, &dp->dp_tmp_userrefs_obj, tx) == 0);
+}
+
+static int
+dsl_pool_user_hold_rele_impl(dsl_pool_t *dp, uint64_t dsobj,
+    const char *tag, time_t *t, dmu_tx_t *tx, boolean_t holding)
+{
+	objset_t *mos = dp->dp_meta_objset;
+	uint64_t zapobj = dp->dp_tmp_userrefs_obj;
+	char *name;
+	int error;
+
+	ASSERT(spa_version(dp->dp_spa) >= SPA_VERSION_USERREFS);
+	ASSERT(dmu_tx_is_syncing(tx));
+
+	/*
+	 * If the pool was created prior to SPA_VERSION_USERREFS, the
+	 * zap object for temporary holds might not exist yet.
+	 */
+	if (zapobj == 0) {
+		if (holding) {
+			dsl_pool_user_hold_create_obj(dp, tx);
+			zapobj = dp->dp_tmp_userrefs_obj;
+		} else {
+			return (ENOENT);
+		}
+	}
+
+	name = kmem_asprintf("%llx-%s", (u_longlong_t)dsobj, tag);
+	if (holding)
+		error = zap_add(mos, zapobj, name, 8, 1, t, tx);
+	else
+		error = zap_remove(mos, zapobj, name, tx);
+	strfree(name);
+
+	return (error);
+}
+
+/*
+ * Add a temporary hold for the given dataset object and tag.
+ */
+int
+dsl_pool_user_hold(dsl_pool_t *dp, uint64_t dsobj, const char *tag,
+    time_t *t, dmu_tx_t *tx)
+{
+	return (dsl_pool_user_hold_rele_impl(dp, dsobj, tag, t, tx, B_TRUE));
+}
+
+/*
+ * Release a temporary hold for the given dataset object and tag.
+ */
+int
+dsl_pool_user_release(dsl_pool_t *dp, uint64_t dsobj, const char *tag,
+    dmu_tx_t *tx)
+{
+	return (dsl_pool_user_hold_rele_impl(dp, dsobj, tag, NULL,
+	    tx, B_FALSE));
+}
--- a/usr/src/uts/common/fs/zfs/spa.c	Tue Aug 18 20:06:58 2009 -0700
+++ b/usr/src/uts/common/fs/zfs/spa.c	Wed Aug 19 11:15:14 2009 -0600
@@ -1606,6 +1606,11 @@
 		 */
 		(void) dmu_objset_find(spa_name(spa),
 		    dsl_destroy_inconsistent, NULL, DS_FIND_CHILDREN);
+
+		/*
+		 * Clean up any stale temporary dataset userrefs.
+		 */
+		dsl_pool_clean_tmp_userrefs(spa->spa_dsl_pool);
 	}
 
 	error = 0;
--- a/usr/src/uts/common/fs/zfs/spa_errlog.c	Tue Aug 18 20:06:58 2009 -0700
+++ b/usr/src/uts/common/fs/zfs/spa_errlog.c	Wed Aug 19 11:15:14 2009 -0600
@@ -58,7 +58,6 @@
  * This is a stripped-down version of strtoull, suitable only for converting
  * lowercase hexidecimal numbers that don't overflow.
  */
-#ifdef _KERNEL
 uint64_t
 strtonum(const char *str, char **nptr)
 {
@@ -85,7 +84,6 @@
 
 	return (val);
 }
-#endif
 
 /*
  * Convert a bookmark to a string.
--- a/usr/src/uts/common/fs/zfs/sys/dmu.h	Tue Aug 18 20:06:58 2009 -0700
+++ b/usr/src/uts/common/fs/zfs/sys/dmu.h	Wed Aug 19 11:15:14 2009 -0600
@@ -200,6 +200,7 @@
 #define	DMU_POOL_HISTORY		"history"
 #define	DMU_POOL_PROPS			"pool_props"
 #define	DMU_POOL_L2CACHE		"l2cache"
+#define	DMU_POOL_TMP_USERREFS		"tmp_userrefs"
 
 /* 4x8 zbookmark_t */
 #define	DMU_POOL_SCRUB_BOOKMARK		"scrub_bookmark"
--- a/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h	Tue Aug 18 20:06:58 2009 -0700
+++ b/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h	Wed Aug 19 11:15:14 2009 -0600
@@ -197,9 +197,11 @@
 int dsl_dataset_clone_swap(dsl_dataset_t *clone, dsl_dataset_t *origin_head,
     boolean_t force);
 int dsl_dataset_user_hold(char *dsname, char *snapname, char *htag,
-    boolean_t recursive);
+    boolean_t recursive, boolean_t temphold);
 int dsl_dataset_user_release(char *dsname, char *snapname, char *htag,
     boolean_t recursive);
+int dsl_dataset_user_release_tmp(struct dsl_pool *dp, uint64_t dsobj,
+    char *htag);
 int dsl_dataset_get_holds(const char *dsname, nvlist_t **nvp);
 
 blkptr_t *dsl_dataset_get_blkptr(dsl_dataset_t *ds);
--- a/usr/src/uts/common/fs/zfs/sys/dsl_pool.h	Tue Aug 18 20:06:58 2009 -0700
+++ b/usr/src/uts/common/fs/zfs/sys/dsl_pool.h	Wed Aug 19 11:15:14 2009 -0600
@@ -85,6 +85,7 @@
 	hrtime_t dp_read_overhead;
 	uint64_t dp_throughput;
 	uint64_t dp_write_limit;
+	uint64_t dp_tmp_userrefs_obj;
 
 	/* Uses dp_lock */
 	kmutex_t dp_lock;
@@ -146,6 +147,12 @@
 
 taskq_t *dsl_pool_vnrele_taskq(dsl_pool_t *dp);
 
+extern int dsl_pool_user_hold(dsl_pool_t *dp, uint64_t dsobj,
+    const char *tag, time_t *t, dmu_tx_t *tx);
+extern int dsl_pool_user_release(dsl_pool_t *dp, uint64_t dsobj,
+    const char *tag, dmu_tx_t *tx);
+extern void dsl_pool_clean_tmp_userrefs(dsl_pool_t *dp);
+
 #ifdef	__cplusplus
 }
 #endif
--- a/usr/src/uts/common/fs/zfs/sys/spa.h	Tue Aug 18 20:06:58 2009 -0700
+++ b/usr/src/uts/common/fs/zfs/sys/spa.h	Wed Aug 19 11:15:14 2009 -0600
@@ -478,6 +478,7 @@
 extern boolean_t spa_is_root(spa_t *spa);
 extern boolean_t spa_writeable(spa_t *spa);
 extern int spa_mode(spa_t *spa);
+extern uint64_t strtonum(const char *str, char **nptr);
 
 /* history logging */
 typedef enum history_log_type {
--- a/usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h	Tue Aug 18 20:06:58 2009 -0700
+++ b/usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h	Wed Aug 19 11:15:14 2009 -0600
@@ -166,6 +166,7 @@
 	struct drr_begin zc_begin_record;
 	zinject_record_t zc_inject_record;
 	boolean_t	zc_defer_destroy;
+	boolean_t	zc_temphold;
 } zfs_cmd_t;
 
 typedef struct zfs_useracct {
--- a/usr/src/uts/common/fs/zfs/zfs_ioctl.c	Tue Aug 18 20:06:58 2009 -0700
+++ b/usr/src/uts/common/fs/zfs/zfs_ioctl.c	Wed Aug 19 11:15:14 2009 -0600
@@ -3474,6 +3474,7 @@
  * zc_value	short name of snap
  * zc_string	user-supplied tag for this reference
  * zc_cookie	recursive flag
+ * zc_temphold	set if hold is temporary
  *
  * outputs:		none
  */
@@ -3486,7 +3487,7 @@
 		return (EINVAL);
 
 	return (dsl_dataset_user_hold(zc->zc_name, zc->zc_value,
-	    zc->zc_string, recursive));
+	    zc->zc_string, recursive, zc->zc_temphold));
 }
 
 /*
--- a/usr/src/uts/common/fs/zfs/zfs_vfsops.c	Tue Aug 18 20:06:58 2009 -0700
+++ b/usr/src/uts/common/fs/zfs/zfs_vfsops.c	Wed Aug 19 11:15:14 2009 -0600
@@ -630,7 +630,6 @@
 fuidstr_to_sid(zfsvfs_t *zfsvfs, const char *fuidstr,
     char *domainbuf, int buflen, uid_t *ridp)
 {
-	extern uint64_t strtonum(const char *str, char **nptr);
 	uint64_t fuid;
 	const char *domain;