]> granicus.if.org Git - zfs/commitdiff
Illumos 4368, 4369.
authorMatthew Ahrens <mahrens@delphix.com>
Wed, 11 Dec 2013 22:33:41 +0000 (14:33 -0800)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Tue, 29 Jul 2014 17:55:29 +0000 (10:55 -0700)
4369 implement zfs bookmarks
4368 zfs send filesystems from readonly pools
Reviewed by: Christopher Siden <christopher.siden@delphix.com>
Reviewed by: George Wilson <george.wilson@delphix.com>
Approved by: Garrett D'Amore <garrett@damore.org>

References:
  https://www.illumos.org/issues/4369
  https://www.illumos.org/issues/4368
  https://github.com/illumos/illumos-gate/commit/78f1710

Ported by: Tim Chase <tim@chase2k.com>
Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov>
Closes #2530

33 files changed:
cmd/zfs/zfs_iter.c
cmd/zfs/zfs_main.c
include/libzfs.h
include/libzfs_core.h
include/libzfs_impl.h
include/sys/Makefile.am
include/sys/dsl_bookmark.h [new file with mode: 0644]
include/sys/dsl_dataset.h
include/sys/dsl_deleg.h
include/sys/fs/zfs.h
include/zfeature_common.h
include/zfs_deleg.h
include/zfs_namecheck.h
lib/libzfs/libzfs_dataset.c
lib/libzfs/libzfs_iter.c
lib/libzfs/libzfs_sendrecv.c
lib/libzfs_core/libzfs_core.c
lib/libzpool/Makefile.am
man/man5/zpool-features.5
man/man8/zfs.8
module/zcommon/zfs_deleg.c
module/zcommon/zfs_namecheck.c
module/zcommon/zfs_prop.c
module/zfs/Makefile.in
module/zfs/dmu_diff.c
module/zfs/dmu_send.c
module/zfs/dsl_bookmark.c [new file with mode: 0644]
module/zfs/dsl_dataset.c
module/zfs/dsl_destroy.c
module/zfs/spa_misc.c
module/zfs/zfeature_common.c
module/zfs/zfs_ctldir.c
module/zfs/zfs_ioctl.c

index eb1d9a54e1ec9471cf57670789364ca3cef2006a..2c16f6981bb2d17de6fbb41cf3a60fb19dccc972 100644 (file)
@@ -23,6 +23,7 @@
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2012 Pawel Jakub Dawidek <pawel@dawidek.net>.
  * Copyright 2013 Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 2013 by Delphix. All rights reserved.
  */
 
 #include <libintl.h>
@@ -71,7 +72,7 @@ uu_avl_pool_t *avl_pool;
  * Include snaps if they were requested or if this a zfs list where types
  * were not specified and the "listsnapshots" property is set on this pool.
  */
-static int
+static boolean_t
 zfs_include_snapshots(zfs_handle_t *zhp, callback_data_t *cb)
 {
        zpool_handle_t *zph;
@@ -91,8 +92,9 @@ static int
 zfs_callback(zfs_handle_t *zhp, void *data)
 {
        callback_data_t *cb = data;
-       int dontclose = 0;
-       int include_snaps = zfs_include_snapshots(zhp, cb);
+       boolean_t dontclose = B_FALSE;
+       boolean_t include_snaps = zfs_include_snapshots(zhp, cb);
+       boolean_t include_bmarks = (cb->cb_types & ZFS_TYPE_BOOKMARK);
 
        if ((zfs_get_type(zhp) & cb->cb_types) ||
            ((zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT) && include_snaps)) {
@@ -118,7 +120,7 @@ zfs_callback(zfs_handle_t *zhp, void *data)
                                }
                        }
                        uu_avl_insert(cb->cb_avl, node, idx);
-                       dontclose = 1;
+                       dontclose = B_TRUE;
                } else {
                        free(node);
                }
@@ -133,11 +135,14 @@ zfs_callback(zfs_handle_t *zhp, void *data)
                cb->cb_depth++;
                if (zfs_get_type(zhp) == ZFS_TYPE_FILESYSTEM)
                        (void) zfs_iter_filesystems(zhp, zfs_callback, data);
-               if ((zfs_get_type(zhp) != ZFS_TYPE_SNAPSHOT) && include_snaps) {
+               if (((zfs_get_type(zhp) & (ZFS_TYPE_SNAPSHOT |
+                   ZFS_TYPE_BOOKMARK)) == 0) && include_snaps)
                        (void) zfs_iter_snapshots(zhp,
                            (cb->cb_flags & ZFS_ITER_SIMPLE) != 0, zfs_callback,
                            data);
-               }
+               if (((zfs_get_type(zhp) & (ZFS_TYPE_SNAPSHOT |
+                   ZFS_TYPE_BOOKMARK)) == 0) && include_bmarks)
+                       (void) zfs_iter_bookmarks(zhp, zfs_callback, data);
                cb->cb_depth--;
        }
 
index 4989b3b47272ebd1dcad6846bf1562f95479e703..521ce3c2bbe945d9ac66b0754d199d856335740d 100644 (file)
@@ -101,6 +101,7 @@ static int zfs_do_hold(int argc, char **argv);
 static int zfs_do_holds(int argc, char **argv);
 static int zfs_do_release(int argc, char **argv);
 static int zfs_do_diff(int argc, char **argv);
+static int zfs_do_bookmark(int argc, char **argv);
 
 /*
  * Enable a reasonable set of defaults for libumem debugging on DEBUG builds.
@@ -147,6 +148,7 @@ typedef enum {
        HELP_HOLDS,
        HELP_RELEASE,
        HELP_DIFF,
+       HELP_BOOKMARK,
 } zfs_help_t;
 
 typedef struct zfs_command {
@@ -173,6 +175,7 @@ static zfs_command_t command_table[] = {
        { "clone",      zfs_do_clone,           HELP_CLONE              },
        { "promote",    zfs_do_promote,         HELP_PROMOTE            },
        { "rename",     zfs_do_rename,          HELP_RENAME             },
+       { "bookmark",   zfs_do_bookmark,        HELP_BOOKMARK           },
        { NULL },
        { "list",       zfs_do_list,            HELP_LIST               },
        { NULL },
@@ -220,11 +223,12 @@ get_usage(zfs_help_t idx)
        case HELP_DESTROY:
                return (gettext("\tdestroy [-fnpRrv] <filesystem|volume>\n"
                    "\tdestroy [-dnpRrv] "
-                   "<filesystem|volume>@<snap>[%<snap>][,...]\n"));
+                   "<filesystem|volume>@<snap>[%<snap>][,...]\n"
+                   "\tdestroy <filesystem|volume>#<bookmark>\n"));
        case HELP_GET:
                return (gettext("\tget [-rHp] [-d max] "
-                   "[-o \"all\" | field[,...]] [-t type[,...]] "
-                   "[-s source[,...]]\n"
+                   "[-o \"all\" | field[,...]]\n"
+                   "\t    [-t type[,...]] [-s source[,...]]\n"
                    "\t    <\"all\" | property[,...]> "
                    "[filesystem|volume|snapshot] ...\n"));
        case HELP_INHERIT:
@@ -250,12 +254,14 @@ get_usage(zfs_help_t idx)
                return (gettext("\trename [-f] <filesystem|volume|snapshot> "
                    "<filesystem|volume|snapshot>\n"
                    "\trename [-f] -p <filesystem|volume> <filesystem|volume>\n"
-                   "\trename -r <snapshot> <snapshot>"));
+                   "\trename -r <snapshot> <snapshot>\n"));
        case HELP_ROLLBACK:
                return (gettext("\trollback [-rRf] <snapshot>\n"));
        case HELP_SEND:
                return (gettext("\tsend [-DnPpRrv] [-[iI] snapshot] "
-                   "<snapshot>\n"));
+                   "<snapshot>\n"
+                   "\tsend [-i snapshot|bookmark] "
+                   "<filesystem|volume|snapshot>\n"));
        case HELP_SET:
                return (gettext("\tset <property=value> "
                    "<filesystem|volume|snapshot> ...\n"));
@@ -263,7 +269,7 @@ get_usage(zfs_help_t idx)
                return (gettext("\tshare <-a | filesystem>\n"));
        case HELP_SNAPSHOT:
                return (gettext("\tsnapshot|snap [-r] [-o property=value] ... "
-                   "<filesystem@snapname|volume@snapname> ...\n"));
+                   "<filesystem|volume>@<snap> ...\n"));
        case HELP_UNMOUNT:
                return (gettext("\tunmount [-f] "
                    "<-a | filesystem|mountpoint>\n"));
@@ -292,11 +298,13 @@ get_usage(zfs_help_t idx)
                    "<filesystem|volume>\n"));
        case HELP_USERSPACE:
                return (gettext("\tuserspace [-Hinp] [-o field[,...]] "
-                   "[-s field]...\n\t    [-S field]... [-t type[,...]] "
+                   "[-s field] ...\n"
+                   "\t    [-S field] ... [-t type[,...]] "
                    "<filesystem|snapshot>\n"));
        case HELP_GROUPSPACE:
                return (gettext("\tgroupspace [-Hinp] [-o field[,...]] "
-                   "[-s field]...\n\t    [-S field]... [-t type[,...]] "
+                   "[-s field] ...\n"
+                   "\t    [-S field] ... [-t type[,...]] "
                    "<filesystem|snapshot>\n"));
        case HELP_HOLD:
                return (gettext("\thold [-r] <tag> <snapshot> ...\n"));
@@ -307,6 +315,8 @@ get_usage(zfs_help_t idx)
        case HELP_DIFF:
                return (gettext("\tdiff [-FHt] <snapshot> "
                    "[snapshot|filesystem]\n"));
+       case HELP_BOOKMARK:
+               return (gettext("\tbookmark <snapshot> <bookmark>\n"));
        }
 
        abort();
@@ -921,6 +931,7 @@ typedef struct destroy_cbdata {
        char            *cb_prevsnap;
        int64_t         cb_snapused;
        char            *cb_snapspec;
+       char            *cb_bookmark;
 } destroy_cbdata_t;
 
 /*
@@ -1190,7 +1201,7 @@ zfs_do_destroy(int argc, char **argv)
        int err = 0;
        int c;
        zfs_handle_t *zhp = NULL;
-       char *at;
+       char *at, *pound;
        zfs_type_t type = ZFS_TYPE_DATASET;
 
        /* check options */
@@ -1242,6 +1253,7 @@ zfs_do_destroy(int argc, char **argv)
        }
 
        at = strchr(argv[0], '@');
