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 
  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 {
  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  */
  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 
  68         if (dsl_dataset_phys(ds)->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  */
  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 
 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 
 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 
 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 
 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;
 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  */
 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,
 250             dsl_bookmark_create_sync, &dbca,
 251             fnvlist_num_pairs(bmarks), ZFS_SPACE_CHECK_NORMAL));
 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)) {
 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  */
 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;
 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 
 352 static int
 353 dsl_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         if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS))
 360                 return (0);
 361 
 362         for (nvpair_t *pair = nvlist_next_nvpair(dbda->dbda_bmarks, NULL);
 363             pair != NULL; pair = nvlist_next_nvpair(dbda->dbda_bmarks, pair)) {
 364                 const char *fullname = nvpair_name(pair);
 365                 dsl_dataset_t *ds;
 366                 zfs_bookmark_phys_t bm;
 367                 int error;
 368                 char *shortname;
 369 
 370                 error = dsl_bookmark_hold_ds(dp, fullname, &ds,
 371                     FTAG, &shortname);
 372                 if (error == ENOENT) {
 373                         /* ignore it; the bookmark is "already destroyed" */
 374                         continue;
 375                 }
 376                 if (error == 0) {
 377                         error = dsl_dataset_bmark_lookup(ds, shortname, &bm);
 378                         dsl_dataset_rele(ds, FTAG);
 379                         if (error == ESRCH) {
 380                                 /*
 381                                  * ignore it; the bookmark is
 382                                  * "already destroyed"
 383                                  */
 384                                 continue;
 385                         }
 386                 }
 387                 if (error == 0) {
 388                         fnvlist_add_boolean(dbda->dbda_success, fullname);
 389                 } else {
 390                         fnvlist_add_int32(dbda->dbda_errors, fullname, error);
 391                         rv = error;
 392                 }
 393         }
 394         return (rv);
 395 }
 396 
 397 static void
 398 dsl_bookmark_destroy_sync(void *arg, dmu_tx_t *tx)
 399 {
 400         dsl_bookmark_destroy_arg_t *dbda = arg;
 401         dsl_pool_t *dp = dmu_tx_pool(tx);
 402         objset_t *mos = dp->dp_meta_objset;
 403 
 404         for (nvpair_t *pair = nvlist_next_nvpair(dbda->dbda_success, NULL);
 405             pair != NULL; pair = nvlist_next_nvpair(dbda->dbda_success, pair)) {
 406                 dsl_dataset_t *ds;
 407                 char *shortname;
 408                 uint64_t zap_cnt;
 409 
 410                 VERIFY0(dsl_bookmark_hold_ds(dp, nvpair_name(pair),
 411                     &ds, FTAG, &shortname));
 412                 VERIFY0(dsl_dataset_bookmark_remove(ds, shortname, tx));
 413 
 414                 /*
 415                  * If all of this dataset's bookmarks have been destroyed,
 416                  * free the zap object and decrement the feature's use count.
 417                  */
 418                 VERIFY0(zap_count(mos, ds->ds_bookmarks,
 419                     &zap_cnt));
 420                 if (zap_cnt == 0) {
 421                         dmu_buf_will_dirty(ds->ds_dbuf, tx);
 422                         VERIFY0(zap_destroy(mos, ds->ds_bookmarks, tx));
 423                         ds->ds_bookmarks = 0;
 424                         spa_feature_decr(dp->dp_spa, SPA_FEATURE_BOOKMARKS, tx);
 425                         VERIFY0(zap_remove(mos, ds->ds_object,
 426                             DS_FIELD_BOOKMARK_NAMES, tx));
 427                 }
 428 
 429                 spa_history_log_internal_ds(ds, "remove bookmark", tx,
 430                     "name=%s", shortname);
 431 
 432                 dsl_dataset_rele(ds, FTAG);
 433         }
 434 }
 435 
 436 /*
 437  * The bookmarks must all be in the same pool.
 438  */
 439 int
 440 dsl_bookmark_destroy(nvlist_t *bmarks, nvlist_t *errors)
 441 {
 442         int rv;
 443         dsl_bookmark_destroy_arg_t dbda;
 444         nvpair_t *pair = nvlist_next_nvpair(bmarks, NULL);
 445         if (pair == NULL)
 446                 return (0);
 447 
 448         dbda.dbda_bmarks = bmarks;
 449         dbda.dbda_errors = errors;
 450         dbda.dbda_success = fnvlist_alloc();
 451 
 452         rv = dsl_sync_task(nvpair_name(pair), dsl_bookmark_destroy_check,
 453             dsl_bookmark_destroy_sync, &dbda, fnvlist_num_pairs(bmarks),
 454             ZFS_SPACE_CHECK_RESERVED);
 455         fnvlist_free(dbda.dbda_success);
 456         return (rv);
 457 }