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 ... .Nm .Cm snapshot Ns | Ns Cm snap .Op Fl r @@ -1877,10 +1879,29 @@ .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 Ar bookmark Ns +.Op , 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 @@ -245,7 +245,7 @@ return (gettext("\tdestroy [-fnpRrv] \n" "\tdestroy [-dnpRrv] " "@[%][,...]\n" - "\tdestroy #\n")); + "\tdestroy [-nvp] #[,...]\n")); case HELP_GET: return (gettext("\tget [-rHp] [-d max] " "[-o \"all\" | field[,...]]\n" @@ -1235,6 +1235,48 @@ return (err); } +/* + * Given an input = "path/to/dataset#b1,b2,...,bN" + * fill nvl with (string,boolean) pairs where string = path/to/dataset#bi + */ +static int +bookmark_spec_to_nvl(const char *input, nvlist_t *nvl) +{ + char *input_cpy, *ds, *next, *bm; + + input_cpy = next = strdup(input); + if (!input_cpy) { + perror("cannot parse bookmark list"); + return (1); + } + + ds = strsep(&next, "#"); + if (ds == NULL) { + (void) fprintf(stderr, "invalid bookmark list: " + "expected '#' delimiting dataset and bookmark\n"); + goto eout; + } + + while ((bm = strsep(&next, ",")) != NULL) { + int n; + char scratch[ZFS_MAX_DATASET_NAME_LEN]; + 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); + } + + free(input_cpy); + return (0); + +eout: + free(input_cpy); + return (1); +} + static int destroy_clones(destroy_cbdata_t *cb) { @@ -1389,12 +1431,7 @@ } 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); - } + nvpair_t *cur; if (cb.cb_defer_destroy) { (void) fprintf(stderr, @@ -1408,23 +1445,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); + } + + cur = NULL; + while ((cur = nvlist_next_nvpair(nvl, cur)) != NULL) { + const char *name; + 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) { + 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 "); + cur = NULL; + 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 */