dvd-sector-copy.c: Sink debugging spew to the end of the `switch' arm.
[dvdrip] / dvd-sector-copy.c
index ea34461..c6b4c19 100644 (file)
@@ -3,34 +3,53 @@
 static void usage(FILE *fp)
 {
   fprintf(fp,
-         "usage: %s [-c] [-B PARAM=VALUE,...] [-R MAP]\n"
+         "usage: %s [-ci] [-B PARAM=VALUE,...] [-R MAP]\n"
          "\t[-b OUTMAP] [-r [START]-[END]] DEVICE OUTFILE\n",
          prog);
 }
 
-static double tvdiff(const struct timeval *tv_lo,
-                    const struct timeval *tv_hi)
+#define PRF_HYPHEN 1u
+static int parse_range(const char *p, unsigned f,
+                      secaddr *start_out, secaddr *end_out)
 {
-  return ((tv_hi->tv_sec - tv_lo->tv_sec) +
-         (tv_hi->tv_usec - tv_lo->tv_usec)/1.0e6);
-}
+  char *q;
+  int err, rc;
+  unsigned long start, end;
+
+  err = errno;
 
-#define DEFVEC(vtype, etype)                                           \
-       typedef struct { etype *v; size_t n, sz; } vtype
-#define VEC_INIT { 0, 0, 0 }
-#define VEC_FREE(vv) do {                                              \
-  free((vv)->v); (vv)->v 0; (vv)->n = (vv)->sz = 0;                    \
-} while (0)
-#define VEC_PUSH(p, vv) do {                                           \
-  size_t _want;                                                                \
-  if ((vv)->n >= (vv)->sz) {                                           \
-    (vv)->sz = (vv)->sz ? 2*(vv)->sz : 32;                             \
-    _want = (vv)->sz*sizeof(*(vv)->v);                                 \
-    (vv)->v = realloc((vv)->v, _want);                                 \
-    if (!(vv)->v) bail("out of memory allocating %zu bytes", _want);   \
-  }                                                                    \
-  (p) = &(vv)->v[(vv)->n++];                                           \
-} while (0)
+  if (ISDIGIT(*p)) {
+    start = strtoul(p, &q, 0);
+    if (errno || start >= SECLIMIT) { rc = -1; goto end; }
+    *start_out = start; p = q;
+  } else if (!(f&PRF_HYPHEN))
+    { rc = -1; goto end; }
+  else
+    start = 0;
+
+  if (f&PRF_HYPHEN) {
+    if (*p != '-')  { rc = -1; goto end; }
+    p++;
+  } else {
+    if (!ISSPACE(*p)) { rc = -1; goto end; }
+    do p++; while (ISSPACE(*p));
+  }
+
+  if (ISDIGIT(*p)) {
+    end = strtoul(p, &q, 0);
+    if (errno || end > SECLIMIT || end < start) { rc = -1; goto end; }
+    *end_out = end; p = q;
+  } else if (!(f&PRF_HYPHEN))
+    { rc = -1; goto end; }
+
+  if (!(f&PRF_HYPHEN)) while (ISSPACE(*p)) p++;
+  if (*p && ((f&PRF_HYPHEN) || *p != '#')) { rc = -1; goto end; }
+
+  rc = 0;
+end:
+  errno = err;
+  return (rc);
+}
 
 #define MAXFILES (1 + 2*99 + 1)
 struct file {
@@ -64,6 +83,39 @@ static int compare_event(const void *a, const void *b)
   return (0);
 }
 
+#ifdef DEBUG
+static void dump_eventq(const char *what)
+{
+  unsigned i;
+  const struct event *ev;
+  char fn[MAXFNSZ];
+
+  printf("\n;; event dump (%s):\n", what);
+  for (i = 0; i < eventq.n; i++) {
+    ev = &eventq.v[i];
+    switch (ev->ev) {
+      case EV_BEGIN:
+       store_filename(fn, filetab.v[ev->file].id);
+       printf(";; %8"PRIuSEC": begin %s\n", ev->pos, fn);
+       break;
+      case EV_END:
+       store_filename(fn, filetab.v[ev->file].id);
+       printf(";; %8"PRIuSEC": end %s\n", ev->pos, fn);
+       break;
+      case EV_WRITE:
+       printf(";; %8"PRIuSEC": write\n", ev->pos);
+       break;
+      case EV_STOP:
+       printf(";; %8"PRIuSEC": stop\n", ev->pos);
+       break;
+      default:
+       printf(";; %8"PRIuSEC": ?%u\n", ev->pos, ev->ev);
+       break;
+    }
+  }
+}
+#endif
+
 typedef uint_least32_t bits;
 static bits live[(MAXFILES + 31)/32];
 
@@ -119,8 +171,8 @@ static void put_menu(dvd_reader_t *dvd, unsigned title)
   store_filename(fn, id);
   start = UDFFindFile(dvd, fn, &len); if (!start) return;
 #ifdef DEBUG
-      printf(";; %8"PRIuSEC" .. %-8"PRIuSEC": %s\n",
-            start, start + SECTORS(len), fn);
+  printf(";; %8"PRIuSEC" .. %-8"PRIuSEC": %s\n",
+        start, start + SECTORS(len), fn);
 #endif
   put_file(id, start, start + SECTORS(len));
 }
@@ -160,17 +212,30 @@ static void put_title(dvd_reader_t *dvd, unsigned title)
           start[0], start[npart - 1] + SECTORS(len[npart - 1]));
 }
 
-static secaddr last_pos, limit, nsectors, ndone;
-static struct timeval last_time;
-static double wsum, wcount;
+static dvd_reader_t *dvd;
+static int dvdfd = -1, outfd = -1;
 static struct file *file;
+static dvd_file_t *vob;
+static const char *mapfile; static FILE *mapfp;
+static const char *errfile; static FILE *errfp;
+static secaddr limit;
 static secaddr bad_start;
 static unsigned retry, max_retries = 4;
+
+static secaddr nsectors, ndone;
+static secaddr last_pos;
+static struct timeval last_time;
+static double alpha = 0.1;
+static double wsum, wcount;
 static int bad_err;
 
 static const char throbber[] = "|<-<|>->";
 static unsigned throbix = 0;
 
+static struct progress_item
+  copy_progress, disc_progress,
+  file_progress, badblock_progress;
+
 static double scale_bytes(double n, const char **unit_out)
 {
   const char *unit = "";
@@ -179,14 +244,9 @@ static double scale_bytes(double n, const char **unit_out)
   if (n > 1600) { n /= 1024; unit = "M"; }
   if (n > 1600) { n /= 1024; unit = "G"; }
   if (n > 1600) { n /= 1024; unit = "T"; }
-
   *unit_out = unit; return (n);
 }
 
-static struct progress_item
-  copy_progress, disc_progress,
-  file_progress, badblock_progress;
-
 #define TIMESTRMAX 16
 static char *fmttime(unsigned long t, char *buf)
 {
@@ -270,8 +330,6 @@ static void render_badblock_progress(struct progress_item *item,
   progress_shownotice(render, bg, 7);
 }
 
-static double alpha = 0.1;
-
 static void update_progress(secaddr pos)
 {
   struct timeval now;
@@ -298,12 +356,6 @@ static void update_progress(secaddr pos)
 static void report_progress(secaddr pos)
   { update_progress(pos); progress_update(&progress); }
 
-static dvd_reader_t *dvd;
-static int dvdfd = -1, outfd = -1;
-static dvd_file_t *vob;
-static const char *mapfile; static FILE *mapfp;
-static const char *errfile; static FILE *errfp;
-
 struct badblock { secaddr start, end; };
 DEFVEC(badblock_v, struct badblock);
 static badblock_v badblocks = VEC_INIT;
@@ -335,7 +387,7 @@ static ssize_t read_sectors(secaddr pos, void *buf, secaddr want)
   if (badblocks.n) {
     best = 0; lo = 0; hi = badblocks.n;
 #ifdef DEBUG
-    progress_clear();
+    progress_clear(&progress);
     printf(";; searching badblocks for %"PRIuSEC" .. %"PRIuSEC"\n",
           pos, pos + want);
 #endif
@@ -347,7 +399,10 @@ static ssize_t read_sectors(secaddr pos, void *buf, secaddr want)
 #endif
       if (pos < bad->start) { D( printf("high\n"); ) best = bad; hi = mid; }
       else if (pos >= bad->end) { D( printf("low\n"); ) lo = mid + 1; }
-      else { D( printf("match!\n"); ) errno = EIO; return (-1); }
+      else {
+       D( printf("match!\n"); )
+       errno = EIO; sit(bad_block_delay); return (-1);
+      }
     }
 #ifdef DEBUG
     if (best)
@@ -357,7 +412,8 @@ static ssize_t read_sectors(secaddr pos, void *buf, secaddr want)
     if (best && pos + want > best->start)
       { want = best->start - pos; fakeerr = EIO; sit(bad_block_delay); }
   }
-  done = 0;
+
+  done = 0; errno = 0;
   while (want) {
     if (vob)
       { errno = 0; n = DVDReadBlocks(vob, pos - file->start, want, p); }
@@ -385,54 +441,6 @@ static ssize_t read_sectors(secaddr pos, void *buf, secaddr want)
   return (!done && errno ? -1 : done);
 }
 
-static void record_bad_sectors(secaddr bad_lo, secaddr bad_hi)
-{
-  char fn[MAXFNSZ];
-
-  if (!mapfile) return;
-
-  open_file_on_demand(mapfile, &mapfp, "bad-sector region map");
-  fprintf(mapfp, "%"PRIuSEC" %"PRIuSEC" # %"PRIuSEC" sectors",
-         bad_lo, bad_hi, bad_hi - bad_lo);
-
-  if (file && id_kind(file->id) != RAW) {
-    store_filename(fn, file->id);
-    fprintf(mapfp, "; `%s' %"PRIuSEC" .. %"PRIuSEC" of %"PRIuSEC"",
-           fn, bad_lo - file->start, bad_hi - file->start,
-           file->end - file->start);
-  }
-
-  fputc('\n', mapfp);
-  check_write(mapfp, "bad-sector region map");
-}
-
-static void recovered(secaddr bad_lo, secaddr bad_hi)
-{
-  char fn[MAXFNSZ];
-
-  progress_clear(&progress);
-
-  if (!file || id_kind(file->id) == RAW)
-    moan("skipping %"PRIuSEC" bad sectors (%"PRIuSEC" .. %"PRIuSEC")",
-        bad_hi - bad_lo, bad_lo, bad_hi);
-  else {
-    store_filename(fn, file->id);
-    moan("skipping %"PRIuSEC" bad sectors (%"PRIuSEC" .. %"PRIuSEC"; "
-        "`%s' %"PRIuSEC" .. %"PRIuSEC" of %"PRIuSEC")",
-        bad_hi - bad_lo, bad_lo, bad_hi,
-        fn, bad_lo - file->start, bad_hi - file->start,
-        file->end - file->start);
-  }
-
-  record_bad_sectors(bad_lo, bad_hi);
-
-  if (lseek(outfd, (off_t)(bad_hi - bad_lo)*SECTORSZ, SEEK_CUR) < 0)
-    bail_syserr(errno, "failed to seek past bad sectors");
-
-  progress_removeitem(&progress, &badblock_progress);
-  progress_update(&progress);
-}
-
 struct recoverybuf {
   unsigned char *buf;
   secaddr sz, pos, start, end;
@@ -442,8 +450,7 @@ struct recoverybuf {
 static void rearrange_sectors(struct recoverybuf *r,
                              secaddr dest, secaddr src, secaddr len)
 {
-  assert(dest + len <= r->sz);
-  assert(src + len <= r->sz);
+  assert(dest + len <= r->sz); assert(src + len <= r->sz);
   memmove(r->buf + dest*SECTORSZ, r->buf + src*SECTORSZ, len*SECTORSZ);
 }
 
@@ -455,7 +462,7 @@ static PRINTF_LIKE(2, 3)
   va_list ap;
 
   va_start(ap, what);
-  progress_clear();
+  progress_clear(&progress);
   printf(";; recovery buffer (");
   vprintf(what, ap);
   printf("): "
@@ -477,6 +484,7 @@ static ssize_t recovery_read_sectors(struct recoverybuf *r,
   ssize_t n;
 
   assert(off <= r->sz); assert(want <= r->sz - off);
+  assert(pos == r->pos + off);
   n = read_sectors(pos, r->buf + off*SECTORSZ, want);
   return (n);
 }
@@ -488,7 +496,7 @@ static ssize_t recovery_read_buffer(struct recoverybuf *r,
   ssize_t n;
 
 #ifdef DEBUG
-  progress_clear();
+  progress_clear(&progress);
   show_recovery_buffer_map(r, "begin(%"PRIuSEC", %"PRIuSEC")", pos, want);
 #endif
 
@@ -508,9 +516,9 @@ static ssize_t recovery_read_buffer(struct recoverybuf *r,
 #endif
     }
   } else if (pos > r->pos + r->end) {
-      r->pos = pos; r->start = r->end = 0;
+    r->pos = pos; r->start = r->end = 0;
 #ifdef DEBUG
-p      show_recovery_buffer_map(r, "cleared; beyond previous region");
+    show_recovery_buffer_map(r, "cleared; beyond previous region");
 #endif
   } else if (pos + want > r->pos + r->sz) {
     diff = (pos + want) - (r->pos + r->sz);
@@ -632,11 +640,50 @@ static secaddr clear_min = 1, clear_max = SECLIMIT;
 static double step_factor = 2.0;
 static secaddr step_min = 1, step_max = 0;
 
+static void recovered(secaddr bad_lo, secaddr bad_hi)
+{
+  char fn[MAXFNSZ];
+
+  progress_clear(&progress);
+
+  if (!file || id_kind(file->id) == RAW)
+    moan("skipping %"PRIuSEC" bad sectors (%"PRIuSEC" .. %"PRIuSEC")",
+        bad_hi - bad_lo, bad_lo, bad_hi);
+  else {
+    store_filename(fn, file->id);
+    moan("skipping %"PRIuSEC" bad sectors (%"PRIuSEC" .. %"PRIuSEC"; "
+        "`%s' %"PRIuSEC" .. %"PRIuSEC" of %"PRIuSEC")",
+        bad_hi - bad_lo, bad_lo, bad_hi,
+        fn, bad_lo - file->start, bad_hi - file->start,
+        file->end - file->start);
+  }
+
+  if (mapfile) {
+    open_file_on_demand(mapfile, &mapfp, "bad-sector region map");
+    fprintf(mapfp, "%"PRIuSEC" %"PRIuSEC" # %"PRIuSEC" sectors",
+           bad_lo, bad_hi, bad_hi - bad_lo);
+
+    if (file && id_kind(file->id) != RAW)
+      fprintf(mapfp, "; `%s' %"PRIuSEC" .. %"PRIuSEC" of %"PRIuSEC"",
+             fn, bad_lo - file->start, bad_hi - file->start,
+             file->end - file->start);
+
+    fputc('\n', mapfp);
+    check_write(mapfp, "bad-sector region map");
+  }
+
+  if (lseek(outfd, (off_t)(bad_hi - bad_lo)*SECTORSZ, SEEK_CUR) < 0)
+    bail_syserr(errno, "failed to seek past bad sectors");
+
+  progress_removeitem(&progress, &badblock_progress);
+  progress_update(&progress);
+}
+
 static secaddr run_length_wanted(secaddr pos, secaddr badlen, secaddr end)
 {
   secaddr want;
 
-  want = clear_factor*badlen;
+  want = ceil(clear_factor*badlen);
   if (want < clear_min) want = clear_min;
   if (want > end - pos) want = end - pos;
   if (clear_max && want > clear_max) want = clear_max;
@@ -657,13 +704,10 @@ static ssize_t find_good_sector(secaddr *pos_inout, secaddr end,
   badblock_progress.render = render_badblock_progress;
   progress_additem(&progress, &badblock_progress);
 
-  r.buf = buf; r.sz = sz; r.pos = r.start = r.end = 0;
-  r.good_lo = r.good_hi = 0;
-
   want = sz; if (want > end - pos) want = end - pos;
   for (retry = 0; retry < max_retries; retry++) {
     report_bad_blocks_progress(pos, errno);
-    n = recovery_read(&r, pos, want);
+    n = read_sectors(pos, buf, want);
 #ifdef DEBUG
     progress_clear(&progress);
     printf(";; [retry] try reading %"PRIuSEC" .. %"PRIuSEC" -> %zd\n",
@@ -678,9 +722,11 @@ static ssize_t find_good_sector(secaddr *pos_inout, secaddr end,
     }
   }
 
+  r.buf = buf; r.sz = sz; r.pos = r.start = r.end = 0;
+  r.good_lo = r.good_hi = 0;
+
   bad_lo = pos; bad_hi = pos + 1;
   for (;;) {
-    report_bad_blocks_progress(bad_hi, errno);
 #ifdef DEBUG
     progress_clear(&progress);
     printf(";; bounding bad-block region: "
@@ -693,11 +739,12 @@ static ssize_t find_good_sector(secaddr *pos_inout, secaddr end,
       recovered(bad_lo, end); *pos_inout = end;
       return (0);
     }
+    report_bad_blocks_progress(bad_hi, errno);
     step = (step_factor - 1)*(bad_hi - bad_lo);
     if (step < step_min) step = step_min;
     if (step_max && step > step_max) step = step_max;
-    if (step > end - bad_hi) step = end - bad_hi;
-    pos = bad_hi + step - 1;
+    step += bad_hi - bad_lo;
+    if (step > end - bad_lo) step = end - bad_lo;
     want = run_length_wanted(pos, step, end);
     n = recovery_read(&r, pos, want);
 #ifdef DEBUG
@@ -711,13 +758,13 @@ static ssize_t find_good_sector(secaddr *pos_inout, secaddr end,
 
   good = pos;
   while (good > bad_hi) {
-    report_bad_blocks_progress(bad_hi, errno);
 #ifdef DEBUG
     progress_clear(&progress);
     printf(";; limiting bad-block region: "
           "%"PRIuSEC" ..%"PRIuSEC".. %"PRIuSEC" ..%"PRIuSEC".. %"PRIuSEC"\n",
           bad_lo, bad_hi - bad_lo, bad_hi, good - bad_hi, good);
 #endif
+    report_bad_blocks_progress(bad_hi, errno);
     pos = bad_hi + (good - bad_hi)/2;
     step = pos - bad_lo;
     want = run_length_wanted(pos, step, end);
@@ -730,7 +777,7 @@ static ssize_t find_good_sector(secaddr *pos_inout, secaddr end,
     if (n == want) good = pos;
     else bad_hi = pos + n + 1;
   }
-  recovered(bad_lo, bad_hi); *pos_inout = good;
+  recovered(bad_lo, good); *pos_inout = good;
   if (good < r.pos + r.start || r.pos + r.end <= good)
     n = 0;
   else {
@@ -795,7 +842,6 @@ static void emit(secaddr start, secaddr end)
          bail("failed to open %s %u",
               id_part(file->id) ? "title" : "menu",
               id_title(file->id));
-       progress_update(&progress);
        break;
       default:
        abort();
@@ -807,6 +853,7 @@ static void emit(secaddr start, secaddr end)
     progress_additem(&progress, &file_progress);
   }
 
+  progress_update(&progress);
   pos = start;
   while (pos < end) {
     want = end - pos; if (want > BUFSECTORS) want = BUFSECTORS;
@@ -826,121 +873,11 @@ static void emit(secaddr start, secaddr end)
 #undef BUFSECTORS
 }
 
-struct buf {
-  char *p;
-  size_t n, sz;
-};
-#define BUF_INIT { 0, 0, 0 }
-#define BUF_REWIND(b) do { (b)->n = 0; } while (0)
-#define BUF_FREE(b) do {                                               \
-  buf *_b = (b);                                                       \
-  free(_b->p); _b->p = 0; _b->n = _b->sz = 0;                          \
-} while (0)
-#define BUF_PUTC(b, ch) do {                                           \
-  struct buf *_b = (b);                                                        \
-  if (_b->n >= _b->sz) {                                               \
-    _b->sz = _b->sz ? 2*_b->sz : 32;                                   \
-    _b->p = realloc(_b->p, _b->sz);                                    \
-    if (!_b->p) bail("out of memory allocating %zu bytes", _b->sz);    \
-  }                                                                    \
-  _b->p[_b->n] = (ch);                                                 \
-} while (0)
-
-static int read_line(FILE *fp, struct buf *b)
-{
-  int ch;
-
-  ch = getc(fp);
-  if (ch == EOF)
-    return (-1);
-  else if (ch != '\n') do {
-    BUF_PUTC(b, ch); b->n++;
-    ch = getc(fp);
-  } while (ch != EOF && ch != '\n');
-  BUF_PUTC(b, 0);
-  return (0);
-}
-
-static double parse_float(const char **p_inout, double min, double max,
-                         const char *what)
-{
-  const char *p;
-  char *q;
-  double x;
-  int err;
-
-  err = errno; errno = 0;
-  p = *p_inout;
-  x = strtod(p, &q);
-  if (errno || x < min || x > max) bail("bad %s `%s'", what, p);
-  *p_inout = q; errno = err;
-  return (x);
-}
-
-static long parse_int(const char **p_inout, long min, long max,
-                     const char *what)
-{
-  const char *p;
-  char *q;
-  long x;
-  int err;
-
-  err = errno; errno = 0;
-  p = *p_inout;
-  x = strtoul(p, &q, 0);
-  if (errno || x < min || x > max) bail("bad %s `%s'", what, p);
-  *p_inout = q; errno = err;
-  return (x);
-}
-
-#define PRF_HYPHEN 1u
-static int parse_range(const char *p, unsigned f,
-                      secaddr *start_out, secaddr *end_out)
-{
-  char *q;
-  int err, rc;
-  unsigned long start, end;
-
-  err = errno;
-
-  if (ISDIGIT(*p)) {
-    start = strtoul(p, &q, 0);
-    if (errno || start >= SECLIMIT) { rc = -1; goto end; }
-    *start_out = start; p = q;
-  } else if (!(f&PRF_HYPHEN))
-    { rc = -1; goto end; }
-  else
-    start = 0;
-
-  if (f&PRF_HYPHEN) {
-    if (*p != '-')  { rc = -1; goto end; }
-    p++;
-  } else {
-    if (!ISSPACE(*p)) { rc = -1; goto end; }
-    do p++; while (ISSPACE(*p));
-  }
-
-  if (ISDIGIT(*p)) {
-    end = strtoul(p, &q, 0);
-    if (errno || end > SECLIMIT || end < start) { rc = -1; goto end; }
-    *end_out = end; p = q;
-  } else if (!(f&PRF_HYPHEN))
-    { rc = -1; goto end; }
-
-  if (!(f&PRF_HYPHEN)) while (ISSPACE(*p)) p++;
-  if (*p && ((f&PRF_HYPHEN) || *p != '#')) { rc = -1; goto end; }
-
-  rc = 0;
-end:
-  errno = err;
-  return (rc);
-}
-
 int main(int argc, char *argv[])
 {
   unsigned f = 0;
   const char *p;
-  uint64_t volsz;
+  off_t volsz;
   secaddr pos;
   off_t off;
   secaddr start, end, last;
@@ -948,15 +885,14 @@ int main(int argc, char *argv[])
   const char *device, *outfile;
   struct badblock *bad;
   int opt, blksz;
-  unsigned n;
   size_t i;
   FILE *fp;
   struct buf buf = BUF_INIT;
   struct timeval tv0, tv1;
   double t, rate, tot;
   const char *rateunit, *totunit;
-  char timebuf[TIMESTRMAX];
-  struct stat st;
+  char timebuf[TIMESTRMAX], id_in[MAXIDSZ], id_out[MAXIDSZ];
+  dvd_reader_t *dvd_out;
 #ifdef DEBUG
   const struct file *file;
   char fn[MAXFNSZ];
@@ -966,11 +902,13 @@ int main(int argc, char *argv[])
 #define f_continue 2u
 #define f_fixup 4u
 #define f_stats 8u
+#define f_checkid 16u
+#define f_retry 32u
 #define f_write 256u
 
   set_prog(argv[0]);
   for (;;) {
-    opt = getopt(argc, argv, "hB:E:FR:X:b:cr:s"); if (opt < 0) break;
+    opt = getopt(argc, argv, "hB:E:FR:X:b:cir:s"); if (opt < 0) break;
     switch (opt) {
       case 'h': usage(stderr); exit(0);
       case 'B':
@@ -979,29 +917,37 @@ int main(int argc, char *argv[])
        (STRNCMP(p, ==, s "=", sizeof(s)) && (p += sizeof(s), 1))
        for (;;) {
          if (SKIP_PREFIX("cf"))
-           clear_factor = parse_float(&p, 0, DBL_MAX, "clear factor");
+           clear_factor = parse_float(&p, PNF_JUNK, 0, DBL_MAX,
+                                      "clear factor");
          else if (SKIP_PREFIX("cmin"))
-           clear_min = parse_int(&p, 1, SECLIMIT, "clear minimum");
+           clear_min = parse_int(&p, PNF_JUNK, 1, SECLIMIT,
+                                 "clear minimum");
          else if (SKIP_PREFIX("cmax"))
-           clear_max = parse_int(&p, 1, SECLIMIT, "clear maximum");
+           clear_max = parse_int(&p, PNF_JUNK, 1, SECLIMIT,
+                                 "clear maximum");
          else if (SKIP_PREFIX("sf"))
-           step_factor = parse_float(&p, 0, DBL_MAX, "step factor");
+           step_factor = parse_float(&p, PNF_JUNK, 0, DBL_MAX,
+                                     "step factor");
          else if (SKIP_PREFIX("smin"))
-           step_min = parse_int(&p, 1, SECLIMIT - 1, "step minimum");
+           step_min = parse_int(&p, PNF_JUNK, 1, SECLIMIT - 1,
+                                "step minimum");
          else if (SKIP_PREFIX("smax"))
-           step_max = parse_int(&p, 1, SECLIMIT - 1, "step maximum");
+           step_max = parse_int(&p, PNF_JUNK, 1, SECLIMIT - 1,
+                                "step maximum");
          else if (SKIP_PREFIX("retry"))
-           max_retries = parse_int(&p, 0, INT_MAX, "retries");
+           max_retries = parse_int(&p, PNF_JUNK, 0, INT_MAX, "retries");
          else if (SKIP_PREFIX("alpha"))
-           alpha = parse_float(&p, 0, 1, "average decay factor");
+           alpha = parse_float(&p, PNF_JUNK, 0, 1, "average decay factor");
          else if (SKIP_PREFIX("_badwait"))
-           bad_block_delay = parse_float(&p, 0, DBL_MAX, "bad-block delay");
+           bad_block_delay = parse_float(&p, PNF_JUNK, 0, DBL_MAX,
+                                         "bad-block delay");
          else if (SKIP_PREFIX("_blkwait"))
-           good_block_delay = parse_float(&p, 0, DBL_MAX, "good block delay");
+           good_block_delay = parse_float(&p, PNF_JUNK, 0, DBL_MAX,
+                                          "good block delay");
          else
            bail("unknown bad blocks parameter `%s'", p);
          if (!*p) break;
-         else if (*p != ',') bail("unexpected junk in parameters");
+         if (*p != ',') bail("unexpected junk in parameters");
          p++;
        }
 #undef SKIP_PREFIX
@@ -1014,8 +960,8 @@ int main(int argc, char *argv[])
          bail_syserr(errno, "failed to open ranges file `%s'", optarg);
        i = 0; last = -1;
        for (;;) {
-         BUF_REWIND(&buf); if (read_line(fp, &buf)) break;
-         p = buf.p; i++;
+         buf_rewind(&buf); if (read_line(fp, &buf)) break;
+         i++; p = buf.p;
          while (ISSPACE(*p)) p++;
          if (!*p || *p == '#') continue;
          if (parse_range(p, 0, &start, &end) ||
@@ -1033,6 +979,7 @@ int main(int argc, char *argv[])
        }
        if (ferror(fp))
          bail_syserr(errno, "failed to read ranges file `%s'", optarg);
+       f |= f_retry;
        break;
       case 'X':
        fp = fopen(optarg, "r");
@@ -1040,15 +987,17 @@ int main(int argc, char *argv[])
          bail_syserr(errno, "failed to open bad-blocks file `%s'", optarg);
        i = 0; last = -1;
        for (;;) {
-         BUF_REWIND(&buf); if (read_line(fp, &buf)) break;
+         buf_rewind(&buf); if (read_line(fp, &buf)) break;
          p = buf.p; i++;
          while (ISSPACE(*p)) p++;
          if (!*p || *p == '#') continue;
          if (parse_range(p, 0, &start, &end) ||
              (last <= SECLIMIT && start < last))
            bail("bad range `%s' at `%s' line %zu", buf.p, optarg, i);
-         if (start < end)
-           { VEC_PUSH(bad, &badblocks); bad->start = start; bad->end = end; }
+         if (start < end) {
+           VEC_PUSH(bad, &badblocks);
+           bad->start = start; bad->end = end;
+         }
        }
        if (ferror(fp))
          bail_syserr(errno, "failed to read bad-blocks file `%s'", optarg);
@@ -1058,8 +1007,9 @@ int main(int argc, char *argv[])
        mapfile = optarg;
        break;
       case 'c': f |= f_continue; break;
+      case 'i': f |= f_checkid; break;
       case 'r':
-       start = 0; end = -1;
+       start = 0; end = -1; f |= f_retry;
        if (parse_range(optarg, PRF_HYPHEN, &start, &end))
          bail("bad range `%s'", optarg);
        if (start < end) {
@@ -1074,8 +1024,6 @@ int main(int argc, char *argv[])
   if (argc - optind != 2) f |= f_bogus;
   if (f&f_bogus) { usage(stderr); exit(2); }
 
-  setlocale(LC_ALL, "");
-  progress_init(&progress);
   device = argv[optind]; outfile = argv[optind + 1];
 
   if (badblocks.n) {
@@ -1089,26 +1037,27 @@ int main(int argc, char *argv[])
 #endif
   }
 
-  open_dvd(device, &dvdfd, &dvd);
-  if (fstat(dvdfd, &st))
-    bail_syserr(errno, "failed to stat device `%s'", device);
-  if (S_ISREG(st.st_mode)) {
-    blksz = SECTORSZ;
-    volsz = st.st_size;
-  } else if (S_ISBLK(st.st_mode)) {
-    if (ioctl(dvdfd, BLKSSZGET, &blksz))
-      bail_syserr(errno, "failed to get block size for `%s'", device);
-    if (ioctl(dvdfd, BLKGETSIZE64, &volsz))
-      bail_syserr(errno, "failed to get volume size for `%s'", device);
-  } else
-    bail("can't use `%s' as source: expected file or block device", device);
+  setlocale(LC_ALL, "");
+  progress_init(&progress);
+  if (open_dvd(device, O_RDONLY, &dvdfd, &dvd)) exit(2);
 
+  blksz = SECTORSZ; volsz = device_size(dvdfd, device, &blksz);
   if (blksz != SECTORSZ)
     bail("device `%s' block size %d /= %d", device, blksz, SECTORSZ);
   if (volsz%SECTORSZ)
     bail("device `%s' volume size %"PRIu64" not a multiple of %d",
         device, volsz, SECTORSZ);
 
+  if (f&f_checkid) {
+    if (open_dvd(outfile, O_RDONLY, 0, &dvd_out)) exit(2);
+    if (dvd_id(id_in, dvd, DIF_MUSTIFOHASH, device) ||
+       dvd_id(id_out, dvd_out, DIF_MUSTIFOHASH, device))
+      exit(2);
+    if (STRCMP(id_in, !=, id_out))
+      bail("DVD id mismatch: input `%s' is `%s'; output `%s' is `%s'",
+          device, id_in, outfile, id_out);
+  }
+
   outfd = open(outfile, O_WRONLY | O_CREAT, 0666);
   if (outfd < 0)
     bail_syserr(errno, "failed to create output file `%s'", outfile);
@@ -1118,8 +1067,9 @@ int main(int argc, char *argv[])
     if (off < 0)
       bail_syserr(errno, "failed to seek to end of output file `%s'",
                  outfile);
-    put_event(EV_WRITE, 0, off/SECTORSZ);
-  } else if (!eventq.n && !(f&f_fixup))
+    put_event(EV_WRITE, 0, off/SECTORSZ); f |= f_retry;
+  }
+  if (!(f&(f_retry | f_fixup)))
     put_event(EV_WRITE, 0, 0);
 
   /* It's fast enough just to check everything. */
@@ -1134,28 +1084,27 @@ int main(int argc, char *argv[])
   for (i = 0, limit = 0; i < filetab.n; i++)
     if (filetab.v[i].end > limit) limit = filetab.v[i].end;
 
-  if (end > limit) end = limit;
-
 #ifdef DEBUG
   printf("\n;; files:\n");
   for (i = 0; i < filetab.n; i++) {
     file = &filetab.v[i];
     store_filename(fn, file->id);
-    printf(";;\t%8"PRIuSEC" %s\n", file->start, fn);
+    printf(";;\t%8"PRIuSEC" .. %-8"PRIuSEC" %s\n",
+          file->start, file->end, fn);
   }
 #endif
 
   qsort(eventq.v, eventq.n, sizeof(struct event), compare_event);
 
-  f &= ~f_write; start = 0; n = 0;
-  for (i = 0; i < eventq.n; i++) {
+  for (i = 0, f &= ~f_write, start = 0; i < eventq.n; i++) {
     ev = &eventq.v[i];
     switch (ev->ev) {
       case EV_WRITE:
        if (f&f_write)
-         bail("overlapping ranges: range from %"PRIuSEC" still open at %"PRIuSEC"",
+         bail("overlapping ranges: range from %"PRIuSEC" "
+              "still open at %"PRIuSEC"",
               start, ev->pos);
-       n++; f |= f_write; start = ev->pos;
+       f |= f_write; start = ev->pos;
        break;
       case EV_STOP:
        f &= ~f_write;
@@ -1163,25 +1112,31 @@ int main(int argc, char *argv[])
     }
   }
 
-  f &= ~f_write; start = 0;
-  for (i = 0; i < eventq.n; i++) {
+#ifdef DEBUG
+  dump_eventq("initial");
+#endif
+  for (i = 0, f &= ~f_write, start = last = 0; i < eventq.n; i++) {
     ev = &eventq.v[i];
-    switch (ev->ev) {
-      case EV_WRITE: start = ev->pos; f |= f_write; break;
-      case EV_STOP: nsectors += ev->pos - start; f &= ~f_write; break;
-    }
+    if (ev->ev == EV_WRITE) { start = ev->pos; f |= f_write; }
     if (ev->pos >= limit) break;
-    if (f&f_fixup) start = ev->pos;
+    if (ev->ev == EV_STOP) { nsectors += ev->pos - start; f &= ~f_write; }
+    if (f&f_fixup) last = ev->pos;
   }
   eventq.n = i;
+#ifdef DEBUG
+  dump_eventq("trimmed");
+#endif
   if (f&f_fixup) {
-    put_event(EV_WRITE, 0, start);
-    n++; f |= f_write;
+    put_event(EV_WRITE, 0, last);
+    f |= f_write;
   }
   if (f&f_write) {
     nsectors += limit - start;
     put_event(EV_STOP, 0, limit);
   }
+#ifdef DEBUG
+  dump_eventq("final");
+#endif
 
   copy_progress.render = render_copy_progress;
   progress_additem(&progress, &copy_progress);
@@ -1197,8 +1152,7 @@ int main(int argc, char *argv[])
 #ifdef DEBUG
   printf("\n;; event sweep:\n");
 #endif
-  f &= ~f_write;
-  for (pos = 0, i = 0; i < eventq.n; i++) {
+  for (pos = 0, i = 0, f &= ~f_write; i < eventq.n; i++) {
     ev = &eventq.v[i];
     if (ev->pos > pos) {
       if (f&f_write) emit(pos, ev->pos);
@@ -1224,11 +1178,11 @@ int main(int argc, char *argv[])
                      "failed to seek to resume position "
                      "(sector %"PRIuSEC") in output file `%s'",
                      ev->pos, outfile);
+       f |= f_write;
 #ifdef DEBUG
        progress_clear(&progress);
        printf(";; %8"PRIuSEC": begin write\n", pos);
 #endif
-       f |= f_write;
        break;
       case EV_STOP:
        f &= ~f_write;
@@ -1256,7 +1210,7 @@ int main(int argc, char *argv[])
 
   if (f&f_stats) {
     gettimeofday(&tv1, 0); t = tvdiff(&tv0, &tv1);
-    if (nsectors == limit - start) { ndone -= start; nsectors -= start; }
+    if (nsectors == limit) { ndone -= start; nsectors -= start; }
     tot = scale_bytes((double)nsectors*SECTORSZ, &totunit);
     rate = scale_bytes((double)nsectors*SECTORSZ/t, &rateunit);
     moan("all done: %.1f %sB in %s -- %.1f %sB/s",