*.dep
*.o
+/dvd-cache-keys
+/dvd-id
/dvd-sector-copy
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
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)
--- /dev/null
+#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);
+}
--- /dev/null
+#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, "<error reading volume info>");
+ 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);
+}
-#define _GNU_SOURCE
-#define _FILE_OFFSET_BITS 64
-
-#include <assert.h>
-#include <ctype.h>
-#include <errno.h>
-#include <inttypes.h>
-#include <math.h>
-#include <stdarg.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-
-#include <unistd.h>
-#include <fcntl.h>
-#include <sys/ioctl.h>
-#include <sys/stat.h>
-#include <sys/time.h>
-
-#include <getopt.h>
-
-#include <linux/fs.h>
-
-#include <dvdread/dvd_reader.h>
-#include <dvdread/dvd_udf.h>
-#include <dvdread/ifo_read.h>
-#include <dvdread/ifo_types.h>
-
-#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 = "<unset>";
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)
(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, "#<raw device>");
- 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;
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;
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;
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;
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
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) {
} 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,
}
#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("): "
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
} 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);
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",
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",
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
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;
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;
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)
int main(int argc, char *argv[])
{
unsigned f = 0;
- char *p;
+ const char *p;
uint64_t volsz;
secaddr pos;
off_t off;
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;
#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':
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) {
#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)) {
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);
} 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++) {
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;
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
}
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;
"(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;
case EV_STOP:
f &= ~f_write;
#ifdef DEBUG
- debug_clear_progress();
+ progress_clear(&progress);
printf(";; %8"PRIuSEC": end write\n", pos);
#endif
break;
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;
}
}
- 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);
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 <<EOF
-usage: $prog [-efrv] [-D DEV] [-a ARCH] [-t TMP] TITLE
+usage: $prog [-defrv] [-D DEV] [-a ARCH] [-t TMP] TITLE
EOF
}
-while getopts "hD:a:efrt:v" opt; do
+while getopts "hB:D:a:defrt:v" opt; do
case $opt in
h) usage; exit 0 ;;
+ B) params=${params+$params,}$OPTARG ;;
D) dev=$OPTARG ;;
a) archive=$OPTARG ;;
+ d) ding=t ;;
e) eject=t ;;
f) force=t ;;
r) retry=t ;;
0,t) warn "output file already exists; will overwrite" ;;
esac
+mkdir -p "$tmp/$tag"
+
+discid=$(dvd-id "$dev")
+if [ -f "$tmp/$tag/discid" ]; then
+ read oldid <"$tmp/$tag/discid"
+ case $force,$oldid in
+ t,"$discid" | nil,"$discid")
+ ;;
+ nil,*)
+ fail "discid mismatch: expected \`$oldid' but drive has \`$discid'"
+ ;;
+ t,*)
+ warn "discid mismatch: expected \`$oldid' but drive has \`$discid'; continuing anway"
+ ;;
+ esac
+fi
+info "copying \`$discid'"
+echo "$discid" >"$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
}
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
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
;;
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
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
--- /dev/null
+#include "lib.h"
+
+const char *prog = "<unset>";
+
+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, "#<raw device>");
+ 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);
+}
--- /dev/null
+#ifndef LIB_H
+#define LIB_H
+
+#define _GNU_SOURCE
+#define _FILE_OFFSET_BITS 64
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <locale.h>
+#include <math.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <getopt.h>
+
+#include <linux/fs.h>
+
+#include <dvdread/dvd_reader.h>
+#include <dvdread/dvd_udf.h>
+#include <dvdread/ifo_read.h>
+#include <dvdread/ifo_types.h>
+
+#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
--- /dev/null
+#define _XOPEN_SOURCE
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+#include <unistd.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+
+#if defined(USE_TERMINFO)
+# include <curses.h>
+# include <term.h>
+#elif defined(USE_TERMCAP)
+# include <termcap.h>
+#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);
+}
--- /dev/null
+#ifndef MULTIPROGRESS_H
+#define MULTIPROGRESS_H
+
+#include <stdio.h>
+#include <sys/time.h>
+
+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