dsl_bookmark.c revision 353759
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 35static int 36dsl_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 */ 58static int 59dsl_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 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 (!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 = 0; 343 344 if (dsl_dataset_phys(ds)->ds_flags & DS_FLAG_CI_DATASET) 345 mt = MT_NORMALIZE; 346 347 return (zap_remove_norm(mos, bmark_zapobj, name, mt, tx)); 348} 349 350static int 351dsl_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; 356 357 ASSERT(nvlist_empty(dbda->dbda_success)); 358 ASSERT(nvlist_empty(dbda->dbda_errors)); 359 360 if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS)) 361 return (0); 362 363 for (nvpair_t *pair = nvlist_next_nvpair(dbda->dbda_bmarks, NULL); 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) { 389 if (dmu_tx_is_syncing(tx)) { 390 fnvlist_add_boolean(dbda->dbda_success, 391 fullname); 392 } 393 } else { 394 fnvlist_add_int32(dbda->dbda_errors, fullname, error); 395 rv = error; 396 } 397 } 398 return (rv); 399} 400 401static void 402dsl_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 408 for (nvpair_t *pair = nvlist_next_nvpair(dbda->dbda_success, NULL); 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 */ 443int 444dsl_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, 457 dsl_bookmark_destroy_sync, &dbda, fnvlist_num_pairs(bmarks), 458 ZFS_SPACE_CHECK_RESERVED); 459 fnvlist_free(dbda.dbda_success); 460 return (rv); 461} 462 463typedef struct dsl_bookmark_rename_arg { 464 const char *dbra_fsname; 465 const char *dbra_oldname; 466 const char *dbra_newname; 467} dsl_bookmark_rename_arg_t; 468 469static int 470dsl_bookmark_rename_check(void *arg, dmu_tx_t *tx) 471{ 472 dsl_bookmark_rename_arg_t *dbra = arg; 473 dsl_pool_t *dp = dmu_tx_pool(tx); 474 dsl_dataset_t *ds; 475 zfs_bookmark_phys_t bmark_phys; 476 int error; 477 478 if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS)) 479 return (SET_ERROR(ENOTSUP)); 480 481 /* Check validity and the full length of the new bookmark name. */ 482 if (zfs_component_namecheck(dbra->dbra_newname, NULL, NULL)) 483 return (SET_ERROR(EINVAL)); 484 if (strlen(dbra->dbra_fsname) + strlen(dbra->dbra_newname) + 1 >= 485 ZFS_MAX_DATASET_NAME_LEN) 486 return (SET_ERROR(ENAMETOOLONG)); 487 488 error = dsl_dataset_hold(dp, dbra->dbra_fsname, FTAG, &ds); 489 if (error != 0) 490 return (error); 491 if (ds->ds_is_snapshot) { 492 dsl_dataset_rele(ds, FTAG); 493 return (SET_ERROR(EINVAL)); 494 } 495 error = dsl_dataset_bmark_lookup(ds, dbra->dbra_oldname, &bmark_phys); 496 if (error != 0) { 497 dsl_dataset_rele(ds, FTAG); 498 return (error); 499 } 500 501 error = dsl_dataset_bmark_lookup(ds, dbra->dbra_newname, &bmark_phys); 502 dsl_dataset_rele(ds, FTAG); 503 if (error == 0) 504 return (SET_ERROR(EEXIST)); 505 if (error != ESRCH) 506 return (error); 507 return (0); 508} 509 510static void 511dsl_bookmark_rename_sync(void *arg, dmu_tx_t *tx) 512{ 513 zfs_bookmark_phys_t bmark_phys; 514 dsl_bookmark_rename_arg_t *dbra = arg; 515 dsl_pool_t *dp = dmu_tx_pool(tx); 516 objset_t *mos; 517 dsl_dataset_t *ds; 518 uint64_t bmark_zapobj; 519 uint64_t int_size, num_ints; 520 matchtype_t mt = 0; 521 int error; 522 523 ASSERT(spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS)); 524 VERIFY0(dsl_dataset_hold(dp, dbra->dbra_fsname, FTAG, &ds)); 525 526 mos = ds->ds_dir->dd_pool->dp_meta_objset; 527 bmark_zapobj = ds->ds_bookmarks; 528 529 if (dsl_dataset_phys(ds)->ds_flags & DS_FLAG_CI_DATASET) 530 mt = MT_NORMALIZE; 531 532 VERIFY0(zap_length(mos, bmark_zapobj, dbra->dbra_oldname, 533 &int_size, &num_ints)); 534 ASSERT3U(int_size, ==, sizeof (uint64_t)); 535 VERIFY0(zap_lookup_norm(mos, bmark_zapobj, dbra->dbra_oldname, int_size, 536 num_ints, &bmark_phys, mt, NULL, 0, NULL)); 537 VERIFY0(zap_remove_norm(mos, bmark_zapobj, dbra->dbra_oldname, mt, tx)); 538 539 VERIFY0(zap_add(mos, bmark_zapobj, dbra->dbra_newname, int_size, 540 num_ints, &bmark_phys, tx)); 541 542 spa_history_log_internal_ds(ds, "rename bookmark", tx, 543 "#%s -> #%s creation_txg=%llu", 544 dbra->dbra_oldname, dbra->dbra_newname, 545 (longlong_t)bmark_phys.zbm_creation_txg); 546 547 dsl_dataset_rele(ds, FTAG); 548} 549 550/* 551 * The bookmarks must all be in the same pool. 552 */ 553int 554dsl_bookmark_rename(const char *fsname, const char *oldbmark, 555 const char *newbmark) 556{ 557 dsl_bookmark_rename_arg_t dbra; 558 559 dbra.dbra_fsname = fsname; 560 dbra.dbra_oldname = oldbmark; 561 dbra.dbra_newname = newbmark; 562 563 return (dsl_sync_task(fsname, dsl_bookmark_rename_check, 564 dsl_bookmark_rename_sync, &dbra, 1, ZFS_SPACE_CHECK_NORMAL)); 565} 566 567