+ n = r->pos + r->end - pos;
+ if (!n && want) n = -1;
+ else if (n > want) n = want;
+
+end:
+#ifdef DEBUG
+ show_recovery_buffer_map(r, "done; return %zd", n);
+#endif
+ return (n);
+}
+
+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 = 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);
+
+ r.buf = buf; r.sz = sz; r.pos = r.start = r.end = 0;
+ r.good_lo = r.good_hi = 0;
+
+ want = sz; if (want > end - pos) want = end - pos;
+ for (retry = 0; retry < max_retries; retry++) {
+ report_bad_blocks_progress(pos, errno);
+ n = recovery_read(&r, pos, 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);
+ }
+ }
+
+ bad_lo = pos; bad_hi = pos + 1;
+ for (;;) {
+ report_bad_blocks_progress(bad_hi, errno);
+#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);
+ }
+ step = (step_factor - 1)*(bad_hi - bad_lo);
+ if (step < step_min) step = step_min;
+ if (step_max && step > step_max) step = step_max;
+ if (step > end - bad_hi) step = end - bad_hi;
+ pos = bad_hi + step - 1;
+ want = run_length_wanted(pos, step, end);
+ n = recovery_read(&r, pos, want);
+#ifdef DEBUG
+ printf(";; [bound] try reading %"PRIuSEC" .. %"PRIuSEC" -> %zd\n",
+ pos, pos + want, n);
+#endif
+ if (n == want) break;
+ if (n < 0) n = 0;
+ bad_hi = pos + n + 1;
+ }
+
+ good = pos;
+ while (good > bad_hi) {
+ report_bad_blocks_progress(bad_hi, errno);
+#ifdef DEBUG
+ 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, end);
+ n = recovery_read(&r, pos, want);
+#ifdef DEBUG
+ printf(";; [limit] try reading %"PRIuSEC" .. %"PRIuSEC" -> %zd\n",
+ pos, pos + want, n);
+#endif
+ if (n < 0) n = 0;
+ if (n == want) good = pos;
+ else bad_hi = pos + n + 1;
+ }
+ recovered(bad_lo, bad_hi); *pos_inout = bad_hi;
+ if (bad_hi < r.pos + r.start || r.pos + r.end <= bad_hi)
+ n = 0;
+ else {
+ n = r.pos + r.end - bad_hi;
+ rearrange_sectors(&r, 0, bad_hi - r.pos, n);
+ }
+#ifdef DEBUG
+ show_recovery_buffer_map(&r, "returning %zd good sectors at %"PRIuSEC"",
+ n, bad_hi);
+#endif