4369 implement zfs bookmarks
4368 zfs send filesystems from readonly pools llumos/illumos-gate@78f1710053
This commit is contained in:
parent
f6eff23569
commit
cbdc759424
@ -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--;
|
||||
}
|
||||
|
||||
|
@ -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 [-rHp] [-d max] "
|
||||
"[-o \"all\" | field[,...]] [-t type[,...]] "
|
||||
"[-s source[,...]]\n"
|
||||
"[-o \"all\" | field[,...]]\n"
|
||||
"\t [-t type[,...]] [-s source[,...]]\n"
|
||||
"\t <\"all\" | property[,...]> "
|
||||
"[filesystem|volume|snapshot] ...\n"));
|
||||
case HELP_INHERIT:
|
||||
@ -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();
|
||||
@ -906,6 +916,7 @@ typedef struct destroy_cbdata {
|
||||
char *cb_prevsnap;
|
||||
int64_t cb_snapused;
|
||||
char *cb_snapspec;
|
||||
char *cb_bookmark;
|
||||
} destroy_cbdata_t;
|
||||
|
||||
/*
|
||||
@ -1175,7 +1186,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 */
|
||||
@ -1227,6 +1238,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. */
|
||||
@ -1288,6 +1300,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)
|
||||
@ -1650,7 +1702,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)) {
|
||||
@ -1664,7 +1717,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:
|
||||
@ -2995,7 +3052,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)) {
|
||||
@ -3009,9 +3067,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"),
|
||||
@ -3214,9 +3275,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
|
||||
@ -3233,52 +3314,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);
|
||||
}
|
||||
@ -3348,7 +3407,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)
|
||||
@ -3643,12 +3704,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);
|
||||
@ -3804,6 +3898,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
|
||||
|
||||
@ -3823,6 +3918,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 },
|
||||
@ -6515,6 +6611,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)
|
||||
{
|
||||
|
@ -197,4 +197,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);
|
||||
}
|
||||
|
@ -48,6 +48,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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
@ -190,6 +190,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);
|
||||
@ -532,6 +533,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;
|
||||
@ -593,6 +595,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 *,
|
||||
@ -662,6 +665,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.
|
||||
|
@ -295,7 +295,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);
|
||||
|
||||
@ -563,6 +563,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
|
||||
@ -2229,6 +2293,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();
|
||||
}
|
||||
@ -3079,6 +3146,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)) {
|
||||
@ -3461,45 +3541,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_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);
|
||||
|
||||
zfs_close(zhp);
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
rollback_destroy(zfs_handle_t *zhp, void *data)
|
||||
{
|
||||
rollback_data_t *cbp = data;
|
||||
|
||||
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) {
|
||||
if (zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG) > cbp->cb_create) {
|
||||
cbp->cb_error |= zfs_iter_dependents(zhp, B_FALSE,
|
||||
rollback_destroy_dependent, cbp);
|
||||
|
||||
cbp->cb_dependent = B_TRUE;
|
||||
cbp->cb_error |= zfs_iter_dependents(zhp, B_FALSE,
|
||||
rollback_destroy, cbp);
|
||||
cbp->cb_dependent = B_FALSE;
|
||||
|
||||
cbp->cb_error |= zfs_destroy(zhp, B_FALSE);
|
||||
}
|
||||
} else {
|
||||
/* We must destroy this clone; first unmount it */
|
||||
prop_changelist_t *clp;
|
||||
|
||||
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);
|
||||
@ -3510,8 +3589,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)
|
||||
@ -3531,7 +3610,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);
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2012 by Delphix. All rights reserved.
|
||||
* Copyright (c) 2013 by Delphix. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef _LIBZFS_IMPL_H
|
||||
@ -186,6 +186,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 **);
|
||||
|
||||
|
@ -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.
|
||||
*/
|
||||
|
||||
@ -143,7 +143,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 (zcmd_alloc_dst_nvlist(zhp->zfs_hdl, &zc, 0) != 0)
|
||||
@ -165,6 +166,59 @@ zfs_iter_snapshots(zfs_handle_t *zhp, zfs_iter_f func, void *data)
|
||||
return ((ret < 0) ? ret : 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Iterate over all bookmarks
|
||||
*/
|
||||
int
|
||||
zfs_iter_bookmarks(zfs_handle_t *zhp, zfs_iter_f func, void *data)
|
||||
{
|
||||
zfs_handle_t *nzhp;
|
||||
nvlist_t *props = NULL;
|
||||
nvlist_t *bmarks = NULL;
|
||||
int err;
|
||||
|
||||
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
|
||||
*/
|
||||
@ -399,13 +453,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;
|
||||
|
||||
|
@ -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 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
|
||||
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"
|
||||
*/
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 @@ def decodeid(w, toidfunc, fmt):
|
||||
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(
|
||||
|
116
man/man1m/zfs.1m
116
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.
|
||||
@ -56,6 +56,11 @@ zfs \- configures ZFS file systems
|
||||
\fBzfs\fR \fBdestroy\fR [\fB-dnpRrv\fR] \fIfilesystem\fR|\fIvolume\fR@\fIsnap\fR[%\fIsnap\fR][,\fIsnap\fR[%\fIsnap\fR]]...
|
||||
.fi
|
||||
|
||||
.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]...
|
||||
@ -163,11 +168,21 @@ zfs \- configures ZFS file systems
|
||||
\fBzfs\fR \fBunshare\fR \fB-a\fR \fIfilesystem\fR|\fImountpoint\fR
|
||||
.fi
|
||||
|
||||
.LP
|
||||
.nf
|
||||
\fBzfs\fR \fBbookmark\fR \fIsnapshot\fR \fIbookmark\fR
|
||||
.fi
|
||||
|
||||
.LP
|
||||
.nf
|
||||
\fBzfs\fR \fBsend\fR [\fB-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
|
||||
@ -1903,6 +1918,17 @@ options, as they can destroy large portions of a pool and cause unexpected
|
||||
behavior for mounted file systems in use.
|
||||
.RE
|
||||
|
||||
.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
|
||||
@ -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
|
||||
|
||||
.RE
|
||||
@ -2313,7 +2339,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
|
||||
@ -2789,6 +2815,24 @@ Unshare the specified filesystem. The command can also be given a path to a
|
||||
|
||||
.RE
|
||||
|
||||
.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
|
||||
@ -2807,11 +2851,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
|
||||
@ -2826,9 +2870,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
|
||||
@ -2913,6 +2957,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
|
||||
|
@ -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
|
||||
@ -295,6 +295,27 @@ this feature are destroyed.
|
||||
|
||||
.RE
|
||||
|
||||
.sp
|
||||
.ne 2
|
||||
.na
|
||||
\fB\fBbookmarks\fR\fR
|
||||
.ad
|
||||
.RS 4n
|
||||
.TS
|
||||
l l .
|
||||
GUID com.delphix:bookmarks
|
||||
READ\-ONLY COMPATIBLE yes
|
||||
DEPENDENCIES extensible_dataset
|
||||
.TE
|
||||
|
||||
This feature enables use of the \fBzfs bookmark\fR subcommand.
|
||||
|
||||
This feature is \fBactive\fR while any bookmarks exist in the pool.
|
||||
All bookmarks in the pool can be listed by running
|
||||
\fBzfs list -t bookmark -r \fIpoolname\fR\fR.
|
||||
|
||||
.RE
|
||||
|
||||
.sp
|
||||
.ne 2
|
||||
.na
|
||||
|
@ -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);
|
||||
|
@ -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 @@ dmu_send_impl(void *tag, dsl_pool_t *dp, dsl_dataset_t *ds,
|
||||
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. */
|
||||
|
454
uts/common/fs/zfs/dsl_bookmark.c
Normal file
454
uts/common/fs/zfs/dsl_bookmark.c
Normal file
@ -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);
|
||||
}
|
@ -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->ds_phys->ds_creation_txg >=
|
||||
later->ds_phys->ds_creation_txg)
|
||||
if (earlier_txg == 0)
|
||||
earlier_txg = earlier->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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -438,7 +438,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';
|
||||
|
||||
|
51
uts/common/fs/zfs/sys/dsl_bookmark.h
Normal file
51
uts/common/fs/zfs/sys/dsl_bookmark.h
Normal file
@ -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 */
|
@ -75,6 +75,13 @@ struct dsl_pool;
|
||||
* They should be of the format <reverse-dns>:<field>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This field's value is the object ID of a zap object which contains the
|
||||
* bookmarks of this dataset. If it is present, then this dataset is counted
|
||||
* in the refcount of the SPA_FEATURES_BOOKMARKS feature.
|
||||
*/
|
||||
#define DS_FIELD_BOOKMARK_NAMES "com.delphix:bookmarks"
|
||||
|
||||
/*
|
||||
* DS_FLAG_CI_DATASET is set if the dataset contains a file system whose
|
||||
* name lookups should be performed case-insensitively.
|
||||
@ -127,6 +134,7 @@ typedef struct dsl_dataset {
|
||||
|
||||
/* only used in syncing context, only valid for non-snapshots: */
|
||||
struct dsl_dataset *ds_prev;
|
||||
uint64_t ds_bookmarks; /* DMU_OTN_ZAP_METADATA */
|
||||
|
||||
/* has internal locking: */
|
||||
dsl_deadlist_t ds_deadlist;
|
||||
@ -247,7 +255,8 @@ int dsl_dataset_set_refquota(const char *dsname, zprop_source_t source,
|
||||
int dsl_dataset_set_refreservation(const char *dsname, zprop_source_t source,
|
||||
uint64_t reservation);
|
||||
|
||||
boolean_t dsl_dataset_is_before(dsl_dataset_t *later, dsl_dataset_t *earlier);
|
||||
boolean_t dsl_dataset_is_before(dsl_dataset_t *later, dsl_dataset_t *earlier,
|
||||
uint64_t earlier_txg);
|
||||
void dsl_dataset_long_hold(dsl_dataset_t *ds, void *tag);
|
||||
void dsl_dataset_long_rele(dsl_dataset_t *ds, void *tag);
|
||||
boolean_t dsl_dataset_long_held(dsl_dataset_t *ds);
|
||||
@ -270,6 +279,7 @@ int dsl_dataset_snap_lookup(dsl_dataset_t *ds, const char *name,
|
||||
int dsl_dataset_snap_remove(dsl_dataset_t *ds, const char *name, dmu_tx_t *tx);
|
||||
void dsl_dataset_set_refreservation_sync_impl(dsl_dataset_t *ds,
|
||||
zprop_source_t source, uint64_t value, dmu_tx_t *tx);
|
||||
void dsl_dataset_zapify(dsl_dataset_t *ds, dmu_tx_t *tx);
|
||||
int dsl_dataset_rollback(const char *fsname, void *owner, nvlist_t *result);
|
||||
|
||||
#ifdef ZFS_DEBUG
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
||||
@ -829,22 +830,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
|
||||
@ -1003,6 +991,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)
|
||||
@ -2586,7 +2643,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)
|
||||
@ -2599,8 +2655,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);
|
||||
@ -3285,7 +3340,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));
|
||||
|
||||
/*
|
||||
@ -3431,10 +3487,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;
|
||||
@ -3443,26 +3499,108 @@ 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)) {
|
||||
const char *name = nvpair_name(pair);
|
||||
|
||||
/*
|
||||
* The snap must be in the specified pool.
|
||||
*/
|
||||
if (strncmp(name, poolname, poollen) != 0 ||
|
||||
(name[poollen] != '/' && name[poollen] != '@'))
|
||||
return (SET_ERROR(EXDEV));
|
||||
|
||||
error = zfs_unmount_snap(name);
|
||||
if (error != 0)
|
||||
return (error);
|
||||
(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 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] != '#'))
|
||||
return (SET_ERROR(EXDEV));
|
||||
}
|
||||
|
||||
error = dsl_bookmark_destroy(innvl, outnvl);
|
||||
return (error);
|
||||
}
|
||||
|
||||
/*
|
||||
* inputs:
|
||||
* zc_name name of dataset to destroy
|
||||
@ -4121,7 +4259,8 @@ zfs_ioc_recv(zfs_cmd_t *zc)
|
||||
* 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)
|
||||
@ -5331,6 +5470,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,
|
||||
@ -5668,7 +5820,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);
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user