+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 void recovered(secaddr bad_lo, secaddr bad_hi)
+{
+ 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);
+ }
+
+ if (mapfile) {
+ open_file_on_demand(mapfile, &mapfp, "bad-sector region map");
+ fprintf(mapfp, "%"PRIuSEC" %"PRIuSEC" # %"PRIuSEC" sectors",
+ bad_lo, bad_hi, bad_hi - bad_lo);
+
+ if (file && id_kind(file->id) != RAW)
+ 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");
+ }
+
+ 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);
+}
+
+static secaddr run_length_wanted(secaddr pos, secaddr badlen, secaddr end)
+{
+ secaddr want;
+
+ want = ceil(clear_factor*badlen);
+ if (want < clear_min) want = clear_min;
+ if (want > end - pos) want = end - pos;
+ 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)
+{
+ 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);
+
+ want = sz; if (want > end - pos) want = end - pos;
+ for (retry = 0; retry < max_retries; retry++) {
+ report_bad_blocks_progress(pos, errno);
+ n = read_sectors(pos, buf, want);
+#ifdef DEBUG
+ progress_clear(&progress);
+ printf(";; [retry] try reading %"PRIuSEC" .. %"PRIuSEC" -> %zd\n",
+ pos, pos + want, n);
+#endif
+ if (n > 0) {
+ progress_clear(&progress);
+ moan("sector %"PRIuSEC" read ok after retry", pos);
+ progress_removeitem(&progress, &badblock_progress);
+ progress_update(&progress);
+ return (n);
+ }
+ }
+
+ r.buf = buf; r.sz = sz; r.pos = r.start = r.end = 0;
+ r.good_lo = r.good_hi = 0;
+
+ bad_lo = pos; bad_hi = pos + 1;
+ for (;;) {
+#ifdef DEBUG
+ progress_clear(&progress);
+ printf(";; bounding bad-block region: "
+ "%"PRIuSEC" ..%"PRIuSEC".. %"PRIuSEC"\n",
+ bad_lo, bad_hi - bad_lo, bad_hi);
+#endif
+ if (bad_hi >= end) {
+ progress_clear(&progress);
+ moan("giving up on this extent");
+ recovered(bad_lo, end); *pos_inout = end;
+ return (0);