From dc53ebfaa3fb887f962b574c6bafa45b160fc765 Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Tue, 1 Mar 2022 16:18:47 +0000 Subject: [PATCH] A big mess of changes all at once. I'm usually quite good about factoring out all of the changes I make, but in this case it doesn't seem worth the bother. --- .gitignore | 2 + Makefile | 22 +- dvd-cache-keys.c | 53 +++++ dvd-id.c | 54 +++++ dvd-sector-copy.c | 654 ++++++++++++++++++++++++++++++------------------------ dvdrip | 55 ++++- lib.c | 150 +++++++++++++ lib.h | 100 +++++++++ multiprogress.c | 587 ++++++++++++++++++++++++++++++++++++++++++++++++ multiprogress.h | 82 +++++++ 10 files changed, 1463 insertions(+), 296 deletions(-) create mode 100644 dvd-cache-keys.c create mode 100644 dvd-id.c create mode 100644 lib.c create mode 100644 lib.h create mode 100644 multiprogress.c create mode 100644 multiprogress.h diff --git a/.gitignore b/.gitignore index 3314751..de68a31 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ *.dep *.o +/dvd-cache-keys +/dvd-id /dvd-sector-copy diff --git a/Makefile b/Makefile index e412278..e0a0805 100644 --- a/Makefile +++ b/Makefile @@ -23,10 +23,26 @@ CFLAGS = -O2 -g -pedantic -Wall -Werror LD = gcc LDFLAGS = +LIBS = -ldvdread -lm + +CFLAGS.terminfo += -DUSE_TERMINFO +LIBS.terminfo += -ltinfo + +CFLAGS.termcap += -DUSE_TERMCAP +LIBS.termcap += -ltermcap + +TERMLIB ?= terminfo +CFLAGS += $(CFLAGS.$(TERMLIB)) +LIBS += $(LIBS.$(TERMLIB)) PROGS += dvd-sector-copy -dvd-sector-copy_SRCS = dvd-sector-copy.c -dvd-sector-copy_LIBS = -ldvdread -lm +dvd-sector-copy_SRCS = dvd-sector-copy.c lib.c multiprogress.c + +PROGS += dvd-cache-keys +dvd-cache-keys_SRCS = dvd-cache-keys.c lib.c multiprogress.c + +PROGS += dvd-id +dvd-id_SRCS = dvd-id.c lib.c multiprogress.c SCRIPTS += dvdrip SCRIPTS += dvdrip-upload @@ -41,7 +57,7 @@ TARGETS += $(PROGS) program-objects = $(call objects,$($1_SRCS),$2) $(PROGS): %: $$(call program-objects,$$*) $$($$*_DEPS) $(call v-tag,LD)$(LD) $(LDFLAGS) -o$@ \ - $(call program-objects,$*) $($*_LIBS) + $(call program-objects,$*) $($*_LIBS) $(LIBS) INSTALL_bin = $(addprefix inst/,$(PROGS) $(SCRIPTS)) install: $(INSTALL_bin) diff --git a/dvd-cache-keys.c b/dvd-cache-keys.c new file mode 100644 index 0000000..6a4a7a0 --- /dev/null +++ b/dvd-cache-keys.c @@ -0,0 +1,53 @@ +#include "lib.h" + +static void usage(FILE *fp) { fprintf(fp, "usage: %s DEVICE\n", prog); } + +static dvd_reader_t *dvd; + +static void kick_vob(int index, int titlep) +{ + dvd_file_t *vob; + + vob = DVDOpenFile(dvd, index, + titlep ? DVD_READ_TITLE_VOBS : DVD_READ_MENU_VOBS); + if (!vob) bail("failed to open %s %d", titlep ? "title" : "menu", index); + DVDCloseFile(vob); +} + +int main(int argc, char *argv[]) +{ + char fn[MAXFNSZ]; + int opt; + unsigned i, f = 0; + secaddr start, len; +#define f_bogus 1u + + set_prog(argv[0]); + for (;;) { + opt = getopt(argc, argv, "h"); if (opt < 0) break; + switch (opt) { + case 'h': usage(stderr); exit(0); + default: f |= f_bogus; break; + } + } + if (argc - optind != 1) f |= f_bogus; + if (f&f_bogus) { usage(stderr); exit(2); } + + setlocale(LC_ALL, ""); + progress_init(&progress); + + open_dvd(argv[optind], 0, &dvd); + + for (i = 0; i < 100; i++) { + store_filename(fn, mkident(VOB, i, 0)); + start = UDFFindFile(dvd, fn, &len); if (start) kick_vob(i, 0); + if (i) { + store_filename(fn, mkident(VOB, i, 1)); + start = UDFFindFile(dvd, fn, &len); if (start) kick_vob(i, 1); + } + } + + if (dvd) DVDClose(dvd); + progress_free(&progress); + return (0); +} diff --git a/dvd-id.c b/dvd-id.c new file mode 100644 index 0000000..971be05 --- /dev/null +++ b/dvd-id.c @@ -0,0 +1,54 @@ +#include "lib.h" + +static void usage(FILE *fp) { fprintf(fp, "usage: %s DEVICE\n", prog); } + +static void puthex(const unsigned char *p, size_t sz, FILE *fp) + { while (sz) { fprintf(fp, "%02x", *p++); sz--; } } + +int main(int argc, char *argv[]) +{ + char volid[33]; + unsigned char volsetid[16], discid[16]; + int rc, opt; + unsigned f = 0; + static dvd_reader_t *dvd; +#define f_bogus 1u + + set_prog(argv[0]); + for (;;) { + opt = getopt(argc, argv, "h"); if (opt < 0) break; + switch (opt) { + case 'h': usage(stderr); exit(0); + default: f |= f_bogus; break; + } + } + if (argc - optind != 1) f |= f_bogus; + if (f&f_bogus) { usage(stderr); exit(2); } + setlocale(LC_ALL, ""); + progress_init(&progress); + + open_dvd(argv[optind], 0, &dvd); + + rc = DVDUDFVolumeInfo(dvd, + volid, sizeof(volid), + volsetid, sizeof(volsetid)); + if (rc) { + moan("failed to read volume info"); + strcpy(volid, ""); + memset(volsetid, 0xff, sizeof(volsetid)); + } + + rc = DVDDiscID(dvd, discid); + if (rc) { + moan("failed to determine disc id"); + memset(discid, 0xff, sizeof(discid)); + } + + fputs(volid, stdout); fputc('-', stdout); + puthex(volsetid, sizeof(volsetid), stdout); fputc(':', stdout); + puthex(discid, sizeof(discid), stdout); fputc('\n', stdout); + + if (dvd) DVDClose(dvd); + progress_free(&progress); + return (0); +} diff --git a/dvd-sector-copy.c b/dvd-sector-copy.c index 5598f6f..7b7e674 100644 --- a/dvd-sector-copy.c +++ b/dvd-sector-copy.c @@ -1,90 +1,20 @@ -#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) +#include "lib.h" -#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; static void usage(FILE *fp) { fprintf(fp, - "usage: %s [-c] [-R MAP] [-b OUTMAP] [-r [START]-[END]]\n" - "\tDEVICE OUTFILE\n", + "usage: %s [-c] [-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, ...) +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); -} - -__attribute__((noreturn, format(printf, 1, 2))) -static void bail(const char *fmt, ...) -{ - 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); + return ((tv_hi->tv_sec - tv_lo->tv_sec) + + (tv_hi->tv_usec - tv_lo->tv_usec)/1.0e6); } static void carefully_write(int fd, const void *buf, size_t sz) @@ -147,45 +77,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,66 +205,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 const char throbber[] = "|<-<|>->"; -static unsigned throbix = 0; + progress_putleft(render, " disc %.1f%% (%"PRIuSEC" of %"PRIuSEC")", + 100.0*frac, last_pos, limit); + progress_showbar(render, frac); +} -static void report_progress(secaddr pos) +static void render_file_progress(struct progress_item *item, + struct progress_render_state *render) +{ + 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 void update_progress(secaddr pos) { - char etastr[32]; struct timeval now; - int eta; - double percent, t, f, g, rate; - char *unit; + double t, f, g; + + gettimeofday(&now, 0); + t = tvdiff(&last_time, &now); #define ALPHA 0.1 #define BETA (1 - ALPHA) - gettimeofday(&now, 0); - t = (now.tv_sec - last_time.tv_sec) + - (now.tv_usec - last_time.tv_usec)/1000000.0; - if (t) { g = wcount ? pow(BETA, t) : 0.0; f = (1 - g)/(1 - BETA); wsum = f*(pos - last_pos)/t + g*wsum; @@ -382,37 +334,15 @@ 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 - ("%c copied %.1f%% (%"PRIuSEC" of %"PRIuSEC"; %.1f %sB/s, ETA %s)", - throbber[throbix], percent, pos, limit, rate, unit, etastr); - throbix++; if (!throbber[throbix]) throbix = 0; - 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; @@ -436,6 +366,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; @@ -447,7 +380,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(); printf(";; searching badblocks for %"PRIuSEC" .. %"PRIuSEC"\n", pos, pos + want); #endif @@ -467,7 +400,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) { @@ -493,46 +426,61 @@ 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"", bad_lo, bad_hi); + + 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 && id_kind(file->id) != RAW) - 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"); + + 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, @@ -544,14 +492,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(); printf(";; recovery buffer ("); vprintf(what, ap); printf("): " @@ -577,14 +525,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(); show_recovery_buffer_map(r, "begin(%"PRIuSEC", %"PRIuSEC")", pos, want); #endif @@ -606,7 +554,7 @@ static ssize_t recovery_read(struct recoverybuf *r, } else if (pos > r->pos + r->end) { r->pos = pos; r->start = r->end = 0; #ifdef DEBUG - show_recovery_buffer_map(r, "cleared; beyond previous region"); +p 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); @@ -675,61 +623,126 @@ 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 - debug_clear_progress(); + 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; + step = step_factor*(bad_hi - bad_lo); + if (step < step_min) step = step_min; + if (step_max && step > step_max) step = step_max; + if (step > end - bad_lo) step = end - bad_lo; pos = bad_lo + step - 1; - want = run_length_wanted(pos, step, sz, end); + want = run_length_wanted(pos, step, end); n = recovery_read(&r, pos, want); #ifdef DEBUG printf(";; [bound] try reading %"PRIuSEC" .. %"PRIuSEC" -> %zd\n", @@ -742,16 +755,16 @@ 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 - debug_clear_progress(); + 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 printf(";; [limit] try reading %"PRIuSEC" .. %"PRIuSEC" -> %zd\n", @@ -817,7 +830,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 @@ -826,12 +839,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; @@ -839,33 +858,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; @@ -902,6 +905,38 @@ static int read_line(FILE *fp, struct buf *b) 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) @@ -948,7 +983,7 @@ end: int main(int argc, char *argv[]) { unsigned f = 0; - char *p; + const char *p; uint64_t volsz; secaddr pos; off_t off; @@ -961,6 +996,10 @@ int main(int argc, char *argv[]) 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; #ifdef DEBUG const struct file *file; @@ -970,13 +1009,45 @@ int main(int argc, char *argv[]) #define f_bogus 1u #define f_continue 2u #define f_fixup 4u +#define f_stats 8u #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:cr: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, 0, DBL_MAX, "clear factor"); + else if (SKIP_PREFIX("cmin")) + clear_min = parse_int(&p, 1, SECLIMIT, "clear minimum"); + else if (SKIP_PREFIX("cmax")) + clear_max = parse_int(&p, 1, SECLIMIT, "clear maximum"); + else if (SKIP_PREFIX("sf")) + step_factor = parse_float(&p, 0, DBL_MAX, "step factor"); + else if (SKIP_PREFIX("smin")) + step_min = parse_int(&p, 1, SECLIMIT - 1, "step minimum"); + else if (SKIP_PREFIX("smax")) + step_max = parse_int(&p, 1, SECLIMIT - 1, "step maximum"); + else if (SKIP_PREFIX("retry")) + max_retries = parse_int(&p, 0, INT_MAX, "retries"); + else if (SKIP_PREFIX("_badwait")) + bad_block_delay = parse_float(&p, 0, DBL_MAX, "bad-block delay"); + else if (SKIP_PREFIX("_blkwait")) + good_block_delay = parse_float(&p, 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': @@ -1038,12 +1109,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) { @@ -1057,9 +1131,7 @@ int main(int argc, char *argv[]) #endif } - dvdfd = open(device, O_RDONLY); - if (dvdfd < 0) - bail_syserr(errno, "failed to open device `%s'", device); + open_dvd(device, &dvdfd, &dvd); if (fstat(dvdfd, &st)) bail_syserr(errno, "failed to stat device `%s'", device); if (S_ISREG(st.st_mode)) { @@ -1079,11 +1151,9 @@ int main(int argc, char *argv[]) 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); - } + 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); @@ -1094,13 +1164,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++) { @@ -1147,7 +1210,8 @@ int main(int argc, char *argv[]) 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; + case EV_STOP: + nsectors += ev->pos - start; f &= ~f_write; break; } if (ev->pos >= limit) break; if (f&f_fixup) start = ev->pos; @@ -1161,19 +1225,27 @@ int main(int argc, char *argv[]) nsectors += limit - start; put_event(EV_STOP, 0, limit); } - if (n == 1 && (f&f_write)) flags |= F_ALLPROGRESS; - f &= ~f_write; + + copy_progress.render = render_copy_progress; + progress_additem(&progress, ©_progress); + if (nsectors != limit) { + 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 } @@ -1182,7 +1254,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; @@ -1194,7 +1266,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; @@ -1202,7 +1274,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; @@ -1210,7 +1282,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; @@ -1218,20 +1290,30 @@ 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); + 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); diff --git a/dvdrip b/dvdrip index 3dc1053..a4b5900 100755 --- a/dvdrip +++ b/dvdrip @@ -6,17 +6,20 @@ tmp=${DVDRIP_TMPDIR-${HOME?}/tmp/dvdrip} archive=${DVDRIP_ARCHIVE-jem.distorted.org.uk:/mnt/dvd/archive} : ${DVD_SECTOR_COPY=dvd-sector-copy} : ${DVDRIP_UPLOAD=dvdrip-upload} -backup=nil eject=nil force=nil retry=nil verbose=nil bogus=nil +backup=nil ding=nil eject=nil force=nil retry=nil verbose=nil bogus=nil +unset params usage () { cat <"$tmp/$tag/discid.new" +mv "$tmp/$tag/discid.new" "$tmp/$tag/discid" + accumulate_badblocks () { if [ -f "$tmp/$tag/badblocks.new" ]; then if [ ! -f "$tmp/$tag/badblocks" ]; then @@ -83,7 +106,6 @@ accumulate_badblocks () { } set -- -mkdir -p "$tmp/$tag" any=nil for i in "$tmp/$tag/dest.new" "$tmp/$tag/dest" "$tmp/$tag/dest.seen"; do if [ -f "$tmp/$tag/dest.new" ]; then any=t; fi @@ -91,6 +113,14 @@ done case $any in nil) printf "%s\n" "$title.iso" >"$tmp/$tag/dest.new" ;; esac +case $eject in + t) touch "$tmp/$tag/eject" ;; + nil) rm -f "$tmp/$tag/eject" ;; +esac +case $ding in + t) touch "$tmp/$tag/ding" ;; + nil) rm -f "$tmp/$tag/ding" ;; +esac accumulate_badblocks case $retry in @@ -109,8 +139,8 @@ case $retry in ;; esac if [ ! -f "$tmp/$tag/iso" ]; then - run "$DVD_SECTOR_COPY" -cs -b"$tmp/$tag/badblocks.new" "$@" \ - "$dev" "$tmp/$tag/iso.new" + run "$DVD_SECTOR_COPY" -cs ${params+"-B$params"} \ + -b"$tmp/$tag/badblocks.new" "$@" "$dev" "$tmp/$tag/iso.new" run mv "$tmp/$tag/iso.new" "$tmp/$tag/iso" accumulate_badblocks case $retry in t) rm -f "$tmp/$tag/badblocks.retry" ;; esac @@ -120,6 +150,17 @@ if [ ! -f "$tmp/$tag/iso" ]; then fi run mv "$tmp/$tag/dest.new" "$tmp/$tag/dest" +if [ -f "$tmp/$tag/eject" ]; then eject=t; else eject=nil; fi +if [ -f "$tmp/$tag/ding" ]; then ding=t; else ding=nil; fi run "$DVDRIP_UPLOAD" case $eject in t) run eject "$dev" ;; esac -printf "\a" +case $ding in + t) + if [ -t 1 ]; then exec 3>&1 + elif [ -t 2 ]; then exec 3>&2 + else exec 3>/dev/tty + fi + printf "\a" >&3 + exec 3>&- + ;; +esac diff --git a/lib.c b/lib.c new file mode 100644 index 0000000..1c815d0 --- /dev/null +++ b/lib.c @@ -0,0 +1,150 @@ +#include "lib.h" + +const char *prog = ""; + +void set_prog(const char *p) + { const char *q = strrchr(p, '/'); prog = q ? q + 1 : p; } + +void vmoan(const char *fmt, va_list ap) + { fprintf(stderr, "%s: ", prog); vfprintf(stderr, fmt, ap); } + +__attribute__((format(printf, 1, 2))) +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))) +void bail(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); vmoan(fmt, ap); va_end(ap); + fputc('\n', stderr); + exit(2); +} + +__attribute__((noreturn, format(printf, 2, 3))) +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); +} + +void sit(double t) +{ + struct timeval tv; + double whole = floor(t); + + if (t) { + tv.tv_sec = whole; tv.tv_usec = floor((t - whole)*1.0e6) + 1; + if (select(0, 0, 0, 0, &tv) < 0) bail_syserr(errno, "failed to sleep"); + } +} + +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(); + } +} + +struct progress_state progress = PROGRESS_STATE_INIT; +static struct banner_progress_item banner_progress; + +static void render_banner_progress(struct progress_item *item, + struct progress_render_state *render) +{ + struct banner_progress_item *bi = (struct banner_progress_item *)item; + + progress_putleft(render, " %s", bi->msg); + progress_shownotice(render, 4, 7); +} + +void show_banner(const char *msg) +{ + banner_progress._base.render = render_banner_progress; + progress_additem(&progress, &banner_progress._base); + banner_progress.msg = msg; + progress_update(&progress); +} + +void hide_banner(void) +{ + if (!progress_removeitem(&progress, &banner_progress._base)) + 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); +} +static const dvd_logger_cb logger = { logfn }; +#endif + +void open_dvd(const char *device, int *fd_out, dvd_reader_t **dvd_out) +{ + int fd; + dvd_reader_t *dvd; + int bannerp = 0; + + for (;;) { + fd = open(device, O_RDONLY); + if (fd >= 0 || errno != ENOMEDIUM) break; + if (!bannerp) { + show_banner("Waiting for disc to be inserted..."); + bannerp = 1; + } + sit(0.2); + } + if (bannerp) hide_banner(); + if (fd < 0) bail_syserr(errno, "failed to open device `%s'", device); + if (dvd_out) { +#ifdef notdef + dvd = DVDOpen2(0, &logger, device); +#else + dvd = DVDOpen(device); +#endif + if (!dvd) bail("failed to open DVD on `%s'", device); + *dvd_out = dvd; + } + if (fd_out) *fd_out = fd; + else close(fd); +} diff --git a/lib.h b/lib.h new file mode 100644 index 0000000..1f08848 --- /dev/null +++ b/lib.h @@ -0,0 +1,100 @@ +#ifndef LIB_H +#define LIB_H + +#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 + +#include +#include +#include +#include + +#include "multiprogress.h" + +#define CTYPE_HACK(fn, ch) fn((unsigned char)(ch)) +#define ISDIGIT(ch) CTYPE_HACK(isdigit, ch) +#define ISSPACE(ch) CTYPE_HACK(isspace, ch) + +#define STRCMP(a, op, b) (strcmp((a), (b)) op 0) +#define STRNCMP(a, op, b, n) (strncmp((a), (b), (n)) op 0) + +#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) +typedef uint_least32_t secaddr; +#define PRIuSEC PRIuLEAST32 +#define SECLIMIT 0x00400000 + +#define PRINTF_LIKE(fmt, dots) __attribute__((format(printf, fmt, dots))) +#define NORETURN __attribute__((noreturn)) + +extern const char *prog; + +extern void set_prog(const char *p); +extern void vmoan(const char *fmt, va_list ap); +extern PRINTF_LIKE(1, 2) void moan(const char *fmt, ...); +extern PRINTF_LIKE(1, 2) NORETURN void bail(const char *fmt, ...); +extern PRINTF_LIKE(2, 3) NORETURN + void bail_syserr(int err, const char *fmt, ...); + +extern void sit(double t); + +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) +extern void store_filename(char *buf, ident id); + +struct banner_progress_item { + struct progress_item _base; + const char *msg; +}; + +extern struct progress_state progress; + +extern void show_banner(const char *msg); +extern void hide_banner(void); + +extern void open_dvd(const char *device, + int *fd_out, dvd_reader_t **dvd_out); + +#endif diff --git a/multiprogress.c b/multiprogress.c new file mode 100644 index 0000000..efad38d --- /dev/null +++ b/multiprogress.c @@ -0,0 +1,587 @@ +#define _XOPEN_SOURCE + +#include +#include +#include +#include +#include + +#include +#include +#include + +#if defined(USE_TERMINFO) +# include +# include +#elif defined(USE_TERMCAP) +# include +#endif + +#include "multiprogress.h" + +static FILE *dup_stream(int fd) +{ + FILE *fp; + int newfd; + + newfd = dup(fd); if (newfd < 0) return (0); + fp = fdopen(newfd, "r+"); if (!fp) return (0); + return (fp); +} + +int progress_init(struct progress_state *progress) +{ +#ifdef USE_TERMCAP + char *term, *capcur; +#endif +#ifdef USE_TERMINFO + int err; +#endif + struct progress_ttyinfo *tty; + const char *t; + int n; + + tty = &progress->tty; + tty->fp = 0; + tty->termbuf = tty->capbuf = 0; + tty->cap.f = 0; + tty->cap.cr = tty->cap.up = tty->cap.ce = tty->cap.cd = + tty->cap.mr = tty->cap.md = tty->cap.me = + tty->cap.af = tty->cap.ab = tty->cap.op = 0; + + progress->items = progress->end_item = 0; + progress->nitems = 0; progress->last_lines = 0; + progress->tv_update.tv_sec = 0; progress->tv_update.tv_usec = 0; + + if (isatty(1)) tty->fp = dup_stream(1); + else if (isatty(2)) tty->fp = dup_stream(2); + else tty->fp = fopen("/dev/tty", "r+"); + if (!tty->fp) return (-1); + +#define SETDIM(dim, var, getcap, dflt) do { \ + t = getenv(var); if (t) { n = atoi(t); if (n) { tty->dim = n; break; } } \ + n = getcap; if (n > 0) { tty->dim = n; break; } \ + tty->dim = dflt; \ +} while (0) + +#if defined(USE_TERMINFO) + + if (setupterm(0, fileno(tty->fp), &err) != OK || err < 1) return (-1); + + tty->cap.cr = tigetstr("cr"); + tty->cap.up = tigetstr("cuu1"); + tty->cap.ce = tigetstr("el"); + tty->cap.cd = tigetstr("ed"); + + if (tigetnum("xmc") < 1) { + tty->cap.mr = tigetstr("rev"); + tty->cap.md = tigetstr("bold"); + tty->cap.me = tigetstr("sgr0"); + + tty->cap.af = tigetstr("setaf"); + tty->cap.ab = tigetstr("setab"); + tty->cap.op = tigetstr("op"); + } + + if (tigetflag("bce") > 0) tty->cap.f |= TCF_BCE; + + SETDIM(defwd, "COLUMNS", tigetnum("co"), 80); + SETDIM(defht, "LINES", tigetnum("li"), 25); + +#elif defined(USE_TERMCAP) + + term = getenv("TERM"); if (!term) return (-1); + if (tgetent(tty->termbuf, term) < 1) return (-1); + + tty->termbuf = malloc(4096); if (!tty->termbuf) return (-1); + tty->capbuf = malloc(4096); if (!tty->capbuf) return (-1); + + capcur = tty->capbuf; + tty->cap.cr = tgetstr("cr", &capcur); + tty->cap.up = tgetstr("up", &capcur); + tty->cap.ce = tgetstr("ce", &capcur); + tty->cap.cd = tgetstr("cd", &capcur); + + if (tgetnum("sg") < 1) { + tty->cap.mr = tgetstr("mr", &capcur); + tty->cap.md = tgetstr("md", &capcur); + tty->cap.me = tgetstr("me", &capcur); + + tty->cap.af = tgetstr("AF", &capcur); + tty->cap.ab = tgetstr("AB", &capcur); + tty->cap.op = tgetstr("op", &capcur); + } + + if (tgetflag("ut") > 0) tty->cap.f |= TCF_BCE; + + t = tgetstr("pc", &capcur); PC = t ? *t : 0; + + SETDIM(defwd, "COLUMNS", tgetnum("co"), 80); + SETDIM(defht, "LINES", tgetnum("li"), 25); + +#else + + SETDIM(defwd, "COLUMNS", -1, 80); + SETDIM(defht, "LINES", -1, 25); + +#endif + +#undef SETDIM + + if (!tty->cap.cr || !tty->cap.up || !tty->cap.ce || !tty->cap.cd) + { fclose(tty->fp); tty->fp = 0; return (-1); } + if (!tty->cap.af || !tty->cap.ab || !tty->cap.op) tty->cap.op = 0; + if (!tty->cap.me) tty->cap.mr = tty->cap.md = 0; + return (0); +} + +void progress_free(struct progress_state *progress) +{ + struct progress_ttyinfo *tty = &progress->tty; + + if (tty->fp) { fclose(tty->fp); tty->fp = 0; } + free(tty->termbuf); free(tty->capbuf); tty->termbuf = tty->capbuf = 0; +} + +#if defined(USE_TERMINFO) +static const struct progress_ttyinfo *curtty = 0; +static int putty(int ch) { return (putc(ch, curtty->fp)); } +static void put_sequence(const struct progress_ttyinfo *tty, + const char *p, unsigned nlines) + { if (p) { curtty = tty; tputs(p, nlines, putty); } } +static void set_fgcolour(const struct progress_ttyinfo *tty, int colour) + { put_sequence(tty, tgoto(tty->cap.af, -1, colour), 1); } +static void set_bgcolour(const struct progress_ttyinfo *tty, int colour) + { put_sequence(tty, tgoto(tty->cap.ab, -1, colour), 1); } +#elif defined(USE_TERMCAP) +static const struct progress_ttyinfo *curtty = 0; +static int putty(int ch) { return (putc(ch, curtty->fp)); } +static void put_sequence(const struct progress_ttyinfo *tty, + const char *p, unsigned nlines) + { if (p) { curtty = tty; tputs(p, nlines, putty); } } +static void set_fgcolour(const struct progress_ttyinfo *tty, int colour) + { put_sequence(tty, tgoto(tty->cap.af, -1, colour), 1); } +static void set_bgcolour(const struct progress_ttyinfo *tty, int colour) + { put_sequence(tty, tgoto(tty->cap.ab, -1, colour), 1); } +#else +static void put_sequence(const struct progress_ttyinfo *tty, + const char *p, unsigned nlines) { ; } +static void set_fgcolour(const struct progress_ttyinfo *tty, int colour) + { ; } +static void set_bgcolour(const struct progress_ttyinfo *tty, int colour) + { ; } +#endif + +#define CLRF_ALL 1u +static int clear_progress(struct progress_state *progress, + struct progress_render_state *render, unsigned f) +{ + const struct progress_ttyinfo *tty = &progress->tty; + unsigned ndel, nleave; + unsigned i; + + if (!tty->fp) return (-1); + + put_sequence(tty, tty->cap.cr, 1); + if (progress->last_lines) { + if (f&CLRF_ALL) + { ndel = progress->last_lines; nleave = 0; } + else { + if (progress->nitems >= progress->last_lines) ndel = 0; + else ndel = progress->last_lines - progress->nitems; + nleave = progress->last_lines - ndel; + } + if (!ndel) + for (i = 0; i < nleave - 1; i++) put_sequence(tty, tty->cap.up, 1); + else { + for (i = 0; i < ndel - 1; i++) put_sequence(tty, tty->cap.up, 1); + put_sequence(tty, tty->cap.cd, ndel); + for (i = 0; i < nleave; i++) put_sequence(tty, tty->cap.up, 1); + } + } + progress->last_lines = 0; + if (ferror(tty->fp)) return (-1); + return (0); +} + +static int grow_linebuf(struct progress_render_state *render, size_t want) +{ + char *newbuf; size_t newsz; + + if (want <= render->linesz) return (0); + if (!render->linesz) newsz = 4*render->width + 1; + else newsz = render->linesz; + while (newsz < want) newsz *= 2; + newbuf = malloc(newsz + 1); if (!newbuf) return (-1); + newbuf[newsz] = 0; + if (render->leftsz) + memcpy(newbuf, render->linebuf, render->leftsz); + if (render->rightsz) + memcpy(newbuf + newsz - render->rightsz, + render->linebuf + render->linesz - render->rightsz, + render->rightsz); + free(render->linebuf); render->linebuf = newbuf; render->linesz = newsz; + return (0); +} + +static int grow_tempbuf(struct progress_render_state *render, size_t want) +{ + char *newbuf; size_t newsz; + + if (want <= render->tempsz) return (0); + if (!render->tempsz) newsz = 4*render->width + 1; + else newsz = render->tempsz; + while (newsz < want) newsz *= 2; + newbuf = malloc(newsz + 1); if (!newbuf) return (-1); + newbuf[newsz] = 0; + if (render->tempsz) memcpy(newbuf, render->tempbuf, render->tempsz); + free(render->tempbuf); render->tempbuf = newbuf; render->tempsz = newsz; + return (0); +} + +static int setup_render_state(struct progress_state *progress, + struct progress_render_state *render) +{ + const struct progress_ttyinfo *tty = &progress->tty; + struct winsize wsz; + int rc = 0; + + render->tty = tty; + render->linebuf = 0; render->linesz = 0; + render->tempbuf = 0; render->tempsz = 0; + +#ifdef USE_TERMCAP + render->old_bc = BC; BC = 0; + render->old_up = UP; UP = 0; +#endif + + if (!ioctl(fileno(tty->fp), TIOCGWINSZ, &wsz)) + { render->width = wsz.ws_col; render->height = wsz.ws_row; } + else + { render->width = tty->defwd; render->height = tty->defht; rc = -1; } + + if (render->width && !tty->cap.op && !tty->cap.mr) render->width--; + + return (rc); +} + +static void free_render_state(struct progress_render_state *render) +{ + fflush(render->tty->fp); + free(render->linebuf); render->linebuf = 0; render->linesz = 0; + free(render->tempbuf); render->tempbuf = 0; render->tempsz = 0; +#ifdef USE_TERMCAP + UP = render->old_up; + BC = render->old_bc; +#endif +} + +#define CONV_MORE ((size_t)-2) +#define CONV_BAD ((size_t)-1) + +struct measure { + mbstate_t ps; + const char *p; size_t i, sz; + unsigned wd; +}; + +static void init_measure(struct measure *m, const char *p, size_t sz) +{ + m->p = p; m->sz = sz; m->i = 0; m->wd = 0; + memset(&m->ps, 0, sizeof(m->ps)); +} + +static int advance_measure(struct measure *m) +{ + wchar_t wch; + unsigned chwd; + size_t n; + + n = mbrtowc(&wch, m->p + m->i, m->sz - m->i, &m->ps); + if (!n) { chwd = 0; n = m->sz - m->i; } + else if (n == CONV_MORE) { chwd = 2; n = m->sz - m->i; } + else if (n == CONV_BAD) { chwd = 2; n = 1; } + else chwd = wcwidth(wch); + + m->i += n; m->wd += chwd; + return (m->i < m->sz); +} + +static unsigned string_width(const char *p, size_t sz) +{ + struct measure m; + + init_measure(&m, p, sz); + while (advance_measure(&m)); + return (m.wd); +} + +static size_t split_string(const char *p, size_t sz, + unsigned *wd_out, unsigned maxwd) +{ + struct measure m; + size_t lasti; unsigned lastwd; + int more; + + init_measure(&m, p, sz); + for (;;) { + lasti = m.i; lastwd = m.wd; + more = advance_measure(&m); + if (m.wd > maxwd) { *wd_out = lastwd; return (lasti); } + else if (!more) { *wd_out = m.wd; return (sz); } + } +} + +enum { LEFT, RIGHT }; +static int putstr(struct progress_render_state *render, unsigned side, + const char *p, size_t n) +{ + unsigned newwd = string_width(p, n); + size_t want; + + if (newwd >= render->width - render->leftwd - render->rightwd) return (-1); + want = render->leftsz + render->rightsz + n; + if (want > render->linesz && grow_linebuf(render, want)) return (-1); + switch (side) { + case LEFT: + memcpy(render->linebuf + render->leftsz, p, n); + render->leftsz += n; render->leftwd += newwd; + break; + case RIGHT: + memcpy(render->linebuf + render->linesz - render->rightsz - n, p, n); + render->rightsz += n; render->rightwd += newwd; + break; + default: + return (-1); + } + return (0); +} + +static int vputf(struct progress_render_state *render, unsigned side, + const char *fmt, va_list ap) +{ + va_list bp; + int rc; + + if (!render->tempsz && grow_tempbuf(render, 2*strlen(fmt))) return (-1); + for (;;) { + va_copy(bp, ap); + rc = vsnprintf(render->tempbuf, render->tempsz, fmt, bp); + va_end(bp); + if (rc < 0) return (-1); + if (rc <= render->tempsz) break; + if (grow_tempbuf(render, 2*(rc + 1))) return (-1); + } + if (putstr(render, side, render->tempbuf, rc)) return (-1); + return (0); +} + +int progress_vputleft(struct progress_render_state *render, + const char *fmt, va_list ap) + { return (vputf(render, LEFT, fmt, ap)); } + +int progress_vputright(struct progress_render_state *render, + const char *fmt, va_list ap) + { return (vputf(render, RIGHT, fmt, ap)); } + +int progress_putleft(struct progress_render_state *render, + const char *fmt, ...) +{ + va_list ap; + int rc; + + va_start(ap, fmt); rc = vputf(render, LEFT, fmt, ap); va_end(ap); + return (rc); +} + +int progress_putright(struct progress_render_state *render, + const char *fmt, ...) +{ + va_list ap; + int rc; + + va_start(ap, fmt); rc = vputf(render, RIGHT, fmt, ap); va_end(ap); + return (rc); +} + +enum { + LEFT_COLOUR, + LEFT_MONO, + LEFT_SIMPLE, + RIGHT_ANY, + STOP +}; + +struct bar_state { + const struct progress_render_state *render; + unsigned pos, nextpos, state; +}; + +static void advance_bar_state(struct bar_state *bar) +{ + const struct progress_render_state *render = bar->render; + const struct progress_ttyinfo *tty = render->tty; + size_t here = bar->nextpos; + + while (bar->nextpos == here) { + switch (bar->state) { + case LEFT_COLOUR: set_bgcolour(tty, 3); goto right; + case LEFT_MONO: put_sequence(tty, tty->cap.me, 1); goto right; + case LEFT_SIMPLE: putc('|', tty->fp); goto right; + right: bar->state = RIGHT_ANY; bar->nextpos = render->width; break; + case RIGHT_ANY: bar->state = STOP; bar->nextpos = UINT_MAX; break; + } + } +} + +static void put_str(FILE *fp, const char *p, size_t sz) + { while (sz--) putc(*p++, fp); } +static void put_spc(FILE *fp, unsigned n) + { while (n--) putc(' ', fp); } + +static void put_barstr(struct bar_state *bar, const char *p, size_t sz) +{ + unsigned wd; + size_t n; + + for (;;) { + n = split_string(p, sz, &wd, bar->nextpos - bar->pos); + if (n == sz && wd < bar->nextpos - bar->pos) break; + put_str(bar->render->tty->fp, p, n); bar->pos += wd; + advance_bar_state(bar); + p += n; sz -= n; + } + put_str(bar->render->tty->fp, p, sz); bar->pos += wd; +} + +static void put_barspc(struct bar_state *bar, unsigned n) +{ + unsigned step; + + for (;;) { + step = bar->nextpos - bar->pos; + if (n < step) break; + put_spc(bar->render->tty->fp, step); bar->pos += step; + advance_bar_state(bar); + n -= step; + } + put_spc(bar->render->tty->fp, n); bar->pos += n; +} + +int progress_showbar(struct progress_render_state *render, double frac) +{ + const struct progress_ttyinfo *tty = render->tty; + struct bar_state bar; + + if (!tty->fp) return (-1); + + bar.render = render; bar.pos = 0; bar.nextpos = frac*render->width + 0.5; + + if (tty->cap.op) { + set_fgcolour(tty, 0); bar.state = LEFT_COLOUR; + if (bar.nextpos) set_bgcolour(tty, 2); + else advance_bar_state(&bar); + } else if (tty->cap.mr) { + if (bar.nextpos) + { bar.state = LEFT_MONO; put_sequence(tty, tty->cap.mr, 1); } + else + { bar.state = RIGHT; bar.nextpos = render->width; } + } else + bar.state = LEFT_SIMPLE; + + put_barstr(&bar, render->linebuf, render->leftsz); + put_barspc(&bar, render->width - render->leftwd - render->rightwd); + put_barstr(&bar, + render->linebuf + render->linesz - render->rightsz, + render->rightsz); + + put_sequence(tty, tty->cap.me, 1); + put_sequence(tty, tty->cap.op, 1); + + return (0); +} + +int progress_shownotice(struct progress_render_state *render, int bg, int fg) +{ + const struct progress_ttyinfo *tty = render->tty; + + if (!tty->fp) return (-1); + + if (tty->cap.op) { set_fgcolour(tty, fg); set_bgcolour(tty, bg); } + else if (tty->cap.mr) put_sequence(tty, tty->cap.mr, 1); + if (tty->cap.md) put_sequence(tty, tty->cap.md, 1); + + put_str(tty->fp, render->linebuf, render->leftsz); + if (!render->rightsz && (tty->cap.f&TCF_BCE) && tty->cap.ce) + put_sequence(tty, tty->cap.ce, 1); + else { + put_spc(tty->fp, render->width - render->leftwd - render->rightwd); + put_str(tty->fp, + render->linebuf + render->linesz - render->rightsz, + render->rightsz); + } + + put_sequence(tty, tty->cap.me, 1); + put_sequence(tty, tty->cap.op, 1); + + return (0); +} + +int progress_additem(struct progress_state *progress, + struct progress_item *item) +{ + if (item->parent) return (-1); + item->prev = progress->end_item; item->next = 0; + if (progress->end_item) progress->end_item->next = item; + else progress->items = item; + progress->end_item = item; item->parent = progress; + progress->nitems++; + + return (0); +} + +int progress_clear(struct progress_state *progress) +{ + struct progress_render_state render; + + if (!progress->tty.fp) return (-1); + if (setup_render_state(progress, &render)) return (-1); + clear_progress(progress, &render, CLRF_ALL); + free_render_state(&render); + return (0); +} + +int progress_update(struct progress_state *progress) +{ + struct progress_render_state render; + struct progress_item *item; + unsigned f = 0; +#define f_any 1u + + if (!progress->tty.fp) return (-1); + if (setup_render_state(progress, &render)) return (-1); + clear_progress(progress, &render, 0); + + for (item = progress->items; item; item = item->next) { + if (f&f_any) fputs("\r\n", progress->tty.fp); + render.leftsz = render.rightsz = 0; + render.leftwd = render.rightwd = 0; + item->render(item, &render); progress->last_lines++; f |= f_any; + if (progress->last_lines > render.height) break; + } + free_render_state(&render); + return (0); +} + +int progress_removeitem(struct progress_state *progress, + struct progress_item *item) +{ + if (!item->parent) return (-1); + if (item->next) item->next->prev = item->prev; + else (progress->end_item) = item->prev; + if (item->prev) item->prev->next = item->next; + else (progress->items) = item->next; + progress->nitems--; item->parent = 0; + + return (0); +} diff --git a/multiprogress.h b/multiprogress.h new file mode 100644 index 0000000..1db97a8 --- /dev/null +++ b/multiprogress.h @@ -0,0 +1,82 @@ +#ifndef MULTIPROGRESS_H +#define MULTIPROGRESS_H + +#include +#include + +struct progress_ttyinfo { + FILE *fp; /* terminal stream */ + char *termbuf, *capbuf; /* buffers for termcap */ + struct { /* terminal capabilities */ + unsigned f; /* various flags */ +#define TCF_BCE 1u /* erases to background colour */ + const char *cr, *up, *ce, *cd; /* cursor motion */ + const char *mr, *md, *me; /* reverse video, bold */ + const char *af, *ab, *op; /* colour */ + } cap; + unsigned defwd, defht; /* default width and height */ +}; +#define PROGRESS_TTYINFO_INIT { 0, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } + +struct progress_state { + struct progress_ttyinfo tty; /* terminal state */ + struct progress_item *items, *end_item; /* list of progress items */ + unsigned nitems; /* number of items */ + unsigned last_lines; /* number written last time */ + struct timeval tv_update; /* last update time */ +}; +#define PROGRESS_STATE_INIT { PROGRESS_TTYINFO_INIT, 0, 0, 0, 0, { 0, 0 } } + +struct progress_render_state { + const struct progress_ttyinfo *tty; /* terminal state */ + unsigned width, height; /* terminal size, in characters */ + char *linebuf; size_t linesz; /* output buffer */ + char *tempbuf; size_t tempsz; /* scratch buffer */ + size_t leftsz, rightsz; /* left and right cursors */ + unsigned leftwd, rightwd; /* left and right widths */ + char *old_bc, *old_up; /* old fixup strings */ +}; + +struct progress_item { + struct progress_state *parent; /* controlling progress state */ + struct progress_item *next, *prev; /* forward and backward links */ + void (*render)(struct progress_item */*item*/, /* render function */ + struct progress_render_state */*rs*/); +}; +#define PROGRESS_ITEM_INIT { 0, 0, 0, 0 } + +extern int progress_init(struct progress_state */*progress*/); +extern void progress_free(struct progress_state */*progress*/); + +extern int progress_clear(struct progress_state */*progress*/); + +extern int progress_update(struct progress_state */*progress*/); + +extern int progress_additem(struct progress_state */*progress*/, + struct progress_item */*item*/); + +extern int progress_removeitem(struct progress_state */*progress*/, + struct progress_item */*item*/); + + +extern int progress_vputleft(struct progress_render_state */*render*/, + const char */*fmt*/, va_list /*ap*/); + +extern int progress_vputright(struct progress_render_state */*render*/, + const char */*fmt*/, va_list /*ap*/); + +__attribute__((format(printf, 2, 3))) +extern int progress_putleft(struct progress_render_state */*render*/, + const char */*fmt*/, ...); + +__attribute__((format(printf, 2, 3))) +extern int progress_putright(struct progress_render_state */*render*/, + const char */*fmt*/, ...); + +extern int progress_showbar(struct progress_render_state */*render*/, + double /*frac*/); + +extern int progress_shownotice(struct progress_render_state */*render*/, + int /*bg*/, int /*fg*/); + +#endif -- 2.11.0