summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKeith M Wesolowski <wesolows@foobazco.org>2013-12-12 21:44:02 +0000
committerKeith M Wesolowski <wesolows@foobazco.org>2013-12-12 21:44:02 +0000
commite2f9756b8e9461119d4c8b16701e32425283b31a (patch)
tree589339198ca8b38dd8280d8664e06d72bca3d83a
parentd2ea917c8c4786e6dce9d0fd7f18c55c9f72f16e (diff)
parent8b36997aa24d9817807faa4efa301ac9c07a2b78 (diff)
downloadillumos-joyent-e2f9756b8e9461119d4c8b16701e32425283b31a.tar.gz
[illumos-gate merge]20131212release-20131212
commit 8b36997aa24d9817807faa4efa301ac9c07a2b78 4391 panic system rather than corrupting pool if we hit bug 4390 commit 78f171005391b928aaf1642b3206c534ed644332 4369 implement zfs bookmarks 4368 zfs send filesystems from readonly pools commit 973c78e94bf9634782164382c9e291bf81161fa5 4121 vdev_label_init should treat request as succeeded when pool is read only Conflicts: usr/src/man/man1m/zfs.1m usr/src/lib/libzfs/common/mapfile-vers usr/src/lib/libzfs/common/libzfs_impl.h usr/src/cmd/zfs/zfs_main.c
-rw-r--r--usr/src/cmd/truss/codes.c8
-rw-r--r--usr/src/cmd/zfs/zfs_iter.c16
-rw-r--r--usr/src/cmd/zfs/zfs_main.c320
-rw-r--r--usr/src/common/zfs/zfeature_common.c9
-rw-r--r--usr/src/common/zfs/zfeature_common.h1
-rw-r--r--usr/src/common/zfs/zfs_deleg.c56
-rw-r--r--usr/src/common/zfs/zfs_deleg.h2
-rw-r--r--usr/src/common/zfs/zfs_namecheck.c7
-rw-r--r--usr/src/common/zfs/zfs_namecheck.h5
-rw-r--r--usr/src/common/zfs/zfs_prop.c13
-rw-r--r--usr/src/lib/libzfs/common/libzfs.h6
-rw-r--r--usr/src/lib/libzfs/common/libzfs_dataset.c146
-rw-r--r--usr/src/lib/libzfs/common/libzfs_impl.h4
-rw-r--r--usr/src/lib/libzfs/common/libzfs_iter.c62
-rw-r--r--usr/src/lib/libzfs/common/libzfs_sendrecv.c56
-rw-r--r--usr/src/lib/libzfs/common/mapfile-vers6
-rw-r--r--usr/src/lib/libzfs_core/common/libzfs_core.c114
-rw-r--r--usr/src/lib/libzfs_core/common/libzfs_core.h32
-rw-r--r--usr/src/lib/libzfs_core/common/mapfile-vers3
-rw-r--r--usr/src/lib/pyzfs/common/allow.py2
-rw-r--r--usr/src/man/man1m/zfs.1m116
-rw-r--r--usr/src/man/man5/zpool-features.531
-rw-r--r--usr/src/test/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg2
-rw-r--r--usr/src/uts/common/Makefile.files1
-rw-r--r--usr/src/uts/common/fs/zfs/bptree.c27
-rw-r--r--usr/src/uts/common/fs/zfs/dmu_diff.c2
-rw-r--r--usr/src/uts/common/fs/zfs/dmu_send.c126
-rw-r--r--usr/src/uts/common/fs/zfs/dmu_traverse.c2
-rw-r--r--usr/src/uts/common/fs/zfs/dsl_bookmark.c454
-rw-r--r--usr/src/uts/common/fs/zfs/dsl_dataset.c46
-rw-r--r--usr/src/uts/common/fs/zfs/dsl_destroy.c6
-rw-r--r--usr/src/uts/common/fs/zfs/dsl_scan.c3
-rw-r--r--usr/src/uts/common/fs/zfs/spa_misc.c4
-rw-r--r--usr/src/uts/common/fs/zfs/sys/dsl_bookmark.h51
-rw-r--r--usr/src/uts/common/fs/zfs/sys/dsl_dataset.h12
-rw-r--r--usr/src/uts/common/fs/zfs/sys/dsl_deleg.h3
-rw-r--r--usr/src/uts/common/fs/zfs/sys/zfs_debug.h1
-rw-r--r--usr/src/uts/common/fs/zfs/vdev_label.c2
-rw-r--r--usr/src/uts/common/fs/zfs/zfs_ctldir.c4
-rw-r--r--usr/src/uts/common/fs/zfs/zfs_ioctl.c212
-rw-r--r--usr/src/uts/common/sys/fs/zfs.h12
41 files changed, 1703 insertions, 282 deletions
diff --git a/usr/src/cmd/truss/codes.c b/usr/src/cmd/truss/codes.c
index 87fae4f257..ac8a9b191f 100644
--- a/usr/src/cmd/truss/codes.c
+++ b/usr/src/cmd/truss/codes.c
@@ -21,7 +21,7 @@
/*
* Copyright (c) 1989, 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 2011 Nexenta Systems, Inc. All rights reserved.
* Copyright (c) 2012, Joyent, Inc. All rights reserved.
* Copyright (c) 2013, OmniTI Computer Consulting, Inc. All rights reserved.
@@ -1268,6 +1268,12 @@ const struct ioc {
"zfs_cmd_t" },
{ (uint_t)ZFS_IOC_CLONE, "ZFS_IOC_CLONE",
"zfs_cmd_t" },
+ { (uint_t)ZFS_IOC_BOOKMARK, "ZFS_IOC_BOOKMARK",
+ "zfs_cmd_t" },
+ { (uint_t)ZFS_IOC_GET_BOOKMARKS, "ZFS_IOC_GET_BOOKMARKS",
+ "zfs_cmd_t" },
+ { (uint_t)ZFS_IOC_DESTROY_BOOKMARKS, "ZFS_IOC_DESTROY_BOOKMARKS",
+ "zfs_cmd_t" },
/* kssl ioctls */
{ (uint_t)KSSL_ADD_ENTRY, "KSSL_ADD_ENTRY",
diff --git a/usr/src/cmd/zfs/zfs_iter.c b/usr/src/cmd/zfs/zfs_iter.c
index 7599ff2bb5..645524061c 100644
--- a/usr/src/cmd/zfs/zfs_iter.c
+++ b/usr/src/cmd/zfs/zfs_iter.c
@@ -22,6 +22,7 @@
/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2013 Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 2013 by Delphix. All rights reserved.
*/
#include <libintl.h>
@@ -70,7 +71,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;
@@ -90,8 +91,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)) {
@@ -117,7 +119,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);
}
@@ -132,8 +134,12 @@ 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, 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--;
}
diff --git a/usr/src/cmd/zfs/zfs_main.c b/usr/src/cmd/zfs/zfs_main.c
index 4cc2e537c1..e2db6b1688 100644
--- a/usr/src/cmd/zfs/zfs_main.c
+++ b/usr/src/cmd/zfs/zfs_main.c
@@ -99,6 +99,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.
@@ -145,6 +146,7 @@ typedef enum {
HELP_HOLDS,
HELP_RELEASE,
HELP_DIFF,
+ HELP_BOOKMARK,
} zfs_help_t;
typedef struct zfs_command {
@@ -171,6 +173,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 },
@@ -218,11 +221,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 [-crHp] [-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:
@@ -248,12 +252,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 [-DnPpRv] [-[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"));
@@ -261,7 +267,7 @@ get_usage(zfs_help_t idx)
return (gettext("\tshare <-a | filesystem>\n"));
case HELP_SNAPSHOT:
return (gettext("\tsnapshot [-r] [-o property=value] ... "
- "<filesystem@snapname|volume@snapname> ...\n"));
+ "<filesystem|volume>@<snap> ...\n"));
case HELP_UNMOUNT:
return (gettext("\tunmount [-f] "
"<-a | filesystem|mountpoint>\n"));
@@ -290,11 +296,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"));
@@ -305,6 +313,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();
@@ -923,6 +933,7 @@ typedef struct destroy_cbdata {
char *cb_prevsnap;
int64_t cb_snapused;
char *cb_snapspec;
+ char *cb_bookmark;
} destroy_cbdata_t;
/*
@@ -1237,7 +1248,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 */
@@ -1292,6 +1303,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. */
@@ -1353,6 +1365,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)
@@ -1718,7 +1770,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)) {
@@ -1732,7 +1785,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:
@@ -3064,7 +3121,8 @@ zfs_do_list(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)) {
@@ -3078,9 +3136,12 @@ zfs_do_list(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:
(void) fprintf(stderr,
gettext("invalid type '%s'\n"),
@@ -3295,9 +3356,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
@@ -3314,52 +3395,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);
}
@@ -3429,7 +3488,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, rollback_check, &cb)) != 0)
+ goto out;
+ if ((ret = zfs_iter_bookmarks(zhp, rollback_check, &cb)) != 0)
goto out;
if ((ret = cb.cb_error) != 0)
@@ -3724,12 +3785,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);
@@ -3885,6 +3979,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
@@ -3904,6 +3999,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 },
@@ -6596,6 +6692,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)
{
diff --git a/usr/src/common/zfs/zfeature_common.c b/usr/src/common/zfs/zfeature_common.c
index cefebca443..4e0030bb83 100644
--- a/usr/src/common/zfs/zfeature_common.c
+++ b/usr/src/common/zfs/zfeature_common.c
@@ -199,4 +199,13 @@ 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);
}
diff --git a/usr/src/common/zfs/zfeature_common.h b/usr/src/common/zfs/zfeature_common.h
index c7586e3110..88e1fe2af3 100644
--- a/usr/src/common/zfs/zfeature_common.h
+++ b/usr/src/common/zfs/zfeature_common.h
@@ -49,6 +49,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;
diff --git a/usr/src/common/zfs/zfs_deleg.c b/usr/src/common/zfs/zfs_deleg.c
index 18681035d6..49540ead1d 100644
--- a/usr/src/common/zfs/zfs_deleg.c
+++ b/usr/src/common/zfs/zfs_deleg.c
@@ -21,8 +21,11 @@
/*
* 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>
@@ -34,43 +37,34 @@
#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
diff --git a/usr/src/common/zfs/zfs_deleg.h b/usr/src/common/zfs/zfs_deleg.h
index 9997dffae7..16133c59f3 100644
--- a/usr/src/common/zfs/zfs_deleg.h
+++ b/usr/src/common/zfs/zfs_deleg.h
@@ -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;
diff --git a/usr/src/common/zfs/zfs_namecheck.c b/usr/src/common/zfs/zfs_namecheck.c
index 5cfafea471..a32955fe2d 100644
--- a/usr/src/common/zfs/zfs_namecheck.c
+++ b/usr/src/common/zfs/zfs_namecheck.c
@@ -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));
}
/*
diff --git a/usr/src/common/zfs/zfs_namecheck.h b/usr/src/common/zfs/zfs_namecheck.h
index 7711da099b..cbefbaa0d5 100644
--- a/usr/src/common/zfs/zfs_namecheck.h
+++ b/usr/src/common/zfs/zfs_namecheck.h
@@ -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
diff --git a/usr/src/common/zfs/zfs_prop.c b/usr/src/common/zfs/zfs_prop.c
index 6441290178..75627c33bf 100644
--- a/usr/src/common/zfs/zfs_prop.c
+++ b/usr/src/common/zfs/zfs_prop.c
@@ -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.
* Copyright (c) 2013, Joyent, Inc. All rights reserved.
*/
@@ -312,7 +312,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");
@@ -378,18 +379,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");
@@ -402,7 +403,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);
}
diff --git a/usr/src/lib/libzfs/common/libzfs.h b/usr/src/lib/libzfs/common/libzfs.h
index 35d2c26a9b..62dce8f2d4 100644
--- a/usr/src/lib/libzfs/common/libzfs.h
+++ b/usr/src/lib/libzfs/common/libzfs.h
@@ -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.
@@ -192,6 +192,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);
@@ -534,6 +535,7 @@ extern int zfs_iter_filesystems(zfs_handle_t *, zfs_iter_f, void *);
extern int zfs_iter_snapshots(zfs_handle_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;
@@ -595,6 +597,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 *,
@@ -664,6 +667,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);
/*
* Mount support functions.
diff --git a/usr/src/lib/libzfs/common/libzfs_dataset.c b/usr/src/lib/libzfs/common/libzfs_dataset.c
index e1fa965893..26e5adc02c 100644
--- a/usr/src/lib/libzfs/common/libzfs_dataset.c
+++ b/usr/src/lib/libzfs/common/libzfs_dataset.c
@@ -296,7 +296,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);
@@ -568,6 +568,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
@@ -2250,6 +2314,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();
}
@@ -3100,6 +3167,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)) {
@@ -3482,45 +3562,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;
+
+ /* 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);
- 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) {
+ zfs_close(zhp);
+ return (0);
+}
- cbp->cb_dependent = B_TRUE;
- cbp->cb_error |= zfs_iter_dependents(zhp, B_FALSE,
- rollback_destroy, cbp);
- cbp->cb_dependent = B_FALSE;
+static int
+rollback_destroy(zfs_handle_t *zhp, void *data)
+{
+ rollback_data_t *cbp = data;
- cbp->cb_error |= zfs_destroy(zhp, B_FALSE);
- }
- } else {
- /* We must destroy this clone; first unmount it */
- prop_changelist_t *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);
- 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_error |= zfs_destroy(zhp, B_FALSE);
}
zfs_close(zhp);
@@ -3531,8 +3610,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)
@@ -3552,7 +3631,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, rollback_destroy, &cb);
+ (void) zfs_iter_bookmarks(zhp, rollback_destroy, &cb);
if (cb.cb_error)
return (-1);
diff --git a/usr/src/lib/libzfs/common/libzfs_impl.h b/usr/src/lib/libzfs/common/libzfs_impl.h
index 62793dcda1..9d98718dcb 100644
--- a/usr/src/lib/libzfs/common/libzfs_impl.h
+++ b/usr/src/lib/libzfs/common/libzfs_impl.h
@@ -22,7 +22,7 @@
/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, Joyent, Inc. All rights reserved.
- * Copyright (c) 2012 by Delphix. All rights reserved.
+ * Copyright (c) 2013 by Delphix. All rights reserved.
*/
#ifndef _LIBZFS_IMPL_H
@@ -189,6 +189,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 **);
diff --git a/usr/src/lib/libzfs/common/libzfs_iter.c b/usr/src/lib/libzfs/common/libzfs_iter.c
index 0bc89cbf8d..19ac0b2625 100644
--- a/usr/src/lib/libzfs/common/libzfs_iter.c
+++ b/usr/src/lib/libzfs/common/libzfs_iter.c
@@ -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.
* Copyright (c) 2012, Joyent, Inc. All rights reserved.
*/
@@ -147,7 +147,8 @@ zfs_iter_snapshots(zfs_handle_t *zhp, zfs_iter_f func, void *data)
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);
if (zhp->zfs_hdl->libzfs_cachedprops &&
@@ -174,6 +175,59 @@ zfs_iter_snapshots(zfs_handle_t *zhp, zfs_iter_f func, void *data)
}
/*
+ * 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;
+
+ 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 (nvpair_t *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
*/
typedef struct zfs_node {
@@ -407,13 +461,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;
diff --git a/usr/src/lib/libzfs/common/libzfs_sendrecv.c b/usr/src/lib/libzfs/common/libzfs_sendrecv.c
index fefaa0d513..8e0e4e1e6e 100644
--- a/usr/src/lib/libzfs/common/libzfs_sendrecv.c
+++ b/usr/src/lib/libzfs/common/libzfs_sendrecv.c
@@ -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.
*/
@@ -1609,6 +1609,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"
*/
diff --git a/usr/src/lib/libzfs/common/mapfile-vers b/usr/src/lib/libzfs/common/mapfile-vers
index d2ce523cf8..0e10528f9e 100644
--- a/usr/src/lib/libzfs/common/mapfile-vers
+++ b/usr/src/lib/libzfs/common/mapfile-vers
@@ -20,7 +20,7 @@
#
# Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
# Copyright 2011 Nexenta Systems, Inc. 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.
#
# MAPFILE HEADER START
@@ -64,6 +64,7 @@ SYMBOL_VERSION SUNWprivate_1.1 {
spa_feature_table;
zfs_allocatable_devs;
zfs_asprintf;
+ zfs_bookmark_exists;
zfs_clone;
zfs_close;
zfs_create;
@@ -87,6 +88,7 @@ SYMBOL_VERSION SUNWprivate_1.1 {
zfs_is_shared;
zfs_is_shared_nfs;
zfs_is_shared_smb;
+ zfs_iter_bookmarks;
zfs_iter_children;
zfs_iter_dependents;
zfs_iter_filesystems;
@@ -137,6 +139,7 @@ SYMBOL_VERSION SUNWprivate_1.1 {
zfs_rollback;
zfs_save_arguments;
zfs_send;
+ zfs_send_one;
zfs_share;
zfs_shareall;
zfs_share_nfs;
@@ -150,6 +153,7 @@ SYMBOL_VERSION SUNWprivate_1.1 {
zfs_snapshot_nvl;
zfs_spa_version;
zfs_spa_version_map;
+ zfs_standard_error;
zfs_type_to_name;
zfs_unmount;
zfs_unmountall;
diff --git a/usr/src/lib/libzfs_core/common/libzfs_core.c b/usr/src/lib/libzfs_core/common/libzfs_core.c
index 20d7329677..7653d028a9 100644
--- a/usr/src/lib/libzfs_core/common/libzfs_core.c
+++ b/usr/src/lib/libzfs_core/common/libzfs_core.c
@@ -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);
+}
diff --git a/usr/src/lib/libzfs_core/common/libzfs_core.h b/usr/src/lib/libzfs_core/common/libzfs_core.h
index 3642dc7afd..484a48afe2 100644
--- a/usr/src/lib/libzfs_core/common/libzfs_core.h
+++ b/usr/src/lib/libzfs_core/common/libzfs_core.h
@@ -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
}
diff --git a/usr/src/lib/libzfs_core/common/mapfile-vers b/usr/src/lib/libzfs_core/common/mapfile-vers
index a4a110f907..ebed35cfd0 100644
--- a/usr/src/lib/libzfs_core/common/mapfile-vers
+++ b/usr/src/lib/libzfs_core/common/mapfile-vers
@@ -41,10 +41,13 @@ SYMBOL_VERSION ILLUMOS_0.1 {
libzfs_core_fini;
libzfs_core_init;
+ lzc_bookmark;
lzc_clone;
lzc_create;
+ lzc_destroy_bookmarks;
lzc_destroy_snaps;
lzc_exists;
+ lzc_get_bookmarks;
lzc_get_holds;
lzc_hold;
lzc_receive;
diff --git a/usr/src/lib/pyzfs/common/allow.py b/usr/src/lib/pyzfs/common/allow.py
index fa8209f697..7ad4b49cc3 100644
--- a/usr/src/lib/pyzfs/common/allow.py
+++ b/usr/src/lib/pyzfs/common/allow.py
@@ -20,6 +20,7 @@
# CDDL HEADER END
#
# Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2013 by Delphix. All rights reserved.
#
"""This module implements the "zfs allow" and "zfs unallow" subcommands.
@@ -219,6 +220,7 @@ perms_subcmd = dict(
hold=_("Allows adding a user hold to a snapshot"),
release=_("Allows releasing a user hold which\n\t\t\t\tmight destroy the snapshot"),
diff=_("Allows lookup of paths within a dataset,\n\t\t\t\tgiven an object number. Ordinary users need this\n\t\t\t\tin order to use zfs diff"),
+ bookmark="",
)
perms_other = dict(
diff --git a/usr/src/man/man1m/zfs.1m b/usr/src/man/man1m/zfs.1m
index e468053a7a..ff3f53ef01 100644
--- a/usr/src/man/man1m/zfs.1m
+++ b/usr/src/man/man1m/zfs.1m
@@ -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) 2013 by Saso Kiselkov. All rights reserved.
.\" Copyright 2013 Nexenta Systems, Inc. All Rights Reserved.
.\" Copyright (c) 2013, Joyent, Inc. All rights reserved.
@@ -58,6 +58,11 @@ zfs \- configures ZFS file systems
.LP
.nf
+\fBzfs\fR \fBdestroy\fR \fIfilesystem\fR|\fIvolume\fR#\fIbookmark\fR
+.fi
+
+.LP
+.nf
\fBzfs\fR \fBsnapshot\fR [\fB-r\fR] [\fB-o\fR \fIproperty\fR=\fIvalue\fR]...
\fIfilesystem@snapname\fR|\fIvolume@snapname\fR...
.fi
@@ -165,11 +170,21 @@ zfs \- configures ZFS file systems
.LP
.nf
+\fBzfs\fR \fBbookmark\fR \fIsnapshot\fR \fIbookmark\fR
+.fi
+
+.LP
+.nf
\fBzfs\fR \fBsend\fR [\fB-DnPpRrv\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\fR [\fB-vnFu\fR] \fIfilesystem\fR|\fIvolume\fR|\fIsnapshot\fR
.fi
@@ -1906,6 +1921,17 @@ behavior for mounted file systems in use.
.sp
.ne 2
.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
@@ -1948,13 +1974,13 @@ 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.
+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.
+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
.na
@@ -1962,7 +1988,7 @@ rollback the individual child snapshots.
.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
@@ -1972,7 +1998,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
+Destroy any more recent snapshots and bookmarks, as well as any clones of those
snapshots.
.RE
@@ -2260,8 +2286,8 @@ Same as the \fB-s\fR option, but sorts by property in descending order.
.sp .6
.RS 4n
A comma-separated list of types to display, where \fItype\fR is one of
-\fBfilesystem\fR, \fBsnapshot\fR , \fBvolume\fR, or \fBall\fR. For example,
-specifying \fB-t snapshot\fR displays only snapshots.
+\fBfilesystem\fR, \fBsnapshot\fR , \fBvolume\fR, \fBbookmark\fR, or \fBall\fR.
+For example, specifying \fB-t snapshot\fR displays only snapshots.
.RE
.sp
@@ -2324,7 +2350,7 @@ 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 given dataset's type (filesystem, volume, snapshot, or bookmark).
.sp
.ne 2
.na
@@ -2816,6 +2842,24 @@ Unshare the specified filesystem. The command can also be given a path to a
.sp
.ne 2
.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-DnPpRrv\fR] [\fB-\fR[\fBiI\fR] \fIsnapshot\fR] \fIsnapshot\fR
.ad
.sp .6
@@ -2831,11 +2875,11 @@ generated.
.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
@@ -2850,9 +2894,9 @@ must be fully specified (for example, \fBpool/fs@origin\fR, not just
.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.
+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
@@ -2949,6 +2993,40 @@ 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
.na
diff --git a/usr/src/man/man5/zpool-features.5 b/usr/src/man/man5/zpool-features.5
index 4df496d0ad..58ec7ab7dc 100644
--- a/usr/src/man/man5/zpool-features.5
+++ b/usr/src/man/man5/zpool-features.5
@@ -224,11 +224,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(1M) 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
@@ -298,6 +298,27 @@ this feature are destroyed.
.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
\fB\fBenabled_txg\fR\fR
.ad
.RS 4n
diff --git a/usr/src/test/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg b/usr/src/test/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg
index 86fe4ba80c..47eede7dc1 100644
--- a/usr/src/test/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg
+++ b/usr/src/test/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg
@@ -35,4 +35,4 @@ typeset -a properties=("size" "capacity" "altroot" "health" "guid" "version"
"listsnapshots" "autoexpand" "feature@async_destroy" "feature@empty_bpobj"
"feature@lz4_compress" "feature@multi_vdev_crash_dump"
"feature@spacemap_histogram" "feature@enabled_txg" "feature@hole_birth"
- "feature@extensible_dataset")
+ "feature@extensible_dataset" "feature@bookmarks")
diff --git a/usr/src/uts/common/Makefile.files b/usr/src/uts/common/Makefile.files
index 1098e9c458..a706bfca8a 100644
--- a/usr/src/uts/common/Makefile.files
+++ b/usr/src/uts/common/Makefile.files
@@ -1349,6 +1349,7 @@ ZFS_COMMON_OBJS += \
dmu_tx.o \
dnode.o \
dnode_sync.o \
+ dsl_bookmark.o \
dsl_dir.o \
dsl_dataset.o \
dsl_deadlist.o \
diff --git a/usr/src/uts/common/fs/zfs/bptree.c b/usr/src/uts/common/fs/zfs/bptree.c
index f70f23c698..83f365864d 100644
--- a/usr/src/uts/common/fs/zfs/bptree.c
+++ b/usr/src/uts/common/fs/zfs/bptree.c
@@ -180,6 +180,7 @@ bptree_iterate(objset_t *os, uint64_t obj, boolean_t free, bptree_itor_t func,
err = 0;
for (i = ba.ba_phys->bt_begin; i < ba.ba_phys->bt_end; i++) {
bptree_entry_phys_t bte;
+ int flags = TRAVERSE_PREFETCH_METADATA | TRAVERSE_POST;
ASSERT(!free || i == ba.ba_phys->bt_begin);
@@ -188,13 +189,13 @@ bptree_iterate(objset_t *os, uint64_t obj, boolean_t free, bptree_itor_t func,
if (err != 0)
break;
+ if (zfs_recover)
+ flags |= TRAVERSE_HARD;
err = traverse_dataset_destroyed(os->os_spa, &bte.be_bp,
- bte.be_birth_txg, &bte.be_zb,
- TRAVERSE_PREFETCH_METADATA | TRAVERSE_POST,
+ bte.be_birth_txg, &bte.be_zb, flags,
bptree_visit_cb, &ba);
if (free) {
- ASSERT(err == 0 || err == ERESTART);
- if (err != 0) {
+ if (err == ERESTART) {
/* save bookmark for future resume */
ASSERT3U(bte.be_zb.zb_objset, ==,
ZB_DESTROYED_OBJSET);
@@ -202,11 +203,21 @@ bptree_iterate(objset_t *os, uint64_t obj, boolean_t free, bptree_itor_t func,
dmu_write(os, obj, i * sizeof (bte),
sizeof (bte), &bte, tx);
break;
- } else {
- ba.ba_phys->bt_begin++;
- (void) dmu_free_range(os, obj,
- i * sizeof (bte), sizeof (bte), tx);
}
+ if (err != 0) {
+ /*
+ * We can not properly handle an i/o
+ * error, because the traversal code
+ * does not know how to resume from an
+ * arbitrary bookmark.
+ */
+ zfs_panic_recover("error %u from "
+ "traverse_dataset_destroyed()", err);
+ }
+
+ ba.ba_phys->bt_begin++;
+ (void) dmu_free_range(os, obj,
+ i * sizeof (bte), sizeof (bte), tx);
}
}
diff --git a/usr/src/uts/common/fs/zfs/dmu_diff.c b/usr/src/uts/common/fs/zfs/dmu_diff.c
index 8d7385539f..a2130b1319 100644
--- a/usr/src/uts/common/fs/zfs/dmu_diff.c
+++ b/usr/src/uts/common/fs/zfs/dmu_diff.c
@@ -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);
diff --git a/usr/src/uts/common/fs/zfs/dmu_send.c b/usr/src/uts/common/fs/zfs/dmu_send.c
index 2d78c16cd5..bdefc75c28 100644
--- a/usr/src/uts/common/fs/zfs/dmu_send.c
+++ b/usr/src/uts/common/fs/zfs/dmu_send.c
@@ -48,6 +48,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;
@@ -356,6 +357,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);
@@ -404,6 +411,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) {
@@ -431,11 +439,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;
@@ -443,18 +452,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);
}
@@ -470,9 +469,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));
}
@@ -487,20 +483,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);
@@ -514,7 +510,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);
@@ -560,7 +556,6 @@ out:
kmem_free(dsp, sizeof (dmu_sendarg_t));
dsl_dataset_long_rele(ds, FTAG);
- dsl_dataset_rele(ds, tag);
return (err);
}
@@ -585,15 +580,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
@@ -602,33 +612,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
@@ -648,7 +704,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/usr/src/uts/common/fs/zfs/dmu_traverse.c b/usr/src/uts/common/fs/zfs/dmu_traverse.c
index 68bcac4884..146aad1fbc 100644
--- a/usr/src/uts/common/fs/zfs/dmu_traverse.c
+++ b/usr/src/uts/common/fs/zfs/dmu_traverse.c
@@ -383,7 +383,7 @@ traverse_visitbp(traverse_data_t *td, const dnode_phys_t *dnp,
(void) arc_buf_remove_ref(buf, &buf);
post:
- if (err == 0 && lasterr == 0 && (td->td_flags & TRAVERSE_POST)) {
+ if (err == 0 && (td->td_flags & TRAVERSE_POST)) {
err = td->td_func(td->td_spa, NULL, bp, zb, dnp, td->td_arg);
if (err == ERESTART)
pause = B_TRUE;
diff --git a/usr/src/uts/common/fs/zfs/dsl_bookmark.c b/usr/src/uts/common/fs/zfs/dsl_bookmark.c
new file mode 100644
index 0000000000..35e55ece4f
--- /dev/null
+++ b/usr/src/uts/common/fs/zfs/dsl_bookmark.c
@@ -0,0 +1,454 @@
+/*
+ * 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;
+
+ if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS))
+ return (SET_ERROR(ENOTSUP));
+
+ for (nvpair_t *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;
+
+ ASSERT(spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS));
+
+ for (nvpair_t *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)) {
+ 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;
+
+ nvlist_t *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;
+
+ if (!spa_feature_is_enabled(dp->dp_spa, SPA_FEATURE_BOOKMARKS))
+ return (0);
+
+ for (nvpair_t *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;
+
+ for (nvpair_t *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);
+}
diff --git a/usr/src/uts/common/fs/zfs/dsl_dataset.c b/usr/src/uts/common/fs/zfs/dsl_dataset.c
index 74395d3077..5fa408f1fa 100644
--- a/usr/src/uts/common/fs/zfs/dsl_dataset.c
+++ b/usr/src/uts/common/fs/zfs/dsl_dataset.c
@@ -47,6 +47,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) \
{ \
@@ -400,6 +401,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);
@@ -1721,6 +1730,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 */
+ nvlist_t *proprequest = fnvlist_alloc();
+ fnvlist_add_boolean(proprequest, zfs_prop_to_name(ZFS_PROP_CREATETXG));
+ nvlist_t *bookmarks = fnvlist_alloc();
+ error = dsl_get_bookmarks_impl(ds, proprequest, bookmarks);
+ fnvlist_free(proprequest);
+ if (error != 0)
+ return (error);
+ for (nvpair_t *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);
@@ -2942,18 +2973,25 @@ 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;
boolean_t ret;
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)
@@ -2968,7 +3006,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);
}
diff --git a/usr/src/uts/common/fs/zfs/dsl_destroy.c b/usr/src/uts/common/fs/zfs/dsl_destroy.c
index 6f75b0df41..9e695cdec6 100644
--- a/usr/src/uts/common/fs/zfs/dsl_destroy.c
+++ b/usr/src/uts/common/fs/zfs/dsl_destroy.c
@@ -808,6 +808,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);
diff --git a/usr/src/uts/common/fs/zfs/dsl_scan.c b/usr/src/uts/common/fs/zfs/dsl_scan.c
index ee8f65cf7a..f5e2ea825d 100644
--- a/usr/src/uts/common/fs/zfs/dsl_scan.c
+++ b/usr/src/uts/common/fs/zfs/dsl_scan.c
@@ -1317,6 +1317,9 @@ dsl_scan_free_should_pause(dsl_scan_t *scn)
{
uint64_t elapsed_nanosecs;
+ if (zfs_recover)
+ return (B_FALSE);
+
elapsed_nanosecs = gethrtime() - scn->scn_sync_start_time;
return (elapsed_nanosecs / NANOSEC > zfs_txg_timeout ||
(NSEC2MSEC(elapsed_nanosecs) > zfs_free_min_time_ms &&
diff --git a/usr/src/uts/common/fs/zfs/spa_misc.c b/usr/src/uts/common/fs/zfs/spa_misc.c
index 928497ca87..fa0658493e 100644
--- a/usr/src/uts/common/fs/zfs/spa_misc.c
+++ b/usr/src/uts/common/fs/zfs/spa_misc.c
@@ -247,6 +247,8 @@ int zfs_flags = 0;
* zfs_recover can be set to nonzero to attempt to recover from
* otherwise-fatal errors, typically caused by on-disk corruption. When
* set, calls to zfs_panic_recover() will turn into warning messages.
+ * This should only be used as a last resort, as it typically results
+ * in leaked space, or worse.
*/
int zfs_recover = 0;
@@ -438,7 +440,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';
diff --git a/usr/src/uts/common/fs/zfs/sys/dsl_bookmark.h b/usr/src/uts/common/fs/zfs/sys/dsl_bookmark.h
new file mode 100644
index 0000000000..3591986d7b
--- /dev/null
+++ b/usr/src/uts/common/fs/zfs/sys/dsl_bookmark.h
@@ -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 */
diff --git a/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h b/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h
index ac4ea20cc4..5132ef9ce8 100644
--- a/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h
+++ b/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h
@@ -76,6 +76,13 @@ struct dsl_pool;
*/
/*
+ * 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
diff --git a/usr/src/uts/common/fs/zfs/sys/dsl_deleg.h b/usr/src/uts/common/fs/zfs/sys/dsl_deleg.h
index 5842639aaf..59e8e05555 100644
--- a/usr/src/uts/common/fs/zfs/sys/dsl_deleg.h
+++ b/usr/src/uts/common/fs/zfs/sys/dsl_deleg.h
@@ -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
diff --git a/usr/src/uts/common/fs/zfs/sys/zfs_debug.h b/usr/src/uts/common/fs/zfs/sys/zfs_debug.h
index c4dcfaec65..e6a1fa2828 100644
--- a/usr/src/uts/common/fs/zfs/sys/zfs_debug.h
+++ b/usr/src/uts/common/fs/zfs/sys/zfs_debug.h
@@ -47,6 +47,7 @@ extern "C" {
#endif
extern int zfs_flags;
+extern int zfs_recover;
#define ZFS_DEBUG_DPRINTF (1<<0)
#define ZFS_DEBUG_DBUF_VERIFY (1<<1)
diff --git a/usr/src/uts/common/fs/zfs/vdev_label.c b/usr/src/uts/common/fs/zfs/vdev_label.c
index 0b4915fca8..c7ae60c68f 100644
--- a/usr/src/uts/common/fs/zfs/vdev_label.c
+++ b/usr/src/uts/common/fs/zfs/vdev_label.c
@@ -640,7 +640,7 @@ vdev_label_init(vdev_t *vd, uint64_t crtxg, vdev_labeltype_t reason)
/* Track the creation time for this vdev */
vd->vdev_crtxg = crtxg;
- if (!vd->vdev_ops->vdev_op_leaf)
+ if (!vd->vdev_ops->vdev_op_leaf || !spa_writeable(spa))
return (0);
/*
diff --git a/usr/src/uts/common/fs/zfs/zfs_ctldir.c b/usr/src/uts/common/fs/zfs/zfs_ctldir.c
index 5928fe75e9..2f17496838 100644
--- a/usr/src/uts/common/fs/zfs/zfs_ctldir.c
+++ b/usr/src/uts/common/fs/zfs/zfs_ctldir.c
@@ -515,7 +515,7 @@ zfsctl_snapshot_zname(vnode_t *vp, const char *name, int len, char *zname)
{
objset_t *os = ((zfsvfs_t *)((vp)->v_vfsp->vfs_data))->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);
if (strlen(zname) + 1 + strlen(name) >= len)
@@ -746,7 +746,7 @@ zfsctl_snapdir_mkdir(vnode_t *dvp, char *dirname, vattr_t *vap, vnode_t **vpp,
static enum symfollow follow = NO_FOLLOW;
static enum uio_seg seg = UIO_SYSSPACE;
- if (snapshot_namecheck(dirname, NULL, NULL) != 0)
+ if (zfs_component_namecheck(dirname, NULL, NULL) != 0)
return (SET_ERROR(EILSEQ));
dmu_objset_name(zfsvfs->z_os, name);
diff --git a/usr/src/uts/common/fs/zfs/zfs_ioctl.c b/usr/src/uts/common/fs/zfs/zfs_ioctl.c
index ec3254213a..15fb771a8b 100644
--- a/usr/src/uts/common/fs/zfs/zfs_ioctl.c
+++ b/usr/src/uts/common/fs/zfs/zfs_ioctl.c
@@ -177,6 +177,7 @@
#include <sys/dmu_objset.h>
#include <sys/dmu_send.h>
#include <sys/dsl_destroy.h>
+#include <sys/dsl_bookmark.h>
#include <sys/dsl_userhold.h>
#include <sys/zfeature.h>
@@ -830,22 +831,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
@@ -1004,6 +992,75 @@ 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;
+
+ for (nvpair_t *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)
@@ -2617,7 +2674,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)
@@ -2630,8 +2686,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 (E2BIG);
}
return (0);
@@ -3320,7 +3375,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));
/*
@@ -3466,10 +3522,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;
@@ -3478,24 +3534,106 @@ 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));
+ }
+
+ 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)
+{
+ for (nvpair_t *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 (nvpair_t *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;
+
+ poollen = strlen(poolname);
+ for (nvpair_t *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);
}
- return (dsl_destroy_snapshots_nvl(snaps, defer, outnvl));
+ error = dsl_bookmark_destroy(innvl, outnvl);
+ return (error);
}
/*
@@ -4156,7 +4294,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)
@@ -5366,6 +5505,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,
@@ -5703,7 +5855,7 @@ zfsdev_ioctl(dev_t dev, int cmd, intptr_t arg, int flag, cred_t *cr, int *rvalp)
goto out;
/* legacy ioctls can modify zc_name */
- len = strcspn(zc->zc_name, "/@") + 1;
+ len = strcspn(zc->zc_name, "/@#") + 1;
saved_poolname = kmem_alloc(len, KM_SLEEP);
(void) strlcpy(saved_poolname, zc->zc_name, len);
diff --git a/usr/src/uts/common/sys/fs/zfs.h b/usr/src/uts/common/sys/fs/zfs.h
index f6eb9aa490..c330784321 100644
--- a/usr/src/uts/common/sys/fs/zfs.h
+++ b/usr/src/uts/common/sys/fs/zfs.h
@@ -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 {
@@ -828,6 +829,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,
ZFS_IOC_LAST
} zfs_ioc_t;