+#if ENABLE_FEATURE_DD_THIRD_STATUS_LINE
+ double seconds;
+ unsigned long long bytes_sec;
+ unsigned long long now_us = monotonic_us(); /* before fprintf */
+#endif
+
+ /* Deliberately using %u, not %d */
+ fprintf(stderr, "%"OFF_FMT"u+%"OFF_FMT"u records in\n"
+ "%"OFF_FMT"u+%"OFF_FMT"u records out\n",
+ G.in_full, G.in_part,
+ 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 */
+ make_human_readable_str(G.total_bytes, 1, 0)
+ );
+ /* Corner cases:
+ * ./busybox dd </dev/null >/dev/null
+ * ./busybox dd bs=1M count=2000 </dev/zero >/dev/null
+ * (echo DONE) | ./busybox dd >/dev/null
+ * (sleep 1; echo DONE) | ./busybox dd >/dev/null
+ */
+ seconds = (now_us - G.begin_time_us) / 1000000.0;
+ bytes_sec = G.total_bytes / seconds;
+ fprintf(stderr, "%f seconds, %sB/s\n",
+ seconds,
+ /* show fractional digit, use suffixes */
+ make_human_readable_str(bytes_sec, 1, 0)
+ );
+#endif
+}
+
+static ssize_t full_write_or_warn(const void *buf, size_t len,
+ const char *const filename)
+{
+ ssize_t n = full_write(ofd, buf, len);
+ if (n < 0)
+ bb_perror_msg("writing '%s'", filename);
+ return n;
+}
+
+static bool write_and_stats(const void *buf, size_t len, size_t obs,
+ const char *filename)
+{
+ ssize_t n = full_write_or_warn(buf, len, filename);
+ if (n < 0)
+ return 1;
+ if ((size_t)n == obs)
+ G.out_full++;
+ else if (n) /* > 0 */
+ G.out_part++;
+#if ENABLE_FEATURE_DD_THIRD_STATUS_LINE
+ G.total_bytes += n;
+#endif
+ return 0;
+}
+
+#if ENABLE_LFS
+# define XATOU_SFX xatoull_sfx
+#else
+# define XATOU_SFX xatoul_sfx
+#endif
+
+#if ENABLE_FEATURE_DD_IBS_OBS
+static int parse_comma_flags(char *val, const char *words, const char *error_in)
+{
+ int flags = 0;
+ while (1) {
+ int n;
+ char *arg;
+ /* find ',', replace them with NUL so we can use val for
+ * index_in_strings() without copying.
+ * We rely on val being non-null, else strchr would fault.
+ */
+ arg = strchr(val, ',');
+ if (arg)
+ *arg = '\0';
+ n = index_in_strings(words, val);
+ if (n < 0)
+ bb_error_msg_and_die(bb_msg_invalid_arg_to, val, error_in);
+ flags |= (1 << n);
+ if (!arg) /* no ',' left, so this was the last specifier */
+ break;
+ *arg = ','; /* to preserve ps listing */
+ val = arg + 1; /* skip this keyword and ',' */
+ }
+ return flags;
+}
+#endif
+
+int dd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int dd_main(int argc UNUSED_PARAM, char **argv)
+{
+ static const char keywords[] ALIGN1 =
+ "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""iflag\0"
+#endif
+ ;
+#if ENABLE_FEATURE_DD_IBS_OBS
+ static const char conv_words[] ALIGN1 =
+ "notrunc\0""sync\0""noerror\0""fsync\0""swab\0";
+ static const char iflag_words[] ALIGN1 =
+ "skip_bytes\0";
+#endif
+#if ENABLE_FEATURE_DD_STATUS
+ static const char status_words[] ALIGN1 =
+ "none\0""noxfer\0";
+#endif
+ enum {
+ OP_bs = 0,
+ OP_count,
+ OP_seek,
+ OP_skip,
+ OP_if,
+ OP_of,
+ IF_FEATURE_DD_STATUS(OP_status,)
+#if ENABLE_FEATURE_DD_IBS_OBS
+ OP_ibs,
+ OP_obs,
+ OP_conv,
+ OP_iflag,
+ /* Must be in the same order as FLAG_XXX! */
+ OP_conv_notrunc = 0,
+ OP_conv_sync,
+ OP_conv_noerror,
+ OP_conv_fsync,
+ OP_conv_swab,
+ /* Unimplemented conv=XXX: */
+ //nocreat do not create the output file
+ //excl fail if the output file already exists
+ //fdatasync physically write output file data before finishing
+ //lcase change upper case to lower case
+ //ucase change lower case to upper case
+ //block pad newline-terminated records with spaces to cbs-size
+ //unblock replace trailing spaces in cbs-size records with newline
+ //ascii from EBCDIC to ASCII
+ //ebcdic from ASCII to EBCDIC
+ //ibm from ASCII to alternate EBCDIC
+ /* Partially implemented: */
+ //swab swap every pair of input bytes: will abort on non-even reads
+ OP_iflag_skip_bytes,
+#endif
+ };
+ smallint exitcode = EXIT_FAILURE;