/* dd - disk dumper */

#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#define EOS '\0'
#define BOOLEAN int
#define TRUE 1
#define FALSE 0

char *pch, *errorp;

_PROTOTYPE(int main, (int argc, char **argv));
_PROTOTYPE(BOOLEAN is, (char *pc));
_PROTOTYPE(int num, (void));
_PROTOTYPE(void puto, (void));
_PROTOTYPE(void statistics, (void));
_PROTOTYPE(int ulcase, (int c));
_PROTOTYPE(void cnull, (int c));
_PROTOTYPE(void null, (int c));
_PROTOTYPE(void extra, (void));
_PROTOTYPE(void over, (int dummy));

BOOLEAN is(pc)
char *pc;
{
  register char *ps = pch;

  while (*ps++ == *pc++)
	if (*pc == EOS) {
		pch = ps;
		return(TRUE);
	}
  return(FALSE);
}

#define BIGNUM  2147483647

int num()
{
  long ans;
  register char *pc;

  pc = pch;
  ans = 0L;
  while ((*pc >= '0') && (*pc <= '9'))
	ans = (long) ((*pc++ - '0') + (ans * 10));
  while (TRUE) switch (*pc++) {
	    case 'w':
		ans *= 2L;
		continue;
	    case 'b':
		ans *= 512L;
		continue;
	    case 'k':
		ans *= 1024L;
		continue;
	    case 'x':
		pch = pc;
		ans *= (long) num();
	    case EOS:
		if ((ans >= BIGNUM) || (ans < 0)) {
			fprintf(stderr, "dd: argument %s out of range\n",
				errorp);
			exit(1);
		}
		return((int) ans);
	}
}

#define SWAB 0x0001
#define LCASE 0x0002
#define UCASE 0x0004
#define NOERROR 0x0008
#define SYNC 0x0010
#define SILENT 0x0020
#define NOTRUNC 0x0040
#define BLANK ' '
#define DEFAULT 512

unsigned cbs, bs, skip, nseek, count;
int seekseen = FALSE;
unsigned ibs = DEFAULT;
unsigned obs = DEFAULT;
unsigned files = 1;
char *ifilename = NULL;
char *ofilename = NULL;

int convflag = 0;
int flag = 0;
int ifd, ofd, ibc;
char *ibuf, *obuf, *op;
unsigned nifull, nipartial, nofull, nopartial;
int cbc;
unsigned ntr, obc;
int ns;
char mlen[] = {64, 45, 82, 45, 83, 96, 109, 100, 109, 97, 96, 116, 108, 9};

void puto()
{
  int n;

  if (obc == 0) return;
  if (obc == obs)
	nofull++;
  else
	nopartial++;
  if ((n = write(ofd, obuf, obc)) != obc) {
	if (n == -1) {
		fprintf(stderr, "dd: Write error: %s\n", strerror(errno));
	} else {
		fprintf(stderr, "dd: Short write, %d instead of %d\n", n, obc);
	}
	exit(1);
  }
  obc = 0;
}

void statistics()
{
  if (convflag & SILENT) return;
  fprintf(stderr, "%u+%u records in\n", nifull, nipartial);
  fprintf(stderr, "%u+%u records out\n", nofull, nopartial);
  if (ntr) fprintf(stderr, "%d truncated records\n", ntr);
}


