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