+       pound = strchr(argv[0], '#');
        if (at != NULL) {
 
                /* Build the list of snaps to destroy in cb_nvl. */
@@ -1303,6 +1315,46 @@ zfs_do_destroy(int argc, char **argv)
 
                if (err != 0)
                        rv = 1;
+       } else if (pound != NULL) {
+               int err;
+               nvlist_t *nvl;
+
+               if (cb.cb_dryrun) {
+                       (void) fprintf(stderr,
+                           "dryrun is not supported with bookmark\n");
+                       return (-1);
+               }
+
+               if (cb.cb_defer_destroy) {
+                       (void) fprintf(stderr,
+                           "defer destroy is not supported with bookmark\n");
+                       return (-1);
+               }
+
+               if (cb.cb_recurse) {
+                       (void) fprintf(stderr,
+                           "recursive is not supported with bookmark\n");
+                       return (-1);
+               }
+
+               if (!zfs_bookmark_exists(argv[0])) {
+                       (void) fprintf(stderr, gettext("bookmark '%s' "
+                           "does not exist.\n"), argv[0]);
+                       return (1);
+               }
+
+               nvl = fnvlist_alloc();
+               fnvlist_add_boolean(nvl, argv[0]);
+
+               err = lzc_destroy_bookmarks(nvl, NULL);
+               if (err != 0) {
+                       (void) zfs_standard_error(g_zfs, err,
+                           "cannot destroy bookmark");
+               }
+
+               nvlist_free(cb.cb_nvl);
+
+               return (err);
        } else {
                /* Open the given dataset */
                if ((zhp = zfs_open(g_zfs, argv[0], type)) == NULL)
@@ -1665,7 +1717,8 @@ zfs_do_get(int argc, char **argv)
                        flags &= ~ZFS_ITER_PROP_LISTSNAPS;
                        while (*optarg != '\0') {
                                static char *type_subopts[] = { "filesystem",
-                                   "volume", "snapshot", "all", NULL };
+                                   "volume", "snapshot", "bookmark",
+                                   "all", NULL };
 
                                switch (getsubopt(&optarg, type_subopts,
                                    &value)) {
@@ -1679,7 +1732,11 @@ zfs_do_get(int argc, char **argv)
                                        types |= ZFS_TYPE_SNAPSHOT;
                                        break;
                                case 3:
-                                       types = ZFS_TYPE_DATASET;
+                                       types |= ZFS_TYPE_BOOKMARK;
+                                       break;
+                               case 4:
+                                       types = ZFS_TYPE_DATASET |
+                                           ZFS_TYPE_BOOKMARK;
                                        break;
 
                                default:
@@ -3027,7 +3084,8 @@ zfs_do_list(int argc, char **argv)
                        flags &= ~ZFS_ITER_PROP_LISTSNAPS;
                        while (*optarg != '\0') {
                                static char *type_subopts[] = { "filesystem",
-                                   "volume", "snapshot", "snap", "all", NULL };
+                                   "volume", "snapshot", "snap", "bookmark",
+                                   "all", NULL };
 
                                switch (getsubopt(&optarg, type_subopts,
                                    &value)) {
@@ -3042,9 +3100,12 @@ zfs_do_list(int argc, char **argv)
                                        types |= ZFS_TYPE_SNAPSHOT;
                                        break;
                                case 4:
-                                       types = ZFS_TYPE_DATASET;
+                                       types |= ZFS_TYPE_BOOKMARK;
+                                       break;
+                               case 5:
+                                       types = ZFS_TYPE_DATASET |
+                                           ZFS_TYPE_BOOKMARK;
                                        break;
-
                                default:
                                        (void) fprintf(stderr,
                                            gettext("invalid type '%s'\n"),
@@ -3254,9 +3315,29 @@ typedef struct rollback_cbdata {
        char            *cb_target;
        int             cb_error;
        boolean_t       cb_recurse;
-       boolean_t       cb_dependent;
 } rollback_cbdata_t;
 
+static int
+rollback_check_dependent(zfs_handle_t *zhp, void *data)
+{
+       rollback_cbdata_t *cbp = data;
+
+       if (cbp->cb_first && cbp->cb_recurse) {
+               (void) fprintf(stderr, gettext("cannot rollback to "
+                   "'%s': clones of previous snapshots exist\n"),
+                   cbp->cb_target);
+               (void) fprintf(stderr, gettext("use '-R' to "
+                   "force deletion of the following clones and "
+                   "dependents:\n"));
+               cbp->cb_first = 0;
+               cbp->cb_error = 1;
+       }
+
+       (void) fprintf(stderr, "%s\n", zfs_get_name(zhp));
+
+       zfs_close(zhp);
+       return (0);
+}
 /*
  * Report any snapshots more recent than the one specified.  Used when '-r' is
  * not specified.  We reuse this same callback for the snapshot dependents - if
@@ -3273,52 +3354,30 @@ rollback_check(zfs_handle_t *zhp, void *data)
                return (0);
        }
 
-       if (!cbp->cb_dependent) {
-               if (strcmp(zfs_get_name(zhp), cbp->cb_target) != 0 &&
-                   zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT &&
-                   zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG) >
-                   cbp->cb_create) {
-
-                       if (cbp->cb_first && !cbp->cb_recurse) {
-                               (void) fprintf(stderr, gettext("cannot "
-                                   "rollback to '%s': more recent snapshots "
-                                   "exist\n"),
-                                   cbp->cb_target);
-                               (void) fprintf(stderr, gettext("use '-r' to "
-                                   "force deletion of the following "
-                                   "snapshots:\n"));
-                               cbp->cb_first = 0;
-                               cbp->cb_error = 1;
-                       }
-
-                       if (cbp->cb_recurse) {
-                               cbp->cb_dependent = B_TRUE;
-                               if (zfs_iter_dependents(zhp, B_TRUE,
-                                   rollback_check, cbp) != 0) {
-                                       zfs_close(zhp);
-                                       return (-1);
-                               }
-                               cbp->cb_dependent = B_FALSE;
-                       } else {
-                               (void) fprintf(stderr, "%s\n",
-                                   zfs_get_name(zhp));
-                       }
-               }
-       } else {
-               if (cbp->cb_first && cbp->cb_recurse) {
-                       (void) fprintf(stderr, gettext("cannot rollback to "
-                           "'%s': clones of previous snapshots exist\n"),
+       if (zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG) > cbp->cb_create) {
+               if (cbp->cb_first && !cbp->cb_recurse) {
+                       (void) fprintf(stderr, gettext("cannot "
+                           "rollback to '%s': more recent snapshots "
+                           "or bookmarks exist\n"),
                            cbp->cb_target);
-                       (void) fprintf(stderr, gettext("use '-R' to "
-                           "force deletion of the following clones and "
-                           "dependents:\n"));
+                       (void) fprintf(stderr, gettext("use '-r' to "
+                           "force deletion of the following "
+                           "snapshots and bookmarks:\n"));
                        cbp->cb_first = 0;
                        cbp->cb_error = 1;
                }
 
-               (void) fprintf(stderr, "%s\n", zfs_get_name(zhp));
+               if (cbp->cb_recurse) {
+                       if (zfs_iter_dependents(zhp, B_TRUE,
+                           rollback_check_dependent, cbp) != 0) {
+                               zfs_close(zhp);
+                               return (-1);
+                       }
+               } else {
+                       (void) fprintf(stderr, "%s\n",
+                           zfs_get_name(zhp));
+               }
        }
-
        zfs_close(zhp);
        return (0);
 }
@@ -3388,7 +3447,9 @@ zfs_do_rollback(int argc, char **argv)
        cb.cb_create = zfs_prop_get_int(snap, ZFS_PROP_CREATETXG);
        cb.cb_first = B_TRUE;
        cb.cb_error = 0;
-       if ((ret = zfs_iter_children(zhp, rollback_check, &cb)) != 0)
+       if ((ret = zfs_iter_snapshots(zhp, B_FALSE, rollback_check, &cb)) != 0)
+               goto out;
+       if ((ret = zfs_iter_bookmarks(zhp, rollback_check, &cb)) != 0)
                goto out;
 
        if ((ret = cb.cb_error) != 0)
@@ -3683,12 +3744,45 @@ zfs_do_send(int argc, char **argv)
                return (1);
        }
 
-       cp = strchr(argv[0], '@');
-       if (cp == NULL) {
-               (void) fprintf(stderr,
-                   gettext("argument must be a snapshot\n"));
-               usage(B_FALSE);
+       /*
+        * Special case sending a filesystem, or from a bookmark.
+        */
+       if (strchr(argv[0], '@') == NULL ||
+           (fromname && strchr(fromname, '#') != NULL)) {
+               char frombuf[ZFS_MAXNAMELEN];
+
+               if (flags.replicate || flags.doall || flags.props ||
+                   flags.dedup || flags.dryrun || flags.verbose ||
+                   flags.progress) {
+                       (void) fprintf(stderr,
+                           gettext("Error: "
+                           "Unsupported flag with filesystem or bookmark.\n"));
+                       return (1);
+               }
+
+               zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_DATASET);
+               if (zhp == NULL)
+                       return (1);
+
+               if (fromname != NULL &&
+                   (fromname[0] == '#' || fromname[0] == '@')) {
+                       /*
+                        * Incremental source name begins with # or @.
+                        * Default to same fs as target.
+                        */
+                       (void) strncpy(frombuf, argv[0], sizeof (frombuf));
+                       cp = strchr(frombuf, '@');
+                       if (cp != NULL)
+                               *cp = '\0';
+                       (void) strlcat(frombuf, fromname, sizeof (frombuf));
+                       fromname = frombuf;
+               }
+               err = zfs_send_one(zhp, fromname, STDOUT_FILENO);
+               zfs_close(zhp);
+               return (err != 0);
        }
+
+       cp = strchr(argv[0], '@');
        *cp = '\0';
        toname = cp + 1;
        zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME);
@@ -3844,6 +3938,7 @@ zfs_do_receive(int argc, char **argv)
 #define        ZFS_DELEG_PERM_HOLD             "hold"
 #define        ZFS_DELEG_PERM_RELEASE          "release"
 #define        ZFS_DELEG_PERM_DIFF             "diff"
+#define        ZFS_DELEG_PERM_BOOKMARK         "bookmark"
 
 #define        ZFS_NUM_DELEG_NOTES ZFS_DELEG_NOTE_NONE
 
@@ -3863,6 +3958,7 @@ static zfs_deleg_perm_tab_t zfs_deleg_perm_tbl[] = {
        { ZFS_DELEG_PERM_SEND, ZFS_DELEG_NOTE_SEND },
        { ZFS_DELEG_PERM_SHARE, ZFS_DELEG_NOTE_SHARE },
        { ZFS_DELEG_PERM_SNAPSHOT, ZFS_DELEG_NOTE_SNAPSHOT },
+       { ZFS_DELEG_PERM_BOOKMARK, ZFS_DELEG_NOTE_BOOKMARK },
 
        { ZFS_DELEG_PERM_GROUPQUOTA, ZFS_DELEG_NOTE_GROUPQUOTA },
        { ZFS_DELEG_PERM_GROUPUSED, ZFS_DELEG_NOTE_GROUPUSED },
@@ -6440,6 +6536,108 @@ zfs_do_diff(int argc, char **argv)
        return (err != 0);
 }
 
+/*
+ * zfs bookmark <fs@snap> <fs#bmark>
+ *
+ * Creates a bookmark with the given name from the given snapshot.
+ */
+static int
+zfs_do_bookmark(int argc, char **argv)
+{
+       char snapname[ZFS_MAXNAMELEN];
+       zfs_handle_t *zhp;
+       nvlist_t *nvl;
+       int ret = 0;
+       int c;
+
+       /* check options */
+       while ((c = getopt(argc, argv, "")) != -1) {
+               switch (c) {
+               case '?':
+                       (void) fprintf(stderr,
+                           gettext("invalid option '%c'\n"), optopt);
+                       goto usage;
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+
+       /* check number of arguments */
+       if (argc < 1) {
+               (void) fprintf(stderr, gettext("missing snapshot argument\n"));
+               goto usage;
+       }
+       if (argc < 2) {
+               (void) fprintf(stderr, gettext("missing bookmark argument\n"));
+               goto usage;
+       }
+
+       if (strchr(argv[1], '#') == NULL) {
+               (void) fprintf(stderr,
+                   gettext("invalid bookmark name '%s' -- "
+                   "must contain a '#'\n"), argv[1]);
+               goto usage;
+       }
+
+       if (argv[0][0] == '@') {
+               /*
+                * Snapshot name begins with @.
+                * Default to same fs as bookmark.
+                */
+               (void) strncpy(snapname, argv[1], sizeof (snapname));
+               *strchr(snapname, '#') = '\0';
+               (void) strlcat(snapname, argv[0], sizeof (snapname));
+       } else {
+               (void) strncpy(snapname, argv[0], sizeof (snapname));
+       }
+       zhp = zfs_open(g_zfs, snapname, ZFS_TYPE_SNAPSHOT);
+       if (zhp == NULL)
+               goto usage;
+       zfs_close(zhp);
+
+
+       nvl = fnvlist_alloc();
+       fnvlist_add_string(nvl, argv[1], snapname);
+       ret = lzc_bookmark(nvl, NULL);
+       fnvlist_free(nvl);
+
+       if (ret != 0) {
+               const char *err_msg;
+               char errbuf[1024];
+
+               (void) snprintf(errbuf, sizeof (errbuf),
+                   dgettext(TEXT_DOMAIN,
+                   "cannot create bookmark '%s'"), argv[1]);
+
+               switch (ret) {
+               case EXDEV:
+                       err_msg = "bookmark is in a different pool";
+                       break;
+               case EEXIST:
+                       err_msg = "bookmark exists";
+                       break;
+               case EINVAL:
+                       err_msg = "invalid argument";
+                       break;
+               case ENOTSUP:
+                       err_msg = "bookmark feature not enabled";
+                       break;
+               default:
+                       err_msg = "unknown error";
+                       break;
+               }
+               (void) fprintf(stderr, "%s: %s\n", errbuf,
+                   dgettext(TEXT_DOMAIN, err_msg));
+       }
+
+       return (ret);
+
+usage:
+       usage(B_FALSE);
+       return (-1);
+}
+
 int
 main(int argc, char **argv)
 {
index 5bc8b03ef4b3ba507884e163ec181c4f2e79abb1..561b34b8756a09ccfb7b211d5305f7e012d4873c 100644 (file)
@@ -21,7 +21,7 @@
 
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2013 by Delphix. All rights reserved.
  * Copyright (c) 2012, Joyent, Inc. All rights reserved.
  * Copyright (c) 2013 Steven Hartland. All rights reserved.
  * Copyright 2013 Nexenta Systems, Inc. All rights reserved.
@@ -204,6 +204,7 @@ extern int zpool_log_history(libzfs_handle_t *, const char *);
 extern int libzfs_errno(libzfs_handle_t *);
 extern const char *libzfs_error_action(libzfs_handle_t *);
 extern const char *libzfs_error_description(libzfs_handle_t *);
+extern int zfs_standard_error(libzfs_handle_t *, int, const char *);
 extern void libzfs_mnttab_init(libzfs_handle_t *);
 extern void libzfs_mnttab_fini(libzfs_handle_t *);
 extern void libzfs_mnttab_cache(libzfs_handle_t *, boolean_t);
@@ -557,6 +558,7 @@ extern int zfs_iter_filesystems(zfs_handle_t *, zfs_iter_f, void *);
 extern int zfs_iter_snapshots(zfs_handle_t *, boolean_t, zfs_iter_f, void *);
 extern int zfs_iter_snapshots_sorted(zfs_handle_t *, zfs_iter_f, void *);
 extern int zfs_iter_snapspec(zfs_handle_t *, const char *, zfs_iter_f, void *);
+extern int zfs_iter_bookmarks(zfs_handle_t *, zfs_iter_f, void *);
 
 typedef struct get_all_cb {
        zfs_handle_t    **cb_handles;
@@ -618,6 +620,7 @@ typedef boolean_t (snapfilter_cb_t)(zfs_handle_t *, void *);
 
 extern int zfs_send(zfs_handle_t *, const char *, const char *,
     sendflags_t *, int, snapfilter_cb_t, void *, nvlist_t **);
+extern int zfs_send_one(zfs_handle_t *, const char *, int);
 
 extern int zfs_promote(zfs_handle_t *);
 extern int zfs_hold(zfs_handle_t *, const char *, const char *,
@@ -687,6 +690,7 @@ extern zfs_handle_t *zfs_path_to_zhandle(libzfs_handle_t *, char *, zfs_type_t);
 extern boolean_t zfs_dataset_exists(libzfs_handle_t *, const char *,
     zfs_type_t);
 extern int zfs_spa_version(zfs_handle_t *, int *);
+extern boolean_t zfs_bookmark_exists(const char *path);
 extern int zfs_append_partition(char *path, size_t max_len);
 extern int zfs_resolve_shortname(const char *name, char *path, size_t pathlen);
 extern int zfs_strcmp_pathname(char *name, char *cmp_name, int wholedisk);
index 3642dc7afdfe338407df14f0805e46449be3e1f4..484a48afe2db2f15a97ecfa8c9a9d06f7cbfc5d2 100644 (file)
@@ -38,27 +38,27 @@ extern "C" {
 int libzfs_core_init(void);
 void libzfs_core_fini(void);
 
-int lzc_snapshot(nvlist_t *snaps, nvlist_t *props, nvlist_t **errlist);
-int lzc_create(const char *fsname, dmu_objset_type_t type, nvlist_t *props);
-int lzc_clone(const char *fsname, const char *origin, nvlist_t *props);
-int lzc_destroy_snaps(nvlist_t *snaps, boolean_t defer, nvlist_t **errlist);
+int lzc_snapshot(nvlist_t *, nvlist_t *, nvlist_t **);
+int lzc_create(const char *, dmu_objset_type_t, nvlist_t *);
+int lzc_clone(const char *, const char *, nvlist_t *);
+int lzc_destroy_snaps(nvlist_t *, boolean_t, nvlist_t **);
+int lzc_bookmark(nvlist_t *, nvlist_t **);
+int lzc_get_bookmarks(const char *, nvlist_t *, nvlist_t **);
+int lzc_destroy_bookmarks(nvlist_t *, nvlist_t **);
 
-int lzc_snaprange_space(const char *firstsnap, const char *lastsnap,
-    uint64_t *usedp);
+int lzc_snaprange_space(const char *, const char *, uint64_t *);
 
-int lzc_hold(nvlist_t *holds, int cleanup_fd, nvlist_t **errlist);
-int lzc_release(nvlist_t *holds, nvlist_t **errlist);
-int lzc_get_holds(const char *snapname, nvlist_t **holdsp);
+int lzc_hold(nvlist_t *, int, nvlist_t **);
+int lzc_release(nvlist_t *, nvlist_t **);
+int lzc_get_holds(const char *, nvlist_t **);
 
-int lzc_send(const char *snapname, const char *fromsnap, int fd);
-int lzc_receive(const char *snapname, nvlist_t *props, const char *origin,
-    boolean_t force, int fd);
-int lzc_send_space(const char *snapname, const char *fromsnap,
-    uint64_t *result);
+int lzc_send(const char *, const char *, int);
+int lzc_receive(const char *, nvlist_t *, const char *, boolean_t, int);
+int lzc_send_space(const char *, const char *, uint64_t *);
 
-boolean_t lzc_exists(const char *dataset);
+boolean_t lzc_exists(const char *);
 
-int lzc_rollback(const char *fsname, char *snapnamebuf, int snapnamelen);
+int lzc_rollback(const char *, char *, int);
 
 #ifdef __cplusplus
 }
index 5502455e09a1a3ecde8ed3980b6a220910201d84..e805e3ee70455f60b2ce76ff6f7b32dd84a5b136 100644 (file)
@@ -21,7 +21,7 @@
 
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2013 by Delphix. All rights reserved.
  */
 
 #ifndef        _LIBZFS_IMPL_H
@@ -190,6 +190,8 @@ int create_parents(libzfs_handle_t *, char *, int);
 boolean_t isa_child_of(const char *dataset, const char *parent);
 
 zfs_handle_t *make_dataset_handle(libzfs_handle_t *, const char *);
+zfs_handle_t *make_bookmark_handle(zfs_handle_t *, const char *,
+    nvlist_t *props);
 
 int zpool_open_silent(libzfs_handle_t *, const char *, zpool_handle_t **);
 
index 4115c6d8f9c89864569645a1d637740811d3c22c..90f3cce1b6b94da1ccdce1da47deeea914ddbd30 100644 (file)
@@ -17,6 +17,7 @@ COMMON_H = \
        $(top_srcdir)/include/sys/dmu_tx.h \
        $(top_srcdir)/include/sys/dmu_zfetch.h \
        $(top_srcdir)/include/sys/dnode.h \
+       $(top_srcdir)/include/sys/dsl_bookmark.h \
        $(top_srcdir)/include/sys/dsl_dataset.h \
        $(top_srcdir)/include/sys/dsl_deadlist.h \
        $(top_srcdir)/include/sys/dsl_deleg.h \
diff --git a/include/sys/dsl_bookmark.h b/include/sys/dsl_bookmark.h
new file mode 100644 (file)
index 0000000..3591986
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * CDDL HEADER START
+ *
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright (c) 2013 by Delphix. All rights reserved.
+ */
+
+#ifndef        _SYS_DSL_BOOKMARK_H
+#define        _SYS_DSL_BOOKMARK_H
+
+#include <sys/zfs_context.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct dsl_pool;
+struct dsl_dataset;
+
+/*
+ * On disk zap object.
+ */
+typedef struct zfs_bookmark_phys {
+       uint64_t zbm_guid;              /* guid of bookmarked dataset */
+       uint64_t zbm_creation_txg;      /* birth transaction group */
+       uint64_t zbm_creation_time;     /* bookmark creation time */
+} zfs_bookmark_phys_t;
+
+int dsl_bookmark_create(nvlist_t *, nvlist_t *);
+int dsl_get_bookmarks(const char *, nvlist_t *, nvlist_t *);
+int dsl_get_bookmarks_impl(dsl_dataset_t *, nvlist_t *, nvlist_t *);
+int dsl_bookmark_destroy(nvlist_t *, nvlist_t *);
+int dsl_bookmark_lookup(struct dsl_pool *, const char *,
+    struct dsl_dataset *, zfs_bookmark_phys_t *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_DSL_BOOKMARK_H */
index 27ef405e0b58cd8e52c87c649a88169dd3360c70..4979ae615b7388589d1c012967ee12798e2e572a 100644 (file)
@@ -75,6 +75,13 @@ struct dsl_pool;
  * They should be of the format <reverse-dns>:<field>.
  */
 
+/*
+ * This field's value is the object ID of a zap object which contains the
+ * bookmarks of this dataset.  If it is present, then this dataset is counted
+ * in the refcount of the SPA_FEATURES_BOOKMARKS feature.
+ */
+#define        DS_FIELD_BOOKMARK_NAMES "com.delphix:bookmarks"
+
 /*
  * DS_FLAG_CI_DATASET is set if the dataset contains a file system whose
  * name lookups should be performed case-insensitively.
@@ -127,6 +134,7 @@ typedef struct dsl_dataset {
 
        /* only used in syncing context, only valid for non-snapshots: */
        struct dsl_dataset *ds_prev;
+       uint64_t ds_bookmarks;  /* DMU_OTN_ZAP_METADATA */
 
        /* has internal locking: */
        dsl_deadlist_t ds_deadlist;
@@ -247,7 +255,8 @@ int dsl_dataset_set_refquota(const char *dsname, zprop_source_t source,
 int dsl_dataset_set_refreservation(const char *dsname, zprop_source_t source,
     uint64_t reservation);
 
-boolean_t dsl_dataset_is_before(dsl_dataset_t *later, dsl_dataset_t *earlier);
+boolean_t dsl_dataset_is_before(dsl_dataset_t *later, dsl_dataset_t *earlier,
+    uint64_t earlier_txg);
 void dsl_dataset_long_hold(dsl_dataset_t *ds, void *tag);
 void dsl_dataset_long_rele(dsl_dataset_t *ds, void *tag);
 boolean_t dsl_dataset_long_held(dsl_dataset_t *ds);
@@ -270,6 +279,7 @@ int dsl_dataset_snap_lookup(dsl_dataset_t *ds, const char *name,
 int dsl_dataset_snap_remove(dsl_dataset_t *ds, const char *name, dmu_tx_t *tx);
 void dsl_dataset_set_refreservation_sync_impl(dsl_dataset_t *ds,
     zprop_source_t source, uint64_t value, dmu_tx_t *tx);
+void dsl_dataset_zapify(dsl_dataset_t *ds, dmu_tx_t *tx);
 int dsl_dataset_rollback(const char *fsname, void *owner, nvlist_t *result);
 
 #ifdef ZFS_DEBUG
index 5842639aafbae1a2730ca9bcb4957bb13c36dd4f..59e8e055551ad0b5ca9ba447e3fa3361d00ca980 100644 (file)
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2013 by Delphix. All rights reserved.
  */
 
 #ifndef        _SYS_DSL_DELEG_H
@@ -56,6 +56,7 @@ extern "C" {
 #define        ZFS_DELEG_PERM_HOLD             "hold"
 #define        ZFS_DELEG_PERM_RELEASE          "release"
 #define        ZFS_DELEG_PERM_DIFF             "diff"
+#define        ZFS_DELEG_PERM_BOOKMARK         "bookmark"
 
 /*
  * Note: the names of properties that are marked delegatable are also
index 7b3ae6cff8f3cd1834fe3af0f4900420b1656262..53f2718ab125b6f8ca5e0449ce04d057b625360b 100644 (file)
@@ -46,10 +46,11 @@ extern "C" {
  * combined into masks that can be passed to various functions.
  */
 typedef enum {
-       ZFS_TYPE_FILESYSTEM     = 0x1,
-       ZFS_TYPE_SNAPSHOT       = 0x2,
-       ZFS_TYPE_VOLUME         = 0x4,
-       ZFS_TYPE_POOL           = 0x8
+       ZFS_TYPE_FILESYSTEM     = (1 << 0),
+       ZFS_TYPE_SNAPSHOT       = (1 << 1),
+       ZFS_TYPE_VOLUME         = (1 << 2),
+       ZFS_TYPE_POOL           = (1 << 3),
+       ZFS_TYPE_BOOKMARK       = (1 << 4)
 } zfs_type_t;
 
 typedef enum dmu_objset_type {
@@ -786,7 +787,7 @@ typedef struct ddt_histogram {
  */
 typedef enum zfs_ioc {
        /*
-        * Illumos - 69/128 numbers reserved.
+        * Illumos - 70/128 numbers reserved.
         */
        ZFS_IOC_FIRST = ('Z' << 8),
        ZFS_IOC = ZFS_IOC_FIRST,
@@ -857,6 +858,9 @@ typedef enum zfs_ioc {
        ZFS_IOC_SEND_NEW,
        ZFS_IOC_SEND_SPACE,
        ZFS_IOC_CLONE,
+       ZFS_IOC_BOOKMARK,
+       ZFS_IOC_GET_BOOKMARKS,
+       ZFS_IOC_DESTROY_BOOKMARKS,
 
        /*
         * Linux - 3/64 numbers reserved.
index bc774184a27b810a7665b9cd4008f770a15d73c8..acf23982973d9372aad39ac28478b729239828d3 100644 (file)
@@ -46,6 +46,7 @@ typedef enum spa_feature {
        SPA_FEATURE_ENABLED_TXG,
        SPA_FEATURE_HOLE_BIRTH,
        SPA_FEATURE_EXTENSIBLE_DATASET,
+       SPA_FEATURE_BOOKMARKS,
        SPA_FEATURES
 } spa_feature_t;
 
index 9997dffae7d0edf3031a580dc50674aa683c98b7..16133c59f33f395b2efb12013e5b88069f9d2c12 100644 (file)
@@ -21,6 +21,7 @@
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
  * Copyright 2010 Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 2013 by Delphix. All rights reserved.
  */
 
 #ifndef        _ZFS_DELEG_H
@@ -65,6 +66,7 @@ typedef enum {
        ZFS_DELEG_NOTE_HOLD,
        ZFS_DELEG_NOTE_RELEASE,
        ZFS_DELEG_NOTE_DIFF,
+       ZFS_DELEG_NOTE_BOOKMARK,
        ZFS_DELEG_NOTE_NONE
 } zfs_deleg_note_t;
 
index 7711da099be98d7b3f67ede5fd0fe27f1bfd56b3..cbefbaa0d5ab6fc11a543b1d5b32402e5ffcef12 100644 (file)
@@ -22,6 +22,9 @@
  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
+/*
+ * Copyright (c) 2013 by Delphix. All rights reserved.
+ */
 
 #ifndef        _ZFS_NAMECHECK_H
 #define        _ZFS_NAMECHECK_H
@@ -48,7 +51,7 @@ typedef enum {
 int pool_namecheck(const char *, namecheck_err_t *, char *);
 int dataset_namecheck(const char *, namecheck_err_t *, char *);
 int mountpoint_namecheck(const char *, namecheck_err_t *);
-int snapshot_namecheck(const char *, namecheck_err_t *, char *);
+int zfs_component_namecheck(const char *, namecheck_err_t *, char *);
 int permset_namecheck(const char *, namecheck_err_t *, char *);
 
 #ifdef __cplusplus
index 7cc1caaf589100abe8f6703c3225436105e4d74b..dbcb92102a08400fa82451782d249a69a17d3018 100644 (file)
@@ -259,7 +259,7 @@ zpool_handle(zfs_handle_t *zhp)
        int len;
        zpool_handle_t *zph;
 
-       len = strcspn(zhp->zfs_name, "/@") + 1;
+       len = strcspn(zhp->zfs_name, "/@#") + 1;
        pool_name = zfs_alloc(zhp->zfs_hdl, len);
        (void) strlcpy(pool_name, zhp->zfs_name, len);
 
@@ -546,6 +546,70 @@ zfs_handle_dup(zfs_handle_t *zhp_orig)
        return (zhp);
 }
 
+boolean_t
+zfs_bookmark_exists(const char *path)
+{
+       nvlist_t *bmarks;
+       nvlist_t *props;
+       char fsname[ZFS_MAXNAMELEN];
+       char *bmark_name;
+       char *pound;
+       int err;
+       boolean_t rv;
+
+
+       (void) strlcpy(fsname, path, sizeof (fsname));
+       pound = strchr(fsname, '#');
+       if (pound == NULL)
+               return (B_FALSE);
+
+       *pound = '\0';
+       bmark_name = pound + 1;
+       props = fnvlist_alloc();
+       err = lzc_get_bookmarks(fsname, props, &bmarks);
+       nvlist_free(props);
+       if (err != 0) {
+               nvlist_free(bmarks);
+               return (B_FALSE);
+       }
+
+       rv = nvlist_exists(bmarks, bmark_name);
+       nvlist_free(bmarks);
+       return (rv);
+}
+
+zfs_handle_t *
+make_bookmark_handle(zfs_handle_t *parent, const char *path,
+    nvlist_t *bmark_props)
+{
+       zfs_handle_t *zhp = calloc(sizeof (zfs_handle_t), 1);
+
+       if (zhp == NULL)
+               return (NULL);
+
+       /* Fill in the name. */
+       zhp->zfs_hdl = parent->zfs_hdl;
+       (void) strlcpy(zhp->zfs_name, path, sizeof (zhp->zfs_name));
+
+       /* Set the property lists. */
+       if (nvlist_dup(bmark_props, &zhp->zfs_props, 0) != 0) {
+               free(zhp);
+               return (NULL);
+       }
+
+       /* Set the types. */
+       zhp->zfs_head_type = parent->zfs_head_type;
+       zhp->zfs_type = ZFS_TYPE_BOOKMARK;
+
+       if ((zhp->zpool_hdl = zpool_handle(zhp)) == NULL) {
+               nvlist_free(zhp->zfs_props);
+               free(zhp);
+               return (NULL);
+       }
+
+       return (zhp);
+}
+
 /*
  * Opens the given snapshot, filesystem, or volume.   The 'types'
  * argument is a mask of acceptable types.  The function will print an
@@ -2295,6 +2359,9 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen,
                case ZFS_TYPE_SNAPSHOT:
                        str = "snapshot";
                        break;
+               case ZFS_TYPE_BOOKMARK:
+                       str = "bookmark";
+                       break;
                default:
                        abort();
                }
@@ -3152,6 +3219,19 @@ zfs_destroy(zfs_handle_t *zhp, boolean_t defer)
 {
        zfs_cmd_t zc = {"\0"};
 
+       if (zhp->zfs_type == ZFS_TYPE_BOOKMARK) {
+               nvlist_t *nv = fnvlist_alloc();
+               fnvlist_add_boolean(nv, zhp->zfs_name);
+               int error = lzc_destroy_bookmarks(nv, NULL);
+               fnvlist_free(nv);
+               if (error != 0) {
+                       return (zfs_standard_error_fmt(zhp->zfs_hdl, errno,
+                           dgettext(TEXT_DOMAIN, "cannot destroy '%s'"),
+                           zhp->zfs_name));
+               }
+               return (0);
+       }
+
        (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
 
        if (ZFS_IS_VOLUME(zhp)) {
@@ -3535,45 +3615,44 @@ typedef struct rollback_data {
        const char      *cb_target;             /* the snapshot */
        uint64_t        cb_create;              /* creation time reference */
        boolean_t       cb_error;
-       boolean_t       cb_dependent;
        boolean_t       cb_force;
 } rollback_data_t;
 
 static int
-rollback_destroy(zfs_handle_t *zhp, void *data)
+rollback_destroy_dependent(zfs_handle_t *zhp, void *data)
 {
        rollback_data_t *cbp = data;
+       prop_changelist_t *clp;
 
-       if (!cbp->cb_dependent) {
-               if (strcmp(zhp->zfs_name, cbp->cb_target) != 0 &&
-                   zfs_get_type(zhp) == ZFS_TYPE_SNAPSHOT &&
-                   zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG) >
-                   cbp->cb_create) {
+       /* We must destroy this clone; first unmount it */
+       clp = changelist_gather(zhp, ZFS_PROP_NAME, 0,
+           cbp->cb_force ? MS_FORCE: 0);
+       if (clp == NULL || changelist_prefix(clp) != 0) {
+               cbp->cb_error = B_TRUE;
+               zfs_close(zhp);
+               return (0);
+       }
+       if (zfs_destroy(zhp, B_FALSE) != 0)
+               cbp->cb_error = B_TRUE;
+       else
+               changelist_remove(clp, zhp->zfs_name);
+       (void) changelist_postfix(clp);
+       changelist_free(clp);
 
-                       cbp->cb_dependent = B_TRUE;
-                       cbp->cb_error |= zfs_iter_dependents(zhp, B_FALSE,
-                           rollback_destroy, cbp);
-                       cbp->cb_dependent = B_FALSE;
+       zfs_close(zhp);
+       return (0);
+}
 
-                       cbp->cb_error |= zfs_destroy(zhp, B_FALSE);
-               }
-       } else {
-               /* We must destroy this clone; first unmount it */
-               prop_changelist_t *clp;
+static int
+rollback_destroy(zfs_handle_t *zhp, void *data)
+{
+       rollback_data_t *cbp = data;
 
-               clp = changelist_gather(zhp, ZFS_PROP_NAME, 0,
-                   cbp->cb_force ? MS_FORCE: 0);
-               if (clp == NULL || changelist_prefix(clp) != 0) {
-                       cbp->cb_error = B_TRUE;
-                       zfs_close(zhp);
-                       return (0);
-               }
-               if (zfs_destroy(zhp, B_FALSE) != 0)
-                       cbp->cb_error = B_TRUE;
-               else
-                       changelist_remove(clp, zhp->zfs_name);
-               (void) changelist_postfix(clp);
-               changelist_free(clp);
+       if (zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG) > cbp->cb_create) {
+               cbp->cb_error |= zfs_iter_dependents(zhp, B_FALSE,
+                   rollback_destroy_dependent, cbp);
+
+               cbp->cb_error |= zfs_destroy(zhp, B_FALSE);
        }
 
        zfs_close(zhp);
@@ -3584,8 +3663,8 @@ rollback_destroy(zfs_handle_t *zhp, void *data)
  * Given a dataset, rollback to a specific snapshot, discarding any
  * data changes since then and making it the active dataset.
  *
- * Any snapshots more recent than the target are destroyed, along with
- * their dependents.
+ * Any snapshots and bookmarks more recent than the target are
+ * destroyed, along with their dependents (i.e. clones).
  */
 int
 zfs_rollback(zfs_handle_t *zhp, zfs_handle_t *snap, boolean_t force)
@@ -3605,7 +3684,8 @@ zfs_rollback(zfs_handle_t *zhp, zfs_handle_t *snap, boolean_t force)
        cb.cb_force = force;
        cb.cb_target = snap->zfs_name;
        cb.cb_create = zfs_prop_get_int(snap, ZFS_PROP_CREATETXG);
-       (void) zfs_iter_children(zhp, rollback_destroy, &cb);
+       (void) zfs_iter_snapshots(zhp, B_FALSE, rollback_destroy, &cb);
+       (void) zfs_iter_bookmarks(zhp, rollback_destroy, &cb);
 
        if (cb.cb_error)
                return (-1);
index e527bdcc5c213d245872081a1857a1bb14542026..e5140f2e17b405bc4568632478febec51d3bb7ed 100644 (file)
@@ -21,7 +21,7 @@
 
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2013 by Delphix. All rights reserved.
  * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
  */
 
@@ -144,7 +144,8 @@ zfs_iter_snapshots(zfs_handle_t *zhp, boolean_t simple, zfs_iter_f func,
        zfs_handle_t *nzhp;
        int ret;
 
-       if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT)
+       if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT ||
+           zhp->zfs_type == ZFS_TYPE_BOOKMARK)
                return (0);
 
        zc.zc_simple = simple;
@@ -170,6 +171,60 @@ zfs_iter_snapshots(zfs_handle_t *zhp, boolean_t simple, zfs_iter_f func,
        return ((ret < 0) ? ret : 0);
 }
 
+/*
+ * Iterate over all bookmarks
+ */
+int
+zfs_iter_bookmarks(zfs_handle_t *zhp, zfs_iter_f func, void *data)
+{
+       zfs_handle_t *nzhp;
+       nvlist_t *props = NULL;
+       nvlist_t *bmarks = NULL;
+       int err;
+       nvpair_t *pair;
+
+       if ((zfs_get_type(zhp) & (ZFS_TYPE_SNAPSHOT | ZFS_TYPE_BOOKMARK)) != 0)
+               return (0);
+
+       /* Setup the requested properties nvlist. */
+       props = fnvlist_alloc();
+       fnvlist_add_boolean(props, zfs_prop_to_name(ZFS_PROP_GUID));
+       fnvlist_add_boolean(props, zfs_prop_to_name(ZFS_PROP_CREATETXG));
+       fnvlist_add_boolean(props, zfs_prop_to_name(ZFS_PROP_CREATION));
+
+       /* Allocate an nvlist to hold the bookmarks. */
+       bmarks = fnvlist_alloc();
+
+       if ((err = lzc_get_bookmarks(zhp->zfs_name, props, &bmarks)) != 0)
+               goto out;
+
+       for (pair = nvlist_next_nvpair(bmarks, NULL);
+           pair != NULL; pair = nvlist_next_nvpair(bmarks, pair)) {
+               char name[ZFS_MAXNAMELEN];
+               char *bmark_name;
+               nvlist_t *bmark_props;
+
+               bmark_name = nvpair_name(pair);
+               bmark_props = fnvpair_value_nvlist(pair);
+
+               (void) snprintf(name, sizeof (name), "%s#%s", zhp->zfs_name,
+                   bmark_name);
+
+               nzhp = make_bookmark_handle(zhp, name, bmark_props);
+               if (nzhp == NULL)
+                       continue;
+
+               if ((err = func(nzhp, data)) != 0)
+                       goto out;
+       }
+
+out:
+       fnvlist_free(props);
+       fnvlist_free(bmarks);
+
+       return (err);
+}
+
 /*
  * Routines for dealing with the sorted snapshot functionality
  */
@@ -404,13 +459,13 @@ static int
 iter_dependents_cb(zfs_handle_t *zhp, void *arg)
 {
        iter_dependents_arg_t *ida = arg;
-       int err;
+       int err = 0;
        boolean_t first = ida->first;
        ida->first = B_FALSE;
 
        if (zhp->zfs_type == ZFS_TYPE_SNAPSHOT) {
                err = zfs_iter_clones(zhp, iter_dependents_cb, ida);
-       } else {
+       } else if (zhp->zfs_type != ZFS_TYPE_BOOKMARK) {
                iter_stack_frame_t isf;
                iter_stack_frame_t *f;
 
index 12ac9bdf9b3a75958dda5c34e7fdc84ffd81da52..a11d4bf36710138e7ca2f38eab5fa2e22a2634b5 100644 (file)
@@ -21,7 +21,7 @@
 
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2013 by Delphix. All rights reserved.
  * Copyright (c) 2012, Joyent, Inc. All rights reserved.
  * Copyright (c) 2012 Pawel Jakub Dawidek <pawel@dawidek.net>.
  * All rights reserved
@@ -1615,6 +1615,60 @@ err_out:
        return (err);
 }
 
+int
+zfs_send_one(zfs_handle_t *zhp, const char *from, int fd)
+{
+       int err;
+       libzfs_handle_t *hdl = zhp->zfs_hdl;
+
+       char errbuf[1024];
+       (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
+           "warning: cannot send '%s'"), zhp->zfs_name);
+
+       err = lzc_send(zhp->zfs_name, from, fd);
+       if (err != 0) {
+               switch (errno) {
+               case EXDEV:
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "not an earlier snapshot from the same fs"));
+                       return (zfs_error(hdl, EZFS_CROSSTARGET, errbuf));
+
+               case ENOENT:
+               case ESRCH:
+                       if (lzc_exists(zhp->zfs_name)) {
+                               zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                                   "incremental source (%s) does not exist"),
+                                   from);
+                       }
+                       return (zfs_error(hdl, EZFS_NOENT, errbuf));
+
+               case EBUSY:
+                       zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+                           "target is busy; if a filesystem, "
+                           "it must not be mounted"));
+                       return (zfs_error(hdl, EZFS_BUSY, errbuf));
+
+               case EDQUOT:
+               case EFBIG:
+               case EIO:
+               case ENOLINK:
+               case ENOSPC:
+               case ENOSTR:
+               case ENXIO:
+               case EPIPE:
+               case ERANGE:
+               case EFAULT:
+               case EROFS:
+                       zfs_error_aux(hdl, strerror(errno));
+                       return (zfs_error(hdl, EZFS_BADBACKUP, errbuf));
+
+               default:
+                       return (zfs_standard_error(hdl, errno, errbuf));
+               }
+       }
+       return (err != 0);
+}
+
 /*
  * Routines specific to "zfs recv"
  */
index 19faa75bd11b53481d3a5c2bb351900adc135d92..0c05bbd275debf64131a83ad63a514f0eda9d990 100644 (file)
@@ -439,18 +439,30 @@ lzc_get_holds(const char *snapname, nvlist_t **holdsp)
 }
 
 /*
- * If fromsnap is NULL, a full (non-incremental) stream will be sent.
+ *
+ * "snapname" is the full name of the snapshot to send (e.g. "pool/fs@snap")
+ *
+ * If "from" is NULL, a full (non-incremental) stream will be sent.
+ * If "from" is non-NULL, it must be the full name of a snapshot or
+ * bookmark to send an incremental from (e.g. "pool/fs@earlier_snap" or
+ * "pool/fs#earlier_bmark").  If non-NULL, the specified snapshot or
+ * bookmark must represent an earlier point in the history of "snapname").
+ * It can be an earlier snapshot in the same filesystem or zvol as "snapname",
+ * or it can be the origin of "snapname"'s filesystem, or an earlier
+ * snapshot in the origin, etc.
+ *
+ * "fd" is the file descriptor to write the send stream to.
  */
 int
-lzc_send(const char *snapname, const char *fromsnap, int fd)
+lzc_send(const char *snapname, const char *from, int fd)
 {
        nvlist_t *args;
        int err;
 
        args = fnvlist_alloc();
        fnvlist_add_int32(args, "fd", fd);
-       if (fromsnap != NULL)
-               fnvlist_add_string(args, "fromsnap", fromsnap);
+       if (from != NULL)
+               fnvlist_add_string(args, "fromsnap", from);
        err = lzc_ioctl(ZFS_IOC_SEND_NEW, snapname, args, NULL);
        nvlist_free(args);
        return (err);
@@ -605,3 +617,97 @@ lzc_rollback(const char *fsname, char *snapnamebuf, int snapnamelen)
        }
        return (err);
 }
+
+/*
+ * Creates bookmarks.
+ *
+ * The bookmarks nvlist maps from name of the bookmark (e.g. "pool/fs#bmark") to
+ * the name of the snapshot (e.g. "pool/fs@snap").  All the bookmarks and
+ * snapshots must be in the same pool.
+ *
+ * The returned results nvlist will have an entry for each bookmark that failed.
+ * The value will be the (int32) error code.
+ *
+ * The return value will be 0 if all bookmarks were created, otherwise it will
+ * be the errno of a (undetermined) bookmarks that failed.
+ */
+int
+lzc_bookmark(nvlist_t *bookmarks, nvlist_t **errlist)
+{
+       nvpair_t *elem;
+       int error;
+       char pool[MAXNAMELEN];
+
+       /* determine the pool name */
+       elem = nvlist_next_nvpair(bookmarks, NULL);
+       if (elem == NULL)
+               return (0);
+       (void) strlcpy(pool, nvpair_name(elem), sizeof (pool));
+       pool[strcspn(pool, "/#")] = '\0';
+
+       error = lzc_ioctl(ZFS_IOC_BOOKMARK, pool, bookmarks, errlist);
+
+       return (error);
+}
+
+/*
+ * Retrieve bookmarks.
+ *
+ * Retrieve the list of bookmarks for the given file system. The props
+ * parameter is an nvlist of property names (with no values) that will be
+ * returned for each bookmark.
+ *
+ * The following are valid properties on bookmarks, all of which are numbers
+ * (represented as uint64 in the nvlist)
+ *
+ * "guid" - globally unique identifier of the snapshot it refers to
+ * "createtxg" - txg when the snapshot it refers to was created
+ * "creation" - timestamp when the snapshot it refers to was created
+ *
+ * The format of the returned nvlist as follows:
+ * <short name of bookmark> -> {
+ *     <name of property> -> {
+ *         "value" -> uint64
+ *     }
+ *  }
+ */
+int
+lzc_get_bookmarks(const char *fsname, nvlist_t *props, nvlist_t **bmarks)
+{
+       return (lzc_ioctl(ZFS_IOC_GET_BOOKMARKS, fsname, props, bmarks));
+}
+
+/*
+ * Destroys bookmarks.
+ *
+ * The keys in the bmarks nvlist are the bookmarks to be destroyed.
+ * They must all be in the same pool.  Bookmarks are specified as
+ * <fs>#<bmark>.
+ *
+ * Bookmarks that do not exist will be silently ignored.
+ *
+ * The return value will be 0 if all bookmarks that existed were destroyed.
+ *
+ * Otherwise the return value will be the errno of a (undetermined) bookmark
+ * that failed, no bookmarks will be destroyed, and the errlist will have an
+ * entry for each bookmarks that failed.  The value in the errlist will be
+ * the (int32) error code.
+ */
+int
+lzc_destroy_bookmarks(nvlist_t *bmarks, nvlist_t **errlist)
+{
+       nvpair_t *elem;
+       int error;
+       char pool[MAXNAMELEN];
+
+       /* determine the pool name */
+       elem = nvlist_next_nvpair(bmarks, NULL);
+       if (elem == NULL)
+               return (0);
+       (void) strlcpy(pool, nvpair_name(elem), sizeof (pool));
+       pool[strcspn(pool, "/#")] = '\0';
+
+       error = lzc_ioctl(ZFS_IOC_DESTROY_BOOKMARKS, pool, bmarks, errlist);
+
+       return (error);
+}
index bc9a85d992fd82ba19c04e92ac0cc6b5f63444f0..b960850fbdbe2e59f7cbedb79fe099c8bd4c36f4 100644 (file)
@@ -38,6 +38,7 @@ libzpool_la_SOURCES = \
        $(top_srcdir)/module/zfs/dmu_zfetch.c \
        $(top_srcdir)/module/zfs/dnode.c \
        $(top_srcdir)/module/zfs/dnode_sync.c \
+       $(top_srcdir)/module/zfs/dsl_bookmark.c \
        $(top_srcdir)/module/zfs/dsl_dataset.c \
        $(top_srcdir)/module/zfs/dsl_deadlist.c \
        $(top_srcdir)/module/zfs/dsl_deleg.c \
index 60437c9bfa3c9d0170d63f3f362dc9ab125fc691..edcdb364dce6824d0955b6e4fa6e7d9598c1bb21 100644 (file)
@@ -223,11 +223,11 @@ When the \fBlz4_compress\fR feature is set to \fBenabled\fR, the
 administrator can turn on \fBlz4\fR compression on any dataset on the
 pool using the \fBzfs\fR(8) command. Please note that doing so will
 immediately activate the \fBlz4_compress\fR feature on the underlying
-pool (even before any data is written). Since this feature is not
-read-only compatible, this operation will render the pool unimportable
-on systems without support for the \fBlz4_compress\fR feature. At the
-moment, this operation cannot be reversed. Booting off of
-\fBlz4\fR-compressed root pools is supported.
+pool (even before any data is written), and the feature will not be
+deactivated. Since this feature is not read-only compatible, this
+operation will render the pool unimportable on systems without support
+for the \fBlz4_compress\fR feature. Booting off of \fBlz4\fR-compressed
+root pools is supported.
 .RE
 
 .sp
@@ -273,6 +273,27 @@ this feature are destroyed.
 
 .RE
 
+.sp
+.ne 2
+.na
+\fB\fBbookmarks\fR\fR
+.ad
+.RS 4n
+.TS
+l l .
+GUID   com.delphix:bookmarks
+READ\-ONLY COMPATIBLE  yes
+DEPENDENCIES   extensible_dataset
+.TE
+
+This feature enables use of the \fBzfs bookmark\fR subcommand.
+
+This feature is \fBactive\fR while any bookmarks exist in the pool.
+All bookmarks in the pool can be listed by running
+\fBzfs list -t bookmark -r \fIpoolname\fR\fR.
+
+.RE
+
 .sp
 .ne 2
 .na
index d6c744ef7aa60e41a2491fabb29ccd678f9a4d7d..09d63ba5a58e04530ff838e91071f5e3bdb9c3d0 100644 (file)
@@ -22,7 +22,7 @@
 .\"
 .\" Copyright (c) 2009 Sun Microsystems, Inc. All Rights Reserved.
 .\" Copyright 2011 Joshua M. Clulow <josh@sysmgr.org>
-.\" Copyright (c) 2012 by Delphix. All rights reserved.
+.\" Copyright (c) 2013 by Delphix. All rights reserved.
 .\" Copyright (c) 2012, Joyent, Inc. All rights reserved.
 .\" Copyright 2012 Nexenta Systems, Inc. All Rights Reserved.
 .\" Copyright (c) 2013 by Saso Kiselkov. All rights reserved.
@@ -56,6 +56,11 @@ zfs \- configures ZFS file systems
 \fBzfs\fR \fBdestroy\fR [\fB-dnpRrv\fR] \fIfilesystem\fR|\fIvolume\fR@\fIsnap\fR[%\fIsnap\fR][,...]
 .fi
 
+.LP
+.nf
+\fBzfs\fR \fBdestroy\fR \fIfilesystem\fR|\fIvolume\fR#\fIbookmark\fR
+.fi
+
 .LP
 .nf
 \fBzfs\fR \fBsnapshot | snap\fR [\fB-r\fR] [\fB-o\fR \fIproperty\fR=\fIvalue\fR] ... 
@@ -162,11 +167,21 @@ zfs \- configures ZFS file systems
 \fBzfs\fR \fBunshare\fR \fB-a\fR \fIfilesystem\fR|\fImountpoint\fR
 .fi
 
+.LP
+.nf
+\fBzfs\fR \fBbookmark\fR \fIsnapshot\fR \fIbookmark\fR
+.fi
+
 .LP
 .nf
 \fBzfs\fR \fBsend\fR [\fB-DnPpRv\fR] [\fB-\fR[\fBiI\fR] \fIsnapshot\fR] \fIsnapshot\fR
 .fi
 
+.LP
+.nf
+\fBzfs\fR \fBsend\fR [\fB-i \fIsnapshot\fR|\fIbookmark\fR]\fR \fIfilesystem\fR|\fIvolume\fR|\fIsnapshot\fR
+.fi
+
 .LP
 .nf
 \fBzfs\fR \fBreceive | recv\fR [\fB-vnFu\fR] \fIfilesystem\fR|\fIvolume\fR|\fIsnapshot\fR
@@ -1684,6 +1699,17 @@ behavior for mounted file systems in use.
 .ne 2
 .mk
 .na
+\fBzfs destroy\fR \fIfilesystem\fR|\fIvolume\fR#\fIbookmark\fR
+.ad
+.sp .6
+.RS 4n
+The given bookmark is destroyed.
+
+.RE
+
+.sp
+.ne 2
+.na
 \fB\fBzfs snapshot\fR [\fB-r\fR] [\fB-o\fR \fIproperty\fR=\fIvalue\fR] ... \fIfilesystem@snapname\fR|\fIvolume@snapname\fR\fR ...
 .ad
 .sp .6
@@ -1721,9 +1747,9 @@ Sets the specified property; see \fBzfs create\fR for details.
 .ad
 .sp .6
 .RS 4n
-Roll back the given dataset to a previous snapshot. When a dataset is rolled back, all data that has changed since the snapshot is discarded, and the dataset reverts to the state at the time of the snapshot. By default, the command refuses to roll back to a snapshot other than the most recent one. In order to do so, all intermediate snapshots must be destroyed by specifying the \fB-r\fR option.
+Roll back the given dataset to a previous snapshot. When a dataset is rolled back, all data that has changed since the snapshot is discarded, and the dataset reverts to the state at the time of the snapshot. By default, the command refuses to roll back to a snapshot other than the most recent one. In order to do so, all intermediate snapshots and bookmarks must be destroyed by specifying the \fB-r\fR option.
 .sp
-The \fB-rR\fR options do not recursively destroy the child snapshots of a recursive snapshot. Only the top-level recursive snapshot is destroyed by either of these options. To completely roll back a recursive snapshot, you must rollback the individual child snapshots.
+The \fB-rR\fR options do not recursively destroy the child snapshots of a recursive snapshot. Only direct snapshots of the specified filesystem are destroyed by either of these options. To completely roll back a recursive snapshot, you must rollback the individual child snapshots.
 .sp
 .ne 2
 .mk
@@ -1732,7 +1758,7 @@ The \fB-rR\fR options do not recursively destroy the child snapshots of a recurs
 .ad
 .sp .6
 .RS 4n
-Recursively destroy any snapshots more recent than the one specified.
+Destroy any snapshots and bookmarks more recent than the one specified.
 .RE
 
 .sp
@@ -1743,7 +1769,7 @@ Recursively destroy any snapshots more recent than the one specified.
 .ad
 .sp .6
 .RS 4n
-Recursively destroy any more recent snapshots, as well as any clones of those snapshots.
+Recursively destroy any more recent snapshots and bookmarks, as well as any clones of those snapshots.
 .RE
 
 .sp
@@ -1999,7 +2025,7 @@ Same as the \fB-s\fR option, but sorts by property in descending order.
 .ad
 .sp .6
 .RS 4n
-A comma-separated list of types to display, where \fItype\fR is one of \fBfilesystem\fR, \fBsnapshot\fR, \fBsnap\fR, \fBvolume\fR, or \fBall\fR. For example, specifying \fB-t snapshot\fR displays only snapshots.
+A comma-separated list of types to display, where \fItype\fR is one of \fBfilesystem\fR, \fBsnapshot\fR, \fBsnap\fR, \fBvolume\fR, \fBbookmark\fR, or \fBall\fR. For example, specifying \fB-t snapshot\fR displays only snapshots.
 .RE
 
 .RE
@@ -2037,7 +2063,7 @@ Displays properties for the given datasets. If no datasets are specified, then t
 
 All columns are displayed by default, though this can be controlled by using the \fB-o\fR option. This command takes a comma-separated list of properties as described in the "Native Properties" and "User Properties" sections.
 .sp
-The special value \fBall\fR can be used to display all properties that apply to the given dataset's type (filesystem, volume, or snapshot).
+The special value \fBall\fR can be used to display all properties that apply to the given dataset's type (filesystem, volume snapshot, or bookmark).
 .sp
 .ne 2
 .mk
@@ -2525,6 +2551,24 @@ Unshare the specified filesystem. The command can also be given a path to a \fBZ
 .ne 2
 .mk
 .na
+\fB\fBzfs bookmark\fR \fIsnapshot\fR \fIbookmark\fR\fR
+.ad
+.sp .6
+.RS 4n
+Creates a bookmark of the given snapshot.  Bookmarks mark the point in time
+when the snapshot was created, and can be used as the incremental source for
+a \fBzfs send\fR command.
+.sp
+This feature must be enabled to be used.
+See \fBzpool-features\fR(5) for details on ZFS feature flags and the
+\fBbookmarks\fR feature.
+.RE
+
+
+.RE
+.sp
+.ne 2
+.na
 \fBzfs send\fR [\fB-DnPpRv\fR] [\fB-\fR[\fBiI\fR] \fIsnapshot\fR] \fIsnapshot\fR
 .ad
 .sp .6
@@ -2538,7 +2582,7 @@ Creates a stream representation of the second \fIsnapshot\fR, which is written t
 .ad
 .sp .6
 .RS 4n
-Generate an incremental stream from the first \fIsnapshot\fR to the second \fIsnapshot\fR. The incremental source (the first \fIsnapshot\fR) can be specified as the last component of the snapshot name (for example, the part after the \fB@\fR), and it is assumed to be from the same file system as the second \fIsnapshot\fR.
+Generate an incremental stream from the first \fIsnapshot\fR (the incremental source) to the second \fIsnapshot\fR (the incremental target).  The incremental source can be specified as the last component of the snapshot name (the \fB@\fR character and following) and it is assumed to be from the same file system as the incremental target.
 .sp
 If the destination is a clone, the source may be the origin snapshot, which must be fully specified (for example, \fBpool/fs@origin\fR, not just \fB@origin\fR).
 .RE
@@ -2551,7 +2595,7 @@ If the destination is a clone, the source may be the origin snapshot, which must
 .ad
 .sp .6
 .RS 4n
-Generate a stream package that sends all intermediary snapshots from the first snapshot to the second snapshot. For example, \fB-I @a fs@d\fR is similar to \fB-i @a fs@b; -i @b fs@c; -i @c fs@d\fR. The incremental source snapshot may be specified as with the \fB-i\fR option.
+Generate a stream package that sends all intermediary snapshots from the first snapshot to the second snapshot. For example, \fB-I @a fs@d\fR is similar to \fB-i @a fs@b; -i @b fs@c; -i @c fs@d\fR. The incremental source may be specified as with the \fB-i\fR option.
 .RE
 
 .sp
@@ -2626,6 +2670,39 @@ includes a per-second report of how much data has been sent.
 The format of the stream is committed. You will be able to receive your streams on future versions of \fBZFS\fR.
 .RE
 
+.RE
+.sp
+.ne 2
+.na
+\fBzfs send\fR [\fB-i\fR \fIsnapshot\fR|\fIbookmark\fR] \fIfilesystem\fR|\fIvolume\fR|\fIsnapshot\fR
+.ad
+.sp .6
+.RS 4n
+Generate a send stream, which may be of a filesystem, and may be
+incremental from a bookmark.  If the destination is a filesystem or volume,
+the pool must be read-only, or the filesystem must not be mounted.  When the
+stream generated from a filesystem or volume is received, the default snapshot
+name will be "--head--".
+
+.sp
+.ne 2
+.na
+\fB-i\fR \fIsnapshot\fR|\fIbookmark\fR
+.ad
+.sp .6
+.RS 4n
+Generate an incremental send stream.  The incremental source must be an earlier
+snapshot in the destination's history. It will commonly be an earlier
+snapshot in the destination's filesystem, in which case it can be
+specified as the last component of the name (the \fB#\fR or \fB@\fR character
+and following).
+.sp
+If the incremental target is a clone, the incremental source can
+be the origin snapshot, or an earlier snapshot in the origin's filesystem,
+or the origin's origin, etc.
+.RE
+
+.RE
 .sp
 .ne 2
 .mk
@@ -2660,6 +2737,7 @@ The \fB-d\fR and \fB-e\fR options cause the file system name of the target snaps
 Discard the first element of the sent snapshot's file system name, using the remaining elements to determine the name of the target file system for the new snapshot as described in the paragraph above.
 .RE
 
+
 .sp
 .ne 2
 .na
index 9de61790c5554ba5cb3e062a39e9ecaf2b19aaf1..a152b4e76e0f55399349f280c3394e55fbfc13d2 100644 (file)
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
  * Copyright 2010 Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 2013 by Delphix. All rights reserved.
  */
 
+#include <sys/zfs_context.h>
+
 #if defined(_KERNEL)
 #include <sys/systm.h>
 #include <sys/sunddi.h>
 #include <libnvpair.h>
 #include <ctype.h>
 #endif
-/* XXX includes zfs_context.h, so why bother with the above? */
 #include <sys/dsl_deleg.h>
 #include "zfs_prop.h"
 #include "zfs_deleg.h"
 #include "zfs_namecheck.h"
 
-/*
- * permission table
- *
- * Keep this table in sorted order
- *
- * This table is used for displaying all permissions for
- * zfs allow
- */
-
 zfs_deleg_perm_tab_t zfs_deleg_perm_tab[] = {
-       {ZFS_DELEG_PERM_ALLOW, ZFS_DELEG_NOTE_ALLOW},
-       {ZFS_DELEG_PERM_CLONE, ZFS_DELEG_NOTE_CLONE },
-       {ZFS_DELEG_PERM_CREATE, ZFS_DELEG_NOTE_CREATE },
-       {ZFS_DELEG_PERM_DESTROY, ZFS_DELEG_NOTE_DESTROY },
-       {ZFS_DELEG_PERM_MOUNT, ZFS_DELEG_NOTE_MOUNT },
-       {ZFS_DELEG_PERM_PROMOTE, ZFS_DELEG_NOTE_PROMOTE },
-       {ZFS_DELEG_PERM_RECEIVE, ZFS_DELEG_NOTE_RECEIVE },
-       {ZFS_DELEG_PERM_RENAME, ZFS_DELEG_NOTE_RENAME },
-       {ZFS_DELEG_PERM_ROLLBACK, ZFS_DELEG_NOTE_ROLLBACK },
-       {ZFS_DELEG_PERM_SNAPSHOT, ZFS_DELEG_NOTE_SNAPSHOT },
-       {ZFS_DELEG_PERM_SHARE, ZFS_DELEG_NOTE_SHARE },
-       {ZFS_DELEG_PERM_SEND, ZFS_DELEG_NOTE_SEND },
-       {ZFS_DELEG_PERM_USERPROP, ZFS_DELEG_NOTE_USERPROP },
-       {ZFS_DELEG_PERM_USERQUOTA, ZFS_DELEG_NOTE_USERQUOTA },
-       {ZFS_DELEG_PERM_GROUPQUOTA, ZFS_DELEG_NOTE_GROUPQUOTA },
-       {ZFS_DELEG_PERM_USERUSED, ZFS_DELEG_NOTE_USERUSED },
-       {ZFS_DELEG_PERM_GROUPUSED, ZFS_DELEG_NOTE_GROUPUSED },
-       {ZFS_DELEG_PERM_HOLD, ZFS_DELEG_NOTE_HOLD },
-       {ZFS_DELEG_PERM_RELEASE, ZFS_DELEG_NOTE_RELEASE },
-       {ZFS_DELEG_PERM_DIFF, ZFS_DELEG_NOTE_DIFF},
-       {NULL, ZFS_DELEG_NOTE_NONE }
+       {ZFS_DELEG_PERM_ALLOW},
+       {ZFS_DELEG_PERM_BOOKMARK},
+       {ZFS_DELEG_PERM_CLONE},
+       {ZFS_DELEG_PERM_CREATE},
+       {ZFS_DELEG_PERM_DESTROY},
+       {ZFS_DELEG_PERM_DIFF},
+       {ZFS_DELEG_PERM_MOUNT},
+       {ZFS_DELEG_PERM_PROMOTE},
+       {ZFS_DELEG_PERM_RECEIVE},
+       {ZFS_DELEG_PERM_RENAME},
+       {ZFS_DELEG_PERM_ROLLBACK},
+       {ZFS_DELEG_PERM_SNAPSHOT},
+       {ZFS_DELEG_PERM_SHARE},
+       {ZFS_DELEG_PERM_SEND},
+       {ZFS_DELEG_PERM_USERPROP},
+       {ZFS_DELEG_PERM_USERQUOTA},
+       {ZFS_DELEG_PERM_GROUPQUOTA},
+       {ZFS_DELEG_PERM_USERUSED},
+       {ZFS_DELEG_PERM_GROUPUSED},
+       {ZFS_DELEG_PERM_HOLD},
+       {ZFS_DELEG_PERM_RELEASE},
+       {NULL}
 };
 
 static int
index 8508bc73442c40cba0d5c36280880c12f42eff5d..ff724be588ccd52c0bfcde73db47950e586a8b0c 100644 (file)
@@ -22,6 +22,9 @@
  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
+/*
+ * Copyright (c) 2013 by Delphix. All rights reserved.
+ */
 
 /*
  * Common name validation routines for ZFS.  These routines are shared by the
@@ -62,7 +65,7 @@ valid_char(char c)
  *     [-_.: ]
  */
 int
-snapshot_namecheck(const char *path, namecheck_err_t *why, char *what)
+zfs_component_namecheck(const char *path, namecheck_err_t *why, char *what)
 {
        const char *loc;
 
@@ -113,7 +116,7 @@ permset_namecheck(const char *path, namecheck_err_t *why, char *what)
                return (-1);
        }
 
-       return (snapshot_namecheck(&path[1], why, what));
+       return (zfs_component_namecheck(&path[1], why, what));
 }
 
 /*
@@ -372,7 +375,7 @@ pool_namecheck(const char *pool, namecheck_err_t *why, char *what)
 }
 
 #if defined(_KERNEL) && defined(HAVE_SPL)
-EXPORT_SYMBOL(snapshot_namecheck);
 EXPORT_SYMBOL(pool_namecheck);
 EXPORT_SYMBOL(dataset_namecheck);
+EXPORT_SYMBOL(zfs_component_namecheck);
 #endif
index d81ff3bc622038779e0ec512b92fcf9fc5eb08fe..92cfa282b3ae7140f23e61f72de2e60905e5ac56 100644 (file)
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2013 by Delphix. All rights reserved.
  * Copyright (c) 2013 by Saso Kiselkov. All rights reserved.
  */
 
@@ -327,7 +327,8 @@ zfs_prop_init(void)
            PROP_INHERIT, ZFS_TYPE_FILESYSTEM, "on | off | share(1M) options",
            "SHARENFS");
        zprop_register_string(ZFS_PROP_TYPE, "type", NULL, PROP_READONLY,
-           ZFS_TYPE_DATASET, "filesystem | volume | snapshot", "TYPE");
+           ZFS_TYPE_DATASET | ZFS_TYPE_BOOKMARK,
+           "filesystem | volume | snapshot | bookmark", "TYPE");
        zprop_register_string(ZFS_PROP_SHARESMB, "sharesmb", "off",
            PROP_INHERIT, ZFS_TYPE_FILESYSTEM,
            "on | off | sharemgr(1M) options", "SHARESMB");
@@ -405,18 +406,18 @@ zfs_prop_init(void)
 
        /* hidden properties */
        zprop_register_hidden(ZFS_PROP_CREATETXG, "createtxg", PROP_TYPE_NUMBER,
-           PROP_READONLY, ZFS_TYPE_DATASET, "CREATETXG");
+           PROP_READONLY, ZFS_TYPE_DATASET | ZFS_TYPE_BOOKMARK, "CREATETXG");
        zprop_register_hidden(ZFS_PROP_NUMCLONES, "numclones", PROP_TYPE_NUMBER,
            PROP_READONLY, ZFS_TYPE_SNAPSHOT, "NUMCLONES");
        zprop_register_hidden(ZFS_PROP_NAME, "name", PROP_TYPE_STRING,
-           PROP_READONLY, ZFS_TYPE_DATASET, "NAME");
+           PROP_READONLY, ZFS_TYPE_DATASET | ZFS_TYPE_BOOKMARK, "NAME");
        zprop_register_hidden(ZFS_PROP_ISCSIOPTIONS, "iscsioptions",
            PROP_TYPE_STRING, PROP_INHERIT, ZFS_TYPE_VOLUME, "ISCSIOPTIONS");
        zprop_register_hidden(ZFS_PROP_STMF_SHAREINFO, "stmf_sbd_lu",
            PROP_TYPE_STRING, PROP_INHERIT, ZFS_TYPE_VOLUME,
            "STMF_SBD_LU");
        zprop_register_hidden(ZFS_PROP_GUID, "guid", PROP_TYPE_NUMBER,
-           PROP_READONLY, ZFS_TYPE_DATASET, "GUID");
+           PROP_READONLY, ZFS_TYPE_DATASET | ZFS_TYPE_BOOKMARK, "GUID");
        zprop_register_hidden(ZFS_PROP_USERACCOUNTING, "useraccounting",
            PROP_TYPE_NUMBER, PROP_READONLY, ZFS_TYPE_DATASET,
            "USERACCOUNTING");
