X-Git-Url: https://git.distorted.org.uk/~mdw/dvdrip/blobdiff_plain/af3973a19d5a28956010b8aba83daedf8c3ef71b..HEAD:/dvd-sector-copy.c diff --git a/dvd-sector-copy.c b/dvd-sector-copy.c index fd15138..6f14bb5 100644 --- a/dvd-sector-copy.c +++ b/dvd-sector-copy.c @@ -1,133 +1,18 @@ -#define _GNU_SOURCE -#define _FILE_OFFSET_BITS 64 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include -#include - -#define CTYPE_HACK(fn, ch) fn((unsigned char)(ch)) -#define ISDIGIT(ch) CTYPE_HACK(isdigit, ch) -#define ISSPACE(ch) CTYPE_HACK(isspace, ch) - -#ifdef DEBUG -# define D(x) x -#else -# define D(x) -#endif - -#define N(v) (sizeof(v)/sizeof((v)[0])) - -#define SECTORSZ 2048 -#define SECTORS(n) (((n) + (SECTORSZ - 1))/SECTORSZ) - -static const char *prog = ""; -static int status = 0; +#include "lib.h" static void usage(FILE *fp) { fprintf(fp, - "usage: %s [-c] [-R MAP] [-b OUTMAP] [-r [START]-[END]]\n" - "\tDEVICE OUTFILE\n", + "usage: %s [-ci] [-B PARAM=VALUE,...] [-R MAP]\n" + "\t[-b OUTMAP] [-r [START]-[END]] DEVICE OUTFILE\n", prog); } -static void vmoan(const char *fmt, va_list ap) - { fprintf(stderr, "%s: ", prog); vfprintf(stderr, fmt, ap); } - -__attribute__((format(printf, 1, 2))) -static void moan(const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); vmoan(fmt, ap); va_end(ap); - fputc('\n', stderr); -} - -__attribute__((noreturn, format(printf, 1, 2))) -static void bail(const char *fmt, ...) +static double tvdiff(const struct timeval *tv_lo, + const struct timeval *tv_hi) { - va_list ap; - - va_start(ap, fmt); vmoan(fmt, ap); va_end(ap); - fputc('\n', stderr); - exit(2); -} - -__attribute__((noreturn, format(printf, 2, 3))) -static void bail_syserr(int err, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); vmoan(fmt, ap); va_end(ap); - if (err) fprintf(stderr, ": %s", strerror(errno)); - fputc('\n', stderr); - exit(2); -} - -static void carefully_write(int fd, const void *buf, size_t sz) -{ - const unsigned char *p = buf; - ssize_t n; - - if (fd < 0) return; - while (sz) { - n = write(fd, p, sz); - if (n < 0) { - if (errno == EINTR) continue; - bail_syserr(errno, "failed to write to output file"); - } - if (!n) bail("unexpected short write to output file"); - p += n; sz -= n; - } -} - -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); + return ((tv_hi->tv_sec - tv_lo->tv_sec) + + (tv_hi->tv_usec - tv_lo->tv_usec)/1.0e6); } #define DEFVEC(vtype, etype) \ @@ -147,45 +32,6 @@ static void carefully_fclose(FILE *fp, const char *what) (p) = &(vv)->v[(vv)->n++]; \ } while (0) -enum { RAW, IFO, VOB, BUP }; -typedef uint_least32_t ident; - -static inline ident mkident(unsigned kind, unsigned title, unsigned part) - { return (((ident)kind << 0) | ((ident)title << 8) | ((ident)part << 16)); } -static inline unsigned id_kind(ident id) { return ((id >> 0)&0x0ff); } -static inline unsigned id_title(ident id) { return ((id >> 8)&0x0ff); } -static inline unsigned id_part(ident id) { return ((id >> 16)&0x0ff); } - -#define MAXFNSZ (1 + 8 + 1 + 12 + 1) - -static void store_filename(char *buf, ident id) -{ - switch (id_kind(id)) { - case RAW: - sprintf(buf, "#"); - break; - case IFO: - if (!id_title(id)) sprintf(buf, "/VIDEO_TS/VIDEO_TS.IFO"); - else sprintf(buf, "/VIDEO_TS/VTS_%02u_0.IFO", id_title(id)); - break; - case BUP: - if (!id_title(id)) sprintf(buf, "/VIDEO_TS/VIDEO_TS.BUP"); - else sprintf(buf, "/VIDEO_TS/VTS_%02u_0.BUP", id_title(id)); - break; - case VOB: - if (!id_title(id)) sprintf(buf, "/VIDEO_TS/VIDEO_TS.VOB"); - else - sprintf(buf, "/VIDEO_TS/VTS_%02u_%u.VOB", id_title(id), id_part(id)); - break; - default: - abort(); - } -} - -typedef uint_least32_t secaddr; -#define PRIuSEC PRIuLEAST32 -#define SECLIMIT 0x00400000 - #define MAXFILES (1 + 2*99 + 1) struct file { ident id; @@ -314,62 +160,127 @@ static void put_title(dvd_reader_t *dvd, unsigned title) start[0], start[npart - 1] + SECTORS(len[npart - 1])); } -static int progresslen = 0; +static secaddr last_pos, limit, nsectors, ndone; +static struct timeval last_time; +static double wsum, wcount; +static struct file *file; +static secaddr bad_start; +static unsigned retry, max_retries = 4; +static int bad_err; + +static const char throbber[] = "|<-<|>->"; +static unsigned throbix = 0; -static void clear_progress_internal(void) +static double scale_bytes(double n, const char **unit_out) { - while (progresslen) { fputs("\b \b", stdout); progresslen--; } - putchar('\r'); + const char *unit = ""; + + if (n > 1600) { n /= 1024; unit = "k"; } + 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 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))) -static void append_progress(const char *fmt, ...) + +static struct progress_item + copy_progress, disc_progress, + file_progress, badblock_progress; + +#define TIMESTRMAX 16 +static char *fmttime(unsigned long t, char *buf) { - va_list ap; + if (t < 60) sprintf(buf, "%ld s", t); + else if (t < 3600) sprintf(buf, "%ld:%02ld", t/60, t%60); + else sprintf(buf, "%ld:%02ld:%02ld", t/3600, (t/60)%60, t%60); + return (buf); +} - va_start(ap, fmt); - vappend_progress(fmt, ap); - va_end(ap); +static void render_perfstats(struct progress_render_state *render) +{ + int eta; + char timebuf[TIMESTRMAX]; + double rate; + const char *unit; + + if (!wsum || !wcount) { rate = 0; eta = -1; } + else { rate = wsum/wcount; eta = (int)((nsectors - ndone)/rate + 0.5); } + + rate = scale_bytes(rate*SECTORSZ, &unit); + progress_putright(render, "ETA %s ", rate ? fmttime(eta, timebuf) : "???"); + progress_putright(render, "%.1f %sB/s, ", rate, unit); } -__attribute__((format(printf, 1, 2))) -static void print_progress(const char *fmt, ...) + +static void render_copy_progress(struct progress_item *item, + struct progress_render_state *render) { - va_list ap; + double frac = (double)ndone/nsectors; - va_start(ap, fmt); - clear_progress_internal(); - vappend_progress(fmt, ap); - va_end(ap); + progress_putleft(render, " %c copied %.1f%%", + throbber[throbix], 100.0*frac); + render_perfstats(render); + progress_putleft(render, " (%"PRIuSEC" of %"PRIuSEC")", ndone, nsectors); + + progress_showbar(render, frac); } -unsigned flags; -# define F_ALLPROGRESS 1u -static secaddr last_pos, limit, nsectors, ndone; -static struct timeval last_time; -static double wsum, wcount; -static struct file *file; +static void render_disc_progress(struct progress_item *item, + struct progress_render_state *render) +{ + double frac = (double)last_pos/limit; -static void report_progress(secaddr pos) + progress_putleft(render, " disc %.1f%% (%"PRIuSEC" of %"PRIuSEC")", + 100.0*frac, last_pos, limit); + progress_showbar(render, frac); +} + +static void render_file_progress(struct progress_item *item, + struct progress_render_state *render) { - char etastr[32]; - struct timeval now; - int eta; - double percent, t, f, g, rate; - char *unit; + secaddr off = last_pos - file->start, len = file->end - file->start; + char fn[MAXFNSZ]; + double frac; + + store_filename(fn, file->id); + frac = (double)off/len; + progress_putleft(render, " `%s' %.1f%% (%"PRIuSEC" of %"PRIuSEC")", + fn, 100.0*frac, off, len); + progress_showbar(render, frac); +} + +static void render_badblock_progress(struct progress_item *item, + struct progress_render_state *render) +{ + secaddr n = last_pos - bad_start; + int bg; + + if (!n) { + progress_putleft(render, " Retrying bad sector %"PRIuSEC"", bad_start); + progress_putright(render, "attempt %u/%u ", retry + 1, max_retries); + bg = 4; + } else { + progress_putleft(render, " Found %"PRIuSEC" bad %s", + n, n == 1 ? "sector" : "sectors"); + progress_putright(render, "%"PRIuSEC" .. %"PRIuSEC" ", + bad_start, last_pos); + bg = 1; + } + if (bad_err && bad_err != EIO) + progress_putleft(render, " (%s)", strerror(bad_err)); + progress_shownotice(render, bg, 7); +} + +static double alpha = 0.1; -#define ALPHA 0.02 -#define BETA (1 - ALPHA) +static void update_progress(secaddr pos) +{ + struct timeval now; + double t, f, g; gettimeofday(&now, 0); - t = (now.tv_sec - last_time.tv_sec) + - (now.tv_usec - last_time.tv_usec)/1000000.0; + t = tvdiff(&last_time, &now); + +#define BETA (1 - alpha) if (t) { g = wcount ? pow(BETA, t) : 0.0; f = (1 - g)/(1 - BETA); @@ -379,36 +290,14 @@ static void report_progress(secaddr pos) last_time = now; last_pos = pos; } - if (!wsum || !wcount) - { rate = 0; strcpy(etastr, "???"); } - else { - rate = wsum/wcount; - eta = (int)((nsectors - ndone)/rate); - sprintf(etastr, "%d:%02d:%02d", eta/3600, (eta/60)%60, eta%60); - } - - rate *= SECTORSZ; unit = ""; - if (rate > 128) { rate /= 1024; unit = "k"; } - if (rate > 128) { rate /= 1024; unit = "M"; } - if (rate > 128) { rate /= 1024; unit = "G"; } - - if (flags&F_ALLPROGRESS) percent = pos*100.0/limit; - else percent = ndone*100.0/nsectors; - print_progress - ("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 %.1f%%", - id_part(file->id) ? "title" : "menu", - id_title(file->id), - (pos - file->start)*100.0/ - (file->end - file->start)); - } - -#undef ALPHA #undef BETA + + throbix++; if (!throbber[throbix]) throbix = 0; } +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; @@ -432,6 +321,9 @@ static int compare_badblock(const void *a, const void *b) return (0); } +static double bad_block_delay = 0.0; +static double good_block_delay = 0.0; + static ssize_t read_sectors(secaddr pos, void *buf, secaddr want) { ssize_t n, done; @@ -443,7 +335,7 @@ static ssize_t read_sectors(secaddr pos, void *buf, secaddr want) if (badblocks.n) { best = 0; lo = 0; hi = badblocks.n; #ifdef DEBUG - debug_clear_progress(); + progress_clear(&progress); printf(";; searching badblocks for %"PRIuSEC" .. %"PRIuSEC"\n", pos, pos + want); #endif @@ -463,7 +355,7 @@ static ssize_t read_sectors(secaddr pos, void *buf, secaddr want) best->start, best->end); #endif if (best && pos + want > best->start) - { want = best->start - pos; fakeerr = EIO; } + { want = best->start - pos; fakeerr = EIO; sit(bad_block_delay); } } done = 0; while (want) { @@ -489,47 +381,62 @@ static ssize_t read_sectors(secaddr pos, void *buf, secaddr want) } else if (errno != EINTR) break; } if (fakeerr && !errno) errno = fakeerr; + else if (done > 0 && good_block_delay) sit(done*good_block_delay); return (!done && errno ? -1 : done); } -static void report_bad_blocks_progress(secaddr lo, secaddr hi, int err) +static void record_bad_sectors(secaddr bad_lo, secaddr bad_hi) { - report_progress(hi); + char fn[MAXFNSZ]; - if (lo == hi) append_progress(": retrying bad sector"); - else - append_progress(": %"PRIuSEC" bad %s", - hi - lo, hi == lo + 1 ? "sector" : "sectors"); - if (err && err != EIO) append_progress(" (%s)", strerror(err)); - fflush(stdout); + 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) { - clear_progress(); - moan("skipping %"PRIuSEC" bad sectors (%"PRIuSEC" .. %"PRIuSEC")", - bad_hi - bad_lo, bad_lo, bad_hi); - if (mapfile) { - 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"); + 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"); - status = 1; + + progress_removeitem(&progress, &badblock_progress); + progress_update(&progress); } struct recoverybuf { unsigned char *buf; secaddr sz, pos, start, end; + secaddr good_lo, good_hi; }; static void rearrange_sectors(struct recoverybuf *r, @@ -541,14 +448,14 @@ static void rearrange_sectors(struct recoverybuf *r, } #ifdef DEBUG -__attribute__((format(printf, 2, 3))) -static void show_recovery_buffer_map(const struct recoverybuf *r, - const char *what, ...) +static PRINTF_LIKE(2, 3) + void show_recovery_buffer_map(const struct recoverybuf *r, + const char *what, ...) { va_list ap; va_start(ap, what); - debug_clear_progress(); + progress_clear(&progress); printf(";; recovery buffer ("); vprintf(what, ap); printf("): " @@ -574,14 +481,14 @@ static ssize_t recovery_read_sectors(struct recoverybuf *r, return (n); } -static ssize_t recovery_read(struct recoverybuf *r, - secaddr pos, secaddr want) +static ssize_t recovery_read_buffer(struct recoverybuf *r, + secaddr pos, secaddr want) { secaddr diff, pp, nn; ssize_t n; #ifdef DEBUG - debug_clear_progress(); + progress_clear(&progress); show_recovery_buffer_map(r, "begin(%"PRIuSEC", %"PRIuSEC")", pos, want); #endif @@ -663,6 +570,7 @@ static ssize_t recovery_read(struct recoverybuf *r, n = r->pos + r->end - pos; if (!n && want) n = -1; + else if (n > want) n = want; end: #ifdef DEBUG @@ -671,58 +579,128 @@ end: return (n); } -static secaddr run_length_wanted(secaddr pos, secaddr badlen, - secaddr sz, secaddr end) +static ssize_t recovery_read_multiple(struct recoverybuf *r, + secaddr pos, secaddr want) +{ + ssize_t n; + secaddr skip, want0 = want; + + while (want > r->sz) { + skip = want - r->sz; + n = recovery_read_buffer(r, pos + skip, r->sz); + if (n < r->sz) return (skip + (n >= 0 ? n : 0)); + want -= r->sz; + } + n = recovery_read_buffer(r, pos, want); + if (n < 0 || n < want) return (n); + return (want0); +} + +static ssize_t recovery_read(struct recoverybuf *r, + secaddr pos, secaddr want) +{ + secaddr lo = pos, hi = pos + want, span; + ssize_t n; + + if (hi < r->good_lo || lo > r->good_hi) { + n = recovery_read_multiple(r, lo, hi - lo); + if (n > 0) { r->good_lo = lo; r->good_hi = lo + n; } + return (n); + } + + if (hi > r->good_hi) { + span = hi - r->good_hi; + n = recovery_read_multiple(r, r->good_hi, span); + if (n > 0) r->good_hi += n; + if (n < 0 || n < span) return (r->good_hi - lo); + } + + if (lo < r->good_lo) { + span = r->good_lo - lo; + n = recovery_read_multiple(r, lo, span); + if (n == span) r->good_lo = lo; + else return (n); + } + + n = r->good_hi - pos; if (n > want) n = want; + if (!n) { errno = EIO; n = -1; } + return (n); +} + +static double clear_factor = 1.5; +static secaddr clear_min = 1, clear_max = SECLIMIT; +static double step_factor = 2.0; +static secaddr step_min = 1, step_max = 0; + +static secaddr run_length_wanted(secaddr pos, secaddr badlen, secaddr end) { secaddr want; - want = 3*badlen/2; - if (!want) want = 1; + want = clear_factor*badlen; + if (want < clear_min) want = clear_min; if (want > end - pos) want = end - pos; - if (want > sz) want = sz; + if (clear_max && want > clear_max) want = clear_max; return (want); } +static void report_bad_blocks_progress(secaddr bad_hi, int err) + { bad_err = err; report_progress(bad_hi); } + static ssize_t find_good_sector(secaddr *pos_inout, secaddr end, unsigned char *buf, secaddr sz) { - int i; secaddr pos = *pos_inout, bad_lo, bad_hi, good, step, want; struct recoverybuf r; ssize_t n; + bad_start = pos; bad_err = errno; + badblock_progress.render = render_badblock_progress; + progress_additem(&progress, &badblock_progress); + r.buf = buf; r.sz = sz; r.pos = r.start = r.end = 0; - report_bad_blocks_progress(pos, pos, errno); + r.good_lo = r.good_hi = 0; want = sz; if (want > end - pos) want = end - pos; - for (i = 0; i < 4; i++) { + for (retry = 0; retry < max_retries; retry++) { + report_bad_blocks_progress(pos, errno); n = recovery_read(&r, pos, want); #ifdef DEBUG - debug_clear_progress(); + progress_clear(&progress); printf(";; [retry] try reading %"PRIuSEC" .. %"PRIuSEC" -> %zd\n", pos, pos + want, n); #endif if (n > 0) { - clear_progress(); + progress_clear(&progress); moan("sector %"PRIuSEC" read ok after retry", pos); + progress_removeitem(&progress, &badblock_progress); + progress_update(&progress); return (n); } } bad_lo = pos; bad_hi = pos + 1; for (;;) { - report_bad_blocks_progress(bad_lo, bad_hi, errno); + report_bad_blocks_progress(bad_hi, errno); +#ifdef DEBUG + progress_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(); + progress_clear(&progress); moan("giving up on this extent"); - recovered(bad_lo, end); *pos_inout = end; return (0); + recovered(bad_lo, end); *pos_inout = end; + return (0); } - step = 2*(bad_hi - bad_lo); if (step > end - bad_lo) step = end - bad_lo; - pos = bad_lo + step - 1; - want = run_length_wanted(pos, step, sz, end); + 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; + want = run_length_wanted(pos, step, end); n = recovery_read(&r, pos, want); #ifdef DEBUG - debug_clear_progress(); printf(";; [bound] try reading %"PRIuSEC" .. %"PRIuSEC" -> %zd\n", pos, pos + want, n); #endif @@ -733,13 +711,18 @@ 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); + 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 pos = bad_hi + (good - bad_hi)/2; step = pos - bad_lo; - want = run_length_wanted(pos, step, sz, end); + want = run_length_wanted(pos, step, end); n = recovery_read(&r, pos, want); #ifdef DEBUG - debug_clear_progress(); printf(";; [limit] try reading %"PRIuSEC" .. %"PRIuSEC" -> %zd\n", pos, pos + want, n); #endif @@ -747,16 +730,16 @@ 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; - if (good < r.pos + r.start || r.pos + r.end <= good) + recovered(bad_lo, bad_hi); *pos_inout = bad_hi; + if (bad_hi < r.pos + r.start || r.pos + r.end <= bad_hi) n = 0; else { - n = r.pos + r.end - good; - rearrange_sectors(&r, 0, good - r.pos, n); + n = r.pos + r.end - bad_hi; + rearrange_sectors(&r, 0, bad_hi - r.pos, n); } #ifdef DEBUG show_recovery_buffer_map(&r, "returning %zd good sectors at %"PRIuSEC"", - n, good); + n, bad_hi); #endif return (n); } @@ -803,7 +786,7 @@ static void emit(secaddr start, secaddr end) vob = 0; break; case VOB: - if (first_time) { clear_progress(); first_time = 0; } + if (first_time) { progress_clear(&progress); first_time = 0; } vob = DVDOpenFile(dvd, id_title(file->id), id_part(file->id) ? DVD_READ_TITLE_VOBS @@ -812,12 +795,18 @@ 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(); } } + if (file && id_kind(file->id) != RAW) { + file_progress.render = render_file_progress; + progress_additem(&progress, &file_progress); + } + pos = start; while (pos < end) { want = end - pos; if (want > BUFSECTORS) want = BUFSECTORS; @@ -825,33 +814,17 @@ static void emit(secaddr start, secaddr end) if (n <= 0) n = find_good_sector(&pos, end, buf, BUFSECTORS); if (n > 0) { carefully_write(outfd, buf, n*SECTORSZ); pos += n; } - report_progress(pos); fflush(stdout); + report_progress(pos); } if (vob) { DVDCloseFile(vob); vob = 0; } -#undef BUFSECTORS -} + if (file && id_kind(file->id) != RAW) + progress_removeitem(&progress, &file_progress); + progress_update(&progress); -#ifdef notdef -static void logfn(void *p, dvd_logger_level_t lev, - const char *fmt, va_list ap) -{ - switch (lev) { - case DVD_LOGGER_LEVEL_ERROR: - fprintf("%s (libdvdread error): ", prog); - break; - case DVD_LOGGER_LEVEL_WARN: - fprintf("%s (libdvdread warning): ", prog); - break; - default: - return; - } - vfprintf(stderr, fmt, ap); - fputc('\n', stderr); +#undef BUFSECTORS } -static const dvd_logger_cb logger = { logfn }; -#endif struct buf { char *p; @@ -923,7 +896,7 @@ static int parse_range(const char *p, unsigned f, { rc = -1; goto end; } if (!(f&PRF_HYPHEN)) while (ISSPACE(*p)) p++; - if (*p && ((f&PRF_HYPHEN) || *p != '=')) { rc = -1; goto end; } + if (*p && ((f&PRF_HYPHEN) || *p != '#')) { rc = -1; goto end; } rc = 0; end: @@ -931,11 +904,44 @@ end: return (rc); } +#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 + int main(int argc, char *argv[]) { unsigned f = 0; - char *p; - uint64_t volsz; + const char *p; + off_t volsz; secaddr pos; off_t off; secaddr start, end, last; @@ -943,11 +949,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 stat st; + struct timeval tv0, tv1; + double t, rate, tot; + const char *rateunit, *totunit; + char timebuf[TIMESTRMAX], id_in[MAXIDSZ], id_out[MAXIDSZ]; + dvd_reader_t *dvd_out; #ifdef DEBUG const struct file *file; char fn[MAXFNSZ]; @@ -956,13 +965,56 @@ int main(int argc, char *argv[]) #define f_bogus 1u #define f_continue 2u #define f_fixup 4u +#define f_stats 8u +#define f_checkid 16u #define f_write 256u - p = strrchr(argv[0], '/'); prog = p ? p + 1 : argv[0]; + set_prog(argv[0]); for (;;) { - opt = getopt(argc, argv, "hE:FR:X:b:cr:"); 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': + p = optarg; +#define SKIP_PREFIX(s) \ + (STRNCMP(p, ==, s "=", sizeof(s)) && (p += sizeof(s), 1)) + for (;;) { + if (SKIP_PREFIX("cf")) + clear_factor = parse_float(&p, PNF_JUNK, 0, DBL_MAX, + "clear factor"); + else if (SKIP_PREFIX("cmin")) + clear_min = parse_int(&p, PNF_JUNK, 1, SECLIMIT, + "clear minimum"); + else if (SKIP_PREFIX("cmax")) + clear_max = parse_int(&p, PNF_JUNK, 1, SECLIMIT, + "clear maximum"); + else if (SKIP_PREFIX("sf")) + step_factor = parse_float(&p, PNF_JUNK, 0, DBL_MAX, + "step factor"); + else if (SKIP_PREFIX("smin")) + step_min = parse_int(&p, PNF_JUNK, 1, SECLIMIT - 1, + "step minimum"); + else if (SKIP_PREFIX("smax")) + step_max = parse_int(&p, PNF_JUNK, 1, SECLIMIT - 1, + "step maximum"); + else if (SKIP_PREFIX("retry")) + max_retries = parse_int(&p, PNF_JUNK, 0, INT_MAX, "retries"); + else if (SKIP_PREFIX("alpha")) + alpha = parse_float(&p, PNF_JUNK, 0, 1, "average decay factor"); + else if (SKIP_PREFIX("_badwait")) + 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, 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"); + p++; + } +#undef SKIP_PREFIX + break; case 'E': errfile = optarg; break; case 'F': f |= f_fixup; break; case 'R': @@ -1015,6 +1067,7 @@ 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; if (parse_range(optarg, PRF_HYPHEN, &start, &end)) @@ -1024,12 +1077,15 @@ int main(int argc, char *argv[]) if (end <= SECLIMIT) put_event(EV_STOP, 0, end); } break; + case 's': f |= f_stats; break; default: f |= f_bogus; break; } } 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) { @@ -1043,34 +1099,29 @@ int main(int argc, char *argv[]) #endif } - dvdfd = open(device, O_RDONLY); - if (dvdfd < 0) - bail_syserr(errno, "failed to open device `%s'", device); - 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); + open_dvd(device, O_RDONLY, &dvdfd, &dvd); + 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 (outfile) { - outfd = open(outfile, O_WRONLY | O_CREAT, 0666); - if (outfd < 0) - bail_syserr(errno, "failed to create output file `%s'", outfile); + if (f&f_checkid) { + open_dvd(outfile, O_RDONLY, 0, &dvd_out); + 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); + if (f&f_continue) { off = lseek(outfd, 0, SEEK_END); if (off < 0) @@ -1080,13 +1131,6 @@ int main(int argc, char *argv[]) } else if (!eventq.n && !(f&f_fixup)) put_event(EV_WRITE, 0, 0); -#ifdef notdef - dvd = DVDOpen2(0, &logger, device); -#else - dvd = DVDOpen(device); -#endif - if (!dvd) bail("failed to open DVD on `%s'", device); - /* It's fast enough just to check everything. */ put_menu(dvd, 0); for (i = 1; i < 100; i++) { @@ -1106,21 +1150,23 @@ int main(int argc, char *argv[]) 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; + f &= ~f_write; start = 0; for (i = 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; @@ -1128,38 +1174,55 @@ int main(int argc, char *argv[]) } } +#ifdef DEBUG + dump_eventq("initial"); +#endif f &= ~f_write; start = 0; for (i = 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 (ev->ev == EV_STOP) { nsectors += ev->pos - start; f &= ~f_write; } if (f&f_fixup) start = 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; + f |= f_write; } if (f&f_write) { nsectors += limit - start; put_event(EV_STOP, 0, limit); } - if (n == 1 && (f&f_write)) flags |= F_ALLPROGRESS; - f &= ~f_write; +#ifdef DEBUG + dump_eventq("final"); +#endif + + copy_progress.render = render_copy_progress; + progress_additem(&progress, ©_progress); + if (nsectors == limit - start) + { ndone = start; nsectors = limit; } + else { + disc_progress.render = render_disc_progress; + progress_additem(&progress, &disc_progress); + } + + if (f&f_stats) gettimeofday(&tv0, 0); #ifdef DEBUG printf("\n;; event sweep:\n"); #endif + f &= ~f_write; for (pos = 0, i = 0; i < eventq.n; i++) { ev = &eventq.v[i]; if (ev->pos > pos) { if (f&f_write) emit(pos, ev->pos); pos = ev->pos; #ifdef DEBUG - debug_clear_progress(); + progress_clear(&progress); printf(";;\n"); #endif } @@ -1168,7 +1231,7 @@ int main(int argc, char *argv[]) set_live(ev->file); #ifdef DEBUG store_filename(fn, filetab.v[ev->file].id); - debug_clear_progress(); + progress_clear(&progress); printf(";; %8"PRIuSEC": begin `%s'\n", pos, fn); #endif break; @@ -1180,7 +1243,7 @@ int main(int argc, char *argv[]) "(sector %"PRIuSEC") in output file `%s'", ev->pos, outfile); #ifdef DEBUG - debug_clear_progress(); + progress_clear(&progress); printf(";; %8"PRIuSEC": begin write\n", pos); #endif f |= f_write; @@ -1188,7 +1251,7 @@ int main(int argc, char *argv[]) case EV_STOP: f &= ~f_write; #ifdef DEBUG - debug_clear_progress(); + progress_clear(&progress); printf(";; %8"PRIuSEC": end write\n", pos); #endif break; @@ -1196,7 +1259,7 @@ int main(int argc, char *argv[]) clear_live(ev->file); #ifdef DEBUG store_filename(fn, filetab.v[ev->file].id); - debug_clear_progress(); + progress_clear(&progress); printf(";; %8"PRIuSEC": end `%s'\n", pos, fn); #endif break; @@ -1204,21 +1267,32 @@ int main(int argc, char *argv[]) } } - if (progresslen) putchar('\n'); + progress_clear(&progress); if (ftruncate(outfd, (off_t)limit*SECTORSZ) < 0) bail_syserr(errno, "failed to set output file `%s' length", outfile); + if (f&f_stats) { + gettimeofday(&tv1, 0); t = tvdiff(&tv0, &tv1); + 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", + tot, totunit, fmttime(t, timebuf), rate, rateunit); + } + if (dvd) DVDClose(dvd); if (dvdfd >= 0) close(dvdfd); if (outfd >= 0) close(outfd); carefully_fclose(mapfp, "bad-sector region map"); carefully_fclose(errfp, "bad-sector error log"); + progress_free(&progress); #undef f_bogus #undef f_continue #undef f_fixup +#undef f_stats #undef f_write - return (status); + return (0); }