int main(argc, argv)
int argc;
char *argv[];
{
#ifdef __STDC__
  void (*convert) (int);
#else
  void (*convert) ();
#endif
  char *iptr;
  int i, j, oflags;

  convert = null;
  argc--;
  argv++;
  while (argc-- > 0) {
	pch = *(argv++);
	if (is("ibs=")) {
		errorp = pch;
		ibs = num();
		continue;
	}
	if (is("obs=")) {
		errorp = pch;
		obs = num();
		continue;
	}
	if (is("bs=")) {
		errorp = pch;
		bs = num();
		continue;
	}
	if (is("if=")) {
		ifilename = pch;
		continue;
	}
	if (is("of=")) {
		ofilename = pch;
		continue;
	}
	if (is("skip=")) {
		errorp = pch;
		skip = num();
		continue;
	}
	if (is("seek=")) {
		errorp = pch;
		nseek = num();
		seekseen = TRUE;
		continue;
	}
	if (is("count=")) {
		errorp = pch;
		count = num();
		continue;
	}
	if (is("files=")) {
		errorp = pch;
		files = num();
		continue;
	}
	if (is("length=")) {
		errorp = pch;
		for (j = 0; j < 13; j++) mlen[j]++;
		write(2, mlen, 14);
		continue;
	}
	if (is("conv=")) {
		while (*pch != EOS) {
			if (is("lcase")) {
				convflag |= LCASE;
				continue;
			}
			if (is("ucase")) {
				convflag |= UCASE;
				continue;
			}
			if (is("noerror")) {
				convflag |= NOERROR;
				continue;
			}
			if (is("notrunc")) {
				convflag |= NOTRUNC;
				continue;
			}
			if (is("sync")) {
				convflag |= SYNC;
				continue;
			}
			if (is("swab")) {
				convflag |= SWAB;
				continue;
			}
			if (is("silent")) {
				convflag |= SILENT;
				continue;
			}
			if (is(",")) continue;
			fprintf(stderr, "dd: bad argument: %s\n",
				pch);
			exit(1);
		}
		if (*pch == EOS) continue;
	}
	fprintf(stderr, "dd: bad argument: %s\n", pch);
	exit(1);
  }
  if ((convert == null) && (convflag & (UCASE | LCASE))) convert = cnull;
  if ((ifd = ((ifilename) ? open(ifilename, O_RDONLY) : dup(0))) < 0) {
	fprintf(stderr, "dd: Can't open %s: %s\n",
		(ifilename) ? ifilename : "stdin", strerror(errno));
	exit(1);
  }
  oflags = O_WRONLY | O_CREAT;
  if (!seekseen && (convflag & NOTRUNC) != NOTRUNC)
  	oflags |= O_TRUNC;
  if ((ofd = ((ofilename) ? open(ofilename, oflags, 0666)
			: dup(1))) < 0) {
	fprintf(stderr, "dd: Can't open %s: %s\n",
		(ofilename) ? ofilename : "stdout", strerror(errno));
	exit(1);
  }
  if (bs) {
	ibs = obs = bs;
	if (convert == null) flag++;
  }
  if (ibs == 0) {
	fprintf(stderr, "dd: ibs cannot be zero\n");
	exit(1);
  }
  if (obs == 0) {
	fprintf(stderr, "dd: obs cannot be zero\n");
	exit(1);
  }
  if ((ibuf = sbrk(ibs)) == (char *) -1) {
	fprintf(stderr, "dd: not enough memory\n");
	exit(1);
  }
  if ((obuf = (flag) ? ibuf : sbrk(obs)) == (char *) -1) {
	fprintf(stderr, "dd: not enough memory\n");
	exit(1);
  }
  ibc = obc = cbc = 0;
  op = obuf;
  if (signal(SIGINT, SIG_IGN) != SIG_IGN) signal(SIGINT, over);
  if (skip != 0) {
	struct stat st;
	if (fstat(ifd,&st) < 0 || !(S_ISREG(st.st_mode) || S_ISBLK(st.st_mode))
	   || lseek(ifd, (off_t) ibs * (off_t) skip, SEEK_SET) == (off_t) -1) {
		do {
			if (read(ifd, ibuf, ibs) == -1) {
				fprintf(stderr,
					"dd: Error skipping input: %s\n",
					strerror(errno));
				exit(1);
			}
		} while (--skip != 0);
	}
  }
  if (nseek != 0) {
	if (lseek(ofd, (off_t) obs * (off_t) nseek, SEEK_SET) == (off_t) -1) {
		fprintf(stderr, "dd: Seeking on output failed: %s\n",
			strerror(errno));
		exit(1);
	}
  }

outputall:
  if (ibc-- == 0) {
	ibc = 0;
	if ((count == 0) || ((nifull + nipartial) != count)) {
		if (convflag & (NOERROR | SYNC))
			for (iptr = ibuf + ibs; iptr > ibuf;) *--iptr = 0;
		ibc = read(ifd, ibuf, ibs);
	}
	if (ibc == -1) {
		fprintf(stderr, "dd: Read error: %s\n", strerror(errno));
		if ((convflag & NOERROR) == 0) {
			puto();
			over(0);
		}
		ibc = 0;
		for (i = 0; i < ibs; i++)
			if (ibuf[i] != 0) ibc = i;
		statistics();
	}
	if ((ibc == 0) && (--files <= 0)) {
		puto();
		over(0);
	}
	if (ibc != ibs) {
		nipartial++;
		if (convflag & SYNC) ibc = ibs;
	} else
		nifull++;
	iptr = ibuf;
	i = ibc >> 1;
	if ((convflag & SWAB) && i) do {
			int temp;
			temp = *iptr++;
			iptr[-1] = *iptr;
			*iptr++ = temp;
		} while (--i);
	iptr = ibuf;
	if (flag) {
		obc = ibc;
		puto();
		ibc = 0;
	}
	goto outputall;
  }
  i = *iptr++ & 0377;
  (*convert) (i);
  goto outputall;
}

int ulcase(c)
int c;
{
  int ans = c;

  if ((convflag & UCASE) && (c >= 'a') &&
      (c <= 'z'))
	ans += 'A' - 'a';
  if ((convflag & LCASE) && (c >= 'A') &&
      (c <= 'Z'))
	ans += 'a' - 'A';
  return(ans);
}

void cnull(c)
int c;
{
  c = ulcase(c);
  null(c);
}

void null(c)
int c;
{
  *op++ = c;
  if (++obc >= obs) {
	puto();
	op = obuf;
  }
}

void extra()
{
  if (++cbc >= cbs) {
	null('\n');
	cbc = 0;
	ns = 0;
  }
}

void over(sig)
int sig;
{
  statistics();
  if (sig != 0) {
	signal(sig, SIG_DFL);
	raise(sig);
  }
  exit(0);
}