@@ -436,7 +437,7 @@ zfs_prop_init(void)
 
        /* oddball properties */
        zprop_register_impl(ZFS_PROP_CREATION, "creation", PROP_TYPE_NUMBER, 0,
-           NULL, PROP_READONLY, ZFS_TYPE_DATASET,
+           NULL, PROP_READONLY, ZFS_TYPE_DATASET | ZFS_TYPE_BOOKMARK,
            "<date>", "CREATION", B_FALSE, B_TRUE, NULL);
 }
 
index 9701ff2bbdcd2c2b024ed44ba8a318b969fbced5..56ecd49186ab2ea60abd6ae2fb54afca4a351644 100644 (file)
@@ -25,6 +25,7 @@ $(MODULE)-objs += @top_srcdir@/module/zfs/dnode_sync.o
 $(MODULE)-objs += @top_srcdir@/module/zfs/dsl_dataset.o
 $(MODULE)-objs += @top_srcdir@/module/zfs/dsl_deadlist.o
 $(MODULE)-objs += @top_srcdir@/module/zfs/dsl_deleg.o
+$(MODULE)-objs += @top_srcdir@/module/zfs/dsl_bookmark.o
 $(MODULE)-objs += @top_srcdir@/module/zfs/dsl_dir.o
 $(MODULE)-objs += @top_srcdir@/module/zfs/dsl_pool.o
 $(MODULE)-objs += @top_srcdir@/module/zfs/dsl_prop.o
