+static ssize_t recovery_read_multiple(struct recoverybuf *r,
+ secaddr pos, secaddr want)
+ /* Try to read WANT sectors, starting at sector address POS, from the
+ * current file, returning a count of the number of sectors read, or
+ * 0 if at end of file, or -1 in the case of a system error, as for
+ * `read_sectors'. Some data might end up in R's buffer, but if WANT
+ * is larger than R->sz then a lot will be just thrown away.
+ *
+ * This is only used by `recovery_read' below.
+ */
+{
+ ssize_t n;
+ secaddr skip, want0 = want;
+
+ /* If the request is larger than the buffer, then we start at the /end/ and
+ * work backwards. If we encounter a bad sector while we're doing this,
+ * then we report a short read as far as the bad sector: the idea is to
+ * find the /latest/ bad sector we can. The caller will want to skip past
+ * the bad sector, so the fact that we implicitly lied about the earlier
+ * data as being `good' won't matter.
+ */
+
+ while (want > r->sz) {
+ /* There's (strictly!) more than a buffer's worth. Fill the buffer with
+ * stuff and reduce the requested size.
+ */
+
+ skip = want - r->sz;
+ n = recovery_read_buffer(r, pos + skip, r->sz);
+
+ /* If it failed, then we always return a positive result, because we're
+ * pretending we managed to read all of the (nonempty) preceding
+ * material.
+ */
+ if (n < r->sz) return (skip + (n >= 0 ? n : 0));
+
+ /* Cross off a buffer's worth and go around again. */
+ want -= r->sz;
+ }
+
+ /* Read the last piece. If it fails or comes up short, then we don't need
+ * to mess with the return code this time.
+ */
+ n = recovery_read_buffer(r, pos, want);
+ if (n < 0 || n < want) return (n);
+
+ /* It all worked. Return the full original amount requested. */
+ return (want0);
+}
+
+static ssize_t recovery_read(struct recoverybuf *r,
+ secaddr pos, secaddr want)
+ /* Try to read WANT sectors, starting at sector address POS, from the
+ * current file, returning a count of the number of
+ * sectors read, or 0 if at end of file, or -1 in the case of a
+ * system error, as for `read_sectors'. Some data might end up in
+ * R's buffer, but if WANT is larger than R->sz then a lot will be
+ * just thrown away.
+ */
+{
+ secaddr lo = pos, hi = pos + want, span; /* calculate the request bounds */
+ ssize_t n;
+
+ /* This is the main piece of the long-range tracking machinery.
+ * Fortunately, it's much simpler than the short-range stuff that we've
+ * just dealt with.
+ */
+
+ if (hi < r->good_lo || lo > r->good_hi) {
+ /* The requested region doesn't abut or overlap with the existing good
+ * region, so it's no good to us. Just read the requested region; if it
+ * worked at all, then replace the current known-good region with the
+ * region that was successfully read.
+ */
+
+ 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) {
+ /* The requested region ends later than the current known-good region.
+ * Read the missing piece. We're doing this first so that we find later
+ * bad sectors.
+ */
+
+ span = hi - r->good_hi;
+ n = recovery_read_multiple(r, r->good_hi, span);
+
+ /* If we read anything at all, then extend the known-good region. */
+ if (n > 0) r->good_hi += n;
+
+ /* If we didn't read everything we wanted, then report this as a short
+ * read (so including some nonempty portion of the known-good region).
+ */
+ if (n < 0 || n < span) return (r->good_hi - lo);
+ }
+
+ if (lo < r->good_lo) {
+ /* The requested region begins earlier than the known-good region. */
+
+ span = r->good_lo - lo;
+ n = recovery_read_multiple(r, lo, span);
+
+ /* If we read everything we wanted, then extend the known-good region.
+ * Otherwise, we're better off keeping the stuff after the bad block.
+ */
+ if (n == span) r->good_lo = lo;
+ else return (n);
+ }
+
+ /* Everything read OK, and we've extended the known-good region to cover
+ * the requested region. So return an appropriate code by consulting the
+ * new known-good region.
+ */
+ n = r->good_hi - pos; if (n > want) n = want;
+ if (!n) { errno = EIO; n = -1; }
+ return (n);
+}
+
+/*----- Skipping past regions of bad sectors ------------------------------*/
+
+static double clear_factor = 0.5; /* proportion of clear sectors needed */
+static secaddr clear_min = 1, clear_max = SECLIMIT; /* absolute bounds */
+static double step_factor = 2.0; /* factor for how far to look ahead */
+static secaddr step_min = 1, step_max = 0; /* and absolute bounds */
+
+static void recovered(secaddr bad_lo, secaddr bad_hi)
+ /* Do all of the things that are necessary when a region of bad
+ * sectors has been found between BAD_LO (inclusive) and BAD_HI
+ * (exclusive).
+ */
+{
+ char fn[MAXFNSZ];
+
+ /* Remove the progress display temporarily. */
+ progress_clear(&progress);
+
+ /* Print a message into the permanent output log. */
+ 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) {
+ /* The user requested a map of the skipped regions, so write an entry. */
+
+ /* Open the file, if it's not open already. */
+ open_file_on_demand(mapfile, &mapfp, "bad-sector region map");
+
+ /* Write the sector range. */
+ fprintf(mapfp, "%"PRIuSEC" %"PRIuSEC" # %"PRIuSEC" sectors",
+ bad_lo, bad_hi, bad_hi - bad_lo);
+
+ /* If we're currently reading from a file then note down the position in
+ * the file in the comment. (Intentional bad sectors are frequently at
+ * the start and end of titles, so this helps a reader to decide how
+ * concerned to be.)
+ */
+ 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);
+
+ /* Done. Flush the output to the file so that we don't lose it if we
+ * crash!
+ */
+ fputc('\n', mapfp);
+ check_write(mapfp, "bad-sector region map");
+ }
+
+ /* Adjust the position in our output file to skip past the bad region.
+ * (This avoids overwriting anything that was there already, which is
+ * almost certainly less wrong than anything we could come up with here.)
+ */
+ if (lseek(outfd, (off_t)(bad_hi - bad_lo)*SECTORSZ, SEEK_CUR) < 0)
+ bail_syserr(errno, "failed to seek past bad sectors");
+
+ /* Remove our notice now that we're no longer messing about with bad
+ * sectors, and reinstate the progress display.
+ */
+ progress_removeitem(&progress, &badblock_progress);
+ progress_update(&progress);
+}
+
+static secaddr run_length_wanted(secaddr pos, secaddr badlen, secaddr end)
+ /* Return the number of good sectors that we want to see before
+ * we're happy, given that we're about to try to read sector POS,
+ * which is BADLEN sectors beyond where we found the first bad
+ * sector, and the current region ends at sector END (i.e., this is
+ * where the next event occurs).
+ */