From 0d754b823cfd6b9a9ed8f072277b1b5bea6e44a4 Mon Sep 17 00:00:00 2001 From: Elliott Hughes Date: Tue, 9 Jul 2019 16:08:16 -0700 Subject: dd: iflags, oflags, fix ^C, fix the fundamental loop. Investigating why the toybox tar tests fail on Android with toybox dd, I realized I was rewriting a part of dd I'd rewritten before! This is a re-send of my 2019-02-22 patch, rebased against the current ToT... This patch was originally motivated because after suggesting to the author of https://stackoverflow.com/questions/17157820/access-vdsolinux/54797221#54797221 that he could tell dd to work in bytes rather than blocks, I realized that our dd doesn't actually support that. But the rewrite of the main loop is necessary to fix the incorrect output from the dd calls in the tar test. Without this patch, `yes | dd bs=65536 count=1 > fweep` basically gives random output, based on how many bytes the pipe feels like giving you in your first read. (As far as I know, dd *without* bs= was fine, but I can't guarantee that that's true, just that I haven't seen it fail.) Also switch to TAGGED_ARRAY and comma_* for conv rather than add two more copies of an undesired idiom. It turned out -- contrary to the belief of cp(1) -- that comma_scan isn't suitable for this because of its magic handling of "no" prefixes. (It's actually harmless in cp because none of the --preserve options begin with "no", but some dd options do.) To this end, comma_remove is a less-magic comma_scan. I've also changed an `if` to a `while` because other implementations allow things like `--preserve=mode,mode` or `conv=sync,sync`. (If we decide this is a bug rather than a feature, we should at least fix the error message to be clear that we're rejecting the *duplication*, not the option itself.) I've also fixed the ^C behavior by simply adding a direct SIGINT handler rather than trying to be clever inside the read loop (which is why we weren't handling the SIGINT until the read returned). I've also removed `strstarteq` and just added the '=' to each literal when calling regular `strstart`. Plus basic tests. --- toys/pending/dd.c | 152 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 96 insertions(+), 56 deletions(-) (limited to 'toys/pending') diff --git a/toys/pending/dd.c b/toys/pending/dd.c index 0c447f74..80a7595f 100644 --- a/toys/pending/dd.c +++ b/toys/pending/dd.c @@ -4,8 +4,6 @@ * Copyright 2013 Kyungwan Han * * See http://opengroup.org/onlinepubs/9699919799/utilities/dd.html - * - * todo: ctrl-c doesn't work, the read() is restarting. USE_DD(NEWTOY(dd, 0, TOYFLAG_USR|TOYFLAG_BIN)) @@ -13,19 +11,22 @@ config DD bool "dd" default n help - usage: dd [if=FILE] [of=FILE] [ibs=N] [obs=N] [bs=N] [count=N] [skip=N] - [seek=N] [conv=notrunc|noerror|sync|fsync] [status=noxfer|none] + usage: dd [if=FILE] [of=FILE] [ibs=N] [obs=N] [iflag=FLAGS] [oflag=FLAGS] + [bs=N] [count=N] [seek=N] [skip=N] + [conv=notrunc|noerror|sync|fsync] [status=noxfer|none] Copy/convert files. if=FILE Read from FILE instead of stdin of=FILE Write to FILE instead of stdout bs=N Read and write N bytes at a time - ibs=N Read N bytes at a time - obs=N Write N bytes at a time + ibs=N Input block size + obs=N Output block size count=N Copy only N input blocks skip=N Skip N input blocks seek=N Skip N output blocks + iflag=FLAGS Set input flags + oflag=FLAGS Set output flags conv=notrunc Don't truncate output file conv=noerror Continue after read errors conv=sync Pad blocks with zeros @@ -33,6 +34,12 @@ config DD status=noxfer Don't show transfer rate status=none Don't show transfer rate or records in/out + FLAGS is a comma-separated list of: + + count_bytes (iflag) interpret count=N in bytes, not blocks + seek_bytes (oflag) interpret seek=N in bytes, not blocks + skip_bytes (iflag) interpret skip=N in bytes, not blocks + 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). */ @@ -51,12 +58,24 @@ GLOBALS( long sz, count; unsigned long long offset; } in, out; + unsigned conv, iflag, oflag; ); -#define C_FSYNC 1 -#define C_NOERROR 2 -#define C_NOTRUNC 4 -#define C_SYNC 8 +struct dd_flag { + char *name; +}; + +static const struct dd_flag dd_conv[] = TAGGED_ARRAY(DD_conv, + {"fsync"}, {"noerror"}, {"notrunc"}, {"sync"}, +); + +static const struct dd_flag dd_iflag[] = TAGGED_ARRAY(DD_iflag, + {"count_bytes"}, {"skip_bytes"}, +); + +static const struct dd_flag dd_oflag[] = TAGGED_ARRAY(DD_oflag, + {"seek_bytes"}, +); static void status() { @@ -79,6 +98,12 @@ static void status() } } +static void dd_sigint(int sig) { + status(); + toys.exitval = sig|128; + xexit(); +} + static void write_out(int all) { TT.out.bp = TT.out.buff; @@ -97,18 +122,24 @@ static void write_out(int all) if (TT.out.count) memmove(TT.out.buff, TT.out.bp, TT.out.count); //move remainder to front } -int strstarteq(char **a, char *b) +static void parse_flags(char *what, char *arg, + const struct dd_flag* flags, int flag_count, unsigned *result) { - char *aa = *a; + char *pre = xstrdup(arg); + int i; - return strstart(&aa, b) && *aa == '=' && (*a = aa+1); + for (i=0; i 0) { + int chunk = off < TT.in.sz ? off : TT.in.sz; + ssize_t n = read(TT.in.fd, TT.in.bp, chunk); if (n < 0) { perror_msg("%s", TT.in.name); - if (conv&C_NOERROR) status(); + if (TT.conv & _DD_conv_noerror) status(); else return; } else if (!n) { xprintf("%s: Can't skip\n", TT.in.name); return; } + off -= chunk; } } } - // seek/truncate as necessary. We handled position zero truncate with - // O_TRUNC on open, so output to /dev/null and such doesn't error. - if ((bs = TT.out.offset*TT.out.sz)) { + // Implement seek= and truncate as necessary. We handled position zero + // truncate with O_TRUNC on open, so output to /dev/null and such doesn't + // error. + bs = TT.out.offset; + if (!(TT.oflag & _DD_oflag_seek_bytes)) bs *= TT.out.sz; + if (bs) { xlseek(TT.out.fd, bs, SEEK_CUR); if (trunc && ftruncate(TT.out.fd, bs)) perror_exit("ftruncate"); } - while (TT.c_count==ULLONG_MAX || (TT.in_full + TT.in_part) < TT.c_count) { + unsigned long long bytes_left = TT.c_count; + if (TT.c_count != ULLONG_MAX && !(TT.iflag & _DD_iflag_count_bytes)) { + bytes_left *= TT.in.sz; + } + while (bytes_left) { + int chunk = bytes_left < TT.in.sz ? bytes_left : TT.in.sz; ssize_t n; // Show progress and exit on SIGINT or just continue on SIGUSR1. @@ -204,16 +243,16 @@ void dd_main() } TT.in.bp = TT.in.buff + TT.in.count; - if (conv&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 (TT.conv & _DD_conv_sync) memset(TT.in.bp, 0, TT.in.sz); + if (!(n = read(TT.in.fd, TT.in.bp, chunk))) break; + if (n < 0) { if (errno == EINTR) continue; //read error case. perror_msg("%s: read error", TT.in.name); - if (!(conv&C_NOERROR)) exit(1); + if (!(TT.conv & _DD_conv_noerror)) exit(1); status(); xlseek(TT.in.fd, TT.in.sz, SEEK_CUR); - if (!(conv&C_SYNC)) continue; + if (!(TT.conv & _DD_conv_sync)) continue; // if SYNC, then treat as full block of nuls n = TT.in.sz; } @@ -222,9 +261,10 @@ void dd_main() TT.in.count += n; } else { TT.in_part++; - if (conv&C_SYNC) TT.in.count += TT.in.sz; + if (TT.conv & _DD_conv_sync) TT.in.count += TT.in.sz; else TT.in.count += n; } + bytes_left -= n; TT.out.count = TT.in.count; if (bs) { @@ -239,7 +279,7 @@ void dd_main() } } if (TT.out.count) write_out(1); //write any remaining input blocks - if ((conv&C_FSYNC) && fsync(TT.out.fd)<0) + if ((TT.conv & _DD_conv_fsync) && fsync(TT.out.fd)<0) perror_exit("%s: fsync", TT.out.name); close(TT.in.fd); -- cgit v1.2.3