index 8d7385539f17e135f9456eb476abe68a3e1cc91e..a2130b1319b57015c07e3e9f37096d5f17f87bdf 100644 (file)
@@ -187,7 +187,7 @@ dmu_diff(const char *tosnap_name, const char *fromsnap_name,
                return (error);
        }
 
-       if (!dsl_dataset_is_before(tosnap, fromsnap)) {
+       if (!dsl_dataset_is_before(tosnap, fromsnap, 0)) {
                dsl_dataset_rele(fromsnap, FTAG);
                dsl_dataset_rele(tosnap, FTAG);
                dsl_pool_rele(dp, FTAG);
index 15abdeacb144f465b3a7866e8950f1a3f5ea56a8..885da23ba2b8cdbf410c1ac45b1a9677272c3b77 100644 (file)
@@ -50,6 +50,7 @@
 #include <sys/zfs_onexit.h>
 #include <sys/dmu_send.h>
 #include <sys/dsl_destroy.h>
+#include <sys/dsl_bookmark.h>
 
 /* Set this tunable to TRUE to replace corrupt data with 0x2f5baddb10c */
 int zfs_send_corrupt_data = B_FALSE;
@@ -385,6 +386,12 @@ backup_cb(spa_t *spa, zilog_t *zilog, const blkptr_t *bp,
        if (zb->zb_object != DMU_META_DNODE_OBJECT &&
            DMU_OBJECT_IS_SPECIAL(zb->zb_object)) {
                return (0);
+       } else if (zb->zb_level == ZB_ZIL_LEVEL) {
+               /*
+                * If we are sending a non-snapshot (which is allowed on
+                * read-only pools), it may have a ZIL, which must be ignored.
+                */
+               return (0);
        } else if (BP_IS_HOLE(bp) &&
            zb->zb_object == DMU_META_DNODE_OBJECT) {
                uint64_t span = BP_SPAN(dnp, zb->zb_level);
@@ -433,6 +440,7 @@ backup_cb(spa_t *spa, zilog_t *zilog, const blkptr_t *bp,
                arc_buf_t *abuf;
                int blksz = BP_GET_LSIZE(bp);
 
+               ASSERT0(zb->zb_level);
                if (arc_read(NULL, spa, bp, arc_getbuf_func, &abuf,
                    ZIO_PRIORITY_ASYNC_READ, ZIO_FLAG_CANFAIL,
                    &aflags, zb) != 0) {
@@ -460,11 +468,12 @@ backup_cb(spa_t *spa, zilog_t *zilog, const blkptr_t *bp,
 }
 
 /*
- * Releases dp, ds, and fromds, using the specified tag.
+ * Releases dp using the specified tag.
  */
 static int
 dmu_send_impl(void *tag, dsl_pool_t *dp, dsl_dataset_t *ds,
-    dsl_dataset_t *fromds, int outfd, vnode_t *vp, offset_t *off)
+    zfs_bookmark_phys_t *fromzb, boolean_t is_clone, int outfd,
+    vnode_t *vp, offset_t *off)
 {
        objset_t *os;
        dmu_replay_record_t *drr;
@@ -472,18 +481,8 @@ dmu_send_impl(void *tag, dsl_pool_t *dp, dsl_dataset_t *ds,
        int err;
        uint64_t fromtxg = 0;
 
-       if (fromds != NULL && !dsl_dataset_is_before(ds, fromds)) {
-               dsl_dataset_rele(fromds, tag);
-               dsl_dataset_rele(ds, tag);
-               dsl_pool_rele(dp, tag);
-               return (SET_ERROR(EXDEV));
-       }
-
        err = dmu_objset_from_ds(ds, &os);
        if (err != 0) {
-               if (fromds != NULL)
-                       dsl_dataset_rele(fromds, tag);
-               dsl_dataset_rele(ds, tag);
                dsl_pool_rele(dp, tag);
                return (err);
        }
@@ -499,9 +498,6 @@ dmu_send_impl(void *tag, dsl_pool_t *dp, dsl_dataset_t *ds,
                uint64_t version;
                if (zfs_get_zplprop(os, ZFS_PROP_VERSION, &version) != 0) {
                        kmem_free(drr, sizeof (dmu_replay_record_t));
-                       if (fromds != NULL)
-                               dsl_dataset_rele(fromds, tag);
-                       dsl_dataset_rele(ds, tag);
                        dsl_pool_rele(dp, tag);
                        return (SET_ERROR(EINVAL));
                }
@@ -516,20 +512,20 @@ dmu_send_impl(void *tag, dsl_pool_t *dp, dsl_dataset_t *ds,
        drr->drr_u.drr_begin.drr_creation_time =
            ds->ds_phys->ds_creation_time;
        drr->drr_u.drr_begin.drr_type = dmu_objset_type(os);
-       if (fromds != NULL && ds->ds_dir != fromds->ds_dir)
+       if (is_clone)
                drr->drr_u.drr_begin.drr_flags |= DRR_FLAG_CLONE;
        drr->drr_u.drr_begin.drr_toguid = ds->ds_phys->ds_guid;
        if (ds->ds_phys->ds_flags & DS_FLAG_CI_DATASET)
                drr->drr_u.drr_begin.drr_flags |= DRR_FLAG_CI_DATA;
 
-       if (fromds != NULL)
-               drr->drr_u.drr_begin.drr_fromguid = fromds->ds_phys->ds_guid;
+       if (fromzb != NULL) {
+               drr->drr_u.drr_begin.drr_fromguid = fromzb->zbm_guid;
+               fromtxg = fromzb->zbm_creation_txg;
+       }
        dsl_dataset_name(ds, drr->drr_u.drr_begin.drr_toname);
-
-       if (fromds != NULL) {
-               fromtxg = fromds->ds_phys->ds_creation_txg;
-               dsl_dataset_rele(fromds, tag);
-               fromds = NULL;
+       if (!dsl_dataset_is_snapshot(ds)) {
+               (void) strlcat(drr->drr_u.drr_begin.drr_toname, "@--head--",
+                   sizeof (drr->drr_u.drr_begin.drr_toname));
        }
 
        dsp = kmem_zalloc(sizeof (dmu_sendarg_t), KM_SLEEP);
@@ -543,7 +539,7 @@ dmu_send_impl(void *tag, dsl_pool_t *dp, dsl_dataset_t *ds,
        dsp->dsa_toguid = ds->ds_phys->ds_guid;
        ZIO_SET_CHECKSUM(&dsp->dsa_zc, 0, 0, 0, 0);
        dsp->dsa_pending_op = PENDING_NONE;
-       dsp->dsa_incremental = (fromtxg != 0);
+       dsp->dsa_incremental = (fromzb != NULL);
 
        mutex_enter(&ds->ds_sendstream_lock);
        list_insert_head(&ds->ds_sendstreams, dsp);
@@ -589,7 +585,6 @@ out:
        kmem_free(dsp, sizeof (dmu_sendarg_t));
 
        dsl_dataset_long_rele(ds, FTAG);
-       dsl_dataset_rele(ds, tag);
 
        return (err);
 }
@@ -614,15 +609,30 @@ dmu_send_obj(const char *pool, uint64_t tosnap, uint64_t fromsnap,
        }
 
        if (fromsnap != 0) {
+               zfs_bookmark_phys_t zb;
+               boolean_t is_clone;
+
                err = dsl_dataset_hold_obj(dp, fromsnap, FTAG, &fromds);
                if (err != 0) {
                        dsl_dataset_rele(ds, FTAG);
                        dsl_pool_rele(dp, FTAG);
                        return (err);
                }
+               if (!dsl_dataset_is_before(ds, fromds, 0))
+                       err = SET_ERROR(EXDEV);
+               zb.zbm_creation_time = fromds->ds_phys->ds_creation_time;
+               zb.zbm_creation_txg = fromds->ds_phys->ds_creation_txg;
+               zb.zbm_guid = fromds->ds_phys->ds_guid;
+               is_clone = (fromds->ds_dir != ds->ds_dir);
+               dsl_dataset_rele(fromds, FTAG);
+               err = dmu_send_impl(FTAG, dp, ds, &zb, is_clone,
+                   outfd, vp, off);
+       } else {
+               err = dmu_send_impl(FTAG, dp, ds, NULL, B_FALSE,
+                   outfd, vp, off);
        }
-
-       return (dmu_send_impl(FTAG, dp, ds, fromds, outfd, vp, off));
+       dsl_dataset_rele(ds, FTAG);
+       return (err);
 }
 
 int
@@ -631,33 +641,79 @@ dmu_send(const char *tosnap, const char *fromsnap,
 {
        dsl_pool_t *dp;
        dsl_dataset_t *ds;
-       dsl_dataset_t *fromds = NULL;
        int err;
+       boolean_t owned = B_FALSE;
 
-       if (strchr(tosnap, '@') == NULL)
-               return (SET_ERROR(EINVAL));
-       if (fromsnap != NULL && strchr(fromsnap, '@') == NULL)
+       if (fromsnap != NULL && strpbrk(fromsnap, "@#") == NULL)
                return (SET_ERROR(EINVAL));
 
        err = dsl_pool_hold(tosnap, FTAG, &dp);
        if (err != 0)
                return (err);
 
-       err = dsl_dataset_hold(dp, tosnap, FTAG, &ds);
+       if (strchr(tosnap, '@') == NULL && spa_writeable(dp->dp_spa)) {
+               /*
+                * We are sending a filesystem or volume.  Ensure
+                * that it doesn't change by owning the dataset.
+                */
+               err = dsl_dataset_own(dp, tosnap, FTAG, &ds);
+               owned = B_TRUE;
+       } else {
+               err = dsl_dataset_hold(dp, tosnap, FTAG, &ds);
+       }
        if (err != 0) {
                dsl_pool_rele(dp, FTAG);
                return (err);
        }
 
        if (fromsnap != NULL) {
-               err = dsl_dataset_hold(dp, fromsnap, FTAG, &fromds);
+               zfs_bookmark_phys_t zb;
+               boolean_t is_clone = B_FALSE;
+               int fsnamelen = strchr(tosnap, '@') - tosnap;
+
+               /*
+                * If the fromsnap is in a different filesystem, then
+                * mark the send stream as a clone.
+                */
+               if (strncmp(tosnap, fromsnap, fsnamelen) != 0 ||
+                   (fromsnap[fsnamelen] != '@' &&
+                   fromsnap[fsnamelen] != '#')) {
+                       is_clone = B_TRUE;
+               }
+
+               if (strchr(fromsnap, '@')) {
+                       dsl_dataset_t *fromds;
+                       err = dsl_dataset_hold(dp, fromsnap, FTAG, &fromds);
+                       if (err == 0) {
+                               if (!dsl_dataset_is_before(ds, fromds, 0))
+                                       err = SET_ERROR(EXDEV);
+                               zb.zbm_creation_time =
+                                   fromds->ds_phys->ds_creation_time;
+                               zb.zbm_creation_txg =
+                                   fromds->ds_phys->ds_creation_txg;
+                               zb.zbm_guid = fromds->ds_phys->ds_guid;
+                               is_clone = (ds->ds_dir != fromds->ds_dir);
+                               dsl_dataset_rele(fromds, FTAG);
+                       }
+               } else {
+                       err = dsl_bookmark_lookup(dp, fromsnap, ds, &zb);
+               }
                if (err != 0) {
                        dsl_dataset_rele(ds, FTAG);
                        dsl_pool_rele(dp, FTAG);
                        return (err);
                }
+               err = dmu_send_impl(FTAG, dp, ds, &zb, is_clone,
+                   outfd, vp, off);
+       } else {
+               err = dmu_send_impl(FTAG, dp, ds, NULL, B_FALSE,
+                   outfd, vp, off);
        }
-       return (dmu_send_impl(FTAG, dp, ds, fromds, outfd, vp, off));
+       if (owned)
+               dsl_dataset_disown(ds, FTAG);
+       else
+               dsl_dataset_rele(ds, FTAG);
+       return (err);
 }
 
 int
@@ -677,7 +733,7 @@ dmu_send_estimate(dsl_dataset_t *ds, dsl_dataset_t *fromds, uint64_t *sizep)
         * fromsnap must be an earlier snapshot from the same fs as tosnap,
         * or the origin's fs.
         */
-       if (fromds != NULL && !dsl_dataset_is_before(ds, fromds))
+       if (fromds != NULL && !dsl_dataset_is_before(ds, fromds, 0))
                return (SET_ERROR(EXDEV));
 
        /* Get uncompressed size estimate of changed data. */
diff --git a/module/zfs/dsl_bookmark.c b/module/zfs/dsl_bookmark.c
new file mode 100644 (file)
index 0000000..2cae5cd
--- /dev/null
@@ -0,0 +1,459 @@
+/*
+ * CDDL HEADER START
+ *
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright (c) 2013 by Delphix. All rights reserved.
+ */
+
+#include <sys/zfs_context.h>
+#include <sys/dsl_dataset.h>
+#include <sys/dsl_dir.h>
+#include <sys/dsl_prop.h>
+#include <sys/dsl_synctask.h>
+#include <sys/dmu_impl.h>
+#include <sys/dmu_tx.h>
+#include <sys/arc.h>
+#include <sys/zap.h>
+#include <sys/zfeature.h>
+#include <sys/spa.h>
+#include <sys/dsl_bookmark.h>
+#include <zfs_namecheck.h>
+
+static int
+dsl_bookmark_hold_ds(dsl_pool_t *dp, const char *fullname,
+    dsl_dataset_t **dsp, void *tag, char **shortnamep)
+{
+       char buf[MAXNAMELEN];
+       char *hashp;
+
+       if (strlen(fullname) >= MAXNAMELEN)
+               return (SET_ERROR(ENAMETOOLONG));
+       hashp = strchr(fullname, '#');
+       if (hashp == NULL)
+               return (SET_ERROR(EINVAL));
+
+       *shortnamep = hashp + 1;
+       if (zfs_component_namecheck(*shortnamep, NULL, NULL))
+               return (SET_ERROR(EINVAL));
+       (void) strlcpy(buf, fullname, hashp - fullname + 1);
+       return (dsl_dataset_hold(dp, buf, tag, dsp));
+}
+
+/*
+ * Returns ESRCH if bookmark is not found.
+ */
+static int
+dsl_dataset_bmark_lookup(dsl_dataset_t *ds, const char *shortname,
+    zfs_bookmark_phys_t *bmark_phys)
+{
+       objset_t *mos = ds->ds_dir->dd_pool->dp_meta_objset;
+       uint64_t bmark_zapobj = ds->ds_bookmarks;
+       matchtype_t mt;
+       int err;
+
+       if (bmark_zapobj == 0)
+               return (SET_ERROR(ESRCH));
+
+       if (ds->ds_phys->ds_flags & DS_FLAG_CI_DATASET)
+               mt = MT_FIRST;
+       else
+               mt = MT_EXACT;
+
+       err = zap_lookup_norm(mos, bmark_zapobj, shortname, sizeof (uint64_t),
+           sizeof (*bmark_phys) / sizeof (uint64_t), bmark_phys, mt,
+           NULL, 0, NULL);
+
+       return (err == ENOENT ? ESRCH : err);
+}
+
+/*
+ * If later_ds is non-NULL, this will return EXDEV if the the specified bookmark
+ * does not represents an earlier point in later_ds's timeline.
+ *
+ * Returns ENOENT if the dataset containing the bookmark does not exist.
+ * Returns ESRCH if the dataset exists but the bookmark was not found in it.
+ */
+int
+dsl_bookmark_lookup(dsl_pool_t *dp, const char *fullname,
+    dsl_dataset_t *later_ds, zfs_bookmark_phys_t *bmp)
+{
+       char *shortname;
+       dsl_dataset_t *ds;
+       int error;
+
+       error = dsl_bookmark_hold_ds(dp, fullname, &ds, FTAG, &shortname);
+       if (error != 0)
+               return (error);
+
+       error = dsl_dataset_bmark_lookup(ds, shortname, bmp);
+       if (error == 0 && later_ds != NULL) {
+               if (!dsl_dataset_is_before(later_ds, ds, bmp->zbm_creation_txg))
+                       error = SET_ERROR(EXDEV);
+       }
+       dsl_dataset_rele(ds, FTAG);
+       return (error);
+}
+
+typedef struct dsl_bookmark_create_arg {
+       nvlist_t *dbca_bmarks;
+       nvlist_t *dbca_errors;
+} dsl_bookmark_create_arg_t;
+
+static int
+dsl_bookmark_create_check_impl(dsl_dataset_t *snapds, const char *bookmark_name,
+    dmu_tx_t *tx)
+{
+       dsl_pool_t *dp = dmu_tx_pool(tx);
+       dsl_dataset_t *bmark_fs;
+       char *shortname;
+       int error;
+       zfs_bookmark_phys_t bmark_phys;
+
+       if (!dsl_dataset_is_snapshot(snapds))
+               return (SET_ERROR(EINVAL));
+
+       error = dsl_bookmark_hold_ds(dp, bookmark_name,
+           &bmark_fs, FTAG, &shortname);
+       if (error != 0)
+               return (error);
+
+       if (!dsl_dataset_is_before(bmark_fs, snapds, 0)) {
+               dsl_dataset_rele(bmark_fs, FTAG);
+               return (SET_ERROR(EINVAL));
+       }
+
+       error = dsl_dataset_bmark_lookup(bmark_fs, shortname,
+           &bmark_phys);
+       dsl_dataset_rele(bmark_fs, FTAG);
+       if (error == 0)
+               return (SET_ERROR(EEXIST));
+       if (error == ESRCH)
+               return (0);
+       return (error);
+}
+
+static int
+dsl_bookmark_create_check(void *arg, dmu_tx_t *tx)
+{
+       dsl_bookmark_create_arg_t *dbca = arg;
+       dsl_pool_t *dp = dmu_tx_pool(tx);
+       int rv = 0;
+       nvpair_t *pair;
+
+       if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS))
+               return (SET_ERROR(ENOTSUP));
+
+       for (pair = nvlist_next_nvpair(dbca->dbca_bmarks, NULL);
+           pair != NULL; pair = nvlist_next_nvpair(dbca->dbca_bmarks, pair)) {
+               dsl_dataset_t *snapds;
+               int error;
+
+               /* note: validity of nvlist checked by ioctl layer */
+               error = dsl_dataset_hold(dp, fnvpair_value_string(pair),
+                   FTAG, &snapds);
+               if (error == 0) {
+                       error = dsl_bookmark_create_check_impl(snapds,
+                           nvpair_name(pair), tx);
+                       dsl_dataset_rele(snapds, FTAG);
+               }
+               if (error != 0) {
+                       fnvlist_add_int32(dbca->dbca_errors,
+                           nvpair_name(pair), error);
+                       rv = error;
+               }
+       }
+
+       return (rv);
+}
+
+static void
+dsl_bookmark_create_sync(void *arg, dmu_tx_t *tx)
+{
+       dsl_bookmark_create_arg_t *dbca = arg;
+       dsl_pool_t *dp = dmu_tx_pool(tx);
+       objset_t *mos = dp->dp_meta_objset;
+       nvpair_t *pair;
+
+       ASSERT(spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS));
+
+       for (pair = nvlist_next_nvpair(dbca->dbca_bmarks, NULL);
+           pair != NULL; pair = nvlist_next_nvpair(dbca->dbca_bmarks, pair)) {
+               dsl_dataset_t *snapds, *bmark_fs;
+               zfs_bookmark_phys_t bmark_phys;
+               char *shortname;
+
+               VERIFY0(dsl_dataset_hold(dp, fnvpair_value_string(pair),
+                   FTAG, &snapds));
+               VERIFY0(dsl_bookmark_hold_ds(dp, nvpair_name(pair),
+                   &bmark_fs, FTAG, &shortname));
+               if (bmark_fs->ds_bookmarks == 0) {
+                       bmark_fs->ds_bookmarks =
+                           zap_create_norm(mos, U8_TEXTPREP_TOUPPER,
+                           DMU_OTN_ZAP_METADATA, DMU_OT_NONE, 0, tx);
+                       spa_feature_incr(dp->dp_spa, SPA_FEATURE_BOOKMARKS, tx);
+
+                       dsl_dataset_zapify(bmark_fs, tx);
+                       VERIFY0(zap_add(mos, bmark_fs->ds_object,
+                           DS_FIELD_BOOKMARK_NAMES,
+                           sizeof (bmark_fs->ds_bookmarks), 1,
+                           &bmark_fs->ds_bookmarks, tx));
+               }
+
+               bmark_phys.zbm_guid = snapds->ds_phys->ds_guid;
+               bmark_phys.zbm_creation_txg = snapds->ds_phys->ds_creation_txg;
+               bmark_phys.zbm_creation_time =
+                   snapds->ds_phys->ds_creation_time;
+
+               VERIFY0(zap_add(mos, bmark_fs->ds_bookmarks,
+                   shortname, sizeof (uint64_t),
+                   sizeof (zfs_bookmark_phys_t) / sizeof (uint64_t),
+                   &bmark_phys, tx));
+
+               spa_history_log_internal_ds(bmark_fs, "bookmark", tx,
+                   "name=%s creation_txg=%llu target_snap=%llu",
+                   shortname,
+                   (longlong_t)bmark_phys.zbm_creation_txg,
+                   (longlong_t)snapds->ds_object);
+
+               dsl_dataset_rele(bmark_fs, FTAG);
+               dsl_dataset_rele(snapds, FTAG);
+       }
+}
+
+/*
+ * The bookmarks must all be in the same pool.
+ */
+int
+dsl_bookmark_create(nvlist_t *bmarks, nvlist_t *errors)
+{
+       nvpair_t *pair;
+       dsl_bookmark_create_arg_t dbca;
+
+       pair = nvlist_next_nvpair(bmarks, NULL);
+       if (pair == NULL)
+               return (0);
+
+       dbca.dbca_bmarks = bmarks;
+       dbca.dbca_errors = errors;
+
+       return (dsl_sync_task(nvpair_name(pair), dsl_bookmark_create_check,
+           dsl_bookmark_create_sync, &dbca, fnvlist_num_pairs(bmarks)));
+}
+
+int
+dsl_get_bookmarks_impl(dsl_dataset_t *ds, nvlist_t *props, nvlist_t *outnvl)
+{
+       int err = 0;
+       zap_cursor_t zc;
+       zap_attribute_t attr;
+       dsl_pool_t *dp = ds->ds_dir->dd_pool;
+
+       uint64_t bmark_zapobj = ds->ds_bookmarks;
+       if (bmark_zapobj == 0)
+               return (0);
+
+       for (zap_cursor_init(&zc, dp->dp_meta_objset, bmark_zapobj);
+           zap_cursor_retrieve(&zc, &attr) == 0;
+           zap_cursor_advance(&zc)) {
+               nvlist_t *out_props;
+               char *bmark_name = attr.za_name;
+               zfs_bookmark_phys_t bmark_phys;
+
+               err = dsl_dataset_bmark_lookup(ds, bmark_name, &bmark_phys);
+               ASSERT3U(err, !=, ENOENT);
+               if (err != 0)
+                       break;
+
+               out_props = fnvlist_alloc();
+               if (nvlist_exists(props,
+                   zfs_prop_to_name(ZFS_PROP_GUID))) {
+                       dsl_prop_nvlist_add_uint64(out_props,
+                           ZFS_PROP_GUID, bmark_phys.zbm_guid);
+               }
+               if (nvlist_exists(props,
+                   zfs_prop_to_name(ZFS_PROP_CREATETXG))) {
+                       dsl_prop_nvlist_add_uint64(out_props,
+                           ZFS_PROP_CREATETXG, bmark_phys.zbm_creation_txg);
+               }
+               if (nvlist_exists(props,
+                   zfs_prop_to_name(ZFS_PROP_CREATION))) {
+                       dsl_prop_nvlist_add_uint64(out_props,
+                           ZFS_PROP_CREATION, bmark_phys.zbm_creation_time);
+               }
+
+               fnvlist_add_nvlist(outnvl, bmark_name, out_props);
+               fnvlist_free(out_props);
+       }
+       zap_cursor_fini(&zc);
+       return (err);
+}
+
+/*
+ * Retrieve the bookmarks that exist in the specified dataset, and the
+ * requested properties of each bookmark.
+ *
+ * The "props" nvlist specifies which properties are requested.
+ * See lzc_get_bookmarks() for the list of valid properties.
+ */
+int
+dsl_get_bookmarks(const char *dsname, nvlist_t *props, nvlist_t *outnvl)
+{
+       dsl_pool_t *dp;
+       dsl_dataset_t *ds;
+       int err;
+
+       err = dsl_pool_hold(dsname, FTAG, &dp);
+       if (err != 0)
+               return (err);
+       err = dsl_dataset_hold(dp, dsname, FTAG, &ds);
+       if (err != 0) {
+               dsl_pool_rele(dp, FTAG);
+               return (err);
+       }
+
+       err = dsl_get_bookmarks_impl(ds, props, outnvl);
+
+       dsl_dataset_rele(ds, FTAG);
+       dsl_pool_rele(dp, FTAG);
+       return (err);
+}
+
+typedef struct dsl_bookmark_destroy_arg {
+       nvlist_t *dbda_bmarks;
+       nvlist_t *dbda_success;
+       nvlist_t *dbda_errors;
+} dsl_bookmark_destroy_arg_t;
+
+static int
+dsl_dataset_bookmark_remove(dsl_dataset_t *ds, const char *name, dmu_tx_t *tx)
+{
+       objset_t *mos = ds->ds_dir->dd_pool->dp_meta_objset;
+       uint64_t bmark_zapobj = ds->ds_bookmarks;
+       matchtype_t mt;
+
+       if (ds->ds_phys->ds_flags & DS_FLAG_CI_DATASET)
+               mt = MT_FIRST;
+       else
+               mt = MT_EXACT;
+
+       return (zap_remove_norm(mos, bmark_zapobj, name, mt, tx));
+}
+
+static int
+dsl_bookmark_destroy_check(void *arg, dmu_tx_t *tx)
+{
+       dsl_bookmark_destroy_arg_t *dbda = arg;
+       dsl_pool_t *dp = dmu_tx_pool(tx);
+       int rv = 0;
+       nvpair_t *pair;
+
+       if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS))
+               return (0);
+
+       for (pair = nvlist_next_nvpair(dbda->dbda_bmarks, NULL);
+           pair != NULL; pair = nvlist_next_nvpair(dbda->dbda_bmarks, pair)) {
+               const char *fullname = nvpair_name(pair);
+               dsl_dataset_t *ds;
+               zfs_bookmark_phys_t bm;
+               int error;
+               char *shortname;
+
+               error = dsl_bookmark_hold_ds(dp, fullname, &ds,
+                   FTAG, &shortname);
+               if (error == ENOENT) {
+                       /* ignore it; the bookmark is "already destroyed" */
+                       continue;
+               }
+               if (error == 0) {
+                       error = dsl_dataset_bmark_lookup(ds, shortname, &bm);
+                       dsl_dataset_rele(ds, FTAG);
+                       if (error == ESRCH) {
+                               /*
+                                * ignore it; the bookmark is
+                                * "already destroyed"
+                                */
+                               continue;
+                       }
+               }
+               if (error == 0) {
+                       fnvlist_add_boolean(dbda->dbda_success, fullname);
+               } else {
+                       fnvlist_add_int32(dbda->dbda_errors, fullname, error);
+                       rv = error;
+               }
+       }
+       return (rv);
+}
+
+static void
+dsl_bookmark_destroy_sync(void *arg, dmu_tx_t *tx)
+{
+       dsl_bookmark_destroy_arg_t *dbda = arg;
+       dsl_pool_t *dp = dmu_tx_pool(tx);
+       objset_t *mos = dp->dp_meta_objset;
+       nvpair_t *pair;
+
+       for (pair = nvlist_next_nvpair(dbda->dbda_success, NULL);
+           pair != NULL; pair = nvlist_next_nvpair(dbda->dbda_success, pair)) {
+               dsl_dataset_t *ds;
+               char *shortname;
+               uint64_t zap_cnt;
+
+               VERIFY0(dsl_bookmark_hold_ds(dp, nvpair_name(pair),
+                   &ds, FTAG, &shortname));
+               VERIFY0(dsl_dataset_bookmark_remove(ds, shortname, tx));
+
+               /*
+                * If all of this dataset's bookmarks have been destroyed,
+                * free the zap object and decrement the feature's use count.
+                */
+               VERIFY0(zap_count(mos, ds->ds_bookmarks,
+                   &zap_cnt));
+               if (zap_cnt == 0) {
+                       dmu_buf_will_dirty(ds->ds_dbuf, tx);
+                       VERIFY0(zap_destroy(mos, ds->ds_bookmarks, tx));
+                       ds->ds_bookmarks = 0;
+                       spa_feature_decr(dp->dp_spa, SPA_FEATURE_BOOKMARKS, tx);
+                       VERIFY0(zap_remove(mos, ds->ds_object,
+                           DS_FIELD_BOOKMARK_NAMES, tx));
+               }
+
+               spa_history_log_internal_ds(ds, "remove bookmark", tx,
+                   "name=%s", shortname);
+
+               dsl_dataset_rele(ds, FTAG);
+       }
+}
+
+/*
+ * The bookmarks must all be in the same pool.
+ */
+int
+dsl_bookmark_destroy(nvlist_t *bmarks, nvlist_t *errors)
+{
+       int rv;
+       dsl_bookmark_destroy_arg_t dbda;
+       nvpair_t *pair = nvlist_next_nvpair(bmarks, NULL);
+       if (pair == NULL)
+               return (0);
+
+       dbda.dbda_bmarks = bmarks;
+       dbda.dbda_errors = errors;
+       dbda.dbda_success = fnvlist_alloc();
+
+       rv = dsl_sync_task(nvpair_name(pair), dsl_bookmark_destroy_check,
+           dsl_bookmark_destroy_sync, &dbda, fnvlist_num_pairs(bmarks));
+       fnvlist_free(dbda.dbda_success);
+       return (rv);
+}
index cae56534d8eaaf398e0c5f4e0dffadafc137a70d..2fe6b858dcb7a317ff6ce41f678c52d8b0bfb6a6 100644 (file)
@@ -48,6 +48,7 @@
 #include <sys/dsl_deadlist.h>
 #include <sys/dsl_destroy.h>
 #include <sys/dsl_userhold.h>
+#include <sys/dsl_bookmark.h>
 
 #define        SWITCH64(x, y) \
        { \
@@ -404,6 +405,14 @@ dsl_dataset_hold_obj(dsl_pool_t *dp, uint64_t dsobj, void *tag,
                                    ds->ds_phys->ds_prev_snap_obj,
                                    ds, &ds->ds_prev);
                        }
+                       if (doi.doi_type == DMU_OTN_ZAP_METADATA) {
+                               int zaperr = zap_lookup(mos, ds->ds_object,
+                                   DS_FIELD_BOOKMARK_NAMES,
+                                   sizeof (ds->ds_bookmarks), 1,
+                                   &ds->ds_bookmarks);
+                               if (zaperr != ENOENT)
+                                       VERIFY0(zaperr);
+                       }
                } else {
                        if (zfs_flags & ZFS_DEBUG_SNAPNAMES)
                                err = dsl_dataset_get_snapname(ds);
@@ -1734,6 +1743,8 @@ dsl_dataset_rollback_check(void *arg, dmu_tx_t *tx)
        dsl_dataset_t *ds;
        int64_t unused_refres_delta;
        int error;
+       nvpair_t *pair;
+       nvlist_t *proprequest, *bookmarks;
 
        error = dsl_dataset_hold(dp, ddra->ddra_fsname, FTAG, &ds);
        if (error != 0)
@@ -1751,6 +1762,28 @@ dsl_dataset_rollback_check(void *arg, dmu_tx_t *tx)
                return (SET_ERROR(EINVAL));
        }
 
+       /* must not have any bookmarks after the most recent snapshot */
+       proprequest = fnvlist_alloc();
+       fnvlist_add_boolean(proprequest, zfs_prop_to_name(ZFS_PROP_CREATETXG));
+       bookmarks = fnvlist_alloc();
+       error = dsl_get_bookmarks_impl(ds, proprequest, bookmarks);
+       fnvlist_free(proprequest);
+       if (error != 0)
+               return (error);
+       for (pair = nvlist_next_nvpair(bookmarks, NULL);
+           pair != NULL; pair = nvlist_next_nvpair(bookmarks, pair)) {
+               nvlist_t *valuenv =
+                   fnvlist_lookup_nvlist(fnvpair_value_nvlist(pair),
+                   zfs_prop_to_name(ZFS_PROP_CREATETXG));
+               uint64_t createtxg = fnvlist_lookup_uint64(valuenv, "value");
+               if (createtxg > ds->ds_phys->ds_prev_snap_txg) {
+                       fnvlist_free(bookmarks);
+                       dsl_dataset_rele(ds, FTAG);
+                       return (SET_ERROR(EEXIST));
+               }
+       }
+       fnvlist_free(bookmarks);
+
        error = dsl_dataset_handoff_check(ds, ddra->ddra_owner, tx);
        if (error != 0) {
                dsl_dataset_rele(ds, FTAG);
@@ -2972,9 +3005,12 @@ dsl_dataset_space_wouldfree(dsl_dataset_t *firstsnap,
  * 'earlier' is before 'later'.  Or 'earlier' could be the origin of
  * 'later's filesystem.  Or 'earlier' could be an older snapshot in the origin's
  * filesystem.  Or 'earlier' could be the origin's origin.
+ *
+ * If non-zero, earlier_txg is used instead of earlier's ds_creation_txg.
  */
 boolean_t
-dsl_dataset_is_before(dsl_dataset_t *later, dsl_dataset_t *earlier)
+dsl_dataset_is_before(dsl_dataset_t *later, dsl_dataset_t *earlier,
+       uint64_t earlier_txg)
 {
        dsl_pool_t *dp = later->ds_dir->dd_pool;
        int error;
@@ -2982,9 +3018,13 @@ dsl_dataset_is_before(dsl_dataset_t *later, dsl_dataset_t *earlier)
        dsl_dataset_t *origin;
 
        ASSERT(dsl_pool_config_held(dp));
+       ASSERT(dsl_dataset_is_snapshot(earlier) || earlier_txg != 0);
+
+       if (earlier_txg == 0)
+               earlier_txg = earlier->ds_phys->ds_creation_txg;
 
-       if (earlier->ds_phys->ds_creation_txg >=
-           later->ds_phys->ds_creation_txg)
+       if (dsl_dataset_is_snapshot(later) &&
+           earlier_txg >= later->ds_phys->ds_creation_txg)
                return (B_FALSE);
 
        if (later->ds_dir == earlier->ds_dir)
@@ -2998,7 +3038,7 @@ dsl_dataset_is_before(dsl_dataset_t *later, dsl_dataset_t *earlier)
            later->ds_dir->dd_phys->dd_origin_obj, FTAG, &origin);
        if (error != 0)
                return (B_FALSE);
-       ret = dsl_dataset_is_before(origin, earlier);
+       ret = dsl_dataset_is_before(origin, earlier, earlier_txg);
        dsl_dataset_rele(origin, FTAG);
        return (ret);
 }
index cbe89739f186ccd01c95c03e4bea57d4d02f366e..113b7261b95788ad95081a9d2c88adfb2806a97b 100644 (file)
@@ -816,6 +816,12 @@ dsl_destroy_head_sync_impl(dsl_dataset_t *ds, dmu_tx_t *tx)
        ASSERT(ds->ds_phys->ds_snapnames_zapobj != 0);
        VERIFY0(zap_destroy(mos, ds->ds_phys->ds_snapnames_zapobj, tx));
 
+       if (ds->ds_bookmarks != 0) {
+               VERIFY0(zap_destroy(mos,
+                   ds->ds_bookmarks, tx));
+               spa_feature_decr(dp->dp_spa, SPA_FEATURE_BOOKMARKS, tx);
+       }
+
        spa_prop_clear_bootfs(dp->dp_spa, ds->ds_object, tx);
 
        ASSERT0(ds->ds_phys->ds_next_clones_obj);
index 88f9e34e371c0ef484007d0130e880f0a111bbc3..02ccb13a2e417e6458022deb29a2576fa20894cd 100644 (file)
@@ -428,7 +428,7 @@ spa_lookup(const char *name)
         * If it's a full dataset name, figure out the pool name and
         * just use that.
         */
-       cp = strpbrk(search.spa_name, "/@");
+       cp = strpbrk(search.spa_name, "/@#");
        if (cp != NULL)
                *cp = '\0';
 
index 35e662ee2a8b73b28df73ef212717b8cf25f2468..2ea22024536f1960f0ec2840ed8c0a11e9e0c927 100644 (file)
@@ -187,8 +187,10 @@ zpool_feature_init(void)
            B_FALSE, NULL);
 
        {
-       static spa_feature_t hole_birth_deps[] = { SPA_FEATURE_ENABLED_TXG,
-           SPA_FEATURE_NONE };
+       static const spa_feature_t hole_birth_deps[] = {
+               SPA_FEATURE_ENABLED_TXG,
+               SPA_FEATURE_NONE
+       };
        zfeature_register(SPA_FEATURE_HOLE_BIRTH,
            "com.delphix:hole_birth", "hole_birth",
            "Retain hole birth txg for more precise zfs send",
@@ -199,4 +201,16 @@ zpool_feature_init(void)
            "com.delphix:extensible_dataset", "extensible_dataset",
            "Enhanced dataset functionality, used by other features.",
            B_FALSE, B_FALSE, B_FALSE, NULL);
+
+       {
+       static const spa_feature_t bookmarks_deps[] = {
+               SPA_FEATURE_EXTENSIBLE_DATASET,
+               SPA_FEATURE_NONE
+       };
+
+       zfeature_register(SPA_FEATURE_BOOKMARKS,
+           "com.delphix:bookmarks", "bookmarks",
+           "\"zfs bookmark\" command",
+           B_TRUE, B_FALSE, B_FALSE, bookmarks_deps);
+       }
 }
index 96520545a2804d12f187db0e5f9749917de10abb..59405de82b00f1831d4944e7f9de23f8fc29ee9d 100644 (file)
@@ -354,7 +354,7 @@ zfsctl_snapshot_zname(struct inode *ip, const char *name, int len, char *zname)
 {
        objset_t *os = ITOZSB(ip)->z_os;
 
-       if (snapshot_namecheck(name, NULL, NULL) != 0)
+       if (zfs_component_namecheck(name, NULL, NULL) != 0)
                return (SET_ERROR(EILSEQ));
 
        dmu_objset_name(os, zname);
@@ -630,7 +630,7 @@ zfsctl_snapdir_mkdir(struct inode *dip, char *dirname, vattr_t *vap,
 
        dsname = kmem_alloc(MAXNAMELEN, KM_SLEEP);
 
-       if (snapshot_namecheck(dirname, NULL, NULL) != 0) {
+       if (zfs_component_namecheck(dirname, NULL, NULL) != 0) {
                error = SET_ERROR(EILSEQ);
                goto out;
        }
index 45357159862e7dcd01ae32a0362ef0b94751389e..409d2c737a5b26f98d571e655b128e32e618a20b 100644 (file)
 
 #include <sys/dmu_send.h>
 #include <sys/dsl_destroy.h>
+#include <sys/dsl_bookmark.h>
 #include <sys/dsl_userhold.h>
 #include <sys/zfeature.h>
 
@@ -812,22 +813,9 @@ zfs_secpolicy_destroy_snaps(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr)
                return (SET_ERROR(EINVAL));
        for (pair = nvlist_next_nvpair(snaps, NULL); pair != NULL;
            pair = nextpair) {
-               dsl_pool_t *dp;
-               dsl_dataset_t *ds;
-
-               error = dsl_pool_hold(nvpair_name(pair), FTAG, &dp);
-               if (error != 0)
-                       break;
                nextpair = nvlist_next_nvpair(snaps, pair);
-               error = dsl_dataset_hold(dp, nvpair_name(pair), FTAG, &ds);
-               if (error == 0)
-                       dsl_dataset_rele(ds, FTAG);
-               dsl_pool_rele(dp, FTAG);
-
-               if (error == 0) {
-                       error = zfs_secpolicy_destroy_perms(nvpair_name(pair),
-                           cr);
-               } else if (error == ENOENT) {
+               error = zfs_secpolicy_destroy_perms(nvpair_name(pair), cr);
+               if (error == ENOENT) {
                        /*
                         * Ignore any snapshots that don't exist (we consider
                         * them "already destroyed").  Remove the name from the
@@ -986,6 +974,76 @@ zfs_secpolicy_snapshot(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr)
        return (error);
 }
 
+/*
+ * Check for permission to create each snapshot in the nvlist.
+ */
+/* ARGSUSED */
+static int
+zfs_secpolicy_bookmark(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr)
+{
+       int error = 0;
+       nvpair_t *pair;
+
+       for (pair = nvlist_next_nvpair(innvl, NULL);
+           pair != NULL; pair = nvlist_next_nvpair(innvl, pair)) {
+               char *name = nvpair_name(pair);
+               char *hashp = strchr(name, '#');
+
+               if (hashp == NULL) {
+                       error = SET_ERROR(EINVAL);
+                       break;
+               }
+               *hashp = '\0';
+               error = zfs_secpolicy_write_perms(name,
+                   ZFS_DELEG_PERM_BOOKMARK, cr);
+               *hashp = '#';
+               if (error != 0)
+                       break;
+       }
+       return (error);
+}
+
+/* ARGSUSED */
+static int
+zfs_secpolicy_destroy_bookmarks(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr)
+{
+       nvpair_t *pair, *nextpair;
+       int error = 0;
+
+       for (pair = nvlist_next_nvpair(innvl, NULL); pair != NULL;
+           pair = nextpair) {
+               char *name = nvpair_name(pair);
+               char *hashp = strchr(name, '#');
+               nextpair = nvlist_next_nvpair(innvl, pair);
+
+               if (hashp == NULL) {
+                       error = SET_ERROR(EINVAL);
+                       break;
+               }
+
+               *hashp = '\0';
+               error = zfs_secpolicy_write_perms(name,
+                   ZFS_DELEG_PERM_DESTROY, cr);
+               *hashp = '#';
+               if (error == ENOENT) {
+                       /*
+                        * Ignore any filesystems that don't exist (we consider
+                        * their bookmarks "already destroyed").  Remove
+                        * the name from the nvl here in case the filesystem
+                        * is created between now and when we try to destroy
+                        * the bookmark (in which case we don't want to
+                        * destroy it since we haven't checked for permission).
+                        */
+                       fnvlist_remove_nvpair(innvl, pair);
+                       error = 0;
+               }
+               if (error != 0)
+                       break;
+       }
+
+       return (error);
+}
+
 /* ARGSUSED */
 static int
 zfs_secpolicy_log_history(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr)
@@ -2551,7 +2609,6 @@ zfs_check_userprops(const char *fsname, nvlist_t *nvl)
 
        while ((pair = nvlist_next_nvpair(nvl, pair)) != NULL) {
                const char *propname = nvpair_name(pair);
-               char *valstr;
 
                if (!zfs_prop_user(propname) ||
                    nvpair_type(pair) != DATA_TYPE_STRING)
@@ -2564,8 +2621,7 @@ zfs_check_userprops(const char *fsname, nvlist_t *nvl)
                if (strlen(propname) >= ZAP_MAXNAMELEN)
                        return (SET_ERROR(ENAMETOOLONG));
 
-               VERIFY(nvpair_value_string(pair, &valstr) == 0);
-               if (strlen(valstr) >= ZAP_MAXVALUELEN)
+               if (strlen(fnvpair_value_string(pair)) >= ZAP_MAXVALUELEN)
                        return (SET_ERROR(E2BIG));
        }
        return (0);
@@ -3242,7 +3298,8 @@ zfs_ioc_snapshot(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl)
                 * The snap name must contain an @, and the part after it must
                 * contain only valid characters.
                 */
-               if (cp == NULL || snapshot_namecheck(cp + 1, NULL, NULL) != 0)
+               if (cp == NULL ||
+                   zfs_component_namecheck(cp + 1, NULL, NULL) != 0)
                        return (SET_ERROR(EINVAL));
 
                /*
@@ -3396,10 +3453,10 @@ zfs_destroy_unmount_origin(const char *fsname)
  *
  * outnvl: snapshot -> error code (int32)
  */
+/* ARGSUSED */
 static int
 zfs_ioc_destroy_snaps(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl)
 {
-       int error, poollen;
        nvlist_t *snaps;
        nvpair_t *pair;
        boolean_t defer;
@@ -3408,25 +3465,110 @@ zfs_ioc_destroy_snaps(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl)
                return (SET_ERROR(EINVAL));
        defer = nvlist_exists(innvl, "defer");
 
-       poollen = strlen(poolname);
        for (pair = nvlist_next_nvpair(snaps, NULL); pair != NULL;
            pair = nvlist_next_nvpair(snaps, pair)) {
+               (void) zfs_unmount_snap(nvpair_name(pair));
+               (void) zvol_remove_minor(nvpair_name(pair));
+       }
+
+       return (dsl_destroy_snapshots_nvl(snaps, defer, outnvl));
+}
+
+/*
+ * Create bookmarks.  Bookmark names are of the form <fs>#<bmark>.
+ * All bookmarks must be in the same pool.
+ *
+ * innvl: {
+ *     bookmark1 -> snapshot1, bookmark2 -> snapshot2
+ * }
+ *
+ * outnvl: bookmark -> error code (int32)
+ *
+ */
+/* ARGSUSED */
+static int
+zfs_ioc_bookmark(const char *poolname, nvlist_t *innvl, nvlist_t *outnvl)
+{
+       nvpair_t *pair, *pair2;
+
+       for (pair = nvlist_next_nvpair(innvl, NULL);
+           pair != NULL; pair = nvlist_next_nvpair(innvl, pair)) {
+               char *snap_name;
+
+               /*
+                * Verify the snapshot argument.
+                */
+               if (nvpair_value_string(pair, &snap_name) != 0)
+                       return (SET_ERROR(EINVAL));
+
+
+               /* Verify that the keys (bookmarks) are unique */
+               for (pair2 = nvlist_next_nvpair(innvl, pair);
+                   pair2 != NULL; pair2 = nvlist_next_nvpair(innvl, pair2)) {
+                       if (strcmp(nvpair_name(pair), nvpair_name(pair2)) == 0)
+                               return (SET_ERROR(EINVAL));
+               }
+       }
+
+       return (dsl_bookmark_create(innvl, outnvl));
+}
+
+/*
+ * innvl: {
+ *     property 1, property 2, ...
+ * }
+ *
+ * outnvl: {
+ *     bookmark name 1 -> { property 1, property 2, ... },
+ *     bookmark name 2 -> { property 1, property 2, ... }
+ * }
+ *
+ */
+static int
+zfs_ioc_get_bookmarks(const char *fsname, nvlist_t *innvl, nvlist_t *outnvl)
+{
+       return (dsl_get_bookmarks(fsname, innvl, outnvl));
+}
+
+/*
+ * innvl: {
+ *     bookmark name 1, bookmark name 2
+ * }
+ *
+ * outnvl: bookmark -> error code (int32)
+ *
+ */
+static int
+zfs_ioc_destroy_bookmarks(const char *poolname, nvlist_t *innvl,
+    nvlist_t *outnvl)
+{
+       int error, poollen;
+       nvpair_t *pair;
+
+       poollen = strlen(poolname);
+       for (pair = nvlist_next_nvpair(innvl, NULL);
+           pair != NULL; pair = nvlist_next_nvpair(innvl, pair)) {
                const char *name = nvpair_name(pair);
+               const char *cp = strchr(name, '#');
 
                /*
-                * The snap must be in the specified pool.
+                * The bookmark name must contain an #, and the part after it
+                * must contain only valid characters.
+                */
+               if (cp == NULL ||
+                   zfs_component_namecheck(cp + 1, NULL, NULL) != 0)
+                       return (SET_ERROR(EINVAL));
+
+               /*
+                * The bookmark must be in the specified pool.
                 */
                if (strncmp(name, poolname, poollen) != 0 ||
-                   (name[poollen] != '/' && name[poollen] != '@'))
+                   (name[poollen] != '/' && name[poollen] != '#'))
                        return (SET_ERROR(EXDEV));
-
-               error = zfs_unmount_snap(name);
-               if (error != 0)
-                       return (error);
-               (void) zvol_remove_minor(name);
        }
 
-       return (dsl_destroy_snapshots_nvl(snaps, defer, outnvl));
+       error = dsl_bookmark_destroy(innvl, outnvl);
+       return (error);
 }
 
 /*
@@ -4097,7 +4239,8 @@ out:
  * zc_guid     if set, estimate size of stream only.  zc_cookie is ignored.
  *             output size in zc_objset_type.
  *
- * outputs: none
+ * outputs:
+ * zc_objset_type      estimated size, if zc_guid is set
  */
 static int
 zfs_ioc_send(zfs_cmd_t *zc)
@@ -5272,6 +5415,19 @@ zfs_ioctl_init(void)
            zfs_ioc_rollback, zfs_secpolicy_rollback, DATASET_NAME,
            POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY, B_FALSE, B_TRUE);
 
+       zfs_ioctl_register("bookmark", ZFS_IOC_BOOKMARK,
+           zfs_ioc_bookmark, zfs_secpolicy_bookmark, POOL_NAME,
+           POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY, B_TRUE, B_TRUE);
+
+       zfs_ioctl_register("get_bookmarks", ZFS_IOC_GET_BOOKMARKS,
+           zfs_ioc_get_bookmarks, zfs_secpolicy_read, DATASET_NAME,
+           POOL_CHECK_SUSPENDED, B_FALSE, B_FALSE);
+
+       zfs_ioctl_register("destroy_bookmarks", ZFS_IOC_DESTROY_BOOKMARKS,
+           zfs_ioc_destroy_bookmarks, zfs_secpolicy_destroy_bookmarks,
+           POOL_NAME,
+           POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY, B_TRUE, B_TRUE);
+
        /* IOCTLS that use the legacy function signature */
 
        zfs_ioctl_register_legacy(ZFS_IOC_POOL_FREEZE, zfs_ioc_pool_freeze,