Index: cddl/contrib/opensolaris/cmd/zfs/zfs.8 =================================================================== --- cddl/contrib/opensolaris/cmd/zfs/zfs.8 +++ cddl/contrib/opensolaris/cmd/zfs/zfs.8 @@ -67,7 +67,9 @@ .Sm on .Nm .Cm destroy -.Ar filesystem Ns | Ns Ar volume Ns # Ns Ar bookmark +.Op Fl nvp +.Ar filesystem Ns | Ns Ar volume Ns # Ns Ar bookmark Ns +.Op Ns bookmark2,... Ns .Nm .Cm snapshot Ns | Ns Cm snap .Op Fl r @@ -1877,10 +1879,28 @@ .It Xo .Nm .Cm destroy -.Ar filesystem Ns | Ns Ar volume Ns # Ns Ar bookmark +.Op Fl nvp +.Ar filesystem Ns | Ns Ar volume Ns # Ns bookmark Ns +.Op Ns bookmark2,... Ns .Xc .Pp -The given bookmark is destroyed. +The given bookmarks are destroyed. +.Pp +Multiple bookmarks of snapshots of one filesystem or volume may be +specified in a comma-separated list. +Only the short bookmark names (the part after the +.Sy # ) +should be specified when using this functionality. +.Bl -tag -width indent +.It Fl n +Do a dry-run ("No-op"). No bookmarks will be deleted. +.It Fl v +Print verbose information about the bookmarks to be deleted. +.It Fl p +Print machine-parsable verbose information about the deleted bookmarks (implies +.Fl v Ns +). +.El .It Xo .Nm .Cm snapshot Ns | Ns Cm snap Index: cddl/contrib/opensolaris/cmd/zfs/zfs_main.c =================================================================== --- cddl/contrib/opensolaris/cmd/zfs/zfs_main.c +++ cddl/contrib/opensolaris/cmd/zfs/zfs_main.c @@ -1235,6 +1235,44 @@ return (err); } +// Given an input = "dataset#b1,b2,...,bN" +// fill nvl with (string,boolean) pairs where string = dataset#bi +static int +bookmark_spec_to_nvl(const char *input, nvlist_t *nvl) +{ + char *input_cpy, *ds, *next, *bm, *pount; + int n; + char scratch[ZFS_MAX_DATASET_NAME_LEN]; + + input_cpy = next = strdup(input); + if (!input_cpy) { + perror("cannot parse bookmark list"); + return (1); + } + + if ((ds = strsep(&next, "#")) == NULL) { + (void) fprintf(stderr, "invalid bookmark list: " + "expected '#' delimiting dataset and bookmark\n"); + goto eout; + } + + while ((bm = strsep(&next, ",")) != NULL) { + n = snprintf(scratch, sizeof(scratch), "%s#%s", ds, bm); + if (n >= sizeof(scratch)) { + (void) fprintf(stderr, "invalid bookmark list: " + "full bookmark name for '#%s' is too long\n", bm); + goto eout; + } + fnvlist_add_boolean(nvl, scratch); + } + + return (0); + +eout: + free(input_cpy); + return (1); +} + static int destroy_clones(destroy_cbdata_t *cb) { @@ -1390,12 +1428,6 @@ 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"); @@ -1408,23 +1440,49 @@ return (-1); } - if (!zfs_bookmark_exists(argv[0])) { - (void) fprintf(stderr, gettext("bookmark '%s' " - "does not exist.\n"), argv[0]); + nvl = fnvlist_alloc(); + err = bookmark_spec_to_nvl(argv[0], nvl); + if (err != 0) { + return (err); + } + + nvpair_t *cur = NULL; + const char *name; + while ((cur = nvlist_next_nvpair(nvl, cur)) != NULL) { + name = nvpair_name(cur); + if (zfs_bookmark_exists(name)) + continue; + (void) fprintf(stderr, + "cannot destroy bookmarks: '%s' does not exist\n", + name); return (1); } - nvl = fnvlist_alloc(); - fnvlist_add_boolean(nvl, argv[0]); + if (cb.cb_verbose) { + cur = NULL; + const char *verb = NULL; + if (cb.cb_parsable) + verb = "destroy\t"; + else if (cb.cb_dryrun) + verb = gettext("would destroy "); + else + verb = gettext("will destroy "); + while ((cur = nvlist_next_nvpair(nvl, cur)) != NULL) { + (void) fprintf(stderr, "%s%s\n", + verb, nvpair_name(cur)); + } + } + + if (cb.cb_dryrun) { + return (0); + } err = lzc_destroy_bookmarks(nvl, NULL); if (err != 0) { (void) zfs_standard_error(g_zfs, err, - "cannot destroy bookmark"); + "cannot destroy bookmarks"); } - nvlist_free(cb.cb_nvl); - return (err); } else { /* Open the given dataset */