From 7fbe0fb9be3e01363235cfbc2a3225a144e9774b Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Mon, 7 Feb 2022 21:00:16 +0000 Subject: [PATCH] Initial commit. --- .gitignore | 1 + Makefile | 71 +++++ dvd-sector-copy.c | 859 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ dvdrip | 155 ++++++++++ dvdrip-upload | 127 ++++++++ 5 files changed, 1213 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 dvd-sector-copy.c create mode 100755 dvdrip create mode 100644 dvdrip-upload diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ee38724 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +obj.*/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..784770d --- /dev/null +++ b/Makefile @@ -0,0 +1,71 @@ +### -*-makefile-*- + +all: +clean:: + +.SECONDEXPANSION: # sorry + +bindir = ../bin + +V = 0 +V_AT = $(V_AT_$V) +V_AT_0 = @ +v-tag = $(call v-tag_$V,$1) +v-tag_0 = @printf " %-12s %s\n" "$1" "$@"; + +CC = gcc +CFLAGS = -O2 -g -pedantic -Wall -Werror + +LD = gcc +LDFLAGS = + +ARCH += i686 +CFLAGS_i386 = -m32 + +ARCH += x86_64 +CFLAGS_amd64 = -m64 + +PROGS += dvd-sector-copy +dvd-sector-copy_SRCS = dvd-sector-copy.c +dvd-sector-copy_LIBS = -ldvdread -lm + +SCRIPTS += dvdrip +SCRIPTS += dvdrip-upload + +define setup-arch +obj.$1/%.o: %.c + $$(V_AT)mkdir -p $$(dir $$@) + $$(call v-tag,CC [$1])$$(CC) -c $$(CFLAGS) $$(CFLAGS_$1) -o$$@ -MD -MF obj.$1/$$*.dep $$< +clean::; rm -rf obj.$1 +endef +$(foreach a,$(ARCH), $(eval $(call setup-arch,$a))) + +objects = $(addsuffix $(or $2,.o),$(basename $(filter %.c %.s %.S,$1))) + +ALL_PROGS += $(foreach a,$(ARCH), \ + $(foreach p,$(PROGS), $(bindir)/$p.$a)) +TARGETS += $(ALL_PROGS) +program-name = $(basename $1) +program-arch = $(patsubst .%,%,$(suffix $1)) +program-sources = $($(call program-name,$1)_SRCS) \ + $($(call program-name,$1)_SRCS_$(call program-arch,$1)) +program-objects = $(addprefix obj.$(call program-arch,$1)/, \ + $(call objects,$(call program-sources,$1),$2)) +$(ALL_PROGS): $(bindir)/%: $$(call program-objects,$$*) $$($$(call program-name,$$*)_DEPS) + $(call v-tag,LD [$(call program-arch,$*)])$(LD) \ + $(LDFLAGS) $(LDFLAGS_$(call program-arch,$*)) -o$@ \ + $(call program-objects,$*) \ + $($(call program-name,$*)_LIBS) \ + $($(call program-name,$*)_LIBS_$(call program-arch,$*)) + +ALL_SCRIPTS = $(addprefix $(bindir)/, $(SCRIPTS)) +TARGETS += $(ALL_SCRIPTS) +$(ALL_SCRIPTS): $(bindir)/%: % + $(call v-tag,SCRIPT)install $* $@ + +p:; : $p + +all: $(TARGETS) +clean::; rm -f $(TARGETS) + +-include $(foreach p,$(notdir $(ALL_PROGS)), $(call program-objects,$p,.dep)) diff --git a/dvd-sector-copy.c b/dvd-sector-copy.c new file mode 100644 index 0000000..9c1c7d8 --- /dev/null +++ b/dvd-sector-copy.c @@ -0,0 +1,859 @@ +#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 + +#define SECTORSZ 2048 +#define SECTORS(n) (((n) + (SECTORSZ - 1))/SECTORSZ) + +#define CTYPE_HACK(fn, ch) fn((unsigned char)(ch)) +#define ISDIGIT(ch) CTYPE_HACK(isdigit, ch) +#define ISSPACE(ch) CTYPE_HACK(isspace, ch) + +#define N(v) (sizeof(v)/sizeof((v)[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); } + +static const char *prog = ""; +static int status = 0; + +static void usage(FILE *fp) +{ + fprintf(fp, + "usage: %s [-c] [-D DEV] [-R MAP] " + "[-b OUTMAP] [-o OUTFILE] [-r [START]-[END]]\n", + prog); +} + +static void vmoan(const char *fmt, va_list ap) + { fprintf(stderr, "%s: ", prog); vfprintf(stderr, fmt, ap); } +__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); +} + +#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(); + } +} + +#define DEFVEC(vtype, etype) \ + typedef struct { etype *v; size_t n, sz; } vtype +#define VEC_INIT { 0, 0, 0 } +#define VEC_FREE(vv) do { \ + free((vv)->v); (vv)->v 0; (vv)->n = (vv)->sz = 0; \ +} while (0) +#define VEC_PUSH(p, vv) do { \ + size_t _want; \ + if ((vv)->n >= (vv)->sz) { \ + (vv)->sz = (vv)->sz ? 2*(vv)->sz : 32; \ + _want = (vv)->sz*sizeof(*(vv)->v); \ + (vv)->v = realloc((vv)->v, _want); \ + if (!(vv)->v) bail("out of memory allocating %zu bytes", _want); \ + } \ + (p) = &(vv)->v[(vv)->n++]; \ +} while (0) + +#define MAXFILES (1 + 2*99 + 1) +struct file { + ident id; + uint32_t start, end; +}; +DEFVEC(file_v, struct file); +static file_v filetab = VEC_INIT; + +enum { EV_WRITE, EV_BEGIN, EV_END, EV_STOP }; +struct event { + unsigned char ev, file; + uint32_t pos; +}; +DEFVEC(event_v, struct event); +static event_v eventq = VEC_INIT; + +typedef uint_least32_t bits; +static bits live[(MAXFILES + 31)/32]; + +static inline int livep(unsigned i) + { return (live[i/32]&((bits)1 << (i%32))); } +static inline void set_live(unsigned i) + { live[i/32] |= (bits)1 << (i%32); } +static inline void clear_live(unsigned i) + { live[i/32] &= ~((bits)1 << (i%32)); } +static inline int least_live(void) +{ + unsigned i, n = (filetab.n + 32)/32; + bits b; + + for (i = 0; i < n; i++) { b = live[i]; if (b) goto found; } + return (-1); +found: + i *= 32; + if (!(b&0x0000ffff)) { b >>= 16; i += 16; } + if (!(b&0x000000ff)) { b >>= 8; i += 8; } + if (!(b&0x0000000f)) { b >>= 4; i += 4; } + if (!(b&0x00000003)) { b >>= 2; i += 2; } + if (!(b&0x00000001)) { b >>= 1; i += 1; } + assert(b&1); + return (i); +} + +static void put_event(unsigned evtype, unsigned file, uint32_t pos) +{ + struct event *ev; + + VEC_PUSH(ev, &eventq); + ev->ev = evtype; ev->file = file; ev->pos = pos; +} + +static void put_file(ident id, uint32_t start, uint32_t end) +{ + struct file *f; + size_t i; + + VEC_PUSH(f, &filetab); i = f - filetab.v; + f->id = id; f->start = start; f->end = end; + put_event(EV_BEGIN, i, start); + put_event(EV_END, i, end); +} + +static void put_menu(dvd_reader_t *dvd, unsigned title) +{ + ident id = mkident(VOB, title, 0); + char fn[MAXFNSZ]; + uint32_t start, len; + + store_filename(fn, id); + start = UDFFindFile(dvd, fn, &len); if (!start) return; +#ifdef DEBUG + printf(";; %8"PRIu32" .. %-8"PRIu32": %s\n", + start, start + SECTORS(len), fn); +#endif + put_file(id, start, start + SECTORS(len)); +} + +static void put_title(dvd_reader_t *dvd, unsigned title) +{ + char fn[MAXFNSZ]; + uint32_t start[9], len[9]; + unsigned i, npart; + + for (i = 0; i < 9; i++) { + store_filename(fn, mkident(VOB, title, i + 1)); + start[i] = UDFFindFile(dvd, fn, &len[i]); if (!start[i]) break; + } + npart = i; if (!npart) return; + +#ifdef DEBUG + for (i = 0; i < npart; i++) { + store_filename(fn, mkident(VOB, title, i + 1)); + printf(";; %8"PRIu32" .. %-8"PRIu32": %s\n", + start[i], start[i] + SECTORS(len[i]), fn); + } +#endif + + if (npart > 1) + for (i = 0; i < npart - 1; i++) { + if (len[i]%SECTORSZ) + bail("title %u part %u length = %"PRIu32" not a multiple of %d", + title, i, len[i], SECTORSZ); + if (start[i] + len[i]/SECTORSZ != start[i + 1]) + bail("title %u part %u end = %"PRIu32" /= part %u start = %"PRIu32"", + title, i, start[i] + len[i]/SECTORSZ, i + 1, start[i + 1]); + } + + put_file(mkident(VOB, title, 1), + start[0], start[npart - 1] + SECTORS(len[npart - 1])); +} + +static int compare_event(const void *a, const void *b) +{ + const struct event *eva = a, *evb = b; + + if (eva->pos < evb->pos) return (-1); + else if (eva->pos > evb->pos) return (+1); + + if (eva->ev < evb->ev) return (-1); + else if (eva->ev > evb->ev) return (+1); + + if (eva->file < evb->file) return (-1); + else if (eva->file > evb->file) return (+1); + + return (0); +} + +static int progresslen = 0; + +static void clear_progress_internal(void) + { while (progresslen) { fputs("\b \b", stdout); progresslen--; } } +static void clear_progress(void) + { clear_progress_internal(); fflush(stdout); } +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, ...) +{ + va_list ap; + + va_start(ap, fmt); + vappend_progress(fmt, ap); + va_end(ap); +} +__attribute__((format(printf, 1, 2))) +static void print_progress(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + clear_progress_internal(); + vappend_progress(fmt, ap); + va_end(ap); +} + +struct source { + dvd_reader_t *dvd; + int dvdfd; + struct file *file; + dvd_file_t *vob; + + uint32_t last_pos, limit, nsectors, ndone; + struct timeval last_time; + double wsum, wcount; + const char *mapfile; FILE *mapfp; +}; +#define SOURCE_INIT { 0, -1, 0, 0, 0, 0, 0, 0, { 0, 0 }, 0.0, 0.0, 0, 0 } + +static void report_progress(struct source *src, uint32_t pos) +{ + char etastr[32]; + struct timeval now; + int eta; + double t, f, g, rate; + char *unit; + +#define ALPHA 0.02 +#define BETA (1 - ALPHA) + + gettimeofday(&now, 0); + t = (now.tv_sec - src->last_time.tv_sec) + + (now.tv_usec - src->last_time.tv_usec)/1000000.0; + + if (t) { + g = src->wcount ? pow(BETA, t) : 0.0; f = (1 - g)/(1 - BETA); + src->wsum = f*(pos - src->last_pos)/t + g*src->wsum; + src->wcount = f + g*src->wcount; + src->ndone += pos - src->last_pos; + src->last_time = now; src->last_pos = pos; + } + + if (!src->wsum || !src->wcount) + { rate = 0; strcpy(etastr, "???"); } + else { + rate = src->wsum/src->wcount; + eta = (int)((src->nsectors - src->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"; } + + print_progress("copied %.1f%% (%"PRIu32" of %"PRIu32"; %.1f %sB/s, ETA %s)", + src->ndone*100.0/src->nsectors, pos, src->limit, + rate, unit, etastr); + if (src->file && id_kind(src->file->id) == VOB) { + append_progress(" -- %s %d %3.1f%%", + id_part(src->file->id) ? "title" : "menu", + id_title(src->file->id), + (pos - src->file->start)*100.0/ + (src->file->end - src->file->start)); + } + +#undef ALPHA +#undef BETA +} + +static void report_bad_blocks_progress(struct source *src, + uint32_t lo, uint32_t hi, int err) +{ + report_progress(src, hi); + + if (lo == hi) append_progress(": retrying bad sector"); + else + append_progress(": %"PRIu32" bad %s", + hi - lo, hi == lo + 1 ? "sector" : "sectors"); + if (err != EIO) append_progress(" (%s)", strerror(err)); + fflush(stdout); +} + +static ssize_t read_sectors(struct source *src, uint32_t pos, + void *buf, uint32_t want) +{ + ssize_t n; + +again: + if (src->vob) + n = DVDReadBlocks(src->vob, pos - src->file->start, want, buf); + else if (src->file) { + if (lseek(src->dvdfd, (off_t)pos*SECTORSZ, SEEK_SET) < 0) + bail_syserr(errno, "failed to seek to sector %"PRIu32"", pos); + n = read(src->dvdfd, buf, want*SECTORSZ); + if (n >= 0) n /= SECTORSZ; + } else { + memset(buf, 0, want*SECTORSZ); + n = want; + } + + if (n < 0 && errno == EINTR) goto again; + return (n); +} + +static void carefully_write(int fd, const void *buf, size_t sz) +{ + const unsigned char *p = buf; + ssize_t n; + + if (fd < 0) return; + while (sz) { + n = write(fd, p, sz); + if (n < 0) { + if (errno == EINTR) continue; + bail_syserr(errno, "failed to write to output file"); + } + if (!n) bail("unexpected short write to output file"); + p += n; sz -= n; + } +} + +static void emit(struct source *src, int outfd, uint32_t start, uint32_t end) +{ +#define BUFSECTORS 512 + + int least, i; + unsigned char buf[BUFSECTORS*SECTORSZ]; + uint32_t pos; + uint32_t bad_lo, bad_hi, good, step; + size_t want; + ssize_t n; + static int first_time = 1; +#ifdef DEBUG + struct file *f; + char fn[MAXFNSZ]; + int act = -1; +#endif + + least = least_live(); + +#ifdef DEBUG + printf(";; %8"PRIu32" .. %"PRIu32"\n", start, end); + + for (i = 0; i < filetab.n; i++) { + if (!livep(i)) continue; + if (act == -1) act = i; + f = &filetab.v[i]; store_filename(fn, f->id); + printf(";;\t\t%8"PRIu32" .. %-8"PRIu32" %s\n", + start - f->start, end - f->start, fn); + } + if (act == -1) printf(";;\t\t#\n"); + assert(act == least); +#endif + + if (least == -1) + { src->file = 0; src->vob = 0; } + else { + src->file = &filetab.v[least]; + switch (id_kind(src->file->id)) { + case RAW: + src->vob = 0; + break; + case VOB: + if (first_time) { clear_progress(); first_time = 0; } + src->vob = DVDOpenFile(src->dvd, id_title(src->file->id), + id_part(src->file->id) + ? DVD_READ_TITLE_VOBS + : DVD_READ_MENU_VOBS); + if (!src->vob) + bail("failed to open %s %u", + id_part(src->file->id) ? "title" : "menu", + id_title(src->file->id)); + break; + default: + abort(); + } + } + + pos = start; + while (pos < end) { + want = end - pos; if (want > BUFSECTORS) want = BUFSECTORS; + n = read_sectors(src, pos, buf, want); + + if (n <= 0) { + report_bad_blocks_progress(src, pos, pos, errno); + for (i = 0; i < 4; i++) { + n = read_sectors(src, pos, buf, 1); + if (n > 0) { + clear_progress(); + fprintf(stderr, "%s: sector %"PRIu32" read ok after retry\n", + prog, pos); + bad_lo = bad_hi = pos; + goto recovered; + } + } + + bad_lo = pos; step = 1; bad_hi = pos + 1; + for (;;) { + report_bad_blocks_progress(src, bad_lo, bad_hi, errno); + if (bad_hi >= end) { + clear_progress(); + fprintf(stderr, "%s: giving up on this extent\n", prog); + n = 0; goto recovered; + } + step *= 2; + if (step > end - bad_lo) step = end - bad_lo; + pos = bad_lo + step - 1; + n = read_sectors(src, pos, buf, 1); + if (n > 0) break; + bad_hi = pos + 1; + } + + good = pos; + while (good > bad_hi) { + report_bad_blocks_progress(src, bad_lo, bad_hi, errno); + pos = bad_hi + (good - bad_hi)/2; + n = read_sectors(src, pos, buf, 1); + if (n > 0) good = pos; + else bad_hi = pos + 1; + } + recovered: + if (bad_hi > bad_lo) { + clear_progress(); + fprintf(stderr, "%s: skipping %"PRIu32" bad sectors " + "(%"PRIu32" .. %"PRIu32")\n", + prog, bad_hi - bad_lo, bad_lo, bad_hi); + if (src->mapfile) { + if (!src->mapfp) { + src->mapfp = fopen(src->mapfile, "w"); + if (!src->mapfp) + bail_syserr(errno, "failed to open bad-sector map file `%s'", + optarg); + fprintf(src->mapfp, "## bad sector map\n\n"); + } + fprintf(src->mapfp, "%"PRIu32" %"PRIu32"\n", bad_lo, bad_hi); + fflush(src->mapfp); + if (ferror(src->mapfp)) + bail_syserr(errno, "error writing bad-sector map file"); + } + if (outfd >= 0 && + lseek(outfd, (off_t)(bad_hi - bad_lo)*SECTORSZ, SEEK_CUR) < 0) + bail_syserr(errno, "failed to seek past bad sectors"); + status = 1; + } + pos = bad_hi; + } + + if (n > 0) { carefully_write(outfd, buf, n*SECTORSZ); pos += n; } + report_progress(src, pos); fflush(stdout); + } + + if (src->vob) { DVDCloseFile(src->vob); src->vob = 0; } + +#undef BUFSECTORS +} + +#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 + +struct buf { + char *p; + size_t n, sz; +}; +#define BUF_INIT { 0, 0, 0 } +#define BUF_REWIND(b) do { (b)->n = 0; } while (0) +#define BUF_FREE(b) do { \ + buf *_b = (b); \ + free(_b->p); _b->p = 0; _b->n = _b->sz = 0; \ +} while (0) +#define BUF_PUTC(b, ch) do { \ + struct buf *_b = (b); \ + if (_b->n >= _b->sz) { \ + _b->sz = _b->sz ? 2*_b->sz : 32; \ + _b->p = realloc(_b->p, _b->sz); \ + if (!_b->p) bail("out of memory allocating %zu bytes", _b->sz); \ + } \ + _b->p[_b->n] = (ch); \ +} while (0) + +static int read_line(FILE *fp, struct buf *b) +{ + int ch; + + ch = getc(fp); + if (ch == EOF) + return (-1); + else if (ch != '\n') do { + BUF_PUTC(b, ch); b->n++; + ch = getc(fp); + } while (ch != EOF && ch != '\n'); + BUF_PUTC(b, 0); + return (0); +} + +int main(int argc, char *argv[]) +{ + unsigned f = 0; + char *p; + uint64_t volsz; + uint32_t pos; + off_t off; + struct source src = SOURCE_INIT; + unsigned long start, end; + const struct event *ev; + const char *device = "/dev/dvd", *outfile = 0; + int opt, err, outfd = -1, blksz; + size_t i; + FILE *fp; + struct buf buf = BUF_INIT; +#ifdef DEBUG + const struct file *file; + char fn[MAXFNSZ]; +#endif + +#define f_bogus 1u +#define f_continue 2u +#define f_fixup 4u +#define f_write 256u + + p = strrchr(argv[0], '/'); prog = p ? p + 1 : argv[0]; + for (;;) { + opt = getopt(argc, argv, "hD:FR:b:co:r:"); if (opt < 0) break; + switch (opt) { + case 'h': usage(stderr); exit(0); + case 'D': device = optarg; break; + case 'F': f |= f_fixup; break; + case 'R': + fp = fopen(optarg, "r"); + if (!fp) + bail_syserr(errno, "failed to open ranges file `%s'", optarg); + i = 0; + for (;;) { + BUF_REWIND(&buf); if (read_line(fp, &buf)) break; + p = buf.p; i++; + while (ISSPACE(*p)) p++; + if (!*p || *p == '#') continue; + if (!ISDIGIT(*p)) goto bad_range_file; + start = strtoul(p, &p, 0); + if (errno || !ISSPACE(*p)) goto bad_range_file; + do p++; while (ISSPACE(*p)); + if (!ISDIGIT(*p)) goto bad_range_file; + end = strtoul(p, &p, 0); + if (errno || (*p && !ISSPACE(*p))) goto bad_range_file; + while (ISSPACE(*p)) p++; + if (*p) goto bad_range_file; + if (start > end) goto bad_range_file; + if (start < end) { + put_event(EV_WRITE, 0, start); + put_event(EV_STOP, 0, end); + } + } + if (ferror(fp)) + bail_syserr(errno, "failed to read ranges file `%s'", optarg); + break; + bad_range_file: + bail("bad range `%s' at `%s' line %zu", buf.p, optarg, i); + case 'b': + if (src.mapfile) bail("can't have multiple map files"); + src.mapfile = optarg; + break; + case 'c': f |= f_continue; break; + case 'o': outfile = optarg; break; + case 'r': + err = errno; errno = 0; + p = optarg; + if (*p == '-') + start = 0; + else { + if (!ISDIGIT(*p)) goto bad_range; + start = strtoul(p, &p, 0); + if (errno || *p != '-') goto bad_range; + } + p++; + if (!*p) + put_event(EV_WRITE, 0, start); + else { + if (!ISDIGIT(*p)) goto bad_range; + end = strtoul(p, &p, 0); + if (errno || *p) goto bad_range; + if (start > end) goto bad_range; + if (start < end) { + put_event(EV_WRITE, 0, start); + put_event(EV_STOP, 0, end); + } + } + errno = err; + break; + bad_range: + bail("bad range `%s'", optarg); + break; + default: f |= f_bogus; break; + } + } + if (optind < argc) f |= f_bogus; + if (f&f_bogus) { usage(stderr); exit(2); } + + src.dvdfd = open(device, O_RDONLY); + if (src.dvdfd < 0) bail_syserr(errno, "failed to open device `%s'", device); + if (ioctl(src.dvdfd, BLKSSZGET, &blksz)) + bail_syserr(errno, "failed to get block size for `%s'", device); + if (ioctl(src.dvdfd, BLKGETSIZE64, &volsz)) + bail_syserr(errno, "failed to get volume size for `%s'", device); + + if (blksz != SECTORSZ) + bail("device `%s' block size %d /= %d", device, blksz, SECTORSZ); + if (volsz%SECTORSZ) + bail("device `%s' volume size %"PRIu64" not a multiple of %d", + device, volsz, SECTORSZ); + + if (outfile) { + outfd = open(outfile, O_WRONLY | O_CREAT, 0666); + if (outfd < 0) + bail_syserr(errno, "failed to create output file `%s'", outfile); + } + + if (f&f_continue) { + if (!outfile) bail("can't continue without output file"); + off = lseek(outfd, 0, SEEK_END); + if (off < 0) + bail_syserr(errno, "failed to seek to end of output file `%s'", + outfile); + put_event(EV_WRITE, 0, off/SECTORSZ); + } else if (!eventq.n && !(f&f_fixup)) + put_event(EV_WRITE, 0, 0); + +#ifdef notdef + src.dvd = DVDOpen2(0, &logger, device); +#else + src.dvd = DVDOpen(device); +#endif + if (!src.dvd) bail("failed to open DVD on `%s'", device); + + /* It's fast enough just to check everything. */ + put_menu(src.dvd, 0); + for (i = 1; i < 100; i++) { + put_menu(src.dvd, i); + put_title(src.dvd, i); + } + put_file(mkident(RAW, 0, 0), 0, volsz/SECTORSZ); + assert(filetab.n <= MAXFILES); + + for (i = 0, src.limit = 0; i < filetab.n; i++) + if (filetab.v[i].end > src.limit) src.limit = filetab.v[i].end; + + if (end > src.limit) end = src.limit; + +#ifdef DEBUG + printf("\n;; files:\n"); + for (i = 0; i < filetab.n; i++) { + file = &filetab.v[i]; + store_filename(fn, file->id); + printf(";;\t%8"PRIu32" %s\n", file->start, fn); + } +#endif + + qsort(eventq.v, eventq.n, sizeof(struct event), compare_event); + + f &= ~f_write; start = 0; + for (i = 0; i < eventq.n; i++) { + ev = &eventq.v[i]; + switch (ev->ev) { + case EV_WRITE: + if (f&f_write) + bail("overlapping ranges: range from %lu still open at %"PRIu32"", + start, ev->pos); + f |= f_write; start = ev->pos; + break; + case EV_STOP: + f &= ~f_write; + break; + } + } + + f &= ~f_write; start = 0; + for (i = 0; i < eventq.n; i++) { + ev = &eventq.v[i]; + switch (ev->ev) { + case EV_WRITE: start = ev->pos; f |= f_write; break; + case EV_STOP: src.nsectors += ev->pos - start; f &= ~f_write; break; + } + if (ev->pos >= src.limit) break; + if (f&f_fixup) start = ev->pos; + } + eventq.n = i; + if (f&f_fixup) { + put_event(EV_WRITE, 0, start); + f |= f_write; + } + if (f&f_write) { + src.nsectors += src.limit - start; + put_event(EV_STOP, 0, src.limit); + } + f &= ~f_write; + +#ifdef DEBUG + printf("\n;; event sweep:\n"); +#endif + for (pos = 0, i = 0; i < eventq.n; i++) { + ev = &eventq.v[i]; + if (ev->pos > pos) { + if (f&f_write) emit(&src, outfd, pos, ev->pos); + pos = ev->pos; +#ifdef DEBUG + clear_progress(); + printf(";;\n"); +#endif + } + switch (ev->ev) { + case EV_BEGIN: + set_live(ev->file); +#ifdef DEBUG + store_filename(fn, filetab.v[ev->file].id); + clear_progress(); + printf(";; %8"PRIu32": begin `%s'\n", pos, fn); +#endif + break; + case EV_WRITE: + gettimeofday(&src.last_time, 0); src.last_pos = pos; + if (outfd >= 0 && + lseek(outfd, (off_t)ev->pos*SECTORSZ, SEEK_SET) < 0) + bail_syserr(errno, + "failed to seek to resume position " + "(sector %"PRIu32") in output file `%s'", + ev->pos, outfile); +#ifdef DEBUG + clear_progress(); + printf(";; %8"PRIu32": begin write\n", pos); +#endif + f |= f_write; + break; + case EV_STOP: + f &= ~f_write; +#ifdef DEBUG + clear_progress(); + printf(";; %8"PRIu32": end write\n", pos); +#endif + break; + case EV_END: + clear_live(ev->file); +#ifdef DEBUG + store_filename(fn, filetab.v[ev->file].id); + clear_progress(); + printf(";; %8"PRIu32": end `%s'\n", pos, fn); +#endif + break; + default: abort(); + } + } + + if (progresslen) putchar('\n'); + + if (outfd >= 0 && ftruncate(outfd, (off_t)src.limit*SECTORSZ) < 0) + bail_syserr(errno, "failed to set output file `%s' length", outfile); + + if (src.dvd) DVDClose(src.dvd); + if (src.dvdfd >= 0) close(src.dvdfd); + if (outfd >= 0) close(outfd); + if (src.mapfp) { + if (ferror(src.mapfp) || fclose(src.mapfp)) + bail_syserr(errno, "error writing bad-sector map file"); + } + +#undef f_bogus +#undef f_continue +#undef f_fixup +#undef f_write + + return (status); +} diff --git a/dvdrip b/dvdrip new file mode 100755 index 0000000..27e96e4 --- /dev/null +++ b/dvdrip @@ -0,0 +1,155 @@ +#! /bin/bash -e + +prog=${0##*/} +dev=${DVDRIP_DEVICE-/dev/dvd} +tmp=${DVDRIP_TMPDIR-${HOME?}/tmp/dvdrip} +archive=${DVDRIP_ARCHIVE-jem.distorted.org.uk:/mnt/dvd/archive} +here=$(realpath "$0"); here=${here%/*} +: ${DVD_SECTOR_COPY=$here/dvd-sector-copy.$(uname -m)} +: ${DVDRIP_UPLOAD=$here/dvdrip-upload} +backup=nil eject=nil force=nil verbose=nil bogus=nil; unset dir sub n label +usage () { + cat <&2; exit 2 ;; esac +case $verbose in t) set -x ;; esac +case $archive in + *:*) archhost=${archive%%:*} archpath=${archive#*:} ;; + *) unset archhost; archpath=$archive ;; +esac + +notify () { + colour=$1 message=$2 + echo "$(tput bold; tput setaf $colour)$message$(tput sgr0; tput op)" +} +fail () { notify 1 "!!! $*"; exit 2; } +warn () { notify 5 "??? $*"; } +info () { notify 6 "--- $*"; } +run_setrc () { + notify 2 "+++ $*"; + set +e; nice "$@"; rc=$?; set -e +} +run () { run_setrc "$@"; case $rc in 0) ;; *) fail "$1: exit $rc" ;; esac; } + +archdo () { + op=$1; shift + case ${archhost+t} in + t) + qq= + for a in "$@"; do + qq="${qq:+$qq }'${a//\'/"'\\''"}'" #" # emacs is confused + done + "$op" ssh "$archhost" "$qq" + ;; + *) + "$op" "$@" + ;; + esac +} +archrun () { archdo run "$@"; } + +case ${dir+t},${n+t} in + t,t | ,) + n=$(printf "%02d" "$n") + ;; + *) + echo >&2 "$prog: must specify both directory and disc number, or neither" + exit 2 + ;; +esac + +hack_label () { + tr "[:lower:]" "[:upper:]" | + tr -Cs "[:alnum:]_\n" "[-*]" | + sed 's/^-//; s/-$//' +} + +case $backup in + t) + case ${label+t},${dir+t} in + t,*) ;; + ,) label=$(printf "%s" "$title" | hack_label) ;; + ,t) label=$(printf "%s_%s" "$dir" "$n" | hack_label) ;; + esac + len=$(printf "%s" "$label" | wc -c) + if [ $len -gt 32 ]; then echo >&2 "$prog: label too long"; exit 2; fi + ;; + nil) + case ${label+t} in + t) echo >&2 "$prog: label only meaningful to \`dvdbackup'"; exit 2 ;; + esac + ;; +esac + +case ${dir+t} in + t) tag="${dir}_${n}_${title}" out="$dir/$n. $title" ;; + *) tag=$title out=$title ;; +esac + +archdo run_setrc test -f "$archpath${sub+/$sub}/$out.iso" +case $rc,$force in + 0,nil) fail "output file already exists" ;; + 0,t) warn "output file already exists; will overwrite" ;; +esac + +mkdir -p "$tmp/$tag" +case $backup in + t) + if [ ! -d "$tmp/$tag/rip" ]; then + rm -rf "$tmp/$tag/rip.new" + run dvdbackup -Mp -i"$dev" -o"$tmp/$tag" -n"rip.new" + run mv "$tmp/$tag/rip.new" "$tmp/$tag/rip" + fi + if [ ! -f "$tmp/$tag/iso" ]; then + run genisoimage -quiet -dvd-video -udf -V "$label" \ + -o "$tmp/$tag/iso.new" "$tmp/$tag/rip" + run mv "$tmp/$tag/iso.new" "$tmp/$tag/iso" + fi + ;; + nil) + if [ ! -f "$tmp/$tag/iso" ]; then + run_setrc "$DVD_SECTOR_COPY" -D"$dev" -c -b"$tmp/$tag/badblocks" -o"$tmp/$tag/iso.new" + case $rc in + 0) + run mv "$tmp/$tag/iso.new" "$tmp/$tag/iso" + ;; + 1) + run mv "$tmp/$tag/iso.new" "$tmp/$tag/iso" + fail "bad sectors found: check \`$tmp/$tag/iso', run again if ok" + ;; + *) + fail "$DVD_SECTOR_COPY: exit $rc" + ;; + esac + fi + ;; +esac +printf "%s\n" "${sub+$sub/}$out.iso" >"$tmp/$tag/dest.new" +mv "$tmp/$tag/dest.new" "$tmp/$tag/dest" +run "$DVDRIP_UPLOAD" +case $eject in t) run eject "$dev" ;; esac diff --git a/dvdrip-upload b/dvdrip-upload new file mode 100644 index 0000000..c8a1f6f --- /dev/null +++ b/dvdrip-upload @@ -0,0 +1,127 @@ +#! /bin/bash -e + +prog=${0##*/} +tmp=${DVDRIP_TMPDIR-${HOME?}/tmp/dvdrip} +archive=${DVDRIP_ARCHIVE-jem.distorted.org.uk:/mnt/dvd/archive} +kill=nil listen=nil verbose=nil bogus=nil opts=; unset dir sub n label +usage () { + cat <&2; exit 2 ;; esac +case $verbose in t) set -x ;; esac + +case $kill in + t) echo quit >"$tmp/upload.wakeup"; exit 0 ;; +esac + +case ${DVDRIP_UPLOAD_LOCKEDP+t} in + t) lockedp=t ;; + *) lockedp=nil; DVDRIP_UPLOAD_LOCKEDP=t; export DVDRIP_UPLOAD_LOCKEDP ;; +esac +case $lockedp,$listen in + t,*) ;; + nil,nil) + if [ -p "$tmp/upload.wakeup" ]; then + echo check >"$tmp/upload.wakeup" + exit 0 + else + exec locking "$tmp/upload.lock" "$0" "${opts+-$opts}" + fi + exit 2 + ;; + nil,t) + exec locking -f "$tmp/upload.lock" "$0" "${opts+-$opts}" + exit 2 + ;; +esac + +notify () { + colour=$1 message=$2 + echo "$(tput bold; tput setaf $colour)$message$(tput sgr0; tput op)" +} +fail () { notify 1 "!!! $*"; exit 2; } +warn () { notify 5 "??? $*"; } +info () { notify 6 "--- $*"; } +run_setrc () { + notify 2 "+++ $*"; + set +e; nice "$@"; rc=$?; set -e +} +run () { run_setrc "$@"; case $rc in 0) ;; *) fail "$1: exit $rc" ;; esac; } + +case $archive in + *:*) archhost=${archive%%:*} archpath=${archive#*:} ;; + *) unset archhost; archpath=$archive ;; +esac + +archdo () { + op=$1; shift + case ${archhost+t} in + t) + qq= + for a in "$@"; do + qq="${qq:+$qq }'${a//\'/"'\\''"}'" #" # emacs is confused + done + "$op" ssh "$archhost" "$qq" + ;; + *) + "$op" "$@" + ;; + esac +} +archrun () { archdo run "$@"; } + +check () { + while :; do + any=nil + for i in "$tmp"/*; do + if [ -f "$i/dest" ]; then + read dest <"$i/dest"; any=t + mv "$i/dest" "$i/dest.seen" + (info "copy $i/iso -> $dest" + case $dest in + */*) dir=${dest%/*} ;; + *) dir= ;; + esac + archrun mkdir -p "$archpath${dir:+/$dir}" + run rsync -svP "$i/iso" "$archive/$dest" + run rm -rf "$i") || : + fi + done + case $any in nil) break ;; esac + done +} + +case $listen in + nil) + check + ;; + t) + rm -f "$tmp/upload.newpipe" + mkfifo "$tmp/upload.newpipe" + mv "$tmp/upload.newpipe" "$tmp/upload.wakeup" + while read line <&3; do + case $line in + check) check ;; + quit) info "quitting on user request"; break ;; + *) warn "unknown command \`$line'" ;; + esac + done 3<>"$tmp/upload.wakeup" + rm -f "$tmp/upload.wakeup" "$tmp/upload.lock" + ;; +esac -- 2.11.0