]>
Commit | Line | Data |
---|---|---|
da536844 MA |
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 | */ | |
9b7b9cd3 | 15 | |
da536844 | 16 | /* |
3d45fdd6 | 17 | * Copyright (c) 2013, 2014 by Delphix. All rights reserved. |
9b7b9cd3 | 18 | * Copyright 2017 Nexenta Systems, Inc. |
da536844 MA |
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 | |
36 | dsl_bookmark_hold_ds(dsl_pool_t *dp, const char *fullname, | |
37 | dsl_dataset_t **dsp, void *tag, char **shortnamep) | |
38 | { | |
eca7b760 | 39 | char buf[ZFS_MAX_DATASET_NAME_LEN]; |
da536844 MA |
40 | char *hashp; |
41 | ||
eca7b760 | 42 | if (strlen(fullname) >= ZFS_MAX_DATASET_NAME_LEN) |
da536844 MA |
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 | |
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; | |
9b7b9cd3 | 64 | matchtype_t mt = 0; |
da536844 MA |
65 | int err; |
66 | ||
67 | if (bmark_zapobj == 0) | |
68 | return (SET_ERROR(ESRCH)); | |
69 | ||
d683ddbb | 70 | if (dsl_dataset_phys(ds)->ds_flags & DS_FLAG_CI_DATASET) |
9b7b9cd3 | 71 | mt = MT_NORMALIZE; |
da536844 MA |
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 | |
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 | |
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 | ||
0c66c32d | 123 | if (!snapds->ds_is_snapshot) |
da536844 MA |
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 | |
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; | |
da536844 MA |
152 | |
153 | if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS)) | |
154 | return (SET_ERROR(ENOTSUP)); | |
155 | ||
1c27024e | 156 | for (nvpair_t *pair = nvlist_next_nvpair(dbca->dbca_bmarks, NULL); |
da536844 MA |
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 | |
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; | |
da536844 MA |
185 | |
186 | ASSERT(spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS)); | |
187 | ||
1c27024e | 188 | for (nvpair_t *pair = nvlist_next_nvpair(dbca->dbca_bmarks, NULL); |
da536844 MA |
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 | ||
d683ddbb JG |
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; | |
da536844 | 214 | bmark_phys.zbm_creation_time = |
d683ddbb | 215 | dsl_dataset_phys(snapds)->ds_creation_time; |
da536844 MA |
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 | |
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, | |
3d45fdd6 MA |
250 | dsl_bookmark_create_sync, &dbca, |
251 | fnvlist_num_pairs(bmarks), ZFS_SPACE_CHECK_NORMAL)); | |
da536844 MA |
252 | } |
253 | ||
254 | int | |
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)) { | |
da536844 MA |
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 | ||
1c27024e | 277 | nvlist_t *out_props = fnvlist_alloc(); |
da536844 MA |
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 | |
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 | |
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; | |
9b7b9cd3 | 342 | matchtype_t mt = 0; |
da536844 | 343 | |
d683ddbb | 344 | if (dsl_dataset_phys(ds)->ds_flags & DS_FLAG_CI_DATASET) |
9b7b9cd3 | 345 | mt = MT_NORMALIZE; |
da536844 MA |
346 | |
347 | return (zap_remove_norm(mos, bmark_zapobj, name, mt, tx)); | |
348 | } | |
349 | ||
350 | static int | |
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; | |
da536844 | 356 | |
c6f6767e MA |
357 | ASSERT(nvlist_empty(dbda->dbda_success)); |
358 | ASSERT(nvlist_empty(dbda->dbda_errors)); | |
359 | ||
da536844 MA |
360 | if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS)) |
361 | return (0); | |
362 | ||
1c27024e | 363 | for (nvpair_t *pair = nvlist_next_nvpair(dbda->dbda_bmarks, NULL); |
da536844 MA |
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) { | |
c6f6767e MA |
389 | if (dmu_tx_is_syncing(tx)) { |
390 | fnvlist_add_boolean(dbda->dbda_success, | |
391 | fullname); | |
392 | } | |
da536844 MA |
393 | } else { |
394 | fnvlist_add_int32(dbda->dbda_errors, fullname, error); | |
395 | rv = error; | |
396 | } | |
397 | } | |
398 | return (rv); | |
399 | } | |
400 | ||
401 | static void | |
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; | |
da536844 | 407 | |
1c27024e | 408 | for (nvpair_t *pair = nvlist_next_nvpair(dbda->dbda_success, NULL); |
da536844 MA |
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 | |
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, | |
3d45fdd6 MA |
457 | dsl_bookmark_destroy_sync, &dbda, fnvlist_num_pairs(bmarks), |
458 | ZFS_SPACE_CHECK_RESERVED); | |
da536844 MA |
459 | fnvlist_free(dbda.dbda_success); |
460 | return (rv); | |
461 | } |