sort-badblocks: Another handy testing utility.
[dvdrip] / dvd-sector-copy.c
index dd97d8b..86540e0 100644 (file)
@@ -104,6 +104,32 @@ static void carefully_write(int fd, const void *buf, size_t sz)
   }
 }
 
+static void open_file_on_demand(const char *file, FILE **fp_inout,
+                               const char *what)
+{
+  FILE *fp;
+
+  if (!*fp_inout) {
+    fp = fopen(file, "w");
+    if (!fp)
+      bail_syserr(errno, "failed to open %s file `%s'", what, file);
+    fprintf(fp, "## %s\n\n", what);
+    *fp_inout = fp;
+  }
+}
+
+static void check_write(FILE *fp, const char *what)
+{
+  fflush(fp);
+  if (ferror(fp)) bail_syserr(errno, "error writing %s file", what);
+}
+
+static void carefully_fclose(FILE *fp, const char *what)
+{
+  if (fp && (ferror(fp) || fclose(fp)))
+    bail_syserr(errno, "error writing %s file", what);
+}
+
 #define DEFVEC(vtype, etype)                                           \
        typedef struct { etype *v; size_t n, sz; } vtype
 #define VEC_INIT { 0, 0, 0 }
@@ -297,6 +323,10 @@ static void clear_progress_internal(void)
 }
 static void clear_progress(void)
   { clear_progress_internal(); fflush(stdout); }
+#ifdef DEBUG
+static void debug_clear_progress(void)
+  { if (progresslen) { putchar('\n'); progresslen = 0; } }
+#endif
 static void vappend_progress(const char *fmt, va_list ap)
   { progresslen += vprintf(fmt, ap); }
 __attribute__((format(printf, 1, 2)))
@@ -368,7 +398,7 @@ static void report_progress(secaddr pos)
     ("copied %.1f%% (%"PRIuSEC" of %"PRIuSEC"; %.1f %sB/s, ETA %s)",
      percent, pos, limit,  rate, unit, etastr);
   if (file && id_kind(file->id) == VOB) {
-    append_progress(" -- %s %d %3.1f%%",
+    append_progress(" -- %s %d %.1f%%",
                    id_part(file->id) ? "title" : "menu",
                    id_title(file->id),
                    (pos - file->start)*100.0/
@@ -383,6 +413,7 @@ 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);
@@ -409,30 +440,31 @@ static ssize_t read_sectors(secaddr pos, void *buf, secaddr want)
   struct badblock *bad, *best;
   unsigned char *p = buf;
 
-  best = 0; lo = 0; hi = badblocks.n;
+  if (badblocks.n) {
+    best = 0; lo = 0; hi = badblocks.n;
 #ifdef DEBUG
-  clear_progress();
-  printf(";; searching badblocks for %"PRIuSEC" .. %"PRIuSEC"\n",
-        pos, pos + want);
+    debug_clear_progress();
+    printf(";; searching badblocks for %"PRIuSEC" .. %"PRIuSEC"\n",
+          pos, pos + want);
 #endif
-  while (lo < hi) {
-    mid = lo + (hi - lo)/2; bad = &badblocks.v[mid];
+    while (lo < hi) {
+      mid = lo + (hi - lo)/2; bad = &badblocks.v[mid];
 #ifdef DEBUG
-    printf(";;   try %zu (%"PRIuSEC" .. %"PRIuSEC")... ",
-          mid, bad->start, bad->end);
+      printf(";;   try %zu (%"PRIuSEC" .. %"PRIuSEC")... ",
+            mid, bad->start, bad->end);
 #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); }
-  }
+      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); }
+    }
 #ifdef DEBUG
-  if (best)
-    printf(";;   next is %"PRIuSEC" .. %"PRIuSEC"\n",
-          best->start, best->end);
+    if (best)
+      printf(";;   next is %"PRIuSEC" .. %"PRIuSEC"\n",
+            best->start, best->end);
 #endif
-  if (best && pos + want > best->start)
-    { want = best->start - pos; fakeerr = EIO; }
-
+    if (best && pos + want > best->start)
+      { want = best->start - pos; fakeerr = EIO; }
+  }
   done = 0;
   while (want) {
     if (vob)
@@ -448,7 +480,13 @@ static ssize_t read_sectors(secaddr pos, void *buf, secaddr want)
     }
 
     if (n > 0) { done += n; pos += n; p += n*SECTORSZ; want -= n; }
-    else if (!n || errno != EINTR) break;
+    else if (!n) break;
+    else if (errno == EIO && errfile) {
+      open_file_on_demand(errfile, &errfp, "bad-sector error log");
+      fprintf(errfp, "%"PRIuSEC" %"PRIuSEC"\n", pos, pos + 1);
+      check_write(errfp, "bad-sector error log");
+      break;
+    } else if (errno != EINTR) break;
   }
   if (fakeerr && !errno) errno = fakeerr;
   return (!done && errno ? -1 : done);
