summaryrefslogtreecommitdiffstats
path: root/usr/utils/dd.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/utils/dd.c')
-rw-r--r--usr/utils/dd.c540
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;
+}