dsl_bookmark.c revision 269006
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 * Copyright (c) 2013, 2014 by Delphix. All rights reserved.
17 */
18
19#include <sys/zfs_context.h>
20#include <sys/dsl_dataset.h>
21#include <sys/dsl_dir.h>
22#include <sys/dsl_prop.h>
23#include <sys/dsl_synctask.h>
24#include <sys/dmu_impl.h>
25#include <sys/dmu_tx.h>
26#include <sys/arc.h>
27#include <sys/zap.h>
28#include <sys/zfeature.h>
29#include <sys/spa.h>
30#include <sys/dsl_bookmark.h>
31#include <zfs_namecheck.h>
32
33static int
34dsl_bookmark_hold_ds(dsl_pool_t *dp, const char *fullname,
35    dsl_dataset_t **dsp, void *tag, char **shortnamep)
36{
37	char buf[MAXNAMELEN];
38	char *hashp;
39
40	if (strlen(fullname) >= MAXNAMELEN)
41		return (SET_ERROR(ENAMETOOLONG));
42	hashp = strchr(fullname, '#');
43	if (hashp == NULL)
44		return (SET_ERROR(EINVAL));
45
46	*shortnamep = hashp + 1;
47	if (zfs_component_namecheck(*shortnamep, NULL, NULL))
48		return (SET_ERROR(EINVAL));
49	(void) strlcpy(buf, fullname, hashp - fullname + 1);
50	return (dsl_dataset_hold(dp, buf, tag, dsp));
51}
52
53/*
54 * Returns ESRCH if bookmark is not found.
55 */
56static int
57dsl_dataset_bmark_lookup(dsl_dataset_t *ds, const char *shortname,
58    zfs_bookmark_phys_t *bmark_phys)
59{
60	objset_t *mos = ds->ds_dir->dd_pool->dp_meta_objset;
61	uint64_t bmark_zapobj = ds->ds_bookmarks;
62	matchtype_t mt;
63	int err;
64
65	if (bmark_zapobj == 0)
66		return (SET_ERROR(ESRCH));
67
68	if (ds->ds_phys->ds_flags & DS_FLAG_CI_DATASET)
69		mt = MT_FIRST;
70	else
71		mt = MT_EXACT;
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 */
87int
88dsl_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
108typedef struct dsl_bookmark_create_arg {
109	nvlist_t *dbca_bmarks;
110	nvlist_t *dbca_errors;
111} dsl_bookmark_create_arg_t;
112
113static int
114dsl_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 (!dsl_dataset_is_snapshot(snapds))
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
146static int
147dsl_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
179static void
180dsl_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 = snapds->ds_phys->ds_guid;
212		bmark_phys.zbm_creation_txg = snapds->ds_phys->ds_creation_txg;
213		bmark_phys.zbm_creation_time =
214		    snapds->ds_phys->ds_creation_time;
215
216		VERIFY0(zap_add(mos, bmark_fs->ds_bookmarks,
217		    shortname, sizeof (uint64_t),
218		    sizeof (zfs_bookmark_phys_t) / sizeof (uint64_t),
219		    &bmark_phys, tx));
220
221		spa_history_log_internal_ds(bmark_fs, "bookmark", tx,
222		    "name=%s creation_txg=%llu target_snap=%llu",
223		    shortname,
224		    (longlong_t)bmark_phys.zbm_creation_txg,
225		    (longlong_t)snapds->ds_object);
226
227		dsl_dataset_rele(bmark_fs, FTAG);
228		dsl_dataset_rele(snapds, FTAG);
229	}
230}
231
232/*
233 * The bookmarks must all be in the same pool.
234 */
235int
236dsl_bookmark_create(nvlist_t *bmarks, nvlist_t *errors)
237{
238	nvpair_t *pair;
239	dsl_bookmark_create_arg_t dbca;
240
241	pair = nvlist_next_nvpair(bmarks, NULL);
242	if (pair == NULL)
243		return (0);
244
245	dbca.dbca_bmarks = bmarks;
246	dbca.dbca_errors = errors;
247
248	return (dsl_sync_task(nvpair_name(pair), dsl_bookmark_create_check,
249	    dsl_bookmark_create_sync, &dbca,
250	    fnvlist_num_pairs(bmarks), ZFS_SPACE_CHECK_NORMAL));
251}
252
253int
254dsl_get_bookmarks_impl(dsl_dataset_t *ds, nvlist_t *props, nvlist_t *outnvl)
255{
256	int err = 0;
257	zap_cursor_t zc;
258	zap_attribute_t attr;
259	dsl_pool_t *dp = ds->ds_dir->dd_pool;
260
261	uint64_t bmark_zapobj = ds->ds_bookmarks;
262	if (bmark_zapobj == 0)
263		return (0);
264
265	for (zap_cursor_init(&zc, dp->dp_meta_objset, bmark_zapobj);
266	    zap_cursor_retrieve(&zc, &attr) == 0;
267	    zap_cursor_advance(&zc)) {
268		char *bmark_name = attr.za_name;
269		zfs_bookmark_phys_t bmark_phys;
270
271		err = dsl_dataset_bmark_lookup(ds, bmark_name, &bmark_phys);
272		ASSERT3U(err, !=, ENOENT);
273		if (err != 0)
274			break;
275
276		nvlist_t *out_props = fnvlist_alloc();
277		if (nvlist_exists(props,
278		    zfs_prop_to_name(ZFS_PROP_GUID))) {
279			dsl_prop_nvlist_add_uint64(out_props,
280			    ZFS_PROP_GUID, bmark_phys.zbm_guid);
281		}
282		if (nvlist_exists(props,
283		    zfs_prop_to_name(ZFS_PROP_CREATETXG))) {
284			dsl_prop_nvlist_add_uint64(out_props,
285			    ZFS_PROP_CREATETXG, bmark_phys.zbm_creation_txg);
286		}
287		if (nvlist_exists(props,
288		    zfs_prop_to_name(ZFS_PROP_CREATION))) {
289			dsl_prop_nvlist_add_uint64(out_props,
290			    ZFS_PROP_CREATION, bmark_phys.zbm_creation_time);
291		}
292
293		fnvlist_add_nvlist(outnvl, bmark_name, out_props);
294		fnvlist_free(out_props);
295	}
296	zap_cursor_fini(&zc);
297	return (err);
298}
299
300/*
301 * Retrieve the bookmarks that exist in the specified dataset, and the
302 * requested properties of each bookmark.
303 *
304 * The "props" nvlist specifies which properties are requested.
305 * See lzc_get_bookmarks() for the list of valid properties.
306 */
307int
308dsl_get_bookmarks(const char *dsname, nvlist_t *props, nvlist_t *outnvl)
309{
310	dsl_pool_t *dp;
311	dsl_dataset_t *ds;
312	int err;
313
314	err = dsl_pool_hold(dsname, FTAG, &dp);
315	if (err != 0)
316		return (err);
317	err = dsl_dataset_hold(dp, dsname, FTAG, &ds);
318	if (err != 0) {
319		dsl_pool_rele(dp, FTAG);
320		return (err);
321	}
322
323	err = dsl_get_bookmarks_impl(ds, props, outnvl);
324
325	dsl_dataset_rele(ds, FTAG);
326	dsl_pool_rele(dp, FTAG);
327	return (err);
328}
329
330typedef struct dsl_bookmark_destroy_arg {
331	nvlist_t *dbda_bmarks;
332	nvlist_t *dbda_success;
333	nvlist_t *dbda_errors;
334} dsl_bookmark_destroy_arg_t;
335
336static int
337dsl_dataset_bookmark_remove(dsl_dataset_t *ds, const char *name, dmu_tx_t *tx)
338{
339	objset_t *mos = ds->ds_dir->dd_pool->dp_meta_objset;
340	uint64_t bmark_zapobj = ds->ds_bookmarks;
341	matchtype_t mt;
342
343	if (ds->ds_phys->ds_flags & DS_FLAG_CI_DATASET)
344		mt = MT_FIRST;
345	else
346		mt = MT_EXACT;
347
348	return (zap_remove_norm(mos, bmark_zapobj, name, mt, tx));
349}
350
351static int
352dsl_bookmark_destroy_check(void *arg, dmu_tx_t *tx)
353{
354	dsl_bookmark_destroy_arg_t *dbda = arg;
355	dsl_pool_t *dp = dmu_tx_pool(tx);
356	int rv = 0;
357
358	if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS))
359		return (0);
360
361	for (nvpair_t *pair = nvlist_next_nvpair(dbda->dbda_bmarks, NULL);
362	    pair != NULL; pair = nvlist_next_nvpair(dbda->dbda_bmarks, pair)) {
363		const char *fullname = nvpair_name(pair);
364		dsl_dataset_t *ds;
365		zfs_bookmark_phys_t bm;
366		int error;
367		char *shortname;
368
369		error = dsl_bookmark_hold_ds(dp, fullname, &ds,
370		    FTAG, &shortname);
371		if (error == ENOENT) {
372			/* ignore it; the bookmark is "already destroyed" */
373			continue;
374		}
375		if (error == 0) {
376			error = dsl_dataset_bmark_lookup(ds, shortname, &bm);
377			dsl_dataset_rele(ds, FTAG);
378			if (error == ESRCH) {
379				/*
380				 * ignore it; the bookmark is
381				 * "already destroyed"
382				 */
383				continue;
384			}
385		}
386		if (error == 0) {
387			fnvlist_add_boolean(dbda->dbda_success, fullname);
388		} else {
389			fnvlist_add_int32(dbda->dbda_errors, fullname, error);
390			rv = error;
391		}
392	}
393	return (rv);
394}
395
396static void
397dsl_bookmark_destroy_sync(void *arg, dmu_tx_t *tx)
398{
399	dsl_bookmark_destroy_arg_t *dbda = arg;
400	dsl_pool_t *dp = dmu_tx_pool(tx);
401	objset_t *mos = dp->dp_meta_objset;
402
403	for (nvpair_t *pair = nvlist_next_nvpair(dbda->dbda_success, NULL);
404	    pair != NULL; pair = nvlist_next_nvpair(dbda->dbda_success, pair)) {
405		dsl_dataset_t *ds;
406		char *shortname;
407		uint64_t zap_cnt;
408
409		VERIFY0(dsl_bookmark_hold_ds(dp, nvpair_name(pair),
410		    &ds, FTAG, &shortname));
411		VERIFY0(dsl_dataset_bookmark_remove(ds, shortname, tx));
412
413		/*
414		 * If all of this dataset's bookmarks have been destroyed,
415		 * free the zap object and decrement the feature's use count.
416		 */
417		VERIFY0(zap_count(mos, ds->ds_bookmarks,
418		    &zap_cnt));
419		if (zap_cnt == 0) {
420			dmu_buf_will_dirty(ds->ds_dbuf, tx);
421			VERIFY0(zap_destroy(mos, ds->ds_bookmarks, tx));
422			ds->ds_bookmarks = 0;
423			spa_feature_decr(dp->dp_spa, SPA_FEATURE_BOOKMARKS, tx);
424			VERIFY0(zap_remove(mos, ds->ds_object,
425			    DS_FIELD_BOOKMARK_NAMES, tx));
426		}
427
428		spa_history_log_internal_ds(ds, "remove bookmark", tx,
429		    "name=%s", shortname);
430
431		dsl_dataset_rele(ds, FTAG);
432	}
433}
434
435/*
436 * The bookmarks must all be in the same pool.
437 */
438int
439dsl_bookmark_destroy(nvlist_t *bmarks, nvlist_t *errors)
440{
441	int rv;
442	dsl_bookmark_destroy_arg_t dbda;
443	nvpair_t *pair = nvlist_next_nvpair(bmarks, NULL);
444	if (pair == NULL)
445		return (0);
446
447	dbda.dbda_bmarks = bmarks;
448	dbda.dbda_errors = errors;
449	dbda.dbda_success = fnvlist_alloc();
450
451	rv = dsl_sync_task(nvpair_name(pair), dsl_bookmark_destroy_check,
452	    dsl_bookmark_destroy_sync, &dbda, fnvlist_num_pairs(bmarks),
453	    ZFS_SPACE_CHECK_RESERVED);
454	fnvlist_free(dbda.dbda_success);
455	return (rv);
456}
457