@@ -472,17 +510,17 @@ static void recovered(secaddr bad_lo, secaddr bad_hi)
   moan("skipping %"PRIuSEC" bad sectors (%"PRIuSEC" .. %"PRIuSEC")",
        bad_hi - bad_lo, bad_lo, bad_hi);
   if (mapfile) {
-    if (!mapfp) {
-      mapfp = fopen(mapfile, "w");
-      if (!mapfp)
-       bail_syserr(errno, "failed to open bad-sector map file `%s'",
-                   optarg);
-      fprintf(mapfp, "## bad sector map\n\n");
-    }
-    fprintf(mapfp, "%"PRIuSEC" %"PRIuSEC"\n", bad_lo, bad_hi);
-    fflush(mapfp);
-    if (ferror(mapfp))
-      bail_syserr(errno, "error writing bad-sector map file");
+    open_file_on_demand(mapfile, &mapfp, "bad-sector region map");
+    fprintf(mapfp, "%"PRIuSEC" %"PRIuSEC"", bad_lo, bad_hi);
+    if (file)
+      fprintf(mapfp, " # %s #%d %"PRIuSEC"..%"PRIuSEC" of %"PRIuSEC" (%.1f%%)",
+             id_part(file->id) ? "title" : "menu",
+             id_title(file->id),
+             bad_lo - file->start, bad_hi - file->start,
+             file->end - file->start,
+             (bad_lo - file->start)*100.0/(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");
@@ -510,7 +548,7 @@ static void show_recovery_buffer_map(const struct recoverybuf *r,
   va_list ap;
 
   va_start(ap, what);
-  clear_progress();
+  debug_clear_progress();
   printf(";; recovery buffer (");
   vprintf(what, ap);
   printf("): "
@@ -543,7 +581,7 @@ static ssize_t recovery_read(struct recoverybuf *r,
   ssize_t n;
 
 #ifdef DEBUG
-  clear_progress();
+  debug_clear_progress();
   show_recovery_buffer_map(r, "begin(%"PRIuSEC", %"PRIuSEC")", pos, want);
 #endif
 
@@ -609,9 +647,9 @@ static ssize_t recovery_read(struct recoverybuf *r,
 #ifdef DEBUG
     printf(";; read high (%"PRIuSEC"@%"PRIuSEC", %"PRIuSEC")",
           r->pos + pp, pp, nn);
-fflush(stdout);
+    fflush(stdout);
 #endif
-    n = recovery_read_sectors(r, pos + pp, pp, nn);
+    n = recovery_read_sectors(r, r->pos + pp, pp, nn);
 #ifdef DEBUG
     printf(" -> %zd\n", n);
 #endif
@@ -625,6 +663,7 @@ fflush(stdout);
 
   n = r->pos + r->end - pos;
   if (!n && want) n = -1;
+  else if (n > want) n = want;
 
 end:
 #ifdef DEBUG
@@ -660,7 +699,7 @@ static ssize_t find_good_sector(secaddr *pos_inout, secaddr end,
   for (i = 0; i < 4; i++) {
     n = recovery_read(&r, pos, want);
 #ifdef DEBUG
-    clear_progress();
+    debug_clear_progress();
     printf(";; [retry] try reading %"PRIuSEC" .. %"PRIuSEC" -> %zd\n",
           pos, pos + want, n);
 #endif
@@ -674,6 +713,12 @@ static ssize_t find_good_sector(secaddr *pos_inout, secaddr end,
   bad_lo = pos; bad_hi = pos + 1;
   for (;;) {
     report_bad_blocks_progress(bad_lo, bad_hi, errno);
+#ifdef DEBUG
+    debug_clear_progress();
+    printf(";; bounding bad-block region: "
+          "%"PRIuSEC" ..%"PRIuSEC".. %"PRIuSEC"\n",
+          bad_lo, bad_hi - bad_lo, bad_hi);
+#endif
     if (bad_hi >= end) {
       clear_progress();
       moan("giving up on this extent");
@@ -684,7 +729,6 @@ static ssize_t find_good_sector(secaddr *pos_inout, secaddr end,
     want = run_length_wanted(pos, step, sz, end);
     n = recovery_read(&r, pos, want);
 #ifdef DEBUG
-    clear_progress();
     printf(";; [bound] try reading %"PRIuSEC" .. %"PRIuSEC" -> %zd\n",
           pos, pos + want, n);
 #endif
@@ -696,12 +740,17 @@ static ssize_t find_good_sector(secaddr *pos_inout, secaddr end,
   good = pos;
   while (good > bad_hi) {
     report_bad_blocks_progress(bad_lo, bad_hi, errno);
+#ifdef DEBUG
+    debug_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
     pos = bad_hi + (good - bad_hi)/2;
     step = pos - bad_lo;
     want = run_length_wanted(pos, step, sz, end);
     n = recovery_read(&r, pos, want);
 #ifdef DEBUG
-    clear_progress();
     printf(";; [limit] try reading %"PRIuSEC" .. %"PRIuSEC" -> %zd\n",
           pos, pos + want, n);
 #endif
@@ -710,10 +759,16 @@ static ssize_t find_good_sector(secaddr *pos_inout, secaddr end,
     else bad_hi = pos + n + 1;
   }
   recovered(bad_lo, bad_hi); *pos_inout = good;
-  if (r.pos + r.start <= good && good <= r.pos + r.end) {
-    rearrange_sectors(&r, 0, good - r.pos, r.pos + r.end - good);
-  } else
+  if (good < r.pos + r.start || r.pos + r.end <= good)
     n = 0;
+  else {
+    n = r.pos + r.end - good;
+    rearrange_sectors(&r, 0, good - r.pos, n);
+  }
+#ifdef DEBUG
+  show_recovery_buffer_map(&r, "returning %zd good sectors at %"PRIuSEC"",
+                          n, good);
+#endif
   return (n);
 }
 
@@ -879,7 +934,7 @@ static int parse_range(const char *p, unsigned f,
     { rc = -1; goto end; }
 
   if (!(f&PRF_HYPHEN)) while (ISSPACE(*p)) p++;
-  if (*p) { rc = -1; goto end; }
+  if (*p && ((f&PRF_HYPHEN) || *p != '=')) { rc = -1; goto end; }
 
   rc = 0;
 end:
@@ -916,9 +971,10 @@ int main(int argc, char *argv[])
 
   p = strrchr(argv[0], '/'); prog = p ? p + 1 : argv[0];
   for (;;) {
-    opt = getopt(argc, argv, "hFR:X:b:cr:"); if (opt < 0) break;
+    opt = getopt(argc, argv, "hE:FR:X:b:cr:"); if (opt < 0) break;
     switch (opt) {
       case 'h': usage(stderr); exit(0);
+      case 'E': errfile = optarg; break;
       case 'F': f |= f_fixup; break;
       case 'R':
        fp = fopen(optarg, "r");
@@ -1114,7 +1170,7 @@ int main(int argc, char *argv[])
       if (f&f_write) emit(pos, ev->pos);
       pos = ev->pos;
 #ifdef DEBUG
-      clear_progress();
+      debug_clear_progress();
       printf(";;\n");
 #endif
     }
@@ -1123,7 +1179,7 @@ int main(int argc, char *argv[])
        set_live(ev->file);
 #ifdef DEBUG
        store_filename(fn, filetab.v[ev->file].id);
-       clear_progress();
+       debug_clear_progress();
        printf(";; %8"PRIuSEC": begin `%s'\n", pos, fn);
 #endif
        break;
@@ -1135,7 +1191,7 @@ int main(int argc, char *argv[])
                      "(sector %"PRIuSEC") in output file `%s'",
                      ev->pos, outfile);
 #ifdef DEBUG
-       clear_progress();
+       debug_clear_progress();
        printf(";; %8"PRIuSEC": begin write\n", pos);
 #endif
        f |= f_write;
@@ -1143,7 +1199,7 @@ int main(int argc, char *argv[])
       case EV_STOP:
        f &= ~f_write;
 #ifdef DEBUG
-       clear_progress();
+       debug_clear_progress();
        printf(";; %8"PRIuSEC": end write\n", pos);
 #endif
        break;
@@ -1151,7 +1207,7 @@ int main(int argc, char *argv[])
        clear_live(ev->file);
 #ifdef DEBUG
        store_filename(fn, filetab.v[ev->file].id);
-       clear_progress();
+       debug_clear_progress();
        printf(";; %8"PRIuSEC": end `%s'\n", pos, fn);
 #endif
        break;
@@ -1167,10 +1223,8 @@ int main(int argc, char *argv[])
   if (dvd) DVDClose(dvd);
   if (dvdfd >= 0) close(dvdfd);
   if (outfd >= 0) close(outfd);
-  if (mapfp) {
-    if (ferror(mapfp) || fclose(mapfp))
-      bail_syserr(errno, "error writing bad-sector map file");
-  }
+  carefully_fclose(mapfp, "bad-sector region map");
+  carefully_fclose(errfp, "bad-sector error log");
 
 #undef f_bogus
 #undef f_continue