dsl_bookmark.c revision 323756
137131Sbrian/*
237131Sbrian * CDDL HEADER START
337131Sbrian *
437131Sbrian * This file and its contents are supplied under the terms of the
537131Sbrian * Common Development and Distribution License ("CDDL"), version 1.0.
637131Sbrian * You may only use this file in accordance with the terms of version
737131Sbrian * 1.0 of the CDDL.
837131Sbrian *
937131Sbrian * A full copy of the text of the CDDL should have accompanied this
1037131Sbrian * source.  A copy of the CDDL is also available via the Internet at
1137131Sbrian * http://www.illumos.org/license/CDDL.
1237131Sbrian *
1337131Sbrian * CDDL HEADER END
1437131Sbrian */
1537131Sbrian/*
1637131Sbrian * Copyright (c) 2013, 2014 by Delphix. All rights reserved.
1737131Sbrian */
1837131Sbrian
1937131Sbrian#include <sys/zfs_context.h>
2037131Sbrian#include <sys/dsl_dataset.h>
2137131Sbrian#include <sys/dsl_dir.h>
2237131Sbrian#include <sys/dsl_prop.h>
2337131Sbrian#include <sys/dsl_synctask.h>
2437131Sbrian#include <sys/dmu_impl.h>
2537131Sbrian#include <sys/dmu_tx.h>
2637131Sbrian#include <sys/arc.h>
2737131Sbrian#include <sys/zap.h>
2837131Sbrian#include <sys/zfeature.h>
2984195Sdillon#include <sys/spa.h>
3084195Sdillon#include <sys/dsl_bookmark.h>
3184195Sdillon#include <zfs_namecheck.h>
32124621Sphk
3337131Sbrianstatic int
3437131Sbriandsl_bookmark_hold_ds(dsl_pool_t *dp, const char *fullname,
3537131Sbrian    dsl_dataset_t **dsp, void *tag, char **shortnamep)
3637131Sbrian{
3737131Sbrian	char buf[ZFS_MAX_DATASET_NAME_LEN];
3837131Sbrian	char *hashp;
3937131Sbrian
4037131Sbrian	if (strlen(fullname) >= ZFS_MAX_DATASET_NAME_LEN)
4137131Sbrian		return (SET_ERROR(ENAMETOOLONG));
4237131Sbrian	hashp = strchr(fullname, '#');
43127094Sdes	if (hashp == NULL)
44127094Sdes		return (SET_ERROR(EINVAL));
45127094Sdes
46127094Sdes	*shortnamep = hashp + 1;
47127094Sdes	if (zfs_component_namecheck(*shortnamep, NULL, NULL))
48127094Sdes		return (SET_ERROR(EINVAL));
49127094Sdes	(void) strlcpy(buf, fullname, hashp - fullname + 1);
50127094Sdes	return (dsl_dataset_hold(dp, buf, tag, dsp));
51127094Sdes}
52127094Sdes
5337131Sbrian/*
5437131Sbrian * Returns ESRCH if bookmark is not found.
5537131Sbrian */
5637131Sbrianstatic int
57127094Sdesdsl_dataset_bmark_lookup(dsl_dataset_t *ds, const char *shortname,
58127094Sdes    zfs_bookmark_phys_t *bmark_phys)
59127094Sdes{
60127094Sdes	objset_t *mos = ds->ds_dir->dd_pool->dp_meta_objset;
6137131Sbrian	uint64_t bmark_zapobj = ds->ds_bookmarks;
6237131Sbrian	matchtype_t mt;
6337131Sbrian	int err;
6437131Sbrian
65127094Sdes	if (bmark_zapobj == 0)
66127094Sdes		return (SET_ERROR(ESRCH));
67127094Sdes
6837131Sbrian	if (dsl_dataset_phys(ds)->ds_flags & DS_FLAG_CI_DATASET)
6937131Sbrian		mt = MT_FIRST;
7037131Sbrian	else
71124621Sphk		mt = MT_EXACT;
7237131Sbrian
73127094Sdes	err = zap_lookup_norm(mos, bmark_zapobj, shortname, sizeof (uint64_t),
7437131Sbrian	    sizeof (*bmark_phys) / sizeof (uint64_t), bmark_phys, mt,
75127094Sdes	    NULL, 0, NULL);
76127094Sdes
77127094Sdes	return (err == ENOENT ? ESRCH : err);
78127094Sdes}
7937131Sbrian
80127094Sdes/*
81127094Sdes * If later_ds is non-NULL, this will return EXDEV if the the specified bookmark
82127094Sdes * does not represents an earlier point in later_ds's timeline.
8337131Sbrian *
84127094Sdes * Returns ENOENT if the dataset containing the bookmark does not exist.
85127094Sdes * Returns ESRCH if the dataset exists but the bookmark was not found in it.
8699207Sbrian */
8737131Sbrianint
88127094Sdesdsl_bookmark_lookup(dsl_pool_t *dp, const char *fullname,
89127094Sdes    dsl_dataset_t *later_ds, zfs_bookmark_phys_t *bmp)
9037131Sbrian{
91127094Sdes	char *shortname;
9237131Sbrian	dsl_dataset_t *ds;
9337131Sbrian	int error;
9437131Sbrian
95124621Sphk	error = dsl_bookmark_hold_ds(dp, fullname, &ds, FTAG, &shortname);
9637131Sbrian	if (error != 0)
97127094Sdes		return (error);
98127094Sdes
99127094Sdes	error = dsl_dataset_bmark_lookup(ds, shortname, bmp);
100127094Sdes	if (error == 0 && later_ds != NULL) {
101127094Sdes		if (!dsl_dataset_is_before(later_ds, ds, bmp->zbm_creation_txg))
102127094Sdes			error = SET_ERROR(EXDEV);
103127094Sdes	}
10437131Sbrian	dsl_dataset_rele(ds, FTAG);
105127094Sdes	return (error);
106127094Sdes}
107127094Sdes
108127094Sdestypedef struct dsl_bookmark_create_arg {
109127094Sdes	nvlist_t *dbca_bmarks;
110127094Sdes	nvlist_t *dbca_errors;
11137131Sbrian} dsl_bookmark_create_arg_t;
112127094Sdes
113127094Sdesstatic int
114127094Sdesdsl_bookmark_create_check_impl(dsl_dataset_t *snapds, const char *bookmark_name,
115127094Sdes    dmu_tx_t *tx)
116127094Sdes{
117127094Sdes	dsl_pool_t *dp = dmu_tx_pool(tx);
118127094Sdes	dsl_dataset_t *bmark_fs;
119127094Sdes	char *shortname;
120127094Sdes	int error;
121127094Sdes	zfs_bookmark_phys_t bmark_phys;
122127094Sdes
12337131Sbrian	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
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 = 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 */
236int
237dsl_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
254int
255dsl_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 */
308int
309dsl_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
331typedef 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
337static int
338dsl_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;
343
344	if (dsl_dataset_phys(ds)->ds_flags & DS_FLAG_CI_DATASET)
345		mt = MT_FIRST;
346	else
347		mt = MT_EXACT;
348
349	return (zap_remove_norm(mos, bmark_zapobj, name, mt, tx));
350}
351
352static int
353dsl_bookmark_destroy_check(void *arg, dmu_tx_t *tx)
354{
355	dsl_bookmark_destroy_arg_t *dbda = arg;
356	dsl_pool_t *dp = dmu_tx_pool(tx);
357	int rv = 0;
358
359	ASSERT(nvlist_empty(dbda->dbda_success));
360	ASSERT(nvlist_empty(dbda->dbda_errors));
361
362	if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS))
363		return (0);
364
365	for (nvpair_t *pair = nvlist_next_nvpair(dbda->dbda_bmarks, NULL);
366	    pair != NULL; pair = nvlist_next_nvpair(dbda->dbda_bmarks, pair)) {
367		const char *fullname = nvpair_name(pair);
368		dsl_dataset_t *ds;
369		zfs_bookmark_phys_t bm;
370		int error;
371		char *shortname;
372
373		error = dsl_bookmark_hold_ds(dp, fullname, &ds,
374		    FTAG, &shortname);
375		if (error == ENOENT) {
376			/* ignore it; the bookmark is "already destroyed" */
377			continue;
378		}
379		if (error == 0) {
380			error = dsl_dataset_bmark_lookup(ds, shortname, &bm);
381			dsl_dataset_rele(ds, FTAG);
382			if (error == ESRCH) {
383				/*
384				 * ignore it; the bookmark is
385				 * "already destroyed"
386				 */
387				continue;
388			}
389		}
390		if (error == 0) {
391			if (dmu_tx_is_syncing(tx)) {
392				fnvlist_add_boolean(dbda->dbda_success,
393				    fullname);
394			}
395		} else {
396			fnvlist_add_int32(dbda->dbda_errors, fullname, error);
397			rv = error;
398		}
399	}
400	return (rv);
401}
402
403static void
404dsl_bookmark_destroy_sync(void *arg, dmu_tx_t *tx)
405{
406	dsl_bookmark_destroy_arg_t *dbda = arg;
407	dsl_pool_t *dp = dmu_tx_pool(tx);
408	objset_t *mos = dp->dp_meta_objset;
409
410	for (nvpair_t *pair = nvlist_next_nvpair(dbda->dbda_success, NULL);
411	    pair != NULL; pair = nvlist_next_nvpair(dbda->dbda_success, pair)) {
412		dsl_dataset_t *ds;
413		char *shortname;
414		uint64_t zap_cnt;
415
416		VERIFY0(dsl_bookmark_hold_ds(dp, nvpair_name(pair),
417		    &ds, FTAG, &shortname));
418		VERIFY0(dsl_dataset_bookmark_remove(ds, shortname, tx));
419
420		/*
421		 * If all of this dataset's bookmarks have been destroyed,
422		 * free the zap object and decrement the feature's use count.
423		 */
424		VERIFY0(zap_count(mos, ds->ds_bookmarks,
425		    &zap_cnt));
426		if (zap_cnt == 0) {
427			dmu_buf_will_dirty(ds->ds_dbuf, tx);
428			VERIFY0(zap_destroy(mos, ds->ds_bookmarks, tx));
429			ds->ds_bookmarks = 0;
430			spa_feature_decr(dp->dp_spa, SPA_FEATURE_BOOKMARKS, tx);
431			VERIFY0(zap_remove(mos, ds->ds_object,
432			    DS_FIELD_BOOKMARK_NAMES, tx));
433		}
434
435		spa_history_log_internal_ds(ds, "remove bookmark", tx,
436		    "name=%s", shortname);
437
438		dsl_dataset_rele(ds, FTAG);
439	}
440}
441
442/*
443 * The bookmarks must all be in the same pool.
444 */
445int
446dsl_bookmark_destroy(nvlist_t *bmarks, nvlist_t *errors)
447{
448	int rv;
449	dsl_bookmark_destroy_arg_t dbda;
450	nvpair_t *pair = nvlist_next_nvpair(bmarks, NULL);
451	if (pair == NULL)
452		return (0);
453
454	dbda.dbda_bmarks = bmarks;
455	dbda.dbda_errors = errors;
456	dbda.dbda_success = fnvlist_alloc();
457
458	rv = dsl_sync_task(nvpair_name(pair), dsl_bookmark_destroy_check,
459	    dsl_bookmark_destroy_sync, &dbda, fnvlist_num_pairs(bmarks),
460	    ZFS_SPACE_CHECK_RESERVED);
461	fnvlist_free(dbda.dbda_success);
462	return (rv);
463}
464