]>
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 | */ | |
15 | /* | |
3d45fdd6 | 16 | * Copyright (c) 2013, 2014 by Delphix. All rights reserved. |
da536844 MA |
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 | ||
33 | static int | |
34 | dsl_bookmark_hold_ds(dsl_pool_t *dp, const char *fullname, | |
35 | dsl_dataset_t **dsp, void *tag, char **shortnamep) | |
36 | { | |
eca7b760 | 37 | char buf[ZFS_MAX_DATASET_NAME_LEN]; |
da536844 MA |
38 | char *hashp; |
39 | ||
eca7b760 | 40 | if (strlen(fullname) >= ZFS_MAX_DATASET_NAME_LEN) |
da536844 MA |
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 | */ | |
56 | static int | |
57 | dsl_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 | ||
d683ddbb | 68 | if (dsl_dataset_phys(ds)->ds_flags & DS_FLAG_CI_DATASET) |
da536844 MA |
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 | */ | |
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; | |
152 | nvpair_t *pair; | |
153 | ||
154 | if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS)) | |
155 | return (SET_ERROR(ENOTSUP)); | |
156 | ||
157 | for (pair = nvlist_next_nvpair(dbca->dbca_bmarks, NULL); | |
158 | pair != NULL; pair = nvlist_next_nvpair(dbca->dbca_bmarks, pair)) { | |
159 | dsl_dataset_t *snapds; | |
160 | int error; | |
161 | ||
162 | /* note: validity of nvlist checked by ioctl layer */ | |
163 | error = dsl_dataset_hold(dp, fnvpair_value_string(pair), | |
164 | FTAG, &snapds); | |
165 | if (error == 0) { | |
166 | error = dsl_bookmark_create_check_impl(snapds, | |
167 | nvpair_name(pair), tx); | |
168 | dsl_dataset_rele(snapds, FTAG); | |
169 | } | |
170 | if (error != 0) { | |
171 | fnvlist_add_int32(dbca->dbca_errors, | |
172 | nvpair_name(pair), error); | |
173 | rv = error; | |
174 | } | |
175 | } | |
176 | ||
177 | return (rv); | |
178 | } | |
179 | ||
180 | static void | |
181 | dsl_bookmark_create_sync(void *arg, dmu_tx_t *tx) | |
182 | { | |
183 | dsl_bookmark_create_arg_t *dbca = arg; | |
184 | dsl_pool_t *dp = dmu_tx_pool(tx); | |
185 | objset_t *mos = dp->dp_meta_objset; | |
186 | nvpair_t *pair; | |
187 | ||
188 | ASSERT(spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS)); | |
189 | ||
190 | for (pair = nvlist_next_nvpair(dbca->dbca_bmarks, NULL); | |
191 | pair != NULL; pair = nvlist_next_nvpair(dbca->dbca_bmarks, pair)) { | |
192 | dsl_dataset_t *snapds, *bmark_fs; | |
193 | zfs_bookmark_phys_t bmark_phys; | |
194 | char *shortname; | |
195 | ||
196 | VERIFY0(dsl_dataset_hold(dp, fnvpair_value_string(pair), | |
197 | FTAG, &snapds)); | |
198 | VERIFY0(dsl_bookmark_hold_ds(dp, nvpair_name(pair), | |
199 | &bmark_fs, FTAG, &shortname)); | |
200 | if (bmark_fs->ds_bookmarks == 0) { | |
201 | bmark_fs->ds_bookmarks = | |
202 | zap_create_norm(mos, U8_TEXTPREP_TOUPPER, | |
203 | DMU_OTN_ZAP_METADATA, DMU_OT_NONE, 0, tx); | |
204 | spa_feature_incr(dp->dp_spa, SPA_FEATURE_BOOKMARKS, tx); | |
205 | ||
206 | dsl_dataset_zapify(bmark_fs, tx); | |
207 | VERIFY0(zap_add(mos, bmark_fs->ds_object, | |
208 | DS_FIELD_BOOKMARK_NAMES, | |
209 | sizeof (bmark_fs->ds_bookmarks), 1, | |
210 | &bmark_fs->ds_bookmarks, tx)); | |
211 | } | |
212 | ||
d683ddbb JG |
213 | bmark_phys.zbm_guid = dsl_dataset_phys(snapds)->ds_guid; |
214 | bmark_phys.zbm_creation_txg = | |
215 | dsl_dataset_phys(snapds)->ds_creation_txg; | |
da536844 | 216 | bmark_phys.zbm_creation_time = |
d683ddbb | 217 | dsl_dataset_phys(snapds)->ds_creation_time; |
da536844 MA |
218 | |
219 | VERIFY0(zap_add(mos, bmark_fs->ds_bookmarks, | |
220 | shortname, sizeof (uint64_t), | |
221 | sizeof (zfs_bookmark_phys_t) / sizeof (uint64_t), | |
222 | &bmark_phys, tx)); | |
223 | ||
224 | spa_history_log_internal_ds(bmark_fs, "bookmark", tx, | |
225 | "name=%s creation_txg=%llu target_snap=%llu", | |
226 | shortname, | |
227 | (longlong_t)bmark_phys.zbm_creation_txg, | |
228 | (longlong_t)snapds->ds_object); | |
229 | ||
230 | dsl_dataset_rele(bmark_fs, FTAG); | |
231 | dsl_dataset_rele(snapds, FTAG); | |
232 | } | |
233 | } | |
234 | ||
235 | /* | |
236 | * The bookmarks must all be in the same pool. | |
237 | */ | |
238 | int | |
239 | dsl_bookmark_create(nvlist_t *bmarks, nvlist_t *errors) | |
240 | { | |
241 | nvpair_t *pair; | |
242 | dsl_bookmark_create_arg_t dbca; | |
243 | ||
244 | pair = nvlist_next_nvpair(bmarks, NULL); | |
245 | if (pair == NULL) | |
246 | return (0); | |
247 | ||
248 | dbca.dbca_bmarks = bmarks; | |
249 | dbca.dbca_errors = errors; | |
250 | ||
251 | return (dsl_sync_task(nvpair_name(pair), dsl_bookmark_create_check, | |
3d45fdd6 MA |
252 | dsl_bookmark_create_sync, &dbca, |
253 | fnvlist_num_pairs(bmarks), ZFS_SPACE_CHECK_NORMAL)); | |
da536844 MA |
254 | } |
255 | ||
256 | int | |
257 | dsl_get_bookmarks_impl(dsl_dataset_t *ds, nvlist_t *props, nvlist_t *outnvl) | |
258 | { | |
259 | int err = 0; | |
260 | zap_cursor_t zc; | |
261 | zap_attribute_t attr; | |
262 | dsl_pool_t *dp = ds->ds_dir->dd_pool; | |
263 | ||
264 | uint64_t bmark_zapobj = ds->ds_bookmarks; | |
265 | if (bmark_zapobj == 0) | |
266 | return (0); | |
267 | ||
268 | for (zap_cursor_init(&zc, dp->dp_meta_objset, bmark_zapobj); | |
269 | zap_cursor_retrieve(&zc, &attr) == 0; | |
270 | zap_cursor_advance(&zc)) { | |
271 | nvlist_t *out_props; | |
272 | char *bmark_name = attr.za_name; | |
273 | zfs_bookmark_phys_t bmark_phys; | |
274 | ||
275 | err = dsl_dataset_bmark_lookup(ds, bmark_name, &bmark_phys); | |
276 | ASSERT3U(err, !=, ENOENT); | |
277 | if (err != 0) | |
278 | break; | |
279 | ||
280 | out_props = fnvlist_alloc(); | |
281 | if (nvlist_exists(props, | |
282 | zfs_prop_to_name(ZFS_PROP_GUID))) { | |
283 | dsl_prop_nvlist_add_uint64(out_props, | |
284 | ZFS_PROP_GUID, bmark_phys.zbm_guid); | |
285 | } | |
286 | if (nvlist_exists(props, | |
287 | zfs_prop_to_name(ZFS_PROP_CREATETXG))) { | |
288 | dsl_prop_nvlist_add_uint64(out_props, | |
289 | ZFS_PROP_CREATETXG, bmark_phys.zbm_creation_txg); | |
290 | } | |
291 | if (nvlist_exists(props, | |
292 | zfs_prop_to_name(ZFS_PROP_CREATION))) { | |
293 | dsl_prop_nvlist_add_uint64(out_props, | |
294 | ZFS_PROP_CREATION, bmark_phys.zbm_creation_time); | |
295 | } | |
296 | ||
297 | fnvlist_add_nvlist(outnvl, bmark_name, out_props); | |
298 | fnvlist_free(out_props); | |
299 | } | |
300 | zap_cursor_fini(&zc); | |
301 | return (err); | |
302 | } | |
303 | ||
304 | /* | |
305 | * Retrieve the bookmarks that exist in the specified dataset, and the | |
306 | * requested properties of each bookmark. | |
307 | * | |
308 | * The "props" nvlist specifies which properties are requested. | |
309 | * See lzc_get_bookmarks() for the list of valid properties. | |
310 | */ | |
311 | int | |
312 | dsl_get_bookmarks(const char *dsname, nvlist_t *props, nvlist_t *outnvl) | |
313 | { | |
314 | dsl_pool_t *dp; | |
315 | dsl_dataset_t *ds; | |
316 | int err; | |
317 | ||
318 | err = dsl_pool_hold(dsname, FTAG, &dp); | |
319 | if (err != 0) | |
320 | return (err); | |
321 | err = dsl_dataset_hold(dp, dsname, FTAG, &ds); | |
322 | if (err != 0) { | |
323 | dsl_pool_rele(dp, FTAG); | |
324 | return (err); | |
325 | } | |
326 | ||
327 | err = dsl_get_bookmarks_impl(ds, props, outnvl); | |
328 | ||
329 | dsl_dataset_rele(ds, FTAG); | |
330 | dsl_pool_rele(dp, FTAG); | |
331 | return (err); | |
332 | } | |
333 | ||
334 | typedef struct dsl_bookmark_destroy_arg { | |
335 | nvlist_t *dbda_bmarks; | |
336 | nvlist_t *dbda_success; | |
337 | nvlist_t *dbda_errors; | |
338 | } dsl_bookmark_destroy_arg_t; | |
339 | ||
340 | static int | |
341 | dsl_dataset_bookmark_remove(dsl_dataset_t *ds, const char *name, dmu_tx_t *tx) | |
342 | { | |
343 | objset_t *mos = ds->ds_dir->dd_pool->dp_meta_objset; | |
344 | uint64_t bmark_zapobj = ds->ds_bookmarks; | |
345 | matchtype_t mt; | |
346 | ||
d683ddbb | 347 | if (dsl_dataset_phys(ds)->ds_flags & DS_FLAG_CI_DATASET) |
da536844 MA |
348 | mt = MT_FIRST; |
349 | else | |
350 | mt = MT_EXACT; | |
351 | ||
352 | return (zap_remove_norm(mos, bmark_zapobj, name, mt, tx)); | |
353 | } | |
354 | ||
355 | static int | |
356 | dsl_bookmark_destroy_check(void *arg, dmu_tx_t *tx) | |
357 | { | |
358 | dsl_bookmark_destroy_arg_t *dbda = arg; | |
359 | dsl_pool_t *dp = dmu_tx_pool(tx); | |
360 | int rv = 0; | |
361 | nvpair_t *pair; | |
362 | ||
363 | if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS)) | |
364 | return (0); | |
365 | ||
366 | for (pair = nvlist_next_nvpair(dbda->dbda_bmarks, NULL); | |
367 | pair != NULL; pair = nvlist_next_nvpair(dbda->dbda_bmarks, pair)) { | |
368 | const char *fullname = nvpair_name(pair); | |
369 | dsl_dataset_t *ds; | |
370 | zfs_bookmark_phys_t bm; | |
371 | int error; | |
372 | char *shortname; | |
373 | ||
374 | error = dsl_bookmark_hold_ds(dp, fullname, &ds, | |
375 | FTAG, &shortname); | |
376 | if (error == ENOENT) { | |
377 | /* ignore it; the bookmark is "already destroyed" */ | |
378 | continue; | |
379 | } | |
380 | if (error == 0) { | |
381 | error = dsl_dataset_bmark_lookup(ds, shortname, &bm); | |
382 | dsl_dataset_rele(ds, FTAG); | |
383 | if (error == ESRCH) { | |
384 | /* | |
385 | * ignore it; the bookmark is | |
386 | * "already destroyed" | |
387 | */ | |
388 | continue; | |
389 | } | |
390 | } | |
391 | if (error == 0) { | |
392 | fnvlist_add_boolean(dbda->dbda_success, fullname); | |
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; | |
407 | nvpair_t *pair; | |
408 | ||
409 | for (pair = nvlist_next_nvpair(dbda->dbda_success, NULL); | |
410 | pair != NULL; pair = nvlist_next_nvpair(dbda->dbda_success, pair)) { | |
411 | dsl_dataset_t *ds; | |
412 | char *shortname; | |
413 | uint64_t zap_cnt; | |
414 | ||
415 | VERIFY0(dsl_bookmark_hold_ds(dp, nvpair_name(pair), | |
416 | &ds, FTAG, &shortname)); | |
417 | VERIFY0(dsl_dataset_bookmark_remove(ds, shortname, tx)); | |
418 | ||
419 | /* | |
420 | * If all of this dataset's bookmarks have been destroyed, | |
421 | * free the zap object and decrement the feature's use count. | |
422 | */ | |
423 | VERIFY0(zap_count(mos, ds->ds_bookmarks, | |
424 | &zap_cnt)); | |
425 | if (zap_cnt == 0) { | |
426 | dmu_buf_will_dirty(ds->ds_dbuf, tx); | |
427 | VERIFY0(zap_destroy(mos, ds->ds_bookmarks, tx)); | |
428 | ds->ds_bookmarks = 0; | |
429 | spa_feature_decr(dp->dp_spa, SPA_FEATURE_BOOKMARKS, tx); | |
430 | VERIFY0(zap_remove(mos, ds->ds_object, | |
431 | DS_FIELD_BOOKMARK_NAMES, tx)); | |
432 | } | |
433 | ||
434 | spa_history_log_internal_ds(ds, "remove bookmark", tx, | |
435 | "name=%s", shortname); | |
436 | ||
437 | dsl_dataset_rele(ds, FTAG); | |
438 | } | |
439 | } | |
440 | ||
441 | /* | |
442 | * The bookmarks must all be in the same pool. | |
443 | */ | |
444 | int | |
445 | dsl_bookmark_destroy(nvlist_t *bmarks, nvlist_t *errors) | |
446 | { | |
447 | int rv; | |
448 | dsl_bookmark_destroy_arg_t dbda; | |
449 | nvpair_t *pair = nvlist_next_nvpair(bmarks, NULL); | |
450 | if (pair == NULL) | |
451 | return (0); | |
452 | ||
453 | dbda.dbda_bmarks = bmarks; | |
454 | dbda.dbda_errors = errors; | |
455 | dbda.dbda_success = fnvlist_alloc(); | |
456 | ||
457 | rv = dsl_sync_task(nvpair_name(pair), dsl_bookmark_destroy_check, | |
3d45fdd6 MA |
458 | dsl_bookmark_destroy_sync, &dbda, fnvlist_num_pairs(bmarks), |
459 | ZFS_SPACE_CHECK_RESERVED); | |
da536844 MA |
460 | fnvlist_free(dbda.dbda_success); |
461 | return (rv); | |
462 | } |