#include #include #include #include #include #include #include #include #include #include #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; }