/* dd.c - convert/copy a file
 *
 * Copyright 2013 Ashwini Kumar <ak.ashwini@gmail.com>
 * Copyright 2013 Kyungwan Han <asura321@gmail.com>
 *
 * See http://opengroup.org/onlinepubs/9699919799/utilities/dd.html
 *
 * Deviations from posix: no conversions, no cbs=
 * TODO: seek=n with unseekable output? (Read output and... write it back?)

USE_DD(NEWTOY(dd, 0, TOYFLAG_USR|TOYFLAG_BIN))

config DD
  bool "dd"
  default y
  help
    usage: dd [if|of=FILE] [ibs|obs|bs|count|seek|skip=N] [conv|status|iflag|oflag=FLAG[,FLAG...]]

    Copy/convert blocks of data from input to output, with the following
    keyword=value modifiers (and their default values):

    if=FILE  Read FILE (stdin)          of=FILE  Write to FILE (stdout)
       bs=N  Block size in bytes (512)  count=N  Stop after copying N blocks (all)
      ibs=N  Input block size (bs=)       obs=N  Output block size (bs=)
     skip=N  Skip N input blocks (0)     seek=N  Skip N output blocks (0)

    Each =N value accepts the normal unit suffixes (see toybox --help).

    These modifiers take a comma separated list of potential options:

    iflag=
      count_bytes count=N is in bytes   direct   Skip caches
      skip_bytes  skip=N is in bytes
    oflag=
      append      Append to file        direct   Skip caches
      seek_bytes  seek=N is in bytes
    status=noxfer,none             don't show transfer rate, no summary info
    conv=
      notrunc  Don't truncate output    noerror  Continue after read errors
      sync     Zero pad short reads     fsync    Flush output to disk at end
      sparse   Seek past zeroed output  excl     Fail if output file exists
      nocreat  Fail if of=FILE missing
*/

#define FOR_dd
#include "toys.h"

GLOBALS(
  // Display fields
  int show_xfer, show_records;
  unsigned long long bytes, in_full, in_part, out_full, out_part, start;
)

struct dd_flag {
  char *name;
};

static const struct dd_flag dd_conv[] = TAGGED_ARRAY(DD_conv,
  {"fsync"}, {"noerror"}, {"notrunc"}, {"sync"}, {"nocreat"}, // TODO sparse excl
);

static const struct dd_flag dd_iflag[] = TAGGED_ARRAY(DD_iflag,
  {"count_bytes"}, {"direct"}, {"skip_bytes"},
);

static const struct dd_flag dd_oflag[] = TAGGED_ARRAY(DD_oflag,
  {"append"}, {"direct"}, {"seek_bytes"},
);

static void status()
{
  unsigned long long now = millitime()-TT.start ? : 1, bytes = TT.bytes*1000;

  if (TT.show_records)
    fprintf(stderr, "%llu+%llu records in\n%llu+%llu records out\n",
            TT.in_full, TT.in_part, TT.out_full, TT.out_part);

  if (TT.show_xfer) {
    human_readable(toybuf, TT.bytes, HR_SPACE|HR_B);
    fprintf(stderr, "%llu bytes (%s) copied, ", TT.bytes, toybuf);
    bytes = (bytes>TT.bytes) ? bytes/now : TT.bytes/((now+999)/1000);
    human_readable(toybuf, bytes, HR_SPACE|HR_B);
    fprintf(stderr, "%llu.%03u s, %s/s\n", now/1000, (int)(now%1000), toybuf);
  }
}

static void parse_flags(char *what, char *arg,
    const struct dd_flag* flags, int flag_count, unsigned *result)
{
  char *pre = xstrdup(arg);
  int i;

  for (i = 0; i<flag_count; ++i)
    while (comma_remove(pre, flags[i].name)) *result |= 1<<i;
  if (*pre) error_exit("bad %s=%s", what, pre);
  free(pre);
}

// Multiply detecting overflow.
static unsigned long long overmul(unsigned long long x, unsigned long long y)
{
  unsigned long long ll = x*y;

  if (x && y && (ll<x || ll<y)) error_exit("overflow");

  return ll;
}

// Handle funky posix 1x2x3 syntax.
static unsigned long long argxarg(char *arg, int cap)
{
  long long ll = 1;
  char x QUIET, *s, *new;

  arg = xstrdup(arg);
  for (new = s = arg;; new = s+1) {
    // atolx() handles 0x hex prefixes, so skip past those looking for separator
    if ((s = strchr(new+2*!strncmp(new, "0x", 2), 'x'))) {
      if (s==new) break;
      x = *s;
      *s = 0;
    }
    ll = overmul(ll, atolx(new));
    if (s) *s = x;
    else break;
  }
  if (s || ll<cap || ll>(cap ? LONG_MAX : LLONG_MAX)) error_exit("bad %s", arg);
  free(arg);

  return ll;
}


// Point 1 or 2 iovec at len bytes at buf, starting at "start" and wrapping
// around at buflen.
int iovwrap(char *buf, unsigned long long buflen, unsigned long long start,
  unsigned long long len, struct iovec *iov)
{
  iov[0].iov_base = buf + start;
  iov[0].iov_len = len;
  if (start+len<=buflen) return 1;

  iov[1].iov_len = len-(iov[0].iov_len = buflen-start);
  iov[1].iov_base = buf;

  return 2;
}

void dd_main()
{
  char **args, *arg, *iname = 0, *oname = 0, *buf;
  unsigned long long bs = 0, seek = 0, skip = 0, ibs = 512, obs = 512,
    count = ULLONG_MAX, buflen;
  long long len;
  struct iovec iov[2];
  int opos, olen, ifd = 0, ofd = 1, trunc = O_TRUNC, iflags, oflags, ii;
  unsigned conv = 0, iflag = 0, oflag = 0;

  TT.show_xfer = TT.show_records = 1;

  for (args = toys.optargs; (arg = *args); args++) {
    if (strstart(&arg, "bs=")) bs = argxarg(arg, 1);
    else if (strstart(&arg, "ibs=")) ibs = argxarg(arg, 1);
    else if (strstart(&arg, "obs=")) obs = argxarg(arg, 1);
    else if (strstart(&arg, "count=")) count = argxarg(arg, 0);
    else if (strstart(&arg, "if=")) iname = arg;
    else if (strstart(&arg, "of=")) oname = arg;
    else if (strstart(&arg, "seek=")) seek = argxarg(arg, 0);
    else if (strstart(&arg, "skip=")) skip = argxarg(arg, 0);
    else if (strstart(&arg, "status=")) {
      if (!strcmp(arg, "noxfer")) TT.show_xfer = 0;
      else if (!strcmp(arg, "none")) TT.show_xfer = TT.show_records = 0;
      else error_exit("unknown status '%s'", arg);
    } else if (strstart(&arg, "conv="))
      parse_flags("conv", arg, dd_conv, ARRAY_LEN(dd_conv), &conv);
    else if (strstart(&arg, "iflag="))
      parse_flags("iflag", arg, dd_iflag, ARRAY_LEN(dd_iflag), &iflag);
    else if (strstart(&arg, "oflag="))
      parse_flags("oflag", arg, dd_oflag, ARRAY_LEN(dd_oflag), &oflag);
    else error_exit("bad arg %s", arg);
  }
  if (bs) ibs = obs = bs; // bs overrides ibs and obs regardless of position

  TT.start = millitime();
  sigatexit(status);
  xsignal(SIGUSR1, status);

  // If bs set, output blocks match input blocks (passing along short reads).
  // Else read ibs blocks and write obs, which worst case requires ibs+obs-1.
  buf = xmalloc(buflen = ibs+obs*!bs);
  if (buflen<ibs || buflen<obs) error_exit("tilt");

  oflags = O_CREAT|O_WRONLY;
  if (conv & _DD_conv_nocreat) oflags &= ~O_CREAT;
  if (conv & _DD_conv_notrunc) trunc = 0;
  if (oflag & _DD_oflag_append) oflags |= O_APPEND;
  if (oflag & _DD_oflag_direct) oflags |= O_DIRECT;

  iflags = O_RDONLY|WARN_ONLY; // WARN_ONLY means the opposite with openro()!
  if (iflag & _DD_iflag_direct) iflags |= O_DIRECT;
  if (iname) ifd = openro(iname, iflags);
  else iname = "stdin";
  if (oname) ofd = xcreate(oname, oflags|(trunc*!seek), 0666);
  else oname = "stdout";

  // Implement skip=
  if (skip) {
    if (!(iflag & _DD_iflag_skip_bytes)) skip *= ibs;
    if (lseek(ifd, skip, SEEK_CUR) < 0) {
      for (; skip > 0; skip -= len) {
        len = read(ifd, buf, minof(skip, ibs));
        if (len < 0) {
          perror_msg_raw(iname);
          if (conv & _DD_conv_noerror) status();
          else return;
        } else if (!len) return xprintf("%s: Can't skip\n", iname);
      }
    }
  }

  // Implement seek= and truncate as necessary. We handled position zero
  // truncate with O_TRUNC on open, so output to /dev/null etc doesn't error.
  if (!(oflag & _DD_oflag_seek_bytes)) seek *= obs;
  if (seek) {
    struct stat st;

    xlseek(ofd, seek, SEEK_CUR);
    if (trunc && !fstat(ofd, &st) && S_ISREG(st.st_mode) && ftruncate(ofd,seek))
      perror_exit("truncate");
  }

  if (count!=ULLONG_MAX && !(iflag & _DD_iflag_count_bytes))
    count = overmul(count, ibs);

  // output start position, output bytes available
  opos = olen = 0;
  for (;;) {
    // Write as many output blocks as we can. Using writev() avoids memmove()
    // to realign data but is still a single atomic write.
    while (olen>=obs || (olen && (bs || !count))) {
      errno = 0;
      len = writev(ofd, iov, iovwrap(buf, buflen, opos, minof(obs, olen), iov));
      if (len<1) {
        if (errno==EINTR) continue;
        perror_exit("%s: write error", oname);
      }
      TT.bytes += len;
      olen -= len;
      if ((opos += len)>=buflen) opos -= buflen;
      if (len == obs) TT.out_full++;
      else TT.out_part++;
    }
    if (!count) break;

    // Read next block of input. (There MUST be enough space, we sized buf.)
    len = opos+olen;
    if (len>buflen) len -= buflen;
    errno = 0;
    if (2 == (ii = iovwrap(buf, buflen, len, minof(count, ibs), iov)))
      memset(iov[1].iov_base, 0, iov[1].iov_len);
    memset(iov[0].iov_base, 0, iov[0].iov_len);
    len = readv(ifd, iov, ii);
    if (len<1) {
      if (errno==EINTR) continue;
      if (!len) count = 0;
      else {
        //read error case.
        perror_msg("%s: read error", iname);
        if (!(conv & _DD_conv_noerror)) xexit();

        // Complain and try to seek past it
        status();
        lseek(ifd, ibs, SEEK_CUR);
        if (conv & _DD_conv_sync) olen += ibs;
      }

      continue;
    }
    if (len == ibs) TT.in_full++;
    else TT.in_part++;
    if (conv & _DD_conv_sync) len = ibs;
    olen += len;
    count -= minof(len, count);
  }
  if ((conv & _DD_conv_fsync) && fsync(ofd)) perror_exit("%s: fsync", oname);

  if (CFG_TOYBOX_FREE) {
    close(ifd);
    close(ofd);
    free(buf);
  }
}
