commit 6ff6bb57ce7ad48b845d503831cbf5d9a0f95757
parent 92f17ad648114ce6bf967d890053d5b6b8504c28
Author: Michael Forney <mforney@mforney.org>
Date: Tue, 28 Apr 2020 22:22:37 -0700
Add implementation of dd(1)
Diffstat:
M | .gitignore | | | 1 | + |
M | Makefile | | | 1 | + |
A | dd.1 | | | 91 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | dd.c | | | 234 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
4 files changed, 327 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -17,6 +17,7 @@
/cron
/cut
/date
+/dd
/dirname
/du
/echo
diff --git a/Makefile b/Makefile
@@ -100,6 +100,7 @@ BIN =\
cron\
cut\
date\
+ dd\
dirname\
du\
echo\
diff --git a/dd.1 b/dd.1
@@ -0,0 +1,91 @@
+.Dd 2020-04-28
+.Dt DD 1
+.Os sbase
+.Sh NAME
+.Nm dd
+.Nd convert and copy a file
+.Sh SYNOPSIS
+.Nm
+.Op Ar operand Ns ...
+.Sh DESCRIPTION
+.Nm
+copies its input to its output, possibly after conversion, using
+the specified block sizes,
+.Pp
+The following operands are available:
+.Bl -tag -width ibs=expr
+.It Cm if= Ns Ar file
+Read from the file named by
+.Ar file
+instead of standard input.
+.It Cm of= Ns Ar file
+Write to the file named by
+.Ar file
+instead of standard output.
+.It Cm ibs= Ns Ar expr
+Set the input block size to
+.Ar expr
+(defaults to 512).
+.It Cm obs= Ns Ar expr
+Set the output block size to
+.Ar expr
+(defaults to 512).
+.It Cm bs= Ns Ar expr
+Set the input and output block sizes to
+.Ar expr .
+Additionally, if no conversion other than
+.Cm noerror ,
+.Cm notrunc ,
+or
+.Cm sync
+is specified, input blocks are copied as single output blocks, even
+when the input block is short.
+.It Cm skip= Ns Ar n
+Skip
+.Ar n
+input blocks before starting to copy.
+.It Cm seek= Ns Ar n
+Skip
+.Ar n
+output blocks before starting to copy.
+.It Cm count= Ns Ar n
+Copy at most
+.Ar n
+input blocks.
+.It Cm conv= Ns Ar value Ns Op , Ns Ar value Ns ...
+Apply the conversions specified by
+.Ar value .
+.Bl -tag -width Ds
+.It Cm lcase
+Map uppercase characters to the corresponding lowercase character
+using
+.Fn tolower .
+.It Cm ucase
+Map lowercase characters to the corresponding uppercase character
+using
+.Fn toupper .
+.It Cm swab
+Swap each pair of bytes in the input block.
+If there is an odd number of bytes in a block, the last one is
+unmodified.
+.It Cm noerror
+In case of an error reading from the input, do not fail.
+Instead, print a diagnostic message and a summary of the current
+status.
+.It Cm notrunc
+Do not truncate the output file.
+.It Cm sync
+In case of a partial input block, pad with null bytes to form a
+complete block.
+.El
+.El
+.Sh STANDARDS
+The
+.Nm
+utility is compliant with the
+.St -p1003.1-2008
+specification, except that it does not implement the
+.Cm block
+and
+.Cm unblock
+conversions.
diff --git a/dd.c b/dd.c
@@ -0,0 +1,234 @@
+/* See LICENSE file for copyright and license details. */
+#include <ctype.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "util.h"
+
+static off_t ifull, ofull, ipart, opart;
+
+static void
+usage(void)
+{
+ eprintf("usage: %s [operand...]\n", argv0);
+}
+
+static size_t
+parsesize(char *expr)
+{
+ char *s = expr;
+ size_t n = 1;
+
+ for (;;) {
+ n *= strtoumax(s, &s, 10);
+ switch (*s) {
+ case 'k': n <<= 10; s++; break;
+ case 'b': n <<= 9; s++; break;
+ }
+ if (*s != 'x' || !s[1])
+ break;
+ s++;
+ }
+ if (*s || n == 0)
+ eprintf("invalid block size expression '%s'\n", expr);
+
+ return n;
+}
+
+static void
+bswap(unsigned char *buf, size_t len)
+{
+ int c;
+
+ for (len &= ~1; len > 0; buf += 2, len -= 2) {
+ c = buf[0];
+ buf[0] = buf[1];
+ buf[1] = c;
+ }
+}
+
+static void
+lcase(unsigned char *buf, size_t len)
+{
+ for (; len > 0; buf++, len--)
+ buf[0] = tolower(buf[0]);
+}
+
+static void
+ucase(unsigned char *buf, size_t len)
+{
+ for (; len > 0; buf++, len--)
+ buf[0] = toupper(buf[0]);
+}
+
+static void
+summary(void)
+{
+ fprintf(stderr, "%"PRIdMAX"+%"PRIdMAX" records in\n", (intmax_t)ifull, (intmax_t)ipart);
+ fprintf(stderr, "%"PRIdMAX"+%"PRIdMAX" records out\n", (intmax_t)ofull, (intmax_t)opart);
+}
+
+int
+main(int argc, char *argv[])
+{
+ enum {
+ LCASE = 1 << 0,
+ UCASE = 1 << 1,
+ SWAB = 1 << 2,
+ NOERROR = 1 << 3,
+ NOTRUNC = 1 << 4,
+ SYNC = 1 << 5,
+ } conv = 0;
+ char *arg, *val, *end;
+ const char *iname = "-", *oname = "-";
+ int ifd = 0, ofd = 1, eof = 0;
+ size_t len, bs = 0, ibs = 512, obs = 512, ipos = 0, opos = 0;
+ off_t skip = 0, seek = 0, count = -1;
+ ssize_t ret;
+ unsigned char *buf;
+
+ argv0 = argc ? (argc--, *argv++) : "dd";
+ for (; argc > 0; argc--, argv++) {
+ arg = *argv;
+ val = strchr(arg, '=');
+ if (!val)
+ usage();
+ *val++ = '\0';
+ if (strcmp(arg, "if") == 0) {
+ iname = val;
+ } else if (strcmp(arg, "of") == 0) {
+ oname = val;
+ } else if (strcmp(arg, "ibs") == 0) {
+ ibs = parsesize(val);
+ } else if (strcmp(arg, "obs") == 0) {
+ obs = parsesize(val);
+ } else if (strcmp(arg, "bs") == 0) {
+ bs = parsesize(val);
+ } else if (strcmp(arg, "skip") == 0) {
+ skip = estrtonum(val, 0, LLONG_MAX);
+ } else if (strcmp(arg, "seek") == 0) {
+ seek = estrtonum(val, 0, LLONG_MAX);
+ } else if (strcmp(arg, "count") == 0) {
+ count = estrtonum(val, 0, LLONG_MAX);
+ } else if (strcmp(arg, "conv") == 0) {
+ do {
+ end = strchr(val, ',');
+ if (end)
+ *end++ = '\0';
+ if (strcmp(val, "lcase") == 0)
+ conv |= LCASE;
+ else if (strcmp(val, "ucase") == 0)
+ conv |= UCASE;
+ else if (strcmp(val, "swab") == 0)
+ conv |= SWAB;
+ else if (strcmp(val, "noerror") == 0)
+ conv |= NOERROR;
+ else if (strcmp(val, "notrunc") == 0)
+ conv |= NOTRUNC;
+ else if (strcmp(val, "sync") == 0)
+ conv |= SYNC;
+ else
+ eprintf("unknown conv flag '%s'\n", val);
+ val = end;
+ } while (val);
+ } else {
+ weprintf("unknown operand '%s'\n", arg);
+ usage();
+ }
+ }
+
+ if (bs)
+ ibs = obs = bs;
+ if (strcmp(iname, "-") != 0) {
+ ifd = open(iname, O_RDONLY);
+ if (ifd < 0)
+ eprintf("open %s:", iname);
+ }
+ if (strcmp(oname, "-") != 0) {
+ ofd = open(oname, O_WRONLY | O_CREAT | (conv & NOTRUNC || seek ? 0 : O_TRUNC), 0666);
+ if (ofd < 0)
+ eprintf("open %s:", oname);
+ }
+
+ len = MAX(ibs, obs) + ibs;
+ buf = emalloc(len);
+ if (skip && lseek(ifd, skip * ibs, SEEK_SET) < 0) {
+ while (skip--) {
+ ret = read(ifd, buf, ibs);
+ if (ret < 0)
+ eprintf("read:");
+ if (ret == 0) {
+ eof = 1;
+ break;
+ }
+ }
+ }
+ if (seek) {
+ if (!(conv & NOTRUNC) && ftruncate(ofd, seek * ibs) != 0)
+ eprintf("ftruncate:");
+ if (lseek(ofd, seek * ibs, SEEK_SET) < 0)
+ eprintf("lseek:");
+ /* XXX: handle non-seekable files */
+ }
+ while (!eof && (count == -1 || ifull + ipart < count)) {
+ while (ipos - opos < obs) {
+ ret = read(ifd, buf + ipos, ibs);
+ if (ret == 0) {
+ eof = 1;
+ break;
+ }
+ if (ret < 0) {
+ weprintf("read:");
+ if (!(conv & NOERROR))
+ return 1;
+ summary();
+ if (!(conv & SYNC))
+ continue;
+ ret = 0;
+ }
+ if (ret < ibs) {
+ ipart++;
+ if (conv & SYNC) {
+ memset(buf + ipos + ret, 0, ibs - ret);
+ ret = ibs;
+ }
+ } else {
+ ifull++;
+ }
+ if (conv & SWAB)
+ bswap(buf + ipos, ret);
+ if (conv & LCASE)
+ lcase(buf + ipos, ret);
+ if (conv & UCASE)
+ ucase(buf + ipos, ret);
+ ipos += ret;
+ if (bs && !(conv & (SWAB | LCASE | UCASE)))
+ break;
+ }
+ if (ipos == opos)
+ break;
+ do {
+ ret = write(ofd, buf + opos, MIN(obs, ipos - opos));
+ if (ret < 0)
+ eprintf("write:");
+ if (ret == 0)
+ eprintf("write returned 0\n");
+ if (ret < obs)
+ opart++;
+ else
+ ofull++;
+ opos += ret;
+ } while ((eof && ipos < opos) || (!eof && ipos - opos >= obs));
+ if (len - ipos < ibs) {
+ memmove(buf, buf + opos, ipos - opos);
+ ipos -= opos;
+ opos = 0;
+ }
+ }
+ summary();
+
+ return 0;
+}