dd: add optional support for status=noxfer/none

While at it, added 'B' number suffixes, upstream compat

function                                             old     new   delta
dd_main                                             1469    1543     +74
dd_suffixes                                           88     112     +24
packed_usage                                       30156   30176     +20
dd_output_status                                     372     388     +16
static.status_words                                    -      13     +13
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 4/0 up/down: 147/0)             Total: 147 bytes

Signed-off-by: Ari Sundholm <ari@tuxera.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/coreutils/dd.c b/coreutils/dd.c
index 2838f63..3024970 100644
--- a/coreutils/dd.c
+++ b/coreutils/dd.c
@@ -8,6 +8,51 @@
  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  */
 
+//config:config DD
+//config:	bool "dd"
+//config:	default y
+//config:	help
+//config:	  dd copies a file (from standard input to standard output,
+//config:	  by default) using specific input and output blocksizes,
+//config:	  while optionally performing conversions on it.
+//config:
+//config:config FEATURE_DD_SIGNAL_HANDLING
+//config:	bool "Enable signal handling for status reporting"
+//config:	default y
+//config:	depends on DD
+//config:	help
+//config:	  Sending a SIGUSR1 signal to a running `dd' process makes it
+//config:	  print to standard error the number of records read and written
+//config:	  so far, then to resume copying.
+//config:
+//config:	  $ dd if=/dev/zero of=/dev/null &
+//config:	  $ pid=$!; kill -USR1 $pid; sleep 1; kill $pid
+//config:	  10899206+0 records in
+//config:	  10899206+0 records out
+//config:
+//config:config FEATURE_DD_THIRD_STATUS_LINE
+//config:	bool "Enable the third status line upon signal"
+//config:	default y
+//config:	depends on DD && FEATURE_DD_SIGNAL_HANDLING
+//config:	help
+//config:	  Displays a coreutils-like third status line with transferred bytes,
+//config:	  elapsed time and speed.
+//config:
+//config:config FEATURE_DD_IBS_OBS
+//config:	bool "Enable ibs, obs and conv options"
+//config:	default y
+//config:	depends on DD
+//config:	help
+//config:	  Enables support for writing a certain number of bytes in and out,
+//config:	  at a time, and performing conversions on the data stream.
+//config:
+//config:config FEATURE_DD_STATUS
+//config:	bool "Enable status display options"
+//config:	default y
+//config:	depends on DD
+//config:	help
+//config:	  Enables support for status=noxfer/none option.
+
 //usage:#define dd_trivial_usage
 //usage:       "[if=FILE] [of=FILE] " IF_FEATURE_DD_IBS_OBS("[ibs=N] [obs=N] ") "[bs=N] [count=N] [skip=N]\n"
 //usage:       "	[seek=N]" IF_FEATURE_DD_IBS_OBS(" [conv=notrunc|noerror|sync|fsync]")
@@ -32,8 +77,12 @@
 //usage:     "\n	conv=fsync	Physically write data out before finishing"
 //usage:     "\n	conv=swab	Swap every pair of bytes"
 //usage:	)
+//usage:	IF_FEATURE_DD_STATUS(
+//usage:     "\n	status=noxfer	Suppress rate output"
+//usage:     "\n	status=none	Suppress all output"
+//usage:	)
 //usage:     "\n"
-//usage:     "\nN may be suffixed by c (1), w (2), b (512), kD (1000), k (1024), MD, M, GD, G"
+//usage:     "\nN may be suffixed by c (1), w (2), b (512), kB (1000), k (1024), MB, M, GB, G"
 //usage:
 //usage:#define dd_example_usage
 //usage:       "$ dd if=/dev/zero of=/dev/ram1 bs=1M count=4\n"
@@ -54,13 +103,18 @@
 	{ "c", 1 },
 	{ "w", 2 },
 	{ "b", 512 },
+	{ "kB", 1000 },
 	{ "kD", 1000 },
 	{ "k", 1024 },
-	{ "K", 1024 },  /* compat with coreutils dd */
+	{ "K", 1024 },  /* compat with coreutils dd (it also accepts KB and KD, TODO?) */
+	{ "MB", 1000000 },
 	{ "MD", 1000000 },
-	{ "M", 1048576 },
+	{ "M", 1024*1024 },
+	{ "GB", 1000000000 },
 	{ "GD", 1000000000 },
-	{ "G", 1073741824 },
+	{ "G", 1024*1024*1024 },
+	/* "D" suffix for decimal is not in coreutils manpage, looks like it's deprecated */
+	/* coreutils also understands TPEZY suffixes for tera- and so on, with B suffix for decimal */
 	{ "", 0 }
 };
 
@@ -70,6 +124,7 @@
 	unsigned long long total_bytes;
 	unsigned long long begin_time_us;
 #endif
+	int flags;
 } FIX_ALIASING;
 #define G (*(struct globals*)&bb_common_bufsiz1)
 #define INIT_G() do { \
@@ -77,6 +132,21 @@
 	memset(&G, 0, sizeof(G)); \
 } while (0)
 
+enum {
+	/* Must be in the same order as OP_conv_XXX! */
+	/* (see "flags |= (1 << what)" below) */
+	FLAG_NOTRUNC = (1 << 0) * ENABLE_FEATURE_DD_IBS_OBS,
+	FLAG_SYNC    = (1 << 1) * ENABLE_FEATURE_DD_IBS_OBS,
+	FLAG_NOERROR = (1 << 2) * ENABLE_FEATURE_DD_IBS_OBS,
+	FLAG_FSYNC   = (1 << 3) * ENABLE_FEATURE_DD_IBS_OBS,
+	FLAG_SWAB    = (1 << 4) * ENABLE_FEATURE_DD_IBS_OBS,
+	/* end of conv flags */
+	FLAG_TWOBUFS = (1 << 5) * ENABLE_FEATURE_DD_IBS_OBS,
+	FLAG_COUNT   = 1 << 6,
+	FLAG_STATUS  = 1 << 7,
+	FLAG_STATUS_NONE = 1 << 7,
+	FLAG_STATUS_NOXFER = 1 << 8,
+};
 
 static void dd_output_status(int UNUSED_PARAM cur_signal)
 {
@@ -93,6 +163,13 @@
 			G.out_full, G.out_part);
 
 #if ENABLE_FEATURE_DD_THIRD_STATUS_LINE
+# if ENABLE_FEATURE_DD_STATUS
+	if (G.flags & FLAG_STATUS_NOXFER) /* status=noxfer active? */
+		return;
+	//TODO: should status=none make dd stop reacting to USR1 entirely?
+	//So far we react to it (we print the stats),
+	//status=none only suppresses final, non-USR1 generated status message.
+# endif
 	fprintf(stderr, "%llu bytes (%sB) copied, ",
 			G.total_bytes,
 			/* show fractional digit, use suffixes */
@@ -148,20 +225,8 @@
 int dd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int dd_main(int argc UNUSED_PARAM, char **argv)
 {
-	enum {
-		/* Must be in the same order as OP_conv_XXX! */
-		/* (see "flags |= (1 << what)" below) */
-		FLAG_NOTRUNC = (1 << 0) * ENABLE_FEATURE_DD_IBS_OBS,
-		FLAG_SYNC    = (1 << 1) * ENABLE_FEATURE_DD_IBS_OBS,
-		FLAG_NOERROR = (1 << 2) * ENABLE_FEATURE_DD_IBS_OBS,
-		FLAG_FSYNC   = (1 << 3) * ENABLE_FEATURE_DD_IBS_OBS,
-		FLAG_SWAB    = (1 << 4) * ENABLE_FEATURE_DD_IBS_OBS,
-		/* end of conv flags */
-		FLAG_TWOBUFS = (1 << 5) * ENABLE_FEATURE_DD_IBS_OBS,
-		FLAG_COUNT   = 1 << 6,
-	};
 	static const char keywords[] ALIGN1 =
-		"bs\0""count\0""seek\0""skip\0""if\0""of\0"
+		"bs\0""count\0""seek\0""skip\0""if\0""of\0"IF_FEATURE_DD_STATUS("status\0")
 #if ENABLE_FEATURE_DD_IBS_OBS
 		"ibs\0""obs\0""conv\0"
 #endif
@@ -170,6 +235,10 @@
 	static const char conv_words[] ALIGN1 =
 		"notrunc\0""sync\0""noerror\0""fsync\0""swab\0";
 #endif
+#if ENABLE_FEATURE_DD_STATUS
+	static const char status_words[] ALIGN1 =
+		"none\0""noxfer\0";
+#endif
 	enum {
 		OP_bs = 0,
 		OP_count,
@@ -177,6 +246,7 @@
 		OP_skip,
 		OP_if,
 		OP_of,
+		IF_FEATURE_DD_STATUS(OP_status,)
 #if ENABLE_FEATURE_DD_IBS_OBS
 		OP_ibs,
 		OP_obs,
@@ -215,14 +285,12 @@
 #endif
 	/* These are all zeroed at once! */
 	struct {
-		int flags;
 		size_t oc;
 		ssize_t prev_read_size; /* for detecting swab failure */
 		off_t count;
 		off_t seek, skip;
 		const char *infile, *outfile;
 	} Z;
-#define flags   (Z.flags  )
 #define oc      (Z.oc     )
 #define prev_read_size (Z.prev_read_size)
 #define count   (Z.count  )
@@ -278,7 +346,7 @@
 				n = index_in_strings(conv_words, val);
 				if (n < 0)
 					bb_error_msg_and_die(bb_msg_invalid_arg, val, "conv");
-				flags |= (1 << n);
+				G.flags |= (1 << n);
 				if (!arg) /* no ',' left, so this was the last specifier */
 					break;
 				/* *arg = ','; - to preserve ps listing? */
@@ -294,7 +362,7 @@
 		}
 		/* These can be large: */
 		if (what == OP_count) {
-			flags |= FLAG_COUNT;
+			G.flags |= FLAG_COUNT;
 			count = XATOU_SFX(val, dd_suffixes);
 			/*continue;*/
 		}
@@ -314,6 +382,16 @@
 			outfile = val;
 			/*continue;*/
 		}
+#if ENABLE_FEATURE_DD_STATUS
+		if (what == OP_status) {
+			int n;
+			n = index_in_strings(status_words, val);
+			if (n < 0)
+				bb_error_msg_and_die(bb_msg_invalid_arg, val, "status");
+			G.flags |= FLAG_STATUS << n;
+			/*continue;*/
+		}
+#endif
 	} /* end of "for (argv[i])" */
 
 //XXX:FIXME for huge ibs or obs, malloc'ing them isn't the brightest idea ever
