1 /*
2  * CDDL HEADER START
3  *
4  * This file and its contents are supplied under the terms of the
5  * Common Development and Distribution License ("CDDL"), version 1.0.
6  * You may only use this file in accordance with the terms of version
7  * 1.0 of the CDDL.
8  *
9  * A full copy of the text of the CDDL should have accompanied this
10  * source.  A copy of the CDDL is also available via the Internet at
11  * http://www.illumos.org/license/CDDL.
12  *
13  * CDDL HEADER END
14  */
15 
16 /*
17  * Copyright (c) 2013, 2014 by Delphix. All rights reserved.
18  * Copyright 2017 Nexenta Systems, Inc.
19  */
20 
21 #include <sys/zfs_context.h>
22 #include <sys/dsl_dataset.h>
23 #include <sys/dsl_dir.h>
24 #include <sys/dsl_prop.h>
25 #include <sys/dsl_synctask.h>
26 #include <sys/dmu_impl.h>
27 #include <sys/dmu_tx.h>
28 #include <sys/arc.h>
29 #include <sys/zap.h>
30 #include <sys/zfeature.h>
31 #include <sys/spa.h>
32 #include <sys/dsl_bookmark.h>
33 #include <zfs_namecheck.h>
34 
35 static int
dsl_bookmark_hold_ds(dsl_pool_t * dp,const char * fullname,dsl_dataset_t ** dsp,void * tag,char ** shortnamep)36 dsl_bookmark_hold_ds(dsl_pool_t *dp, const char *fullname,
37     dsl_dataset_t **dsp, void *tag, char **shortnamep)
38 {
39 	char buf[ZFS_MAX_DATASET_NAME_LEN];
40 	char *hashp;
41 
42 	if (strlen(fullname) >= ZFS_MAX_DATASET_NAME_LEN)
43 		return (SET_ERROR(ENAMETOOLONG));
44 	hashp = strchr(fullname, '#');
45 	if (hashp == NULL)
46 		return (SET_ERROR(EINVAL));
47 
48 	*shortnamep = hashp + 1;
49 	if (zfs_component_namecheck(*shortnamep, NULL, NULL))
50 		return (SET_ERROR(EINVAL));
51 	(void) strlcpy(buf, fullname, hashp - fullname + 1);
52 	return (dsl_dataset_hold(dp, buf, tag, dsp));
53 }
54 
55 /*
56  * Returns ESRCH if bookmark is not found.
57  */
58 static int
dsl_dataset_bmark_lookup(dsl_dataset_t * ds,const char * shortname,zfs_bookmark_phys_t * bmark_phys)59 dsl_dataset_bmark_lookup(dsl_dataset_t *ds, const char *shortname,
60     zfs_bookmark_phys_t *bmark_phys)
61 {
62 	objset_t *mos = ds->ds_dir->dd_pool->dp_meta_objset;
63 	uint64_t bmark_zapobj = ds->ds_bookmarks;
64 	matchtype_t mt = 0;
65 	int err;
66 
67 	if (bmark_zapobj == 0)
68 		return (SET_ERROR(ESRCH));
69 
70 	if (dsl_dataset_phys(ds)->ds_flags & DS_FLAG_CI_DATASET)
71 		mt = MT_NORMALIZE;
72 
73 	err = zap_lookup_norm(mos, bmark_zapobj, shortname, sizeof (uint64_t),
74 	    sizeof (*bmark_phys) / sizeof (uint64_t), bmark_phys, mt,
75 	    NULL, 0, NULL);
76 
77 	return (err == ENOENT ? ESRCH : err);
78 }
79 
80 /*
81  * If later_ds is non-NULL, this will return EXDEV if the the specified bookmark
82  * does not represents an earlier point in later_ds's timeline.
83  *
84  * Returns ENOENT if the dataset containing the bookmark does not exist.
85  * Returns ESRCH if the dataset exists but the bookmark was not found in it.
86  */
87 int
dsl_bookmark_lookup(dsl_pool_t * dp,const char * fullname,dsl_dataset_t * later_ds,zfs_bookmark_phys_t * bmp)88 dsl_bookmark_lookup(dsl_pool_t *dp, const char *fullname,
89     dsl_dataset_t *later_ds, zfs_bookmark_phys_t *bmp)
90 {
91 	char *shortname;
92 	dsl_dataset_t *ds;
93 	int error;
94 
95 	error = dsl_bookmark_hold_ds(dp, fullname, &ds, FTAG, &shortname);
96 	if (error != 0)
97 		return (error);
98 
99 	error = dsl_dataset_bmark_lookup(ds, shortname, bmp);
100 	if (error == 0 && later_ds != NULL) {
101 		if (!dsl_dataset_is_before(later_ds, ds, bmp->zbm_creation_txg))
102 			error = SET_ERROR(EXDEV);
103 	}
104 	dsl_dataset_rele(ds, FTAG);
105 	return (error);
106 }
107 
108 typedef struct dsl_bookmark_create_arg {
109 	nvlist_t *dbca_bmarks;
110 	nvlist_t *dbca_errors;
111 } dsl_bookmark_create_arg_t;
112 
113 static int
dsl_bookmark_create_check_impl(dsl_dataset_t * snapds,const char * bookmark_name,dmu_tx_t * tx)114 dsl_bookmark_create_check_impl(dsl_dataset_t *snapds, const char *bookmark_name,
115     dmu_tx_t *tx)
116 {
117 	dsl_pool_t *dp = dmu_tx_pool(tx);
118 	dsl_dataset_t *bmark_fs;
119 	char *shortname;
120 	int error;
121 	zfs_bookmark_phys_t bmark_phys;
122 
123 	if (!snapds->ds_is_snapshot)
124 		return (SET_ERROR(EINVAL));
125 
126 	error = dsl_bookmark_hold_ds(dp, bookmark_name,
127 	    &bmark_fs, FTAG, &shortname);
128 	if (error != 0)
129 		return (error);
130 
131 	if (!dsl_dataset_is_before(bmark_fs, snapds, 0)) {
132 		dsl_dataset_rele(bmark_fs, FTAG);
133 		return (SET_ERROR(EINVAL));
134 	}
135 
136 	error = dsl_dataset_bmark_lookup(bmark_fs, shortname,
137 	    &bmark_phys);
138 	dsl_dataset_rele(bmark_fs, FTAG);
139 	if (error == 0)
140 		return (SET_ERROR(EEXIST));
141 	if (error == ESRCH)
142 		return (0);
143 	return (error);
144 }
145 
146 static int
dsl_bookmark_create_check(void * arg,dmu_tx_t * tx)147 dsl_bookmark_create_check(void *arg, dmu_tx_t *tx)
148 {
149 	dsl_bookmark_create_arg_t *dbca = arg;
150 	dsl_pool_t *dp = dmu_tx_pool(tx);
151 	int rv = 0;
152 
153 	if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS))
154 		return (SET_ERROR(ENOTSUP));
155 
156 	for (nvpair_t *pair = nvlist_next_nvpair(dbca->dbca_bmarks, NULL);
157 	    pair != NULL; pair = nvlist_next_nvpair(dbca->dbca_bmarks, pair)) {
158 		dsl_dataset_t *snapds;
159 		int error;
160 
161 		/* note: validity of nvlist checked by ioctl layer */
162 		error = dsl_dataset_hold(dp, fnvpair_value_string(pair),
163 		    FTAG, &snapds);
164 		if (error == 0) {
165 			error = dsl_bookmark_create_check_impl(snapds,
166 			    nvpair_name(pair), tx);
167 			dsl_dataset_rele(snapds, FTAG);
168 		}
169 		if (error != 0) {
170 			fnvlist_add_int32(dbca->dbca_errors,
171 			    nvpair_name(pair), error);
172 			rv = error;
173 		}
174 	}
175 
176 	return (rv);
177 }
178 
179 static void
dsl_bookmark_create_sync(void * arg,dmu_tx_t * tx)180 dsl_bookmark_create_sync(void *arg, dmu_tx_t *tx)
181 {
182 	dsl_bookmark_create_arg_t *dbca = arg;
183 	dsl_pool_t *dp = dmu_tx_pool(tx);
184 	objset_t *mos = dp->dp_meta_objset;
185 
186 	ASSERT(spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS));
187 
188 	for (nvpair_t *pair = nvlist_next_nvpair(dbca->dbca_bmarks, NULL);
189 	    pair != NULL; pair = nvlist_next_nvpair(dbca->dbca_bmarks, pair)) {
190 		dsl_dataset_t *snapds, *bmark_fs;
191 		zfs_bookmark_phys_t bmark_phys;
192 		char *shortname;
193 
194 		VERIFY0(dsl_dataset_hold(dp, fnvpair_value_string(pair),
195 		    FTAG, &snapds));
196 		VERIFY0(dsl_bookmark_hold_ds(dp, nvpair_name(pair),
197 		    &bmark_fs, FTAG, &shortname));
198 		if (bmark_fs->ds_bookmarks == 0) {
199 			bmark_fs->ds_bookmarks =
200 			    zap_create_norm(mos, U8_TEXTPREP_TOUPPER,
201 			    DMU_OTN_ZAP_METADATA, DMU_OT_NONE, 0, tx);
202 			spa_feature_incr(dp->dp_spa, SPA_FEATURE_BOOKMARKS, tx);
203 
204 			dsl_dataset_zapify(bmark_fs, tx);
205 			VERIFY0(zap_add(mos, bmark_fs->ds_object,
206 			    DS_FIELD_BOOKMARK_NAMES,
207 			    sizeof (bmark_fs->ds_bookmarks), 1,
208 			    &bmark_fs->ds_bookmarks, tx));
209 		}
210 
211 		bmark_phys.zbm_guid = dsl_dataset_phys(snapds)->ds_guid;
212 		bmark_phys.zbm_creation_txg =
213 		    dsl_dataset_phys(snapds)->ds_creation_txg;
214 		bmark_phys.zbm_creation_time =
215 		    dsl_dataset_phys(snapds)->ds_creation_time;
216 
217 		VERIFY0(zap_add(mos, bmark_fs->ds_bookmarks,
218 		    shortname, sizeof (uint64_t),
219 		    sizeof (zfs_bookmark_phys_t) / sizeof (uint64_t),
220 		    &bmark_phys, tx));
221 
222 		spa_history_log_internal_ds(bmark_fs, "bookmark", tx,
223 		    "name=%s creation_txg=%llu target_snap=%llu",
224 		    shortname,
225 		    (longlong_t)bmark_phys.zbm_creation_txg,
226 		    (longlong_t)snapds->ds_object);
227 
228 		dsl_dataset_rele(bmark_fs, FTAG);
229 		dsl_dataset_rele(snapds, FTAG);
230 	}
231 }
232 
233 /*
234  * The bookmarks must all be in the same pool.
235  */
236 int
dsl_bookmark_create(nvlist_t * bmarks,nvlist_t * errors)237 dsl_bookmark_create(nvlist_t *bmarks, nvlist_t *errors)
238 {
239 	nvpair_t *pair;
240 	dsl_bookmark_create_arg_t dbca;
241 
242 	pair = nvlist_next_nvpair(bmarks, NULL);
243 	if (pair == NULL)
244 		return (0);
245 
246 	dbca.dbca_bmarks = bmarks;
247 	dbca.dbca_errors = errors;
248 
249 	return (dsl_sync_task(nvpair_name(pair), dsl_bookmark_create_check,
250 	    dsl_bookmark_create_sync, &dbca,
251 	    fnvlist_num_pairs(bmarks), ZFS_SPACE_CHECK_NORMAL));
252 }
253 
254 int
dsl_get_bookmarks_impl(dsl_dataset_t * ds,nvlist_t * props,nvlist_t * outnvl)255 dsl_get_bookmarks_impl(dsl_dataset_t *ds, nvlist_t *props, nvlist_t *outnvl)
256 {
257 	int err = 0;
258 	zap_cursor_t zc;
259 	zap_attribute_t attr;
260 	dsl_pool_t *dp = ds->ds_dir->dd_pool;
261 
262 	uint64_t bmark_zapobj = ds->ds_bookmarks;
263 	if (bmark_zapobj == 0)
264 		return (0);
265 
266 	for (zap_cursor_init(&zc, dp->dp_meta_objset, bmark_zapobj);
267 	    zap_cursor_retrieve(&zc, &attr) == 0;
268 	    zap_cursor_advance(&zc)) {
269 		char *bmark_name = attr.za_name;
270 		zfs_bookmark_phys_t bmark_phys;
271 
272 		err = dsl_dataset_bmark_lookup(ds, bmark_name, &bmark_phys);
273 		ASSERT3U(err, !=, ENOENT);
274 		if (err != 0)
275 			break;
276 
277 		nvlist_t *out_props = fnvlist_alloc();
278 		if (nvlist_exists(props,
279 		    zfs_prop_to_name(ZFS_PROP_GUID))) {
280 			dsl_prop_nvlist_add_uint64(out_props,
281 			    ZFS_PROP_GUID, bmark_phys.zbm_guid);
282 		}
283 		if (nvlist_exists(props,
284 		    zfs_prop_to_name(ZFS_PROP_CREATETXG))) {
285 			dsl_prop_nvlist_add_uint64(out_props,
286 			    ZFS_PROP_CREATETXG, bmark_phys.zbm_creation_txg);
287 		}
288 		if (nvlist_exists(props,
289 		    zfs_prop_to_name(ZFS_PROP_CREATION))) {
290 			dsl_prop_nvlist_add_uint64(out_props,
291 			    ZFS_PROP_CREATION, bmark_phys.zbm_creation_time);
292 		}
293 
294 		fnvlist_add_nvlist(outnvl, bmark_name, out_props);
295 		fnvlist_free(out_props);
296 	}
297 	zap_cursor_fini(&zc);
298 	return (err);
299 }
300 
301 /*
302  * Retrieve the bookmarks that exist in the specified dataset, and the
303  * requested properties of each bookmark.
304  *
305  * The "props" nvlist specifies which properties are requested.
306  * See lzc_get_bookmarks() for the list of valid properties.
307  */
308 int
dsl_get_bookmarks(const char * dsname,nvlist_t * props,nvlist_t * outnvl)309 dsl_get_bookmarks(const char *dsname, nvlist_t *props, nvlist_t *outnvl)
310 {
311 	dsl_pool_t *dp;
312 	dsl_dataset_t *ds;
313 	int err;
314 
315 	err = dsl_pool_hold(dsname, FTAG, &dp);
316 	if (err != 0)
317 		return (err);
318 	err = dsl_dataset_hold(dp, dsname, FTAG, &ds);
319 	if (err != 0) {
320 		dsl_pool_rele(dp, FTAG);
321 		return (err);
322 	}
323 
324 	err = dsl_get_bookmarks_impl(ds, props, outnvl);
325 
326 	dsl_dataset_rele(ds, FTAG);
327 	dsl_pool_rele(dp, FTAG);
328 	return (err);
329 }
330 
331 typedef struct dsl_bookmark_destroy_arg {
332 	nvlist_t *dbda_bmarks;
333 	nvlist_t *dbda_success;
334 	nvlist_t *dbda_errors;
335 } dsl_bookmark_destroy_arg_t;
336 
337 static int
dsl_dataset_bookmark_remove(dsl_dataset_t * ds,const char * name,dmu_tx_t * tx)338 dsl_dataset_bookmark_remove(dsl_dataset_t *ds, const char *name, dmu_tx_t *tx)
339 {
340 	objset_t *mos = ds->ds_dir->dd_pool->dp_meta_objset;
341 	uint64_t bmark_zapobj = ds->ds_bookmarks;
342 	matchtype_t mt = 0;
343 
344 	if (dsl_dataset_phys(ds)->ds_flags & DS_FLAG_CI_DATASET)
345 		mt = MT_NORMALIZE;
346 
347 	return (zap_remove_norm(mos, bmark_zapobj, name, mt, tx));
348 }
349 
350 static int
dsl_bookmark_destroy_check(void * arg,dmu_tx_t * tx)351 dsl_bookmark_destroy_check(void *arg, dmu_tx_t *tx)
352 {
353 	dsl_bookmark_destroy_arg_t *dbda = arg;
354 	dsl_pool_t *dp = dmu_tx_pool(tx);
355 	int rv = 0;
356 
357 	ASSERT(nvlist_empty(dbda->dbda_success));
358 	ASSERT(nvlist_empty(dbda->dbda_errors));
359 
360 	if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS))
361 		return (0);
362 
363 	for (nvpair_t *pair = nvlist_next_nvpair(dbda->dbda_bmarks, NULL);
364 	    pair != NULL; pair = nvlist_next_nvpair(dbda->dbda_bmarks, pair)) {
365 		const char *fullname = nvpair_name(pair);
366 		dsl_dataset_t *ds;
367 		zfs_bookmark_phys_t bm;
368 		int error;
369 		char *shortname;
370 
371 		error = dsl_bookmark_hold_ds(dp, fullname, &ds,
372 		    FTAG, &shortname);
373 		if (error == ENOENT) {
374 			/* ignore it; the bookmark is "already destroyed" */
375 			continue;
376 		}
377 		if (error == 0) {
378 			error = dsl_dataset_bmark_lookup(ds, shortname, &bm);
379 			dsl_dataset_rele(ds, FTAG);
380 			if (error == ESRCH) {
381 				/*
382 				 * ignore it; the bookmark is
383 				 * "already destroyed"
384 				 */
385 				continue;
386 			}
387 		}
388 		if (error == 0) {
389 			if (dmu_tx_is_syncing(tx)) {
390 				fnvlist_add_boolean(dbda->dbda_success,
391 				    fullname);
392 			}
393 		} else {
394 			fnvlist_add_int32(dbda->dbda_errors, fullname, error);
395 			rv = error;
396 		}
397 	}
398 	return (rv);
399 }
400 
401 static void
dsl_bookmark_destroy_sync(void * arg,dmu_tx_t * tx)402 dsl_bookmark_destroy_sync(void *arg, dmu_tx_t *tx)
403 {
404 	dsl_bookmark_destroy_arg_t *dbda = arg;
405 	dsl_pool_t *dp = dmu_tx_pool(tx);
406 	objset_t *mos = dp->dp_meta_objset;
407 
408 	for (nvpair_t *pair = nvlist_next_nvpair(dbda->dbda_success, NULL);
409 	    pair != NULL; pair = nvlist_next_nvpair(dbda->dbda_success, pair)) {
410 		dsl_dataset_t *ds;
411 		char *shortname;
412 		uint64_t zap_cnt;
413 
414 		VERIFY0(dsl_bookmark_hold_ds(dp, nvpair_name(pair),
415 		    &ds, FTAG, &shortname));
416 		VERIFY0(dsl_dataset_bookmark_remove(ds, shortname, tx));
417 
418 		/*
419 		 * If all of this dataset's bookmarks have been destroyed,
420 		 * free the zap object and decrement the feature's use count.
421 		 */
422 		VERIFY0(zap_count(mos, ds->ds_bookmarks,
423 		    &zap_cnt));
424 		if (zap_cnt == 0) {
425 			dmu_buf_will_dirty(ds->ds_dbuf, tx);
426 			VERIFY0(zap_destroy(mos, ds->ds_bookmarks, tx));
427 			ds->ds_bookmarks = 0;
428 			spa_feature_decr(dp->dp_spa, SPA_FEATURE_BOOKMARKS, tx);
429 			VERIFY0(zap_remove(mos, ds->ds_object,
430 			    DS_FIELD_BOOKMARK_NAMES, tx));
431 		}
432 
433 		spa_history_log_internal_ds(ds, "remove bookmark", tx,
434 		    "name=%s", shortname);
435 
436 		dsl_dataset_rele(ds, FTAG);
437 	}
438 }
439 
440 /*
441  * The bookmarks must all be in the same pool.
442  */
443 int
dsl_bookmark_destroy(nvlist_t * bmarks,nvlist_t * errors)444 dsl_bookmark_destroy(nvlist_t *bmarks, nvlist_t *errors)
445 {
446 	int rv;
447 	dsl_bookmark_destroy_arg_t dbda;
448 	nvpair_t *pair = nvlist_next_nvpair(bmarks, NULL);
449 	if (pair == NULL)
450 		return (0);
451 
452 	dbda.dbda_bmarks = bmarks;
453 	dbda.dbda_errors = errors;
454 	dbda.dbda_success = fnvlist_alloc();
455 
456 	rv = dsl_sync_task(nvpair_name(pair), dsl_bookmark_destroy_check,
457 	    dsl_bookmark_destroy_sync, &dbda, fnvlist_num_pairs(bmarks),
458 	    ZFS_SPACE_CHECK_RESERVED);
459 	fnvlist_free(dbda.dbda_success);
460 	return (rv);
461 }
462 
463 typedef struct dsl_bookmark_rename_arg {
464 	const char *dbra_fsname;
465 	const char *dbra_oldname;
466 	const char *dbra_newname;
467 } dsl_bookmark_rename_arg_t;
468 
469 static int
dsl_bookmark_rename_check(void * arg,dmu_tx_t * tx)470 dsl_bookmark_rename_check(void *arg, dmu_tx_t *tx)
471 {
472 	dsl_bookmark_rename_arg_t *dbra = arg;
473 	dsl_pool_t *dp = dmu_tx_pool(tx);
474 	dsl_dataset_t *ds;
475 	zfs_bookmark_phys_t bmark_phys;
476 	int error;
477 
478 	if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS))
479 		return (SET_ERROR(ENOTSUP));
480 
481 	/* Check validity and the full length of the new bookmark name. */
482 	if (zfs_component_namecheck(dbra->dbra_newname, NULL, NULL))
483 		return (SET_ERROR(EINVAL));
484 	if (strlen(dbra->dbra_fsname) + strlen(dbra->dbra_newname) + 1 >=
485 	    ZFS_MAX_DATASET_NAME_LEN)
486 		return (SET_ERROR(ENAMETOOLONG));
487 
488 	error = dsl_dataset_hold(dp, dbra->dbra_fsname, FTAG, &ds);
489 	if (error != 0)
490 		return (error);
491 	if (ds->ds_is_snapshot) {
492 		dsl_dataset_rele(ds, FTAG);
493 		return (SET_ERROR(EINVAL));
494 	}
495 	error = dsl_dataset_bmark_lookup(ds, dbra->dbra_oldname, &bmark_phys);
496 	if (error != 0) {
497 		dsl_dataset_rele(ds, FTAG);
498 		return (error);
499 	}
500 
501 	error = dsl_dataset_bmark_lookup(ds, dbra->dbra_newname, &bmark_phys);
502 	dsl_dataset_rele(ds, FTAG);
503 	if (error == 0)
504 		return (SET_ERROR(EEXIST));
505 	if (error != ESRCH)
506 		return (error);
507 	return (0);
508 }
509 
510 static void
dsl_bookmark_rename_sync(void * arg,dmu_tx_t * tx)511 dsl_bookmark_rename_sync(void *arg, dmu_tx_t *tx)
512 {
513 	zfs_bookmark_phys_t bmark_phys;
514 	dsl_bookmark_rename_arg_t *dbra = arg;
515 	dsl_pool_t *dp = dmu_tx_pool(tx);
516 	objset_t *mos;
517 	dsl_dataset_t *ds;
518 	uint64_t bmark_zapobj;
519 	uint64_t int_size, num_ints;
520 	matchtype_t mt = 0;
521 	int error;
522 
523 	ASSERT(spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS));
524 	VERIFY0(dsl_dataset_hold(dp, dbra->dbra_fsname, FTAG, &ds));
525 
526 	mos = ds->ds_dir->dd_pool->dp_meta_objset;
527 	bmark_zapobj = ds->ds_bookmarks;
528 
529 	if (dsl_dataset_phys(ds)->ds_flags & DS_FLAG_CI_DATASET)
530 		mt = MT_NORMALIZE;
531 
532 	VERIFY0(zap_length(mos, bmark_zapobj, dbra->dbra_oldname,
533 	    &int_size, &num_ints));
534 	ASSERT3U(int_size, ==, sizeof (uint64_t));
535 	VERIFY0(zap_lookup_norm(mos, bmark_zapobj, dbra->dbra_oldname, int_size,
536 	    num_ints, &bmark_phys, mt, NULL, 0, NULL));
537 	VERIFY0(zap_remove_norm(mos, bmark_zapobj, dbra->dbra_oldname, mt, tx));
538 
539 	VERIFY0(zap_add(mos, bmark_zapobj, dbra->dbra_newname, int_size,
540 	    num_ints, &bmark_phys, tx));
541 
542 	spa_history_log_internal_ds(ds, "rename bookmark", tx,
543 	    "#%s -> #%s creation_txg=%llu",
544 	    dbra->dbra_oldname, dbra->dbra_newname,
545 	    (longlong_t)bmark_phys.zbm_creation_txg);
546 
547 	dsl_dataset_rele(ds, FTAG);
548 }
549 
550 /*
551  * The bookmarks must all be in the same pool.
552  */
553 int
dsl_bookmark_rename(const char * fsname,const char * oldbmark,const char * newbmark)554 dsl_bookmark_rename(const char *fsname, const char *oldbmark,
555     const char *newbmark)
556 {
557 	dsl_bookmark_rename_arg_t dbra;
558 
559 	dbra.dbra_fsname = fsname;
560 	dbra.dbra_oldname = oldbmark;
561 	dbra.dbra_newname = newbmark;
562 
563 	return (dsl_sync_task(fsname, dsl_bookmark_rename_check,
564 	    dsl_bookmark_rename_sync, &dbra, 1, ZFS_SPACE_CHECK_NORMAL));
565 }
566 
567