diff options
Diffstat (limited to 'usr/src/uts/common/fs/zfs/dsl_dataset.c')
| -rw-r--r-- | usr/src/uts/common/fs/zfs/dsl_dataset.c | 506 | 
1 files changed, 400 insertions, 106 deletions
| diff --git a/usr/src/uts/common/fs/zfs/dsl_dataset.c b/usr/src/uts/common/fs/zfs/dsl_dataset.c index 90f4956e81..cf2f290f47 100644 --- a/usr/src/uts/common/fs/zfs/dsl_dataset.c +++ b/usr/src/uts/common/fs/zfs/dsl_dataset.c @@ -1616,7 +1616,6 @@ dsl_dataset_snapshot_tmp(const char *fsname, const char *snapname,  	return (error);  } -  void  dsl_dataset_sync(dsl_dataset_t *ds, zio_t *zio, dmu_tx_t *tx)  { @@ -1684,30 +1683,17 @@ dsl_dataset_sync_done(dsl_dataset_t *ds, dmu_tx_t *tx)  	dmu_buf_rele(ds->ds_dbuf, ds);  } -static void -get_clones_stat(dsl_dataset_t *ds, nvlist_t *nv) +int +get_clones_stat_impl(dsl_dataset_t *ds, nvlist_t *val)  {  	uint64_t count = 0;  	objset_t *mos = ds->ds_dir->dd_pool->dp_meta_objset;  	zap_cursor_t zc;  	zap_attribute_t za; -	nvlist_t *propval = fnvlist_alloc(); -	nvlist_t *val;  	ASSERT(dsl_pool_config_held(ds->ds_dir->dd_pool));  	/* -	 * We use nvlist_alloc() instead of fnvlist_alloc() because the -	 * latter would allocate the list with NV_UNIQUE_NAME flag. -	 * As a result, every time a clone name is appended to the list -	 * it would be (linearly) searched for for a duplicate name. -	 * We already know that all clone names must be unique and we -	 * want avoid the quadratic complexity of double-checking that -	 * because we can have a large number of clones. -	 */ -	VERIFY0(nvlist_alloc(&val, 0, KM_SLEEP)); - -	/*  	 * There may be missing entries in ds_next_clones_obj  	 * due to a bug in a previous version of the code.  	 * Only trust it if it has the right number of entries. @@ -1716,8 +1702,9 @@ get_clones_stat(dsl_dataset_t *ds, nvlist_t *nv)  		VERIFY0(zap_count(mos, dsl_dataset_phys(ds)->ds_next_clones_obj,  		    &count));  	} -	if (count != dsl_dataset_phys(ds)->ds_num_children - 1) -		goto fail; +	if (count != dsl_dataset_phys(ds)->ds_num_children - 1) { +		return (ENOENT); +	}  	for (zap_cursor_init(&zc, mos,  	    dsl_dataset_phys(ds)->ds_next_clones_obj);  	    zap_cursor_retrieve(&zc, &za) == 0; @@ -1731,15 +1718,42 @@ get_clones_stat(dsl_dataset_t *ds, nvlist_t *nv)  		dsl_dataset_rele(clone, FTAG);  	}  	zap_cursor_fini(&zc); -	fnvlist_add_nvlist(propval, ZPROP_VALUE, val); -	fnvlist_add_nvlist(nv, zfs_prop_to_name(ZFS_PROP_CLONES), propval); -fail: -	nvlist_free(val); -	nvlist_free(propval); +	return (0);  } -static void -get_receive_resume_stats(dsl_dataset_t *ds, nvlist_t *nv) +void +get_clones_stat(dsl_dataset_t *ds, nvlist_t *nv) +{ +	nvlist_t *propval = fnvlist_alloc(); +	nvlist_t *val; + +	/* +	 * We use nvlist_alloc() instead of fnvlist_alloc() because the +	 * latter would allocate the list with NV_UNIQUE_NAME flag. +	 * As a result, every time a clone name is appended to the list +	 * it would be (linearly) searched for for a duplicate name. +	 * We already know that all clone names must be unique and we +	 * want avoid the quadratic complexity of double-checking that +	 * because we can have a large number of clones. +	 */ +	VERIFY0(nvlist_alloc(&val, 0, KM_SLEEP)); + +	if (get_clones_stat_impl(ds, val) == 0) { +		fnvlist_add_nvlist(propval, ZPROP_VALUE, val); +		fnvlist_add_nvlist(nv, zfs_prop_to_name(ZFS_PROP_CLONES), +		    propval); +	} else { +		nvlist_free(val); +		nvlist_free(propval); +	} +} + +/* + * Returns a string that represents the receive resume stats token. It should + * be freed with strfree(). + */ +char * +get_receive_resume_stats_impl(dsl_dataset_t *ds)  {  	dsl_pool_t *dp = ds->ds_dir->dd_pool; @@ -1807,84 +1821,359 @@ get_receive_resume_stats(dsl_dataset_t *ds, nvlist_t *nv)  		    ZFS_SEND_RESUME_TOKEN_VERSION,  		    (longlong_t)cksum.zc_word[0],  		    (longlong_t)packed_size, str); -		dsl_prop_nvlist_add_string(nv, -		    ZFS_PROP_RECEIVE_RESUME_TOKEN, propval);  		kmem_free(packed, packed_size);  		kmem_free(str, compressed_size * 2 + 1);  		kmem_free(compressed, packed_size); -		strfree(propval); +		return (propval); +	} +	return (strdup("")); +} + +/* + * Returns a string that represents the receive resume stats token of the + * dataset's child. It should be freed with strfree(). + */ +char * +get_child_receive_stats(dsl_dataset_t *ds) +{ +	char recvname[ZFS_MAX_DATASET_NAME_LEN + 6]; +	dsl_dataset_t *recv_ds; +	dsl_dataset_name(ds, recvname); +	if (strlcat(recvname, "/", sizeof (recvname)) < +	    sizeof (recvname) && +	    strlcat(recvname, recv_clone_name, sizeof (recvname)) < +	    sizeof (recvname) && +	    dsl_dataset_hold(ds->ds_dir->dd_pool, recvname, FTAG, +	    &recv_ds)  == 0) { +		char *propval = get_receive_resume_stats_impl(recv_ds); +		dsl_dataset_rele(recv_ds, FTAG); +		return (propval);  	} +	return (strdup("")); +} + +static void +get_receive_resume_stats(dsl_dataset_t *ds, nvlist_t *nv) +{ +	char *propval = get_receive_resume_stats_impl(ds); +	if (strcmp(propval, "") != 0) { +		dsl_prop_nvlist_add_string(nv, +		    ZFS_PROP_RECEIVE_RESUME_TOKEN, propval); +	} else { +		char *childval = get_child_receive_stats(ds); +		if (strcmp(childval, "") != 0) { +			dsl_prop_nvlist_add_string(nv, +			    ZFS_PROP_RECEIVE_RESUME_TOKEN, childval); +		} +		strfree(childval); +	} +	strfree(propval); +} + +uint64_t +dsl_get_refratio(dsl_dataset_t *ds) +{ +	uint64_t ratio = dsl_dataset_phys(ds)->ds_compressed_bytes == 0 ? 100 : +	    (dsl_dataset_phys(ds)->ds_uncompressed_bytes * 100 / +	    dsl_dataset_phys(ds)->ds_compressed_bytes); +	return (ratio); +} + +uint64_t +dsl_get_logicalreferenced(dsl_dataset_t *ds) +{ +	return (dsl_dataset_phys(ds)->ds_uncompressed_bytes); +} + +uint64_t +dsl_get_compressratio(dsl_dataset_t *ds) +{ +	if (ds->ds_is_snapshot) { +		return (dsl_get_refratio(ds)); +	} else { +		dsl_dir_t *dd = ds->ds_dir; +		mutex_enter(&dd->dd_lock); +		uint64_t val = dsl_dir_get_compressratio(dd); +		mutex_exit(&dd->dd_lock); +		return (val); +	} +} + +uint64_t +dsl_get_used(dsl_dataset_t *ds) +{ +	if (ds->ds_is_snapshot) { +		return (dsl_dataset_phys(ds)->ds_unique_bytes); +	} else { +		dsl_dir_t *dd = ds->ds_dir; +		mutex_enter(&dd->dd_lock); +		uint64_t val = dsl_dir_get_used(dd); +		mutex_exit(&dd->dd_lock); +		return (val); +	} +} + +uint64_t +dsl_get_creation(dsl_dataset_t *ds) +{ +	return (dsl_dataset_phys(ds)->ds_creation_time); +} + +uint64_t +dsl_get_creationtxg(dsl_dataset_t *ds) +{ +	return (dsl_dataset_phys(ds)->ds_creation_txg); +} + +uint64_t +dsl_get_refquota(dsl_dataset_t *ds) +{ +	return (ds->ds_quota); +} + +uint64_t +dsl_get_refreservation(dsl_dataset_t *ds) +{ +	return (ds->ds_reserved); +} + +uint64_t +dsl_get_guid(dsl_dataset_t *ds) +{ +	return (dsl_dataset_phys(ds)->ds_guid); +} + +uint64_t +dsl_get_unique(dsl_dataset_t *ds) +{ +	return (dsl_dataset_phys(ds)->ds_unique_bytes); +} + +uint64_t +dsl_get_objsetid(dsl_dataset_t *ds) +{ +	return (ds->ds_object); +} + +uint64_t +dsl_get_userrefs(dsl_dataset_t *ds) +{ +	return (ds->ds_userrefs); +} + +uint64_t +dsl_get_defer_destroy(dsl_dataset_t *ds) +{ +	return (DS_IS_DEFER_DESTROY(ds) ? 1 : 0); +} + +uint64_t +dsl_get_referenced(dsl_dataset_t *ds) +{ +	return (dsl_dataset_phys(ds)->ds_referenced_bytes); +} + +uint64_t +dsl_get_numclones(dsl_dataset_t *ds) +{ +	ASSERT(ds->ds_is_snapshot); +	return (dsl_dataset_phys(ds)->ds_num_children - 1); +} + +uint64_t +dsl_get_inconsistent(dsl_dataset_t *ds) +{ +	return ((dsl_dataset_phys(ds)->ds_flags & DS_FLAG_INCONSISTENT) ? +	    1 : 0); +} + +uint64_t +dsl_get_available(dsl_dataset_t *ds) +{ +	uint64_t refdbytes = dsl_get_referenced(ds); +	uint64_t availbytes = dsl_dir_space_available(ds->ds_dir, +	    NULL, 0, TRUE); +	if (ds->ds_reserved > dsl_dataset_phys(ds)->ds_unique_bytes) { +		availbytes += +		    ds->ds_reserved - dsl_dataset_phys(ds)->ds_unique_bytes; +	} +	if (ds->ds_quota != 0) { +		/* +		 * Adjust available bytes according to refquota +		 */ +		if (refdbytes < ds->ds_quota) { +			availbytes = MIN(availbytes, +			    ds->ds_quota - refdbytes); +		} else { +			availbytes = 0; +		} +	} +	return (availbytes); +} + +int +dsl_get_written(dsl_dataset_t *ds, uint64_t *written) +{ +	dsl_pool_t *dp = ds->ds_dir->dd_pool; +	dsl_dataset_t *prev; +	int err = dsl_dataset_hold_obj(dp, +	    dsl_dataset_phys(ds)->ds_prev_snap_obj, FTAG, &prev); +	if (err == 0) { +		uint64_t comp, uncomp; +		err = dsl_dataset_space_written(prev, ds, written, +		    &comp, &uncomp); +		dsl_dataset_rele(prev, FTAG); +	} +	return (err); +} + +/* + * 'snap' should be a buffer of size ZFS_MAX_DATASET_NAME_LEN. + */ +int +dsl_get_prev_snap(dsl_dataset_t *ds, char *snap) +{ +	dsl_pool_t *dp = ds->ds_dir->dd_pool; +	if (ds->ds_prev != NULL && ds->ds_prev != dp->dp_origin_snap) { +		dsl_dataset_name(ds->ds_prev, snap); +		return (0); +	} else { +		return (ENOENT); +	} +} + +/* + * Returns the mountpoint property and source for the given dataset in the value + * and source buffers. The value buffer must be at least as large as MAXPATHLEN + * and the source buffer as least as large a ZFS_MAX_DATASET_NAME_LEN. + * Returns 0 on success and an error on failure. + */ +int +dsl_get_mountpoint(dsl_dataset_t *ds, const char *dsname, char *value, +    char *source) +{ +	int error; +	dsl_pool_t *dp = ds->ds_dir->dd_pool; + +	/* Retrieve the mountpoint value stored in the zap opbject */ +	error = dsl_prop_get_ds(ds, zfs_prop_to_name(ZFS_PROP_MOUNTPOINT), 1, +	    ZAP_MAXVALUELEN, value, source); +	if (error != 0) { +		return (error); +	} + +	/* Process the dsname and source to find the full mountpoint string */ +	if (value[0] == '/') { +		char *buf = kmem_alloc(ZAP_MAXVALUELEN, KM_SLEEP); +		char *root = buf; +		const char *relpath; + +		/* +		 * If we inherit the mountpoint, even from a dataset +		 * with a received value, the source will be the path of +		 * the dataset we inherit from. If source is +		 * ZPROP_SOURCE_VAL_RECVD, the received value is not +		 * inherited. +		 */ +		if (strcmp(source, ZPROP_SOURCE_VAL_RECVD) == 0) { +			relpath = ""; +		} else { +			ASSERT0(strncmp(dsname, source, strlen(source))); +			relpath = dsname + strlen(source); +			if (relpath[0] == '/') +				relpath++; +		} + +		spa_altroot(dp->dp_spa, root, ZAP_MAXVALUELEN); + +		/* +		 * Special case an alternate root of '/'. This will +		 * avoid having multiple leading slashes in the +		 * mountpoint path. +		 */ +		if (strcmp(root, "/") == 0) +			root++; + +		/* +		 * If the mountpoint is '/' then skip over this +		 * if we are obtaining either an alternate root or +		 * an inherited mountpoint. +		 */ +		char *mnt = value; +		if (value[1] == '\0' && (root[0] != '\0' || +		    relpath[0] != '\0')) +			mnt = value + 1; + +		if (relpath[0] == '\0') { +			(void) snprintf(value, ZAP_MAXVALUELEN, "%s%s", +			    root, mnt); +		} else { +			(void) snprintf(value, ZAP_MAXVALUELEN, "%s%s%s%s", +			    root, mnt, relpath[0] == '@' ? "" : "/", +			    relpath); +		} +		kmem_free(buf, ZAP_MAXVALUELEN); +	} else { +		/* 'legacy' or 'none' */ +		(void) snprintf(value, ZAP_MAXVALUELEN, "%s", value); +	} +	return (0);  }  void  dsl_dataset_stats(dsl_dataset_t *ds, nvlist_t *nv)  {  	dsl_pool_t *dp = ds->ds_dir->dd_pool; -	uint64_t refd, avail, uobjs, aobjs, ratio;  	ASSERT(dsl_pool_config_held(dp)); -	ratio = dsl_dataset_phys(ds)->ds_compressed_bytes == 0 ? 100 : -	    (dsl_dataset_phys(ds)->ds_uncompressed_bytes * 100 / -	    dsl_dataset_phys(ds)->ds_compressed_bytes); - -	dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_REFRATIO, ratio); +	dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_REFRATIO, +	    dsl_get_refratio(ds));  	dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_LOGICALREFERENCED, -	    dsl_dataset_phys(ds)->ds_uncompressed_bytes); +	    dsl_get_logicalreferenced(ds)); +	dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_COMPRESSRATIO, +	    dsl_get_compressratio(ds)); +	dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_USED, +	    dsl_get_used(ds));  	if (ds->ds_is_snapshot) { -		dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_COMPRESSRATIO, ratio); -		dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_USED, -		    dsl_dataset_phys(ds)->ds_unique_bytes);  		get_clones_stat(ds, nv);  	} else { -		if (ds->ds_prev != NULL && ds->ds_prev != dp->dp_origin_snap) { -			char buf[ZFS_MAX_DATASET_NAME_LEN]; -			dsl_dataset_name(ds->ds_prev, buf); -			dsl_prop_nvlist_add_string(nv, ZFS_PROP_PREV_SNAP, buf); -		} - +		char buf[ZFS_MAX_DATASET_NAME_LEN]; +		if (dsl_get_prev_snap(ds, buf) == 0) +			dsl_prop_nvlist_add_string(nv, ZFS_PROP_PREV_SNAP, +			    buf);  		dsl_dir_stats(ds->ds_dir, nv);  	} -	dsl_dataset_space(ds, &refd, &avail, &uobjs, &aobjs); -	dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_AVAILABLE, avail); -	dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_REFERENCED, refd); - +	dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_AVAILABLE, +	    dsl_get_available(ds)); +	dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_REFERENCED, +	    dsl_get_referenced(ds));  	dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_CREATION, -	    dsl_dataset_phys(ds)->ds_creation_time); +	    dsl_get_creation(ds));  	dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_CREATETXG, -	    dsl_dataset_phys(ds)->ds_creation_txg); +	    dsl_get_creationtxg(ds));  	dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_REFQUOTA, -	    ds->ds_quota); +	    dsl_get_refquota(ds));  	dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_REFRESERVATION, -	    ds->ds_reserved); +	    dsl_get_refreservation(ds));  	dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_GUID, -	    dsl_dataset_phys(ds)->ds_guid); +	    dsl_get_guid(ds));  	dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_UNIQUE, -	    dsl_dataset_phys(ds)->ds_unique_bytes); +	    dsl_get_unique(ds));  	dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_OBJSETID, -	    ds->ds_object); +	    dsl_get_objsetid(ds));  	dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_USERREFS, -	    ds->ds_userrefs); +	    dsl_get_userrefs(ds));  	dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_DEFER_DESTROY, -	    DS_IS_DEFER_DESTROY(ds) ? 1 : 0); +	    dsl_get_defer_destroy(ds));  	if (dsl_dataset_phys(ds)->ds_prev_snap_obj != 0) { -		uint64_t written, comp, uncomp; -		dsl_pool_t *dp = ds->ds_dir->dd_pool; -		dsl_dataset_t *prev; - -		int err = dsl_dataset_hold_obj(dp, -		    dsl_dataset_phys(ds)->ds_prev_snap_obj, FTAG, &prev); -		if (err == 0) { -			err = dsl_dataset_space_written(prev, ds, &written, -			    &comp, &uncomp); -			dsl_dataset_rele(prev, FTAG); -			if (err == 0) { -				dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_WRITTEN, -				    written); -			} +		uint64_t written; +		if (dsl_get_written(ds, &written) == 0) { +			dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_WRITTEN, +			    written);  		}  	} @@ -1921,27 +2210,19 @@ dsl_dataset_fast_stat(dsl_dataset_t *ds, dmu_objset_stats_t *stat)  	dsl_pool_t *dp = ds->ds_dir->dd_pool;  	ASSERT(dsl_pool_config_held(dp)); -	stat->dds_creation_txg = dsl_dataset_phys(ds)->ds_creation_txg; -	stat->dds_inconsistent = -	    dsl_dataset_phys(ds)->ds_flags & DS_FLAG_INCONSISTENT; -	stat->dds_guid = dsl_dataset_phys(ds)->ds_guid; +	stat->dds_creation_txg = dsl_get_creationtxg(ds); +	stat->dds_inconsistent = dsl_get_inconsistent(ds); +	stat->dds_guid = dsl_get_guid(ds);  	stat->dds_origin[0] = '\0';  	if (ds->ds_is_snapshot) {  		stat->dds_is_snapshot = B_TRUE; -		stat->dds_num_clones = -		    dsl_dataset_phys(ds)->ds_num_children - 1; +		stat->dds_num_clones = dsl_get_numclones(ds);  	} else {  		stat->dds_is_snapshot = B_FALSE;  		stat->dds_num_clones = 0;  		if (dsl_dir_is_clone(ds->ds_dir)) { -			dsl_dataset_t *ods; - -			VERIFY0(dsl_dataset_hold_obj(dp, -			    dsl_dir_phys(ds->ds_dir)->dd_origin_obj, -			    FTAG, &ods)); -			dsl_dataset_name(ods, stat->dds_origin); -			dsl_dataset_rele(ods, FTAG); +			dsl_dir_get_origin(ds->ds_dir, stat->dds_origin);  		}  	}  } @@ -2333,22 +2614,12 @@ struct promotenode {  	dsl_dataset_t *ds;  }; -typedef struct dsl_dataset_promote_arg { -	const char *ddpa_clonename; -	dsl_dataset_t *ddpa_clone; -	list_t shared_snaps, origin_snaps, clone_snaps; -	dsl_dataset_t *origin_origin; /* origin of the origin */ -	uint64_t used, comp, uncomp, unique, cloneusedsnap, originusedsnap; -	char *err_ds; -	cred_t *cr; -} dsl_dataset_promote_arg_t; -  static int snaplist_space(list_t *l, uint64_t mintxg, uint64_t *spacep);  static int promote_hold(dsl_dataset_promote_arg_t *ddpa, dsl_pool_t *dp,      void *tag);  static void promote_rele(dsl_dataset_promote_arg_t *ddpa, void *tag); -static int +int  dsl_dataset_promote_check(void *arg, dmu_tx_t *tx)  {  	dsl_dataset_promote_arg_t *ddpa = arg; @@ -2360,14 +2631,19 @@ dsl_dataset_promote_check(void *arg, dmu_tx_t *tx)  	uint64_t unused;  	uint64_t ss_mv_cnt;  	size_t max_snap_len; +	boolean_t conflicting_snaps;  	err = promote_hold(ddpa, dp, FTAG);  	if (err != 0)  		return (err);  	hds = ddpa->ddpa_clone; +	snap = list_head(&ddpa->shared_snaps); +	origin_ds = snap->ds;  	max_snap_len = MAXNAMELEN - strlen(ddpa->ddpa_clonename) - 1; +	snap = list_head(&ddpa->origin_snaps); +  	if (dsl_dataset_phys(hds)->ds_flags & DS_FLAG_NOPROMOTE) {  		promote_rele(ddpa, FTAG);  		return (SET_ERROR(EXDEV)); @@ -2382,9 +2658,6 @@ dsl_dataset_promote_check(void *arg, dmu_tx_t *tx)  		return (0);  	} -	snap = list_head(&ddpa->shared_snaps); -	origin_ds = snap->ds; -  	/* compute origin's new unique space */  	snap = list_tail(&ddpa->clone_snaps);  	ASSERT3U(dsl_dataset_phys(snap->ds)->ds_prev_snap_obj, ==, @@ -2408,6 +2681,7 @@ dsl_dataset_promote_check(void *arg, dmu_tx_t *tx)  	 * Note however, if we stop before we reach the ORIGIN we get:  	 * uN + kN + kN-1 + ... + kM - uM-1  	 */ +	conflicting_snaps = B_FALSE;  	ss_mv_cnt = 0;  	ddpa->used = dsl_dataset_phys(origin_ds)->ds_referenced_bytes;  	ddpa->comp = dsl_dataset_phys(origin_ds)->ds_compressed_bytes; @@ -2436,12 +2710,12 @@ dsl_dataset_promote_check(void *arg, dmu_tx_t *tx)  		}  		err = dsl_dataset_snap_lookup(hds, ds->ds_snapname, &val);  		if (err == 0) { -			(void) strcpy(ddpa->err_ds, snap->ds->ds_snapname); -			err = SET_ERROR(EEXIST); +			fnvlist_add_boolean(ddpa->err_ds, +			    snap->ds->ds_snapname); +			conflicting_snaps = B_TRUE; +		} else if (err != ENOENT) {  			goto out;  		} -		if (err != ENOENT) -			goto out;  		/* The very first snapshot does not have a deadlist */  		if (dsl_dataset_phys(ds)->ds_prev_snap_obj == 0) @@ -2455,6 +2729,15 @@ dsl_dataset_promote_check(void *arg, dmu_tx_t *tx)  	}  	/* +	 * In order to return the full list of conflicting snapshots, we check +	 * whether there was a conflict after traversing all of them. +	 */ +	if (conflicting_snaps) { +		err = SET_ERROR(EEXIST); +		goto out; +	} + +	/*  	 * If we are a clone of a clone then we never reached ORIGIN,  	 * so we need to subtract out the clone origin's used space.  	 */ @@ -2516,7 +2799,7 @@ out:  	return (err);  } -static void +void  dsl_dataset_promote_sync(void *arg, dmu_tx_t *tx)  {  	dsl_dataset_promote_arg_t *ddpa = arg; @@ -2841,6 +3124,7 @@ dsl_dataset_promote(const char *name, char *conflsnap)  	dsl_dataset_promote_arg_t ddpa = { 0 };  	uint64_t numsnaps;  	int error; +	nvpair_t *snap_pair;  	objset_t *os;  	/* @@ -2858,12 +3142,22 @@ dsl_dataset_promote(const char *name, char *conflsnap)  		return (error);  	ddpa.ddpa_clonename = name; -	ddpa.err_ds = conflsnap; +	ddpa.err_ds = fnvlist_alloc();  	ddpa.cr = CRED(); -	return (dsl_sync_task(name, dsl_dataset_promote_check, +	error = dsl_sync_task(name, dsl_dataset_promote_check,  	    dsl_dataset_promote_sync, &ddpa, -	    2 + numsnaps, ZFS_SPACE_CHECK_RESERVED)); +	    2 + numsnaps, ZFS_SPACE_CHECK_RESERVED); + +	/* +	 * Return the first conflicting snapshot found. +	 */ +	snap_pair = nvlist_next_nvpair(ddpa.err_ds, NULL); +	if (snap_pair != NULL && conflsnap != NULL) +		(void) strcpy(conflsnap, nvpair_name(snap_pair)); + +	fnvlist_free(ddpa.err_ds); +	return (error);  }  int | 