@@ -321,7 +399,7 @@
 	obuf = ibuf;
 #if ENABLE_FEATURE_DD_IBS_OBS
 	if (ibs != obs) {
-		flags |= FLAG_TWOBUFS;
+		G.flags |= FLAG_TWOBUFS;
 		obuf = xmalloc(obs);
 	}
 #endif
@@ -341,12 +419,12 @@
 	if (outfile) {
 		int oflag = O_WRONLY | O_CREAT;
 
-		if (!seek && !(flags & FLAG_NOTRUNC))
+		if (!seek && !(G.flags & FLAG_NOTRUNC))
 			oflag |= O_TRUNC;
 
 		xmove_fd(xopen(outfile, oflag), ofd);
 
-		if (seek && !(flags & FLAG_NOTRUNC)) {
+		if (seek && !(G.flags & FLAG_NOTRUNC)) {
 			if (ftruncate(ofd, seek * obs) < 0) {
 				struct stat st;
 
@@ -377,7 +455,7 @@
 			goto die_outfile;
 	}
 
-	while (!(flags & FLAG_COUNT) || (G.in_full + G.in_part != count)) {
+	while (!(G.flags & FLAG_COUNT) || (G.in_full + G.in_part != count)) {
 		ssize_t n;
 
 		n = safe_read(ifd, ibuf, ibs);
@@ -385,7 +463,7 @@
 			break;
 		if (n < 0) {
 			/* "Bad block" */
-			if (!(flags & FLAG_NOERROR))
+			if (!(G.flags & FLAG_NOERROR))
 				goto die_infile;
 			bb_simple_perror_msg(infile);
 			/* GNU dd with conv=noerror skips over bad blocks */
@@ -394,7 +472,7 @@
 			 * conv=noerror just ignores input bad blocks */
 			n = 0;
 		}
-		if (flags & FLAG_SWAB) {
+		if (G.flags & FLAG_SWAB) {
 			uint16_t *p16;
 			ssize_t n2;
 
@@ -419,12 +497,12 @@
 			G.in_full++;
 		else {
 			G.in_part++;
-			if (flags & FLAG_SYNC) {
+			if (G.flags & FLAG_SYNC) {
 				memset(ibuf + n, 0, ibs - n);
 				n = ibs;
 			}
 		}
-		if (flags & FLAG_TWOBUFS) {
+		if (G.flags & FLAG_TWOBUFS) {
 			char *tmp = ibuf;
 			while (n) {
 				size_t d = obs - oc;
@@ -446,7 +524,7 @@
 				goto out_status;
 		}
 
-		if (flags & FLAG_FSYNC) {
+		if (G.flags & FLAG_FSYNC) {
 			if (fsync(ofd) < 0)
 				goto die_outfile;
 		}
@@ -468,11 +546,12 @@
 
 	exitcode = EXIT_SUCCESS;
  out_status:
-	dd_output_status(0);
+	if (!ENABLE_FEATURE_DD_STATUS || !(G.flags & FLAG_STATUS_NONE))
+		dd_output_status(0);
 
 	if (ENABLE_FEATURE_CLEAN_UP) {
 		free(obuf);
-		if (flags & FLAG_TWOBUFS)
+		if (G.flags & FLAG_TWOBUFS)
 			free(ibuf);
 	}