diff options
Diffstat (limited to 'usr/utils/dd.c')
-rw-r--r-- | usr/utils/dd.c | 540 |
1 files changed, 540 insertions, 0 deletions
diff --git a/usr/utils/dd.c b/usr/utils/dd.c new file mode 100644 index 0000000..706b8c3 --- /dev/null +++ b/usr/utils/dd.c @@ -0,0 +1,540 @@ +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <setjmp.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + +static char *progname; + +struct option { + const char *opt; + char *str; + char *arg; +}; + +struct conv { + const char str[8]; + unsigned int set; + unsigned int exclude; +}; + +#define CONV_BLOCK (1<<0) +#define CONV_UNBLOCK (1<<1) + +#define CONV_LCASE (1<<2) +#define CONV_UCASE (1<<3) + +#define CONV_SWAB (1<<4) +#define CONV_NOERROR (1<<5) +#define CONV_NOTRUNC (1<<6) +#define CONV_SYNC (1<<7) + +static struct option options[] = { + {"bs", NULL, NULL}, +#define OPT_BS (&options[0]) + {"cbs", NULL, NULL}, +#define OPT_CBS (&options[1]) + {"conv", NULL, NULL}, +#define OPT_CONV (&options[2]) + {"count", NULL, NULL}, +#define OPT_COUNT (&options[3]) + {"ibs", NULL, NULL}, +#define OPT_IBS (&options[4]) + {"if", NULL, NULL}, +#define OPT_IF (&options[5]) + {"obs", NULL, NULL}, +#define OPT_OBS (&options[6]) + {"of", NULL, NULL}, +#define OPT_OF (&options[7]) + {"seek", NULL, NULL}, +#define OPT_SEEK (&options[8]) + {"skip", NULL, NULL} +#define OPT_SKIP (&options[9]) +}; + +static const struct conv conv_opts[] = { + {"block", CONV_BLOCK, CONV_UNBLOCK}, + {"unblock", CONV_UNBLOCK, CONV_BLOCK}, + {"lcase", CONV_LCASE, CONV_UCASE}, + {"ucase", CONV_UCASE, CONV_LCASE}, + {"swab", CONV_SWAB, 0}, + {"noerror", CONV_NOERROR, 0}, + {"notrunc", CONV_NOTRUNC, 0}, + {"sync", CONV_SYNC, 0}, +}; + +static size_t cbs; +static unsigned int conv; +static unsigned int count; +static size_t ibs = 512; +static size_t obs = 512; +static unsigned int seek; +static unsigned int skip; +static char *in_buf; +static char *out_buf; + +static size_t parse_bs(struct option *opt) +{ + unsigned long val, realval = 1; + char *str = opt->str; + int err = 0; + + do { + char *s = str; + val = strtoul(str, &str, 10); + if (s == str || (val == ULONG_MAX && errno == ERANGE)) { + err = 1; + break; + } + + /* + * This option may be followed by + * 'b', 'k' or 'x' + */ + if (*str == 'b') { + val *= 512; + str++; + } else if (*str == 'k') { + val *= 1024; + str++; + } + realval *= val; + if (*str != 'x') + break; + str++; + } while (1); + + if (*str != '\0') + err = 1; + + if (err) { + fprintf(stderr, "%s: bad operand `%s'\n", progname, opt->arg); + exit(1); + } + + return (size_t) realval; +} + +static unsigned int parse_num(struct option *opt) +{ + unsigned long val; + char *str = opt->str; + + val = strtoul(str, &str, 10); + if (str == opt->str || (val == ULONG_MAX && errno == ERANGE) || + val > UINT_MAX) { + fprintf(stderr, "%s: bad operand `%s'\n", progname, opt->arg); + exit(1); + } + + return (unsigned int)val; +} + +static int parse_options(int argc, char *argv[]) +{ + unsigned int i; + char *p, *s; + int arg; + + /* + * We cheat here; we don't parse the operand values + * themselves here. We merely split the operands + * up. This means that bs=foo bs=1 won't produce + * an error. + */ + for (arg = 1; arg < argc; arg++) { + unsigned int len; + + s = strchr(argv[arg], '='); + if (!s) + s = argv[arg]; /* don't recognise this arg */ + + len = s - argv[arg]; + for (i = 0; i < ARRAY_SIZE(options); i++) { + if (strncmp(options[i].opt, argv[arg], len) != 0) + continue; + + options[i].str = s + 1; + options[i].arg = argv[arg]; + break; + } + + if (i == ARRAY_SIZE(options)) { + fprintf(stderr, "%s: bad operand `%s'\n", + progname, argv[arg]); + return 1; + } + } + + /* + * Translate numeric operands. + */ + if (OPT_IBS->str) + ibs = parse_bs(OPT_IBS); + if (OPT_OBS->str) + obs = parse_bs(OPT_OBS); + if (OPT_CBS->str) + cbs = parse_bs(OPT_CBS); + if (OPT_COUNT->str) + count = parse_num(OPT_COUNT); + if (OPT_SEEK->str) + seek = parse_num(OPT_SEEK); + if (OPT_SKIP->str) + skip = parse_num(OPT_SKIP); + + /* + * If bs= is specified, it overrides ibs= and obs= + */ + if (OPT_BS->str) + ibs = obs = parse_bs(OPT_BS); + + /* + * And finally conv= + */ + if (OPT_CONV->str) { + p = OPT_CONV->str; + + while ((s = strsep(&p, ",")) != NULL) { + for (i = 0; i < ARRAY_SIZE(conv_opts); i++) { + if (strcmp(s, conv_opts[i].str) != 0) + continue; + conv &= ~conv_opts[i].exclude; + conv |= conv_opts[i].set; + break; + } + + if (i == ARRAY_SIZE(conv_opts)) { + fprintf(stderr, "%s: bad conversion `%s'\n", + progname, s); + return 1; + } + } + } + + if (conv & (CONV_BLOCK | CONV_UNBLOCK) && cbs == 0) { + fprintf(stderr, "%s: block/unblock conversion with zero cbs\n", + progname); + return 1; + } + + return 0; +} + +static int safe_read(int fd, void *buf, size_t size) +{ + int ret, count = 0; + char *p = buf; + + while (size) { + ret = read(fd, p, size); + + /* + * If we got EINTR, go again. + */ + if (ret == -1 && errno == EINTR) + continue; + + /* + * If we encountered an error condition + * or read 0 bytes (EOF) return what we + * have. + */ + if (ret == -1 || ret == 0) + return count ? count : ret; + + /* + * We read some bytes. + */ + count += ret; + size -= ret; + p += ret; + } + + return count; +} + +static int skip_blocks(int fd, void *buf, unsigned int blks, size_t size) +{ + unsigned int blk; + int ret = 0; + + /* + * Try to seek. + */ + for (blk = 0; blk < blks; blk++) { + ret = lseek(fd, size, SEEK_CUR); + if (ret == -1) + break; + } + + /* + * If we failed to seek, read instead. + * FIXME: we don't handle short reads here, or + * EINTR correctly. + */ + if (blk == 0 && ret == -1 && errno == ESPIPE) { + for (blk = 0; blk < blks; blk++) { + ret = safe_read(fd, buf, size); + if (ret != (int)size) + break; + } + } + + if (ret == -1) { + perror("seek/skip"); + return 1; + } + return 0; +} + +struct stats { + unsigned int in_full; + unsigned int in_partial; + unsigned int out_full; + unsigned int out_partial; + unsigned int truncated; +}; + +static int do_dd(int rd, int wr, struct stats *stats) +{ + unsigned int i; + int ret; + int fill_val = 0; + size_t out_size = 0; + size_t in_size; + char *buf; + + if (conv & (CONV_BLOCK | CONV_UNBLOCK)) + fill_val = ' '; + + while (!OPT_COUNT->str || count-- != 0) { + buf = in_buf; + + /* + * 1. read ibs-sized buffer + */ + in_size = ret = read(rd, in_buf, ibs); + if (ret == -1 || (ret == 0 && (conv & CONV_NOERROR) == 0)) + break; + + if (in_size == ibs) { + stats->in_full++; + } else { + stats->in_partial++; + + /* + * 2. zero (or append spaces) + */ + if (conv & CONV_SYNC) { + memset(in_buf + in_size, fill_val, + ibs - in_size); + in_size = ibs; + } + } + + /* + * 4. swab conversion. With an odd number of bytes, + * last byte does not get swapped. + */ + if (conv & CONV_SWAB) { + char c; + + for (i = 1; i < in_size; i += 2) { + c = in_buf[i - 1]; + in_buf[i - 1] = in_buf[i]; + in_buf[i] = c; + } + } + + /* + * 5. remaining conversions. + */ + if (conv & CONV_LCASE) + for (i = 0; i < in_size; i++) + in_buf[i] = tolower(in_buf[i]); + + if (conv & CONV_UCASE) + for (i = 0; i < in_size; i++) + in_buf[i] = toupper(in_buf[i]); + + /* block/unblock ? */ + + /* + * 6. Aggregate into obs sized buffers. + * If the in_size is obs-sized and we have no + * data waiting, just write "buf" to the output. + */ + if (out_size == 0 && in_size == obs) { + write(wr, buf, obs); + stats->out_full++; + } else { + /* + * We had data waiting, or we didn't have an + * obs-sized input block. We need to append + * the input data to the output buffer. + */ + unsigned int space; + char *in_ptr = in_buf; + + do { + space = obs - out_size; + if (space > in_size) + space = in_size; + + memcpy(out_buf + out_size, in_ptr, space); + out_size += space; + in_size -= space; + in_ptr += space; + + if (out_size == obs) { + write(wr, out_buf, obs); + stats->out_full++; + out_size = 0; + } + } while (out_size == 0 && in_size); + + if (in_size) { + memcpy(out_buf, in_ptr, in_size); + out_size = in_size; + } + } + } + + if (out_size) { + write(wr, out_buf, out_size); + stats->out_partial++; + } + + return 0; +} + +static sigjmp_buf jmp; + +static void sigint_handler(int sig) +{ + siglongjmp(jmp, -sig); +} + +static int dd(int rd_fd, int wr_fd, struct stats *stats) +{ + int ret; + + ret = sigsetjmp(jmp, 1); + if (ret == 0) { + sysv_signal(SIGINT, sigint_handler); + ret = do_dd(rd_fd, wr_fd, stats); + } + + sysv_signal(SIGINT, SIG_DFL); + return ret; +} + +int main(int argc, char *argv[]) +{ + struct stats stats; + int ret; + int rd_fd = 0, wr_fd = 1; + + progname = argv[0]; + + ret = parse_options(argc, argv); + if (ret) + return ret; + + if (conv & (CONV_BLOCK | CONV_UNBLOCK)) { + fprintf(stderr, "%s: block/unblock not implemented\n", + progname); + return 1; + } + + in_buf = malloc(ibs); + if (!in_buf) { + perror("malloc ibs"); + return 1; + } + + out_buf = malloc(obs); + if (!out_buf) { + perror("malloc obs"); + return 1; + } + + /* + * Open the input file, if specified. + */ + if (OPT_IF->str) { + rd_fd = open(OPT_IF->str, O_RDONLY); + if (rd_fd == -1) { + perror("open input file"); + return 1; + } + } + + /* + * Open the output file, if specified. + */ + if (OPT_OF->str) { + int flags = O_WRONLY|O_CREAT; + flags |= (conv & CONV_NOTRUNC) ? 0 : O_TRUNC; + wr_fd = open(OPT_OF->str, flags, 0666); + if (wr_fd == -1) { + perror("open output file"); + close(rd_fd); + return 1; + } + } + + /* + * Skip obs-sized blocks of output file. + */ + if (OPT_SEEK->str && skip_blocks(wr_fd, out_buf, seek, obs)) { + close(rd_fd); + close(wr_fd); + return 1; + } + + /* + * Skip ibs-sized blocks of input file. + */ + if (OPT_SKIP->str && skip_blocks(rd_fd, in_buf, skip, ibs)) { + close(rd_fd); + close(wr_fd); + return 1; + } + + memset(&stats, 0, sizeof(stats)); + + /* + * Do the real work + */ + ret = dd(rd_fd, wr_fd, &stats); + + if (close(rd_fd) == -1) + perror(OPT_IF->str ? OPT_IF->str : "stdin"); + if (close(wr_fd) == -1) + perror(OPT_OF->str ? OPT_OF->str : "stdout"); + + fprintf(stderr, "%u+%u records in\n", stats.in_full, stats.in_partial); + fprintf(stderr, "%u+%u records out\n", + stats.out_full, stats.out_partial); + if (stats.truncated) + fprintf(stderr, "%u truncated record%s\n", + stats.truncated, stats.truncated == 1 ? "" : "s"); + + /* + * ret will be -SIGINT if we got a SIGINT. Raise + * the signal again to cause us to terminate with + * SIGINT status. + */ + if (ret == -SIGINT) + raise(SIGINT); + + return ret; +} |