From 835db58592d7d947e5818eb7281882e2a46073e0 Mon Sep 17 00:00:00 2001
From: LOLi <loli10K@users.noreply.github.com>
Date: Sat, 9 Sep 2017 00:24:31 +0200
Subject: Add -vnP support to 'zfs send' for bookmarks

This leverages the functionality introduced in cf7684b to expose
verbose, dry-run and parsable 'zfs send' options for bookmarks.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: loli10K <ezomori.nozomu@gmail.com>
Closes #3666
Closes #6601
---
 cmd/zfs/zfs_main.c                                 | 19 ++------
 include/libzfs.h                                   |  2 +-
 lib/libzfs/libzfs_sendrecv.c                       | 53 ++++++++++++++++------
 man/man8/zfs.8                                     | 25 ++++++++--
 .../cli_root/zfs_send/zfs_send_006_pos.ksh         | 44 ++++++++++--------
 5 files changed, 90 insertions(+), 53 deletions(-)

diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c
index be8389518..4331df403 100644
--- a/cmd/zfs/zfs_main.c
+++ b/cmd/zfs/zfs_main.c
@@ -3995,13 +3995,11 @@ zfs_do_send(int argc, char **argv)
 	if (strchr(argv[0], '@') == NULL ||
 	    (fromname && strchr(fromname, '#') != NULL)) {
 		char frombuf[ZFS_MAX_DATASET_NAME_LEN];
-		enum lzc_send_flags lzc_flags = 0;
 
 		if (flags.replicate || flags.doall || flags.props ||
-		    flags.dedup || flags.dryrun || flags.verbose ||
-		    flags.progress) {
-			(void) fprintf(stderr,
-			    gettext("Error: "
+		    flags.dedup || (strchr(argv[0], '@') == NULL &&
+		    (flags.dryrun || flags.verbose || flags.progress))) {
+			(void) fprintf(stderr, gettext("Error: "
 			    "Unsupported flag with filesystem or bookmark.\n"));
 			return (1);
 		}
@@ -4010,15 +4008,6 @@ zfs_do_send(int argc, char **argv)
 		if (zhp == NULL)
 			return (1);
 
-		if (flags.largeblock)
-			lzc_flags |= LZC_SEND_FLAG_LARGE_BLOCK;
-		if (flags.embed_data)
-			lzc_flags |= LZC_SEND_FLAG_EMBED_DATA;
-		if (flags.compress)
-			lzc_flags |= LZC_SEND_FLAG_COMPRESS;
-		if (flags.raw)
-			lzc_flags |= LZC_SEND_FLAG_RAW;
-
 		if (fromname != NULL &&
 		    (fromname[0] == '#' || fromname[0] == '@')) {
 			/*
@@ -4032,7 +4021,7 @@ zfs_do_send(int argc, char **argv)
 			(void) strlcat(frombuf, fromname, sizeof (frombuf));
 			fromname = frombuf;
 		}
-		err = zfs_send_one(zhp, fromname, STDOUT_FILENO, lzc_flags);
+		err = zfs_send_one(zhp, fromname, STDOUT_FILENO, flags);
 		zfs_close(zhp);
 		return (err != 0);
 	}
diff --git a/include/libzfs.h b/include/libzfs.h
index b5c35c491..df8d738b7 100644
--- a/include/libzfs.h
+++ b/include/libzfs.h
@@ -677,7 +677,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, enum lzc_send_flags);
+extern int zfs_send_one(zfs_handle_t *, const char *, int, sendflags_t flags);
 extern int zfs_send_resume(libzfs_handle_t *, sendflags_t *, int outfd,
     const char *);
 extern nvlist_t *zfs_send_resume_token_to_nvlist(libzfs_handle_t *hdl,
diff --git a/lib/libzfs/libzfs_sendrecv.c b/lib/libzfs/libzfs_sendrecv.c
index fddcd9c02..857abda3d 100644
--- a/lib/libzfs/libzfs_sendrecv.c
+++ b/lib/libzfs/libzfs_sendrecv.c
@@ -1242,16 +1242,14 @@ send_print_verbose(FILE *fout, const char *tosnap, const char *fromsnap,
 		}
 	}
 
-	if (size != 0) {
-		if (parsable) {
-			(void) fprintf(fout, "\t%llu",
-			    (longlong_t)size);
-		} else {
-			char buf[16];
-			zfs_nicebytes(size, buf, sizeof (buf));
-			(void) fprintf(fout, dgettext(TEXT_DOMAIN,
-			    " estimated size is %s"), buf);
-		}
+	if (parsable) {
+		(void) fprintf(fout, "\t%llu",
+		    (longlong_t)size);
+	} else if (size != 0) {
+		char buf[16];
+		zfs_nicebytes(size, buf, sizeof (buf));
+		(void) fprintf(fout, dgettext(TEXT_DOMAIN,
+		    " estimated size is %s"), buf);
 	}
 	(void) fprintf(fout, "\n");
 }
@@ -2113,17 +2111,42 @@ err_out:
 }
 
 int
-zfs_send_one(zfs_handle_t *zhp, const char *from, int fd,
-    enum lzc_send_flags flags)
+zfs_send_one(zfs_handle_t *zhp, const char *from, int fd, sendflags_t flags)
 {
-	int err;
+	int err = 0;
 	libzfs_handle_t *hdl = zhp->zfs_hdl;
-
+	enum lzc_send_flags lzc_flags = 0;
+	FILE *fout = (flags.verbose && flags.dryrun) ? stdout : stderr;
 	char errbuf[1024];
+
+	if (flags.largeblock)
+		lzc_flags |= LZC_SEND_FLAG_LARGE_BLOCK;
+	if (flags.embed_data)
+		lzc_flags |= LZC_SEND_FLAG_EMBED_DATA;
+	if (flags.compress)
+		lzc_flags |= LZC_SEND_FLAG_COMPRESS;
+	if (flags.raw)
+		lzc_flags |= LZC_SEND_FLAG_RAW;
+
+	if (flags.verbose) {
+		uint64_t size = 0;
+		err = lzc_send_space(zhp->zfs_name, from, lzc_flags, &size);
+		if (err == 0) {
+			send_print_verbose(fout, zhp->zfs_name, from, size,
+			    flags.parsable);
+		} else {
+			(void) fprintf(stderr, "Cannot estimate send size: "
+			    "%s\n", strerror(errno));
+		}
+	}
+
+	if (flags.dryrun)
+		return (err);
+
 	(void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
 	    "warning: cannot send '%s'"), zhp->zfs_name);
 
-	err = lzc_send(zhp->zfs_name, from, fd, flags);
+	err = lzc_send(zhp->zfs_name, from, fd, lzc_flags);
 	if (err != 0) {
 		switch (errno) {
 		case EXDEV:
diff --git a/man/man8/zfs.8 b/man/man8/zfs.8
index 89541eed9..eb5b6ffb9 100644
--- a/man/man8/zfs.8
+++ b/man/man8/zfs.8
@@ -171,7 +171,7 @@
 .Ar snapshot
 .Nm
 .Cm send
-.Op Fl Lcew
+.Op Fl LPcenvw
 .Op Fl i Ar snapshot Ns | Ns Ar bookmark
 .Ar filesystem Ns | Ns Ar volume Ns | Ns Ar snapshot
 .Nm
@@ -3253,14 +3253,14 @@ The receiving system must also support this feature.
 .It Fl v, -verbose
 Print verbose information about the stream package generated.
 This information includes a per-second report of how much data has been sent.
-.El
 .Pp
 The format of the stream is committed.
 You will be able to receive your streams on future versions of ZFS.
+.El
 .It Xo
 .Nm
 .Cm send
-.Op Fl Lce
+.Op Fl LPcenvw
 .Op Fl i Ar snapshot Ns | Ns Ar bookmark
 .Ar filesystem Ns | Ns Ar volume Ns | Ns Ar snapshot
 .Xc
@@ -3287,6 +3287,8 @@ See
 for details on ZFS feature flags and the
 .Sy large_blocks
 feature.
+.It Fl P, -parsable
+Print machine-parsable verbose information about the stream package generated.
 .It Fl c, -compressed
 Generate a more compact stream by using compressed WRITE records for blocks
 which are compressed on disk and in memory
@@ -3360,6 +3362,23 @@ character and following
 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.
+.It Fl n, -dryrun
+Do a dry-run
+.Pq Qq No-op
+send.
+Do not generate any actual send data.
+This is useful in conjunction with the
+.Fl v
+or
+.Fl P
+flags to determine what data will be sent.
+In this case, the verbose output will be written to standard output
+.Po contrast with a non-dry-run, where the stream is written to standard output
+and the verbose output goes to standard error
+.Pc .
+.It Fl v, -verbose
+Print verbose information about the stream package generated.
+This information includes a per-second report of how much data has been sent.
 .El
 .It Xo
 .Nm
diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_send/zfs_send_006_pos.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_send/zfs_send_006_pos.ksh
index 801633e1f..1fe10a760 100755
--- a/tests/zfs-tests/tests/functional/cli_root/zfs_send/zfs_send_006_pos.ksh
+++ b/tests/zfs-tests/tests/functional/cli_root/zfs_send/zfs_send_006_pos.ksh
@@ -60,10 +60,8 @@ function get_estimate_size
 		typeset total_size=$(zfs send $option $base_snapshot $snapshot \
 		     2>&1 | tail -1)
 	fi
-	if [[ $options == *"P"* ]]; then
-		total_size=$(echo "$total_size" | awk '{print $2}')
-	else
-		total_size=$(echo "$total_size" | awk '{print $5}')
+	total_size=$(echo "$total_size" | awk '{print $NF}')
+	if [[ $options != *"P"* ]]; then
 		total_size=${total_size%M}
 		total_size=$(echo "$total_size * $block_count" | bc)
 	fi
@@ -106,14 +104,18 @@ for block_size in 64 128 256; do
 	log_must dd if=/dev/urandom of=/$TESTPOOL/$TESTFS1/file$block_size \
 	    bs=1M count=$block_size
 	log_must zfs snapshot $TESTPOOL/$TESTFS1@snap$block_size
+	log_must zfs bookmark $TESTPOOL/$TESTFS1@snap$block_size \
+	    "$TESTPOOL/$TESTFS1#bmark$block_size"
 done
 
 full_snapshot="$TESTPOOL/$TESTFS1@snap64"
-increamental_snapshot="$TESTPOOL/$TESTFS1@snap256"
+incremental_snapshot="$TESTPOOL/$TESTFS1@snap256"
+full_bookmark="$TESTPOOL/$TESTFS1#bmark64"
+incremental_bookmark="$TESTPOOL/$TESTFS1#bmark256"
 
 full_size=$(zfs send $full_snapshot 2>&1 | wc -c)
-increamental_size=$(zfs send $increamental_snapshot 2>&1 | wc -c)
-increamental_send=$(zfs send -i $full_snapshot $increamental_snapshot 2>&1 | wc -c)
+incremental_size=$(zfs send $incremental_snapshot 2>&1 | wc -c)
+incremental_send=$(zfs send -i $full_snapshot $incremental_snapshot 2>&1 | wc -c)
 
 log_note "verify zfs send -nv"
 options="-nv"
@@ -129,31 +131,35 @@ log_must verify_size_estimates $options $full_size
 
 log_note "verify zfs send -nv for multiple snapshot send"
 options="-nv"
-refer_size=$(get_prop refer $increamental_snapshot)
+refer_size=$(get_prop refer $incremental_snapshot)
 
-estimate_size=$(get_estimate_size $increamental_snapshot $options)
-log_must verify_size_estimates $options $increamental_size
+estimate_size=$(get_estimate_size $incremental_snapshot $options)
+log_must verify_size_estimates $options $incremental_size
 
 log_note "verify zfs send -vPn for multiple snapshot send"
 options="-vPn"
 
-estimate_size=$(get_estimate_size $increamental_snapshot $options)
-log_must verify_size_estimates $options $increamental_size
+estimate_size=$(get_estimate_size $incremental_snapshot $options)
+log_must verify_size_estimates $options $incremental_size
 
-log_note "verify zfs send -inv for increamental send"
+log_note "verify zfs send -inv for incremental send"
 options="-nvi"
-refer_size=$(get_prop refer $increamental_snapshot)
+refer_size=$(get_prop refer $incremental_snapshot)
 deduct_size=$(get_prop refer $full_snapshot)
 refer_size=$(echo "$refer_size - $deduct_size" | bc)
 
-estimate_size=$(get_estimate_size $increamental_snapshot $options $full_snapshot)
-log_must verify_size_estimates $options $increamental_send
+estimate_size=$(get_estimate_size $incremental_snapshot $options $full_snapshot)
+log_must verify_size_estimates $options $incremental_send
+estimate_size=$(get_estimate_size $incremental_snapshot $options $full_bookmark)
+log_must verify_size_estimates $options $incremental_send
 
-log_note "verify zfs send -ivPn for increamental send"
+log_note "verify zfs send -ivPn for incremental send"
 options="-vPni"
 
-estimate_size=$(get_estimate_size $increamental_snapshot $options $full_snapshot)
-log_must verify_size_estimates $options $increamental_send
+estimate_size=$(get_estimate_size $incremental_snapshot $options $full_snapshot)
+log_must verify_size_estimates $options $incremental_send
+estimate_size=$(get_estimate_size $incremental_snapshot $options $full_bookmark)
+log_must verify_size_estimates $options $incremental_send
 
 log_must zfs destroy -r $TESTPOOL/$TESTFS1
 
-- 
cgit v1.2.3