2 #define _FILE_OFFSET_BITS 64
18 #include <sys/ioctl.h>
26 #include <dvdread/dvd_reader.h>
27 #include <dvdread/dvd_udf.h>
28 #include <dvdread/ifo_read.h>
29 #include <dvdread/ifo_types.h>
31 #define CTYPE_HACK(fn, ch) fn((unsigned char)(ch))
32 #define ISDIGIT(ch) CTYPE_HACK(isdigit, ch)
33 #define ISSPACE(ch) CTYPE_HACK(isspace, ch)
35 #define N(v) (sizeof(v)/sizeof((v)[0]))
38 #define SECTORS(n) (((n) + (SECTORSZ - 1))/SECTORSZ)
40 static const char *prog
= "<unset>";
41 static int status
= 0;
43 static void usage(FILE *fp
)
46 "usage: %s [-c] [-D DEV] [-R MAP] "
47 "[-b OUTMAP] [-o OUTFILE] [-r [START]-[END]]\n",
51 static void vmoan(const char *fmt
, va_list ap
)
52 { fprintf(stderr
, "%s: ", prog
); vfprintf(stderr
, fmt
, ap
); }
54 __attribute__((format(printf
, 1, 2)))
55 static void moan(const char *fmt
, ...)
59 va_start(ap
, fmt
); vmoan(fmt
, ap
); va_end(ap
);
63 __attribute__((noreturn
, format(printf
, 1, 2)))
64 static void bail(const char *fmt
, ...)
68 va_start(ap
, fmt
); vmoan(fmt
, ap
); va_end(ap
);
73 __attribute__((noreturn
, format(printf
, 2, 3)))
74 static void bail_syserr(int err
, const char *fmt
, ...)
78 va_start(ap
, fmt
); vmoan(fmt
, ap
); va_end(ap
);
79 if (err
) fprintf(stderr
, ": %s", strerror(errno
));
84 static void carefully_write(int fd
, const void *buf
, size_t sz
)
86 const unsigned char *p
= buf
;
93 if (errno
== EINTR
) continue;
94 bail_syserr(errno
, "failed to write to output file");
96 if (!n
) bail("unexpected short write to output file");
101 #define DEFVEC(vtype, etype) \
102 typedef struct { etype *v; size_t n, sz; } vtype
103 #define VEC_INIT { 0, 0, 0 }
104 #define VEC_FREE(vv) do { \
105 free((vv)->v); (vv)->v 0; (vv)->n = (vv)->sz = 0; \
107 #define VEC_PUSH(p, vv) do { \
109 if ((vv)->n >= (vv)->sz) { \
110 (vv)->sz = (vv)->sz ? 2*(vv)->sz : 32; \
111 _want = (vv)->sz*sizeof(*(vv)->v); \
112 (vv)->v = realloc((vv)->v, _want); \
113 if (!(vv)->v) bail("out of memory allocating %zu bytes", _want); \
115 (p) = &(vv)->v[(vv)->n++]; \
118 enum { RAW
, IFO
, VOB
, BUP
};
119 typedef uint_least32_t ident
;
121 static inline ident
mkident(unsigned kind
, unsigned title
, unsigned part
)
122 { return (((ident
)kind
<< 0) | ((ident
)title
<< 8) | ((ident
)part
<< 16)); }
123 static inline unsigned id_kind(ident id
) { return ((id
>> 0)&0x0ff); }
124 static inline unsigned id_title(ident id
) { return ((id
>> 8)&0x0ff); }
125 static inline unsigned id_part(ident id
) { return ((id
>> 16)&0x0ff); }
127 #define MAXFNSZ (1 + 8 + 1 + 12 + 1)
129 static void store_filename(char *buf
, ident id
)
131 switch (id_kind(id
)) {
133 sprintf(buf
, "#<raw device>");
136 if (!id_title(id
)) sprintf(buf
, "/VIDEO_TS/VIDEO_TS.IFO");
137 else sprintf(buf
, "/VIDEO_TS/VTS_%02u_0.IFO", id_title(id
));
140 if (!id_title(id
)) sprintf(buf
, "/VIDEO_TS/VIDEO_TS.BUP");
141 else sprintf(buf
, "/VIDEO_TS/VTS_%02u_0.BUP", id_title(id
));
144 if (!id_title(id
)) sprintf(buf
, "/VIDEO_TS/VIDEO_TS.VOB");
146 sprintf(buf
, "/VIDEO_TS/VTS_%02u_%u.VOB", id_title(id
), id_part(id
));
153 typedef uint_least32_t secaddr
;
154 #define PRIuSEC PRIuLEAST32
155 #define SECLIMIT 0x00400000
157 #define MAXFILES (1 + 2*99 + 1)
162 DEFVEC(file_v
, struct file
);
163 static file_v filetab
= VEC_INIT
;
165 enum { EV_STOP
, EV_BEGIN
, EV_END
, EV_WRITE
};
167 unsigned char ev
, file
;
170 DEFVEC(event_v
, struct event
);
171 static event_v eventq
= VEC_INIT
;
173 static int compare_event(const void *a
, const void *b
)
175 const struct event
*eva
= a
, *evb
= b
;
177 if (eva
->pos
< evb
->pos
) return (-1);
178 else if (eva
->pos
> evb
->pos
) return (+1);
180 if (eva
->ev
< evb
->ev
) return (-1);
181 else if (eva
->ev
> evb
->ev
) return (+1);
183 if (eva
->file
< evb
->file
) return (-1);
184 else if (eva
->file
> evb
->file
) return (+1);
189 typedef uint_least32_t bits
;
190 static bits live
[(MAXFILES
+ 31)/32];
192 static inline int livep(unsigned i
)
193 { return (live
[i
/32]&((bits
)1 << (i
%32))); }
194 static inline void set_live(unsigned i
)
195 { live
[i
/32] |= (bits
)1 << (i
%32); }
196 static inline void clear_live(unsigned i
)
197 { live
[i
/32] &= ~((bits
)1 << (i
%32)); }
198 static inline int least_live(void)
200 unsigned i
, n
= (filetab
.n
+ 32)/32;
203 for (i
= 0; i
< n
; i
++) { b
= live
[i
]; if (b
) goto found
; }
207 if (!(b
&0x0000ffff)) { b
>>= 16; i
+= 16; }
208 if (!(b
&0x000000ff)) { b
>>= 8; i
+= 8; }
209 if (!(b
&0x0000000f)) { b
>>= 4; i
+= 4; }
210 if (!(b
&0x00000003)) { b
>>= 2; i
+= 2; }
211 if (!(b
&0x00000001)) { b
>>= 1; i
+= 1; }
216 static void put_event(unsigned evtype
, unsigned file
, secaddr pos
)
220 VEC_PUSH(ev
, &eventq
);
221 ev
->ev
= evtype
; ev
->file
= file
; ev
->pos
= pos
;
224 static void put_file(ident id
, secaddr start
, secaddr end
)
229 VEC_PUSH(f
, &filetab
); i
= f
- filetab
.v
;
230 f
->id
= id
; f
->start
= start
; f
->end
= end
;
231 put_event(EV_BEGIN
, i
, start
);
232 put_event(EV_END
, i
, end
);
235 static void put_menu(dvd_reader_t
*dvd
, unsigned title
)
237 ident id
= mkident(VOB
, title
, 0);
241 store_filename(fn
, id
);
242 start
= UDFFindFile(dvd
, fn
, &len
); if (!start
) return;
244 printf(";; %8"PRIuSEC
" .. %-8"PRIuSEC
": %s\n",
245 start
, start
+ SECTORS(len
), fn
);
247 put_file(id
, start
, start
+ SECTORS(len
));
250 static void put_title(dvd_reader_t
*dvd
, unsigned title
)
253 secaddr start
[9], len
[9];
256 for (i
= 0; i
< 9; i
++) {
257 store_filename(fn
, mkident(VOB
, title
, i
+ 1));
258 start
[i
] = UDFFindFile(dvd
, fn
, &len
[i
]); if (!start
[i
]) break;
260 npart
= i
; if (!npart
) return;
263 for (i
= 0; i
< npart
; i
++) {
264 store_filename(fn
, mkident(VOB
, title
, i
+ 1));
265 printf(";; %8"PRIuSEC
" .. %-8"PRIuSEC
": %s\n",
266 start
[i
], start
[i
] + SECTORS(len
[i
]), fn
);
271 for (i
= 0; i
< npart
- 1; i
++) {
273 bail("title %u part %u length = %"PRIuSEC
" not a multiple of %d",
274 title
, i
, len
[i
], SECTORSZ
);
275 if (start
[i
] + len
[i
]/SECTORSZ
!= start
[i
+ 1])
277 ("title %u part %u end = %"PRIuSEC
" /= part %u start = %"PRIuSEC
"",
278 title
, i
, start
[i
] + len
[i
]/SECTORSZ
, i
+ 1, start
[i
+ 1]);
281 put_file(mkident(VOB
, title
, 1),
282 start
[0], start
[npart
- 1] + SECTORS(len
[npart
- 1]));
285 static int progresslen
= 0;
287 static void clear_progress_internal(void)
288 { while (progresslen
) { fputs("\b \b", stdout
); progresslen
--; } }
289 static void clear_progress(void)
290 { clear_progress_internal(); fflush(stdout
); }
291 static void vappend_progress(const char *fmt
, va_list ap
)
292 { progresslen
+= vprintf(fmt
, ap
); }
293 __attribute__((format(printf
, 1, 2)))
294 static void append_progress(const char *fmt
, ...)
299 vappend_progress(fmt
, ap
);
302 __attribute__((format(printf
, 1, 2)))
303 static void print_progress(const char *fmt
, ...)
308 clear_progress_internal();
309 vappend_progress(fmt
, ap
);
314 # define F_ALLPROGRESS 1u
315 static secaddr last_pos
, limit
, nsectors
, ndone
;
316 static struct timeval last_time
;
317 static double wsum
, wcount
;
318 static struct file
*file
;
320 static void report_progress(secaddr pos
)
325 double percent
, t
, f
, g
, rate
;
329 #define BETA (1 - ALPHA)
331 gettimeofday(&now
, 0);
332 t
= (now
.tv_sec
- last_time
.tv_sec
) +
333 (now
.tv_usec
- last_time
.tv_usec
)/1000000.0;
336 g
= wcount ?
pow(BETA
, t
) : 0.0; f
= (1 - g
)/(1 - BETA
);
337 wsum
= f
*(pos
- last_pos
)/t
+ g
*wsum
;
338 wcount
= f
+ g
*wcount
;
339 ndone
+= pos
- last_pos
;
340 last_time
= now
; last_pos
= pos
;
343 if (!wsum
|| !wcount
)
344 { rate
= 0; strcpy(etastr
, "???"); }
347 eta
= (int)((nsectors
- ndone
)/rate
);
348 sprintf(etastr
, "%d:%02d:%02d", eta
/3600, (eta
/60)%60, eta
%60);
351 rate
*= SECTORSZ
; unit
= "";
352 if (rate
> 128) { rate
/= 1024; unit
= "k"; }
353 if (rate
> 128) { rate
/= 1024; unit
= "M"; }
354 if (rate
> 128) { rate
/= 1024; unit
= "G"; }
356 if (flags
&F_ALLPROGRESS
) percent
= pos
*100.0/limit
;
357 else percent
= ndone
*100.0/nsectors
;
359 ("copied %.1f%% (%"PRIuSEC
" of %"PRIuSEC
"; %.1f %sB/s, ETA %s)",
360 percent
, pos
, limit
, rate
, unit
, etastr
);
361 if (file
&& id_kind(file
->id
) == VOB
) {
362 append_progress(" -- %s %d %3.1f%%",
363 id_part(file
->id
) ?
"title" : "menu",
365 (pos
- file
->start
)*100.0/
366 (file
->end
- file
->start
));
373 static void report_bad_blocks_progress(secaddr lo
, secaddr hi
, int err
)
377 if (lo
== hi
) append_progress(": retrying bad sector");
379 append_progress(": %"PRIuSEC
" bad %s",
380 hi
- lo
, hi
== lo
+ 1 ?
"sector" : "sectors");
381 if (err
!= EIO
) append_progress(" (%s)", strerror(err
));
385 static dvd_reader_t
*dvd
;
386 static int dvdfd
= -1, outfd
= -1;
387 static dvd_file_t
*vob
;
388 static const char *mapfile
; static FILE *mapfp
;
390 static ssize_t
read_sectors(secaddr pos
, void *buf
, secaddr want
)
396 n
= DVDReadBlocks(vob
, pos
- file
->start
, want
, buf
);
398 if (lseek(dvdfd
, (off_t
)pos
*SECTORSZ
, SEEK_SET
) < 0)
399 bail_syserr(errno
, "failed to seek to sector %"PRIuSEC
"", pos
);
400 n
= read(dvdfd
, buf
, want
*SECTORSZ
);
401 if (n
>= 0) n
/= SECTORSZ
;
403 memset(buf
, 0, want
*SECTORSZ
);
407 if (n
< 0 && errno
== EINTR
) goto again
;
411 static void emit(secaddr start
, secaddr end
)
413 #define BUFSECTORS 512
416 unsigned char buf
[BUFSECTORS
*SECTORSZ
];
418 secaddr bad_lo
, bad_hi
, good
, step
;
421 static int first_time
= 1;
428 least
= least_live();
431 printf(";; %8"PRIuSEC
" .. %"PRIuSEC
"\n", start
, end
);
433 for (i
= 0; i
< filetab
.n
; i
++) {
434 if (!livep(i
)) continue;
435 if (act
== -1) act
= i
;
436 f
= &filetab
.v
[i
]; store_filename(fn
, f
->id
);
437 printf(";;\t\t%8"PRIuSEC
" .. %-8"PRIuSEC
" %s\n",
438 start
- f
->start
, end
- f
->start
, fn
);
440 if (act
== -1) printf(";;\t\t#<no live source>\n");
441 assert(act
== least
);
445 { file
= 0; vob
= 0; }
447 file
= &filetab
.v
[least
];
448 switch (id_kind(file
->id
)) {
453 if (first_time
) { clear_progress(); first_time
= 0; }
454 vob
= DVDOpenFile(dvd
, id_title(file
->id
),
456 ? DVD_READ_TITLE_VOBS
457 : DVD_READ_MENU_VOBS
);
459 bail("failed to open %s %u",
460 id_part(file
->id
) ?
"title" : "menu",
470 want
= end
- pos
; if (want
> BUFSECTORS
) want
= BUFSECTORS
;
471 n
= read_sectors(pos
, buf
, want
);
474 report_bad_blocks_progress(pos
, pos
, errno
);
475 for (i
= 0; i
< 4; i
++) {
476 n
= read_sectors(pos
, buf
, 1);
479 moan("sector %"PRIuSEC
" read ok after retry", pos
);
480 bad_lo
= bad_hi
= pos
;
485 bad_lo
= pos
; step
= 1; bad_hi
= pos
+ 1;
487 report_bad_blocks_progress(bad_lo
, bad_hi
, errno
);
490 moan("giving up on this extent");
491 n
= 0; goto recovered
;
494 if (step
> end
- bad_lo
) step
= end
- bad_lo
;
495 pos
= bad_lo
+ step
- 1;
496 n
= read_sectors(pos
, buf
, 1);
502 while (good
> bad_hi
) {
503 report_bad_blocks_progress(bad_lo
, bad_hi
, errno
);
504 pos
= bad_hi
+ (good
- bad_hi
)/2;
505 n
= read_sectors(pos
, buf
, 1);
506 if (n
> 0) good
= pos
;
507 else bad_hi
= pos
+ 1;
510 if (bad_hi
> bad_lo
) {
512 moan("skipping %"PRIuSEC
" bad sectors (%"PRIuSEC
" .. %"PRIuSEC
")",
513 bad_hi
- bad_lo
, bad_lo
, bad_hi
);
516 mapfp
= fopen(mapfile
, "w");
518 bail_syserr(errno
, "failed to open bad-sector map file `%s'",
520 fprintf(mapfp
, "## bad sector map\n\n");
522 fprintf(mapfp
, "%"PRIuSEC
" %"PRIuSEC
"\n", bad_lo
, bad_hi
);
525 bail_syserr(errno
, "error writing bad-sector map file");
528 lseek(outfd
, (off_t
)(bad_hi
- bad_lo
)*SECTORSZ
, SEEK_CUR
) < 0)
529 bail_syserr(errno
, "failed to seek past bad sectors");
535 if (n
> 0) { carefully_write(outfd
, buf
, n
*SECTORSZ
); pos
+= n
; }
536 report_progress(pos
); fflush(stdout
);
539 if (vob
) { DVDCloseFile(vob
); vob
= 0; }
545 static void logfn(void *p
, dvd_logger_level_t lev
,
546 const char *fmt
, va_list ap
)
549 case DVD_LOGGER_LEVEL_ERROR
:
550 fprintf("%s (libdvdread error): ", prog
);
552 case DVD_LOGGER_LEVEL_WARN
:
553 fprintf("%s (libdvdread warning): ", prog
);
558 vfprintf(stderr
, fmt
, ap
);
561 static const dvd_logger_cb logger
= { logfn
};
568 #define BUF_INIT { 0, 0, 0 }
569 #define BUF_REWIND(b) do { (b)->n = 0; } while (0)
570 #define BUF_FREE(b) do { \
572 free(_b->p); _b->p = 0; _b->n = _b->sz = 0; \
574 #define BUF_PUTC(b, ch) do { \
575 struct buf *_b = (b); \
576 if (_b->n >= _b->sz) { \
577 _b->sz = _b->sz ? 2*_b->sz : 32; \
578 _b->p = realloc(_b->p, _b->sz); \
579 if (!_b->p) bail("out of memory allocating %zu bytes", _b->sz); \
581 _b->p[_b->n] = (ch); \
584 static int read_line(FILE *fp
, struct buf
*b
)
591 else if (ch
!= '\n') do {
592 BUF_PUTC(b
, ch
); b
->n
++;
594 } while (ch
!= EOF
&& ch
!= '\n');
599 #define PRF_HYPHEN 1u
600 static int parse_range(const char *p
, unsigned f
,
601 secaddr
*start_out
, secaddr
*end_out
)
605 unsigned long start
, end
;
610 start
= strtoul(p
, &q
, 0);
611 if (errno
|| start
>= SECLIMIT
) { rc
= -1; goto end
; }
612 *start_out
= start
; p
= q
;
613 } else if (!(f
&PRF_HYPHEN
))
614 { rc
= -1; goto end
; }
619 if (*p
!= '-') { rc
= -1; goto end
; }
622 if (!ISSPACE(*p
)) { rc
= -1; goto end
; }
623 do p
++; while (ISSPACE(*p
));
627 end
= strtoul(p
, &q
, 0);
628 if (errno
|| end
> SECLIMIT
|| end
< start
) { rc
= -1; goto end
; }
629 *end_out
= end
; p
= q
;
630 } else if (!(f
&PRF_HYPHEN
))
631 { rc
= -1; goto end
; }
633 if (!(f
&PRF_HYPHEN
)) while (ISSPACE(*p
)) p
++;
634 if (*p
) { rc
= -1; goto end
; }
642 int main(int argc
, char *argv
[])
650 const struct event
*ev
;
651 const char *device
= "/dev/dvd", *outfile
= 0;
656 struct buf buf
= BUF_INIT
;
659 const struct file
*file
;
664 #define f_continue 2u
668 p
= strrchr(argv
[0], '/'); prog
= p ? p
+ 1 : argv
[0];
670 opt
= getopt(argc
, argv
, "hD:FR:b:co:r:"); if (opt
< 0) break;
672 case 'h': usage(stderr
); exit(0);
673 case 'D': device
= optarg
; break;
674 case 'F': f
|= f_fixup
; break;
676 fp
= fopen(optarg
, "r");
678 bail_syserr(errno
, "failed to open ranges file `%s'", optarg
);
681 BUF_REWIND(&buf
); if (read_line(fp
, &buf
)) break;
683 while (ISSPACE(*p
)) p
++;
684 if (!*p
|| *p
== '#') continue;
685 if (parse_range(p
, 0, &start
, &end
) ||
686 (last
<= SECLIMIT
&& start
< last
))
687 bail("bad range `%s' at `%s' line %zu", buf
.p
, optarg
, i
);
689 put_event(EV_WRITE
, 0, start
);
690 put_event(EV_STOP
, 0, end
);
694 bail_syserr(errno
, "failed to read ranges file `%s'", optarg
);
697 if (mapfile
) bail("can't have multiple map files");
700 case 'c': f
|= f_continue
; break;
701 case 'o': outfile
= optarg
; break;
704 if (parse_range(optarg
, PRF_HYPHEN
, &start
, &end
))
705 bail("bad range `%s'", optarg
);
707 put_event(EV_WRITE
, 0, start
);
708 if (end
<= SECLIMIT
) put_event(EV_STOP
, 0, end
);
711 default: f
|= f_bogus
; break;
714 if (optind
< argc
) f
|= f_bogus
;
715 if (f
&f_bogus
) { usage(stderr
); exit(2); }
717 dvdfd
= open(device
, O_RDONLY
);
719 bail_syserr(errno
, "failed to open device `%s'", device
);
720 if (fstat(dvdfd
, &st
))
721 bail_syserr(errno
, "failed to stat device `%s'", device
);
722 if (S_ISREG(st
.st_mode
)) {
725 } else if (S_ISBLK(st
.st_mode
)) {
726 if (ioctl(dvdfd
, BLKSSZGET
, &blksz
))
727 bail_syserr(errno
, "failed to get block size for `%s'", device
);
728 if (ioctl(dvdfd
, BLKGETSIZE64
, &volsz
))
729 bail_syserr(errno
, "failed to get volume size for `%s'", device
);
731 bail("can't use `%s' as source: expected file or block device", device
);
733 if (blksz
!= SECTORSZ
)
734 bail("device `%s' block size %d /= %d", device
, blksz
, SECTORSZ
);
736 bail("device `%s' volume size %"PRIu64
" not a multiple of %d",
737 device
, volsz
, SECTORSZ
);
740 outfd
= open(outfile
, O_WRONLY
| O_CREAT
, 0666);
742 bail_syserr(errno
, "failed to create output file `%s'", outfile
);
746 if (!outfile
) bail("can't continue without output file");
747 off
= lseek(outfd
, 0, SEEK_END
);
749 bail_syserr(errno
, "failed to seek to end of output file `%s'",
751 put_event(EV_WRITE
, 0, off
/SECTORSZ
);
752 } else if (!eventq
.n
&& !(f
&f_fixup
))
753 put_event(EV_WRITE
, 0, 0);
756 dvd
= DVDOpen2(0, &logger
, device
);
758 dvd
= DVDOpen(device
);
760 if (!dvd
) bail("failed to open DVD on `%s'", device
);
762 /* It's fast enough just to check everything. */
764 for (i
= 1; i
< 100; i
++) {
768 put_file(mkident(RAW
, 0, 0), 0, volsz
/SECTORSZ
);
769 assert(filetab
.n
<= MAXFILES
);
771 for (i
= 0, limit
= 0; i
< filetab
.n
; i
++)
772 if (filetab
.v
[i
].end
> limit
) limit
= filetab
.v
[i
].end
;
774 if (end
> limit
) end
= limit
;
777 printf("\n;; files:\n");
778 for (i
= 0; i
< filetab
.n
; i
++) {
779 file
= &filetab
.v
[i
];
780 store_filename(fn
, file
->id
);
781 printf(";;\t%8"PRIuSEC
" %s\n", file
->start
, fn
);
785 qsort(eventq
.v
, eventq
.n
, sizeof(struct event
), compare_event
);
787 f
&= ~f_write
; start
= 0; n
= 0;
788 for (i
= 0; i
< eventq
.n
; i
++) {
793 bail("overlapping ranges: range from %"PRIuSEC
" still open at %"PRIuSEC
"",
795 n
++; f
|= f_write
; start
= ev
->pos
;
803 f
&= ~f_write
; start
= 0;
804 for (i
= 0; i
< eventq
.n
; i
++) {
807 case EV_WRITE
: start
= ev
->pos
; f
|= f_write
; break;
808 case EV_STOP
: nsectors
+= ev
->pos
- start
; f
&= ~f_write
; break;
810 if (ev
->pos
>= limit
) break;
811 if (f
&f_fixup
) start
= ev
->pos
;
815 put_event(EV_WRITE
, 0, start
);
819 nsectors
+= limit
- start
;
820 put_event(EV_STOP
, 0, limit
);
822 if (n
== 1 && (f
&f_write
)) flags
|= F_ALLPROGRESS
;
826 printf("\n;; event sweep:\n");
828 for (pos
= 0, i
= 0; i
< eventq
.n
; i
++) {
831 if (f
&f_write
) emit(pos
, ev
->pos
);
842 store_filename(fn
, filetab
.v
[ev
->file
].id
);
844 printf(";; %8"PRIuSEC
": begin `%s'\n", pos
, fn
);
848 gettimeofday(&last_time
, 0); last_pos
= pos
;
850 lseek(outfd
, (off_t
)ev
->pos
*SECTORSZ
, SEEK_SET
) < 0)
852 "failed to seek to resume position "
853 "(sector %"PRIuSEC
") in output file `%s'",
857 printf(";; %8"PRIuSEC
": begin write\n", pos
);
865 printf(";; %8"PRIuSEC
": end write\n", pos
);
869 clear_live(ev
->file
);
871 store_filename(fn
, filetab
.v
[ev
->file
].id
);
873 printf(";; %8"PRIuSEC
": end `%s'\n", pos
, fn
);
880 if (progresslen
) putchar('\n');
882 if (outfd
>= 0 && ftruncate(outfd
, (off_t
)limit
*SECTORSZ
) < 0)
883 bail_syserr(errno
, "failed to set output file `%s' length", outfile
);
885 if (dvd
) DVDClose(dvd
);
886 if (dvdfd
>= 0) close(dvdfd
);
887 if (outfd
>= 0) close(outfd
);
889 if (ferror(mapfp
) || fclose(mapfp
))
890 bail_syserr(errno
, "error writing bad-sector map file");