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 	/*
74 	 * Zero out the bookmark in case the one stored on disk
75 	 * is in an older, shorter format.
76 	 */
77 	bzero(bmark_phys, sizeof (*bmark_phys));
78 
79 	err = zap_lookup_norm(mos, bmark_zapobj, shortname, sizeof (uint64_t),
80 	    sizeof (*bmark_phys) / sizeof (uint64_t), bmark_phys, mt,
81 	    NULL, 0, NULL);
82 
83 	return (err == ENOENT ? ESRCH : err);
84 }
85 
86 /*
87  * If later_ds is non-NULL, this will return EXDEV if the the specified bookmark
88  * does not represents an earlier point in later_ds's timeline.
89  *
90  * Returns ENOENT if the dataset containing the bookmark does not exist.
91  * Returns ESRCH if the dataset exists but the bookmark was not found in it.
92  */
93 int
dsl_bookmark_lookup(dsl_pool_t * dp,const char * fullname,dsl_dataset_t * later_ds,zfs_bookmark_phys_t * bmp)94 dsl_bookmark_lookup(dsl_pool_t *dp, const char *fullname,
95     dsl_dataset_t *later_ds, zfs_bookmark_phys_t *bmp)
96 {
97 	char *shortname;
98 	dsl_dataset_t *ds;
99 	int error;
100 
101 	error = dsl_bookmark_hold_ds(dp, fullname, &ds, FTAG, &shortname);
102 	if (error != 0)
103 		return (error);
104 
105 	error = dsl_dataset_bmark_lookup(ds, shortname, bmp);
106 	if (error == 0 && later_ds != NULL) {
107 		if (!dsl_dataset_is_before(later_ds, ds, bmp->zbm_creation_txg))
108 			error = SET_ERROR(EXDEV);
109 	}
110 	dsl_dataset_rele(ds, FTAG);
111 	return (error);
112 }
113 
114 typedef struct dsl_bookmark_create_arg {
115 	nvlist_t *dbca_bmarks;
116 	nvlist_t *dbca_errors;
117 } dsl_bookmark_create_arg_t;
118 
119 static int
dsl_bookmark_create_check_impl(dsl_dataset_t * snapds,const char * bookmark_name,dmu_tx_t * tx)120 dsl_bookmark_create_check_impl(dsl_dataset_t *snapds, const char *bookmark_name,
121     dmu_tx_t *tx)
122 {
123 	dsl_pool_t *dp = dmu_tx_pool(tx);
124 	dsl_dataset_t *bmark_fs;
125 	char *shortname;
126 	int error;
127 	zfs_bookmark_phys_t bmark_phys;
128 
129 	if (!snapds->ds_is_snapshot)
130 		return (SET_ERROR(EINVAL));
131 
132 	error = dsl_bookmark_hold_ds(dp, bookmark_name,
133 	    &bmark_fs, FTAG, &shortname);
134 	if (error != 0)
135 		return (error);
136 
137 	if (!dsl_dataset_is_before(bmark_fs, snapds, 0)) {
138 		dsl_dataset_rele(bmark_fs, FTAG);
139 		return (SET_ERROR(EINVAL));
140 	}
141 
142 	error = dsl_dataset_bmark_lookup(bmark_fs, shortname,
143 	    &bmark_phys);
144 	dsl_dataset_rele(bmark_fs, FTAG);
145 	if (error == 0)
146 		return (SET_ERROR(EEXIST));
147 	if (error == ESRCH)
148 		return (0);
149 	return (error);
150 }
151 
152 static int
dsl_bookmark_create_check(void * arg,dmu_tx_t * tx)153 dsl_bookmark_create_check(void *arg, dmu_tx_t *tx)
154 {
155 	dsl_bookmark_create_arg_t *dbca = arg;
156 	dsl_pool_t *dp = dmu_tx_pool(tx);
157 	int rv = 0;
158 
159 	if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS))
160 		return (SET_ERROR(ENOTSUP));
161 
162 	for (nvpair_t *pair = nvlist_next_nvpair(dbca->dbca_bmarks, NULL);
163 	    pair != NULL; pair = nvlist_next_nvpair(dbca->dbca_bmarks, pair)) {
164 		dsl_dataset_t *snapds;
165 		int error;
166 
167 		/* note: validity of nvlist checked by ioctl layer */
168 		error = dsl_dataset_hold(dp, fnvpair_value_string(pair),
169 		    FTAG, &snapds);
170 		if (error == 0) {
171 			error = dsl_bookmark_create_check_impl(snapds,
172 			    nvpair_name(pair), tx);
173 			dsl_dataset_rele(snapds, FTAG);
174 		}
175 		if (error != 0) {
176 			fnvlist_add_int32(dbca->dbca_errors,
177 			    nvpair_name(pair), error);
178 			rv = error;
179 		}
180 	}
181 
182 	return (rv);
183 }
184 
185 static void
dsl_bookmark_create_sync(void * arg,dmu_tx_t * tx)186 dsl_bookmark_create_sync(void *arg, dmu_tx_t *tx)
187 {
188 	dsl_bookmark_create_arg_t *dbca = arg;
189 	dsl_pool_t *dp = dmu_tx_pool(tx);
190 	objset_t *mos = dp->dp_meta_objset;
191 
192 	ASSERT(spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS));
193 
194 	for (nvpair_t *pair = nvlist_next_nvpair(dbca->dbca_bmarks, NULL);
195 	    pair != NULL; pair = nvlist_next_nvpair(dbca->dbca_bmarks, pair)) {
196 		dsl_dataset_t *snapds, *bmark_fs;
197 		zfs_bookmark_phys_t bmark_phys = { 0 };
198 		char *shortname;
199 		uint32_t bmark_len = BOOKMARK_PHYS_SIZE_V1;
200 
201 		VERIFY0(dsl_dataset_hold(dp, fnvpair_value_string(pair),
202 		    FTAG, &snapds));
203 		VERIFY0(dsl_bookmark_hold_ds(dp, nvpair_name(pair),
204 		    &bmark_fs, FTAG, &shortname));
205 		if (bmark_fs->ds_bookmarks == 0) {
206 			bmark_fs->ds_bookmarks =
207 			    zap_create_norm(mos, U8_TEXTPREP_TOUPPER,
208 			    DMU_OTN_ZAP_METADATA, DMU_OT_NONE, 0, tx);
209 			spa_feature_incr(dp->dp_spa, SPA_FEATURE_BOOKMARKS, tx);
210 
211 			dsl_dataset_zapify(bmark_fs, tx);
212 			VERIFY0(zap_add(mos, bmark_fs->ds_object,
213 			    DS_FIELD_BOOKMARK_NAMES,
214 			    sizeof (bmark_fs->ds_bookmarks), 1,
215 			    &bmark_fs->ds_bookmarks, tx));
216 		}
217 
218 		bmark_phys.zbm_guid = dsl_dataset_phys(snapds)->ds_guid;
219 		bmark_phys.zbm_creation_txg =
220 		    dsl_dataset_phys(snapds)->ds_creation_txg;
221 		bmark_phys.zbm_creation_time =
222 		    dsl_dataset_phys(snapds)->ds_creation_time;
223 
224 		/*
225 		 * If the dataset is encrypted create a larger bookmark to
226 		 * accommodate the IVset guid. The IVset guid was added
227 		 * after the encryption feature to prevent a problem with
228 		 * raw sends. If we encounter an encrypted dataset without
229 		 * an IVset guid we fall back to a normal bookmark.
230 		 */
231 		if (snapds->ds_dir->dd_crypto_obj != 0 &&
232 		    spa_feature_is_enabled(dp->dp_spa,
233 		    SPA_FEATURE_BOOKMARK_V2)) {
234 			int err = zap_lookup(mos, snapds->ds_object,
235 			    DS_FIELD_IVSET_GUID, sizeof (uint64_t), 1,
236 			    &bmark_phys.zbm_ivset_guid);
237 			if (err == 0) {
238 				bmark_len = BOOKMARK_PHYS_SIZE_V2;
239 				spa_feature_incr(dp->dp_spa,
240 				    SPA_FEATURE_BOOKMARK_V2, tx);
241 			}
242 		}
243 
244 		VERIFY0(zap_add(mos, bmark_fs->ds_bookmarks,
245 		    shortname, sizeof (uint64_t),
246 		    bmark_len / sizeof (uint64_t), &bmark_phys, tx));
247 
248 		spa_history_log_internal_ds(bmark_fs, "bookmark", tx,
249 		    "name=%s creation_txg=%llu target_snap=%llu",
250 		    shortname,
251 		    (longlong_t)bmark_phys.zbm_creation_txg,
252 		    (longlong_t)snapds->ds_object);
253 
254 		dsl_dataset_rele(bmark_fs, FTAG);
255 		dsl_dataset_rele(snapds, FTAG);
256 	}
257 }
258 
259 /*
260  * The bookmarks must all be in the same pool.
261  */
262 int
dsl_bookmark_create(nvlist_t * bmarks,nvlist_t * errors)263 dsl_bookmark_create(nvlist_t *bmarks, nvlist_t *errors)
264 {
265 	nvpair_t *pair;
266 	dsl_bookmark_create_arg_t dbca;
267 
268 	pair = nvlist_next_nvpair(bmarks, NULL);
269 	if (pair == NULL)
270 		return (0);
271 
272 	dbca.dbca_bmarks = bmarks;
273 	dbca.dbca_errors = errors;
274 
275 	return (dsl_sync_task(nvpair_name(pair), dsl_bookmark_create_check,
276 	    dsl_bookmark_create_sync, &dbca,
277 	    fnvlist_num_pairs(bmarks), ZFS_SPACE_CHECK_NORMAL));
278 }
279 
280 int
dsl_get_bookmarks_impl(dsl_dataset_t * ds,nvlist_t * props,nvlist_t * outnvl)281 dsl_get_bookmarks_impl(dsl_dataset_t *ds, nvlist_t *props, nvlist_t *outnvl)
282 {
283 	int err = 0;
284 	zap_cursor_t zc;
285 	zap_attribute_t attr;
286 	dsl_pool_t *dp = ds->ds_dir->dd_pool;
287 
288 	uint64_t bmark_zapobj = ds->ds_bookmarks;
289 	if (bmark_zapobj == 0)
290 		return (0);
291 
292 	for (zap_cursor_init(&zc, dp->dp_meta_objset, bmark_zapobj);
293 	    zap_cursor_retrieve(&zc, &attr) == 0;
294 	    zap_cursor_advance(&zc)) {
295 		char *bmark_name = attr.za_name;
296 		zfs_bookmark_phys_t bmark_phys = { 0 };
297 
298 		err = dsl_dataset_bmark_lookup(ds, bmark_name, &bmark_phys);
299 		ASSERT3U(err, !=, ENOENT);
300 		if (err != 0)
301 			break;
302 
303 		nvlist_t *out_props = fnvlist_alloc();
304 		if (nvlist_exists(props,
305 		    zfs_prop_to_name(ZFS_PROP_GUID))) {
306 			dsl_prop_nvlist_add_uint64(out_props,
307 			    ZFS_PROP_GUID, bmark_phys.zbm_guid);
308 		}
309 		if (nvlist_exists(props,
310 		    zfs_prop_to_name(ZFS_PROP_CREATETXG))) {
311 			dsl_prop_nvlist_add_uint64(out_props,
312 			    ZFS_PROP_CREATETXG, bmark_phys.zbm_creation_txg);
313 		}
314 		if (nvlist_exists(props,
315 		    zfs_prop_to_name(ZFS_PROP_CREATION))) {
316 			dsl_prop_nvlist_add_uint64(out_props,
317 			    ZFS_PROP_CREATION, bmark_phys.zbm_creation_time);
318 		}
319 		if (nvlist_exists(props,
320 		    zfs_prop_to_name(ZFS_PROP_IVSET_GUID))) {
321 			dsl_prop_nvlist_add_uint64(out_props,
322 			    ZFS_PROP_IVSET_GUID, bmark_phys.zbm_ivset_guid);
323 		}
324 
325 		fnvlist_add_nvlist(outnvl, bmark_name, out_props);
326 		fnvlist_free(out_props);
327 	}
328 	zap_cursor_fini(&zc);
329 	return (err);
330 }
331 
332 /*
333  * Retrieve the bookmarks that exist in the specified dataset, and the
334  * requested properties of each bookmark.
335  *
336  * The "props" nvlist specifies which properties are requested.
337  * See lzc_get_bookmarks() for the list of valid properties.
338  */
339 int
dsl_get_bookmarks(const char * dsname,nvlist_t * props,nvlist_t * outnvl)340 dsl_get_bookmarks(const char *dsname, nvlist_t *props, nvlist_t *outnvl)
341 {
342 	dsl_pool_t *dp;
343 	dsl_dataset_t *ds;
344 	int err;
345 
346 	err = dsl_pool_hold(dsname, FTAG, &dp);
347 	if (err != 0)
348 		return (err);
349 	err = dsl_dataset_hold(dp, dsname, FTAG, &ds);
350 	if (err != 0) {
351 		dsl_pool_rele(dp, FTAG);
352 		return (err);
353 	}
354 
355 	err = dsl_get_bookmarks_impl(ds, props, outnvl);
356 
357 	dsl_dataset_rele(ds, FTAG);
358 	dsl_pool_rele(dp, FTAG);
359 	return (err);
360 }
361 
362 typedef struct dsl_bookmark_destroy_arg {
363 	nvlist_t *dbda_bmarks;
364 	nvlist_t *dbda_success;
365 	nvlist_t *dbda_errors;
366 } dsl_bookmark_destroy_arg_t;
367 
368 static int
dsl_dataset_bookmark_remove(dsl_dataset_t * ds,const char * name,dmu_tx_t * tx)369 dsl_dataset_bookmark_remove(dsl_dataset_t *ds, const char *name, dmu_tx_t *tx)
370 {
371 	int err;
372 	objset_t *mos = ds->ds_dir->dd_pool->dp_meta_objset;
373 	uint64_t bmark_zapobj = ds->ds_bookmarks;
374 	matchtype_t mt = 0;
375 	uint64_t int_size, num_ints;
376 
377 	if (dsl_dataset_phys(ds)->ds_flags & DS_FLAG_CI_DATASET)
378 		mt = MT_NORMALIZE;
379 
380 	err = zap_length(mos, bmark_zapobj, name, &int_size, &num_ints);
381 	if (err != 0)
382 		return (err);
383 
384 	ASSERT3U(int_size, ==, sizeof (uint64_t));
385 
386 	if (num_ints * int_size > BOOKMARK_PHYS_SIZE_V1) {
387 		spa_feature_decr(dmu_objset_spa(mos),
388 		    SPA_FEATURE_BOOKMARK_V2, tx);
389 	}
390 
391 	return (zap_remove_norm(mos, bmark_zapobj, name, mt, tx));
392 }
393 
394 static int
dsl_bookmark_destroy_check(void * arg,dmu_tx_t * tx)395 dsl_bookmark_destroy_check(void *arg, dmu_tx_t *tx)
396 {
397 	dsl_bookmark_destroy_arg_t *dbda = arg;
398 	dsl_pool_t *dp = dmu_tx_pool(tx);
399 	int rv = 0;
400 
401 	ASSERT(nvlist_empty(dbda->dbda_success));
402 	ASSERT(nvlist_empty(dbda->dbda_errors));
403 
404 	if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS))
405 		return (0);
406 
407 	for (nvpair_t *pair = nvlist_next_nvpair(dbda->dbda_bmarks, NULL);
408 	    pair != NULL; pair = nvlist_next_nvpair(dbda->dbda_bmarks, pair)) {
409 		const char *fullname = nvpair_name(pair);
410 		dsl_dataset_t *ds;
411 		zfs_bookmark_phys_t bm;
412 		int error;
413 		char *shortname;
414 
415 		error = dsl_bookmark_hold_ds(dp, fullname, &ds,
416 		    FTAG, &shortname);
417 		if (error == ENOENT) {
418 			/* ignore it; the bookmark is "already destroyed" */
419 			continue;
420 		}
421 		if (error == 0) {
422 			error = dsl_dataset_bmark_lookup(ds, shortname, &bm);
423 			dsl_dataset_rele(ds, FTAG);
424 			if (error == ESRCH) {
425 				/*
426 				 * ignore it; the bookmark is
427 				 * "already destroyed"
428 				 */
429 				continue;
430 			}
431 		}
432 		if (error == 0) {
433 			if (dmu_tx_is_syncing(tx)) {
434 				fnvlist_add_boolean(dbda->dbda_success,
435 				    fullname);
436 			}
437 		} else {
438 			fnvlist_add_int32(dbda->dbda_errors, fullname, error);
439 			rv = error;
440 		}
441 	}
442 	return (rv);
443 }
444 
445 static void
dsl_bookmark_destroy_sync(void * arg,dmu_tx_t * tx)446 dsl_bookmark_destroy_sync(void *arg, dmu_tx_t *tx)
447 {
448 	dsl_bookmark_destroy_arg_t *dbda = arg;
449 	dsl_pool_t *dp = dmu_tx_pool(tx);
450 	objset_t *mos = dp->dp_meta_objset;
451 
452 	for (nvpair_t *pair = nvlist_next_nvpair(dbda->dbda_success, NULL);
453 	    pair != NULL; pair = nvlist_next_nvpair(dbda->dbda_success, pair)) {
454 		dsl_dataset_t *ds;
455 		char *shortname;
456 		uint64_t zap_cnt;
457 
458 		VERIFY0(dsl_bookmark_hold_ds(dp, nvpair_name(pair),
459 		    &ds, FTAG, &shortname));
460 		VERIFY0(dsl_dataset_bookmark_remove(ds, shortname, tx));
461 
462 		/*
463 		 * If all of this dataset's bookmarks have been destroyed,
464 		 * free the zap object and decrement the feature's use count.
465 		 */
466 		VERIFY0(zap_count(mos, ds->ds_bookmarks,
467 		    &zap_cnt));
468 		if (zap_cnt == 0) {
469 			dmu_buf_will_dirty(ds->ds_dbuf, tx);
470 			VERIFY0(zap_destroy(mos, ds->ds_bookmarks, tx));
471 			ds->ds_bookmarks = 0;
472 			spa_feature_decr(dp->dp_spa, SPA_FEATURE_BOOKMARKS, tx);
473 			VERIFY0(zap_remove(mos, ds->ds_object,
474 			    DS_FIELD_BOOKMARK_NAMES, tx));
475 		}
476 
477 		spa_history_log_internal_ds(ds, "remove bookmark", tx,
478 		    "name=%s", shortname);
479 
480 		dsl_dataset_rele(ds, FTAG);
481 	}
482 }
483 
484 /*
485  * The bookmarks must all be in the same pool.
486  */
487 int
dsl_bookmark_destroy(nvlist_t * bmarks,nvlist_t * errors)488 dsl_bookmark_destroy(nvlist_t *bmarks, nvlist_t *errors)
489 {
490 	int rv;
491 	dsl_bookmark_destroy_arg_t dbda;
492 	nvpair_t *pair = nvlist_next_nvpair(bmarks, NULL);
493 	if (pair == NULL)
494 		return (0);
495 
496 	dbda.dbda_bmarks = bmarks;
497 	dbda.dbda_errors = errors;
498 	dbda.dbda_success = fnvlist_alloc();
499 
500 	rv = dsl_sync_task(nvpair_name(pair), dsl_bookmark_destroy_check,
501 	    dsl_bookmark_destroy_sync, &dbda, fnvlist_num_pairs(bmarks),
502 	    ZFS_SPACE_CHECK_RESERVED);
503 	fnvlist_free(dbda.dbda_success);
504 	return (rv);
505 }
506