diff options
author | Elliott Hughes <enh@google.com> | 2016-07-07 21:48:33 -0700 |
---|---|---|
committer | Rob Landley <rob@landley.net> | 2016-07-08 00:03:57 -0500 |
commit | d5088a059649daf34e729995bb3daa3eb64fa432 (patch) | |
tree | 792e43241ddd494ad12c44cd2b36f025579ea7b7 | |
parent | 008fb6444d7d960a9c96236334ad3e71f218d54c (diff) | |
download | toybox-d5088a059649daf34e729995bb3daa3eb64fa432.tar.gz |
dd improvements.
Support hex (requested by users).
Support status=noxfer and status=none (requested by users).
Fix status output.
Clarify large numbers in --help output.
Use O_TRUNC rather than ftruncate unless we're also seeking.
New tests.
Also partial cleanup (reuse of existing code, removal of non-GLOBALS globals,
and merge dd_main and do_dd).
-rw-r--r-- | lib/lib.h | 1 | ||||
-rwxr-xr-x | tests/dd.test | 13 | ||||
-rw-r--r-- | toys/pending/dd.c | 349 |
3 files changed, 188 insertions, 175 deletions
@@ -293,6 +293,7 @@ struct mtab_list *xgetmountlist(char *path); // signal void generic_signal(int signal); +void exit_signal(int signal); void sigatexit(void *handler); int sig_to_num(char *pidstr); char *num_to_sig(int sig); diff --git a/tests/dd.test b/tests/dd.test index 42cd170a..82214506 100755 --- a/tests/dd.test +++ b/tests/dd.test @@ -10,6 +10,12 @@ opt="2>/dev/null" #testing "name" "command" "result" "infile" "stdin" +# Test suffixed number parsing; `count` is representative. +testing "count=2" "dd if=input count=2 ibs=1 $opt" "hi" "high\n" "" +testing "count= 2" "dd if=input 'count= 2' ibs=1 $opt" "hi" "high\n" "" +testing "count=0x2" "dd if=input 'count=0x2' ibs=1 $opt" "hi" "high\n" "" +testing "count=-2" "dd if=input 'count=-2' ibs=1 2>&1" "dd: invalid number '-2'\n" "" "" + testing "if=(file)" "dd if=input $opt" "I WANT\n" "I WANT\n" "" testing "of=(file)" "dd of=file $opt && cat file" "I WANT\n" "" "I WANT\n" testing "if=file of=file" "dd if=input of=foo $opt && cat foo && rm -f foo" \ @@ -61,9 +67,6 @@ testing "with skip if" "dd skip=0 if=input $opt" "I WANT\n" "I WANT\n" "" testing "with seek" "dd seek=0 $opt" "I WANT\n" "" "I WANT\n" testing "with seek if" "dd seek=0 if=input $opt" "I WANT\n" "I WANT\n" "" -# These type of conv is not supported in toybox: 'ascii', 'ebcdic', 'ibm', -# 'block', 'unblock', 'nocreat', 'notronc', 'lcase', 'ucase', 'excl', 'swab' - # Testing only 'notrunc', 'noerror', 'fsync', 'sync' testing "conv=notrunc" "dd conv=notrunc $opt" "I WANT\n" "" "I WANT\n" @@ -81,3 +84,7 @@ testing "conv=fsync with IF" "dd conv=fsync if=input $opt" "I WANT\n" \ testing "conv=sync" "dd conv=sync $opt | head -n 1" "I WANT\n" "" "I WANT\n" testing "conv=sync with IF" "dd conv=sync if=input $opt | head -n 1" "I WANT\n" \ "I WANT\n" "" + +# status=noxfer|none +testing "status=noxfer" "dd if=input status=noxfer ibs=1 2>&1" "input\n6+0 records in\n0+1 records out\n" "input\n" "" +testing "status=none" "dd if=input status=none ibs=1 2>&1" "input\n" "input\n" "" diff --git a/toys/pending/dd.c b/toys/pending/dd.c index 24d95657..9990a0f4 100644 --- a/toys/pending/dd.c +++ b/toys/pending/dd.c @@ -4,14 +4,15 @@ * Copyright 2013 Kyungwan Han <asura321@gmail.com> * * See http://opengroup.org/onlinepubs/9699919799/utilities/dd.html + USE_DD(NEWTOY(dd, NULL, TOYFLAG_USR|TOYFLAG_BIN)) config DD bool "dd" default n - help + help usage: dd [if=FILE] [of=FILE] [ibs=N] [obs=N] [bs=N] [count=N] [skip=N] - [seek=N] [conv=notrunc|noerror|sync|fsync] + [seek=N] [conv=notrunc|noerror|sync|fsync] [status=noxfer|none] Options: if=FILE Read from FILE instead of stdin @@ -26,17 +27,30 @@ config DD conv=noerror Continue after read errors conv=sync Pad blocks with zeros conv=fsync Physically write data out before finishing + status=noxfer Don't show transfer rate + status=none Don't show transfer rate or records in/out - Numbers may be suffixed by c (x1), w (x2), b (x512), kD (x1000), k (x1024), - MD (x1000000), M (x1048576), GD (x1000000000) or G (x1073741824) - Copy a file, converting and formatting according to the operands. + Numbers may be suffixed by c (*1), w (*2), b (*512), kD (*1000), k (*1024), + MD (*1000*1000), M (*1024*1024), GD (*1000*1000*1000) or G (*1024*1024*1024). */ + #define FOR_dd #include "toys.h" GLOBALS( - int sig; -) + int show_xfer; + int show_records; + unsigned long long bytes, c_count, in_full, in_part, out_full, out_part; + struct timeval start; + struct { + char *name; + int fd; + unsigned char *buff, *bp; + long sz, count; + unsigned long long offset; + } in, out; +); + #define C_CONV 0x0000 #define C_BS 0x0001 #define C_COUNT 0x0002 @@ -50,19 +64,7 @@ GLOBALS( #define C_FSYNC 0x0200 #define C_NOERROR 0x0400 #define C_NOTRUNC 0x0800 - -struct io { - char *name; - int fd; - unsigned char *buff, *bp; - long sz, count; - unsigned long long offset; -}; - -struct iostat { - unsigned long long in_full, in_part, out_full, out_part, bytes; - struct timeval start; -}; +#define C_STATUS 0x1000 struct pair { char *name; @@ -85,63 +87,60 @@ static struct pair clist[] = { static struct pair operands[] = { // keep the array sorted by name, bsearch() can be used. - { "bs", C_BS }, - { "conv", C_CONV }, - { "count", C_COUNT}, - { "ibs", C_IBS }, - { "if", C_IF }, - { "obs", C_OBS }, - { "of", C_OF }, - { "seek", C_SEEK }, - { "skip", C_SKIP }, + { "bs", C_BS }, + { "conv", C_CONV }, + { "count", C_COUNT }, + { "ibs", C_IBS }, + { "if", C_IF }, + { "obs", C_OBS }, + { "of", C_OF }, + { "seek", C_SEEK }, + { "skip", C_SKIP }, + { "status", C_STATUS}, }; -static struct io in, out; -static struct iostat st; -static unsigned long long c_count; - -static unsigned long long strsuftoll(char* arg, int def, unsigned long long max) +static unsigned long long strsuftoll(char *arg, int def, unsigned long long max) { unsigned long long result; - char *endp, *ch = arg; + char *p = arg; int i, idx = -1; - errno = 0; - while (isspace(*ch)) ch++; - if (*ch == '-') error_exit("invalid number '%s'",arg); - result = strtoull(arg, &endp, 10); + while (isspace(*p)) p++; + if (*p == '-') error_exit("invalid number '%s'", arg); + + errno = 0; + result = strtoull(p, &p, 0); if (errno == ERANGE || result > max || result < def) - perror_exit("invalid number '%s'",arg); - if (*endp != '\0') { + perror_exit("invalid number '%s'", arg); + if (*p != '\0') { for (i = 0; i < ARRAY_LEN(suffixes); i++) - if (!strcmp(endp, suffixes[i].name)) idx = i; + if (!strcmp(p, suffixes[i].name)) idx = i; if (idx == -1 || (max/suffixes[idx].val < result)) - error_exit("invalid number '%s'",arg); - result = result* suffixes[idx].val; + error_exit("invalid number '%s'", arg); + result *= suffixes[idx].val; } return result; } -static void summary() +static void status() { - double seconds = 5.0; + double seconds; struct timeval now; gettimeofday(&now, NULL); - seconds = ((now.tv_sec * 1000000 + now.tv_usec) - (st.start.tv_sec * 1000000 - + st.start.tv_usec))/1000000.0; - //out to STDERR - fprintf(stderr,"%llu+%llu records in\n%llu+%llu records out\n", st.in_full, st.in_part, - st.out_full, st.out_part); - human_readable(toybuf, st.bytes, HR_SPACE|HR_B); - fprintf(stderr, "%llu bytes (%s) copied, ",st.bytes, toybuf); - human_readable(toybuf, st.bytes/seconds, HR_SPACE|HR_B); - fprintf(stderr, "%f s, %s/s\n", seconds, toybuf); -} - -static void sig_handler(int sig) -{ - TT.sig = sig; + seconds = ((now.tv_sec * 1000000 + now.tv_usec) - + (TT.start.tv_sec * 1000000 + TT.start.tv_usec))/1000000.0; + + if (TT.show_records) + fprintf(stderr, "%llu+%llu records in\n%llu+%llu records out\n", + TT.in_full, TT.in_part, TT.out_full, TT.out_part); + + if (TT.show_xfer) { + human_readable(toybuf, TT.bytes, HR_SPACE|HR_B); + fprintf(stderr, "%llu bytes (%s) copied, ", TT.bytes, toybuf); + human_readable(toybuf, TT.bytes/seconds, HR_SPACE|HR_B); + fprintf(stderr, "%f s, %s/s\n", seconds, toybuf); + } } static int xmove_fd(int fd) @@ -156,130 +155,67 @@ static int xmove_fd(int fd) static void setup_inout() { - ssize_t n; - /* for C_BS, in/out is done as it is. so only in.sz is enough. * With Single buffer there will be overflow in a read following partial read */ - in.buff = out.buff = xmalloc(in.sz + ((toys.optflags & C_BS)? 0: out.sz)); - in.bp = out.bp = in.buff; - atexit(summary); + TT.in.buff = TT.out.buff = xmalloc(TT.in.sz + ((toys.optflags & C_BS)? 0: TT.out.sz)); + TT.in.bp = TT.out.bp = TT.in.buff; //setup input - if (!in.name) { - in.name = "stdin"; - in.fd = STDIN_FILENO; + if (!TT.in.name) { + TT.in.name = "stdin"; + TT.in.fd = STDIN_FILENO; } else { - in.fd = xopen(in.name, O_RDONLY); - in.fd = xmove_fd(in.fd); + TT.in.fd = xopen(TT.in.name, O_RDONLY); + TT.in.fd = xmove_fd(TT.in.fd); } //setup outout - if (!out.name) { - out.name = "stdout"; - out.fd = STDOUT_FILENO; + if (!TT.out.name) { + TT.out.name = "stdout"; + TT.out.fd = STDOUT_FILENO; } else { - out.fd = xcreate(out.name, O_WRONLY | O_CREAT, 0666); - out.fd = xmove_fd(out.fd); + int flags = O_WRONLY|O_CREAT; + if (!(toys.optflags&C_NOTRUNC)) flags |= O_TRUNC; + TT.out.fd = xcreate(TT.out.name, flags, 0666); + TT.out.fd = xmove_fd(TT.out.fd); } - if (in.offset) { - if (lseek(in.fd, (off_t)(in.offset * in.sz), SEEK_CUR) < 0) { - while (in.offset--) { - if ((n = read(in.fd, in.bp, in.sz)) < 0) { - if (toys.optflags & C_NOERROR) { //warn message and summary - error_msg("%s: read error", in.name); - summary(); - } else perror_exit("%s: read error", in.name); + if (TT.in.offset) { + if (lseek(TT.in.fd, (off_t)(TT.in.offset * TT.in.sz), SEEK_CUR) < 0) { + while (TT.in.offset--) { + ssize_t n = read(TT.in.fd, TT.in.bp, TT.in.sz); + + if (n < 0) { + if (toys.optflags & C_NOERROR) { //warn message and status + error_msg("%s: read error", TT.in.name); + status(); + } else perror_exit("%s: read error", TT.in.name); } else if (!n) { - xprintf("%s: Can't skip\n", in.name); + xprintf("%s: Can't skip\n", TT.in.name); exit(0); } } } } - if (out.offset) xlseek(out.fd, (off_t)(out.offset * out.sz), SEEK_CUR); + if (TT.out.offset) xlseek(TT.out.fd, (off_t)(TT.out.offset * TT.out.sz), SEEK_CUR); } static void write_out(int all) { - ssize_t nw; - out.bp = out.buff; - while (out.count) { - nw = writeall(out.fd, out.bp, ((all)? out.count : out.sz)); - all = 0; //further writes will be on obs - if (nw <= 0) perror_exit("%s: write error",out.name); - if (nw == out.sz) st.out_full++; - else st.out_part++; - out.count -= nw; - out.bp += nw; - st.bytes += nw; - if (out.count < out.sz) break; - } - if (out.count) memmove(out.buff, out.bp, out.count); //move remainder to front -} - -static void do_dd(void) -{ - ssize_t n; - struct sigaction sa; - - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = sig_handler; - sigaction(SIGINT, &sa, NULL); - sigaction(SIGUSR1, &sa, NULL); - setup_inout(); - gettimeofday(&st.start, NULL); - - if (toys.optflags & (C_OF | C_SEEK) && !(toys.optflags & C_NOTRUNC)) - ftruncate(out.fd, (off_t)out.offset * out.sz); - - while (!(toys.optflags & C_COUNT) || (st.in_full + st.in_part) < c_count) { - if (TT.sig == SIGUSR1) { - summary(); - TT.sig = 0; - } else if (TT.sig == SIGINT) exit(TT.sig | 128); - in.bp = in.buff + in.count; - if (toys.optflags & C_SYNC) memset(in.bp, 0, in.sz); - if (!(n = read(in.fd, in.bp, in.sz))) break; - if (n < 0) { - if (errno == EINTR) continue; - //read error case. - perror_msg("%s: read error", in.name); - if (!(toys.optflags & C_NOERROR)) exit(1); - summary(); - xlseek(in.fd, in.sz, SEEK_CUR); - if (!(toys.optflags & C_SYNC)) continue; - // if SYNC, then treat as full block of nuls - n = in.sz; - } - if (n == in.sz) { - st.in_full++; - in.count += n; - } else { - st.in_part++; - if (toys.optflags & C_SYNC) in.count += in.sz; - else in.count += n; - } - - out.count = in.count; - if (toys.optflags & C_BS) { - write_out(1); - in.count = 0; - continue; - } + TT.out.bp = TT.out.buff; + while (TT.out.count) { + ssize_t nw = writeall(TT.out.fd, TT.out.bp, ((all)? TT.out.count : TT.out.sz)); - if (in.count >= out.sz) { - write_out(0); - in.count = out.count; - } + all = 0; //further writes will be on obs + if (nw <= 0) perror_exit("%s: write error", TT.out.name); + if (nw == TT.out.sz) TT.out_full++; + else TT.out_part++; + TT.out.count -= nw; + TT.out.bp += nw; + TT.bytes += nw; + if (TT.out.count < TT.out.sz) break; } - if (out.count) write_out(1); //write any remaining input blocks - if (toys.optflags & C_FSYNC && fsync(out.fd) < 0) - perror_exit("%s: fsync fail", out.name); - - close(in.fd); - close(out.fd); - if (in.buff) free(in.buff); + if (TT.out.count) memmove(TT.out.buff, TT.out.bp, TT.out.count); //move remainder to front } static int comp(const void *a, const void *b) //const to shut compiler up @@ -293,7 +229,9 @@ void dd_main() char *arg; long sz; - in.sz = out.sz = 512; //default io block size + TT.show_xfer = TT.show_records = 1; + + TT.in.sz = TT.out.sz = 512; //default io block size while (*toys.optargs) { if (!(arg = strchr(*toys.optargs, '='))) error_exit("unknown arg %s", *toys.optargs); *arg++ = '\0'; @@ -303,32 +241,37 @@ void dd_main() comp))) error_exit("unknown arg %s", key.name); toys.optflags |= res->val; - switch(res->val) { + switch (res->val) { case C_BS: - in.sz = out.sz = strsuftoll(arg, 1, LONG_MAX); + TT.in.sz = TT.out.sz = strsuftoll(arg, 1, LONG_MAX); break; case C_IBS: sz = strsuftoll(arg, 1, LONG_MAX); - if (!(toys.optflags & C_BS)) in.sz = sz; + if (!(toys.optflags & C_BS)) TT.in.sz = sz; break; case C_OBS: sz = strsuftoll(arg, 1, LONG_MAX); - if (!(toys.optflags & C_BS)) out.sz = sz; + if (!(toys.optflags & C_BS)) TT.out.sz = sz; break; case C_COUNT: - c_count = strsuftoll(arg, 0, ULLONG_MAX); + TT.c_count = strsuftoll(arg, 0, ULLONG_MAX); break; case C_IF: - in.name = arg; + TT.in.name = arg; break; case C_OF: - out.name = arg; + TT.out.name = arg; break; case C_SEEK: - out.offset = strsuftoll(arg, 0, ULLONG_MAX); + TT.out.offset = strsuftoll(arg, 0, ULLONG_MAX); break; case C_SKIP: - in.offset = strsuftoll(arg, 0, ULLONG_MAX); + TT.in.offset = strsuftoll(arg, 0, ULLONG_MAX); + break; + case C_STATUS: + if (!strcmp(arg, "noxfer")) TT.show_xfer = 0; + else if (!strcmp(arg, "none")) TT.show_xfer = TT.show_records = 0; + else error_exit("unknown status '%s'", arg); break; case C_CONV: while (arg) { @@ -344,5 +287,67 @@ void dd_main() toys.optargs++; } - do_dd(); + signal(SIGINT, generic_signal); + signal(SIGUSR1, generic_signal); + + setup_inout(); + gettimeofday(&TT.start, NULL); + + if ((toys.optflags&C_SEEK) && !(toys.optflags & C_NOTRUNC)) + ftruncate(TT.out.fd, TT.out.offset * TT.out.sz); + + while (!(toys.optflags & C_COUNT) || (TT.in_full + TT.in_part) < TT.c_count) { + ssize_t n; + + if (toys.signal) { + // Show progress and exit on SIGINT or just continue on SIGUSR1. + status(); + if (toys.signal == SIGINT) exit_signal(toys.signal); + toys.signal = 0; + } + + TT.in.bp = TT.in.buff + TT.in.count; + if (toys.optflags & C_SYNC) memset(TT.in.bp, 0, TT.in.sz); + if (!(n = read(TT.in.fd, TT.in.bp, TT.in.sz))) break; + if (n < 0) { + if (errno == EINTR) continue; + //read error case. + perror_msg("%s: read error", TT.in.name); + if (!(toys.optflags & C_NOERROR)) exit(1); + status(); + xlseek(TT.in.fd, TT.in.sz, SEEK_CUR); + if (!(toys.optflags & C_SYNC)) continue; + // if SYNC, then treat as full block of nuls + n = TT.in.sz; + } + if (n == TT.in.sz) { + TT.in_full++; + TT.in.count += n; + } else { + TT.in_part++; + if (toys.optflags & C_SYNC) TT.in.count += TT.in.sz; + else TT.in.count += n; + } + + TT.out.count = TT.in.count; + if (toys.optflags & C_BS) { + write_out(1); + TT.in.count = 0; + continue; + } + + if (TT.in.count >= TT.out.sz) { + write_out(0); + TT.in.count = TT.out.count; + } + } + if (TT.out.count) write_out(1); //write any remaining input blocks + if (toys.optflags & C_FSYNC && fsync(TT.out.fd) < 0) + perror_exit("%s: fsync fail", TT.out.name); + + close(TT.in.fd); + close(TT.out.fd); + if (TT.in.buff) free(TT.in.buff); + + status(); } |