+ /* If we read some stuff then update the buffer pointer and lengths. If
+ * we hit end-of-file then stop. If we hit a bad sector then maybe make
+ * a note of it in the bad-sector log. On any other kind of error, just
+ * stop.
+ */
+ if (n > 0) { done += n; pos += n; p += n*SECTORSZ; want -= n; }
+ else if (!n) break;
+ else if (errno == EIO && errfile) {
+ open_file_on_demand(errfile, &errfp, "bad-sector error log");
+ fprintf(errfp, "%"PRIuSEC" %"PRIuSEC"\n", pos, pos + 1);
+ check_write(errfp, "bad-sector error log");
+ break;
+ } else if (errno != EINTR) break;
+ }
+
+ /* We made it. If we saved up a fake error, and there wasn't a real error
+ * (which should obviously take priority) then present the fake error to
+ * the caller. If there wasn't an error, then everything must have been
+ * good so impose the good-block delay -- note that a bad-block delay will
+ * already have been imposed above. Finally, return the accumulated count
+ * of sectors successfully read, or report the end-of-file or error
+ * condition as applicable.
+ */
+ if (fakeerr && !errno) errno = fakeerr;
+ else if (done > 0 && good_block_delay) sit(done*good_block_delay);
+ return (!done && errno ? -1 : done);
+}
+
+/*----- Tracking machinery for the bad-sector algorithm -------------------*
+ *
+ * While we're probing around trying to find the end of the bad region, we'll
+ * have read some good data. We want to try to keep as much good data as we
+ * can, and avoid re-reading it because (a) it's pointless I/O work, but more
+ * importantly (b) it might not work the second time. The machinery here
+ * is for making this work properly.
+ *
+ * There are two parts to this which don't really intersect, but for
+ * convenience the tracking information for them is kept in the same
+ * `recoverybuf' structure.
+ *
+ * * The `short-range' machinery keeps track of a contiguous region of good
+ * data stored in the caller's buffer.
+ *
+ * * The `long-range' machinery keeps track of a contiguous region of good
+ * data that's beyond the range of the buffer.
+ */
+
+struct recoverybuf {
+ /* Information used to keep track of where good and bad sectors are
+ * while we're trying to find the end of a region of bad sectors.
+ */
+
+ /* Short-range buffer tracking. */
+ unsigned char *buf; /* pointer to the actual buffer */
+ secaddr sz; /* size of the buffer in sectors */
+ secaddr pos; /* sector address corresponding to
+ * the start of the buffer */
+ secaddr start, end; /* bounds of the live region within
+ * the buffer, as offsets in
+ * sectors from the buffer start */
+
+ /* Long-range tracking. */
+ secaddr good_lo, good_hi; /* known-good region, as absolute
+ * sector addresses */
+};
+
+static void rearrange_sectors(struct recoverybuf *r,
+ secaddr dest, secaddr src, secaddr len)
+ /* Shuffle data about in R's buffer. Specifically, move LEN sectors
+ * starting SRC sectors from the start of the buffer to a new
+ * position DEST sectors from the start.
+ *
+ * Unsurprisingly, this is a trivial wrapper around `memmove', with
+ * some range checking thrown in; it's only used by `recovery_read_-
+ * buffer' and `find_good_sector' below.
+ */
+{
+ assert(dest + len <= r->sz); assert(src + len <= r->sz);
+ memmove(r->buf + dest*SECTORSZ, r->buf + src*SECTORSZ, len*SECTORSZ);
+}
+
+#ifdef DEBUG
+static PRINTF_LIKE(2, 3)
+ void show_recovery_buffer_map(const struct recoverybuf *r,
+ const char *what, ...)
+ /* Dump a simple visualization of the short-range tracking state. */
+{
+ va_list ap;
+
+ va_start(ap, what);
+ progress_clear(&progress);
+ printf(";; recovery buffer (");
+ vprintf(what, ap);
+ printf("): "
+ "(%"PRIuSEC") ..%"PRIuSEC".. "
+ "[%"PRIuSEC" ..%"PRIuSEC".. %"PRIuSEC"] "
+ "..%"PRIuSEC".. (%"PRIuSEC")\n",
+ r->pos, r->start,
+ r->pos + r->start, r->end - r->start, r->pos + r->end,
+ r->sz - r->end, r->pos + r->sz);
+ va_end(ap);
+ assert(r->start <= r->end);
+ assert(r->end <= r->sz);
+}
+#endif
+
+static ssize_t recovery_read_sectors(struct recoverybuf *r,
+ secaddr pos, secaddr off, secaddr want)
+ /* Try to read WANT sectors starting at sector address POS from the
+ * current file into R's buffer, at offset OFF sectors from the start
+ * of the buffer. Return the number of sectors read, zero if at end
+ * of file, or -1 in the event of a system error.
+ *
+ * This is a trivial wrapper around `read_sectors' with some
+ * additional range checking, used only by `recovery_read_buffer'
+ * below.
+ */
+{
+ ssize_t n;
+
+ assert(off <= r->sz); assert(want <= r->sz - off);
+ assert(pos == r->pos + off);
+ n = read_sectors(pos, r->buf + off*SECTORSZ, want);
+ return (n);
+}
+
+static ssize_t recovery_read_buffer(struct recoverybuf *r,
+ secaddr pos, secaddr want)
+ /* Try to read WANT sectors, starting at sector address POS, from the
+ * current file into the buffer R, 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'. The data will end up
+ * /somewhere/ in the buffer, but not necessarily at the start.
+ */
+{
+ secaddr diff, pp, nn;
+ ssize_t n;
+
+ /* This is the main piece of the short-range tracking machinery. It's
+ * rather complicated, so hold on tight. (It's much simpler -- and less
+ * broken -- than earlier versions were, though.)
+ */
+
+#ifdef DEBUG
+ progress_clear(&progress);
+ show_recovery_buffer_map(r, "begin(%"PRIuSEC", %"PRIuSEC")", pos, want);
+#endif
+
+ /* The first order of business is to make space in the buffer for this new
+ * data. We therefore start with a case analysis.
+ */
+ if (pos < r->pos) {
+ /* The new position is before the current start of the buffer, so we have
+ * no choice but to decrease the buffer position, which will involve
+ * shifting the existing material upwards.
+ */
+
+ /* Determine how far up we'll need to shift. */
+ diff = r->pos - pos;
+
+ if (r->start + diff >= r->sz) {
+ /* The material that's currently in the buffer would be completely
+ * shifted off the end, so we have no choice but to discard it
+ * completely.
+ */
+
+ r->pos = pos; r->start = r->end = 0;
+#ifdef DEBUG
+ show_recovery_buffer_map(r, "cleared; shift up by %"PRIuSEC"", diff);
+#endif
+ } else {
+ /* Some of the material in the buffer will still be there. We might
+ * lose some stuff off the end: start by throwing that away, and then
+ * whatever's left can be moved easily.
+ */
+
+ if (r->end + diff > r->sz) r->end = r->sz - diff;
+ rearrange_sectors(r, r->start + diff, r->start, r->end - r->start);
+ r->pos -= diff; r->start += diff; r->end += diff;
+#ifdef DEBUG
+ show_recovery_buffer_map(r, "shifted up by %"PRIuSEC"", diff);
+#endif
+ }
+ } else if (pos > r->pos + r->end) {
+ /* The new position is strictly beyond the old region. We /could/ maybe
+ * keep this material, but it turns out to be better not to. To keep it,
+ * we'd have to also read the stuff that's in between the end of the old
+ * region and the start of the new one, and that might contain bad
+ * sectors which the caller is specifically trying to skip. We just
+ * discard the entire region here so as not to subvert the caller's
+ * optimizations.
+ */
+
+ r->pos = pos; r->start = r->end = 0;
+#ifdef DEBUG
+ show_recovery_buffer_map(r, "cleared; beyond previous region");
+#endif
+ } else if (pos + want > r->pos + r->sz) {
+ /* The requested range of sectors extends beyond the region currently
+ * covered by the buffer. We must therefore increase the buffer position
+ * which will involve shifting the existing material downwards.
+ */
+
+ /* Determine how far down we'll need to shift. */
+ diff = (pos + want) - (r->pos + r->sz);
+
+ if (r->end <= diff) {
+ /* The material that's currently in the buffer would be completely
+ * shifted off the beginning, so we have no choice but to discard it
+ * completely.
+ */
+
+ r->pos = pos; r->start = r->end = 0;
+#ifdef DEBUG
+ show_recovery_buffer_map(r, "cleared; shift down by %"PRIuSEC"", diff);
+#endif
+ } else {
+ /* Some of the material in the buffer will still be there. We might
+ * lose some stuff off the beginning: start by throwing that away, and
+ * then whatever's left can be moved easily.
+ */
+
+ if (r->start < diff) r->start = diff;
+ rearrange_sectors(r, r->start - diff, r->start, r->end - r->start);
+ r->pos += diff; r->start -= diff; r->end -= diff;
+#ifdef DEBUG
+ show_recovery_buffer_map(r, "shifted down by %"PRIuSEC"", diff);
+#endif
+ }
+ }
+
+ /* We now have space in the buffer in which to put the new material.
+ * However, the buffer already contains some stuff. We may need to read
+ * some data from the input file into an area before the existing
+ * material, or into an area following the existing stuff, or both, or
+ * (possibly) neither.
+ */
+
+ if (pos < r->pos + r->start) {
+ /* The requested position is before the current good material, so we'll
+ * need to read some stuff there.
+ */
+
+ /* Determine the place in the buffer where this data will be placed, and
+ * how long it will need to be. Try to extend it all the way to the
+ * existing region even if this is more than the caller wants, because it
+ * will mean that we can join it onto the existing region rather than
+ * having to decide which of two disconnected parts to throw away.
+ */
+ pp = pos - r->pos; nn = r->start - pp;
+
+ /* Read the data. */
+#ifdef DEBUG
+ printf(";; read low (%"PRIuSEC"@%"PRIuSEC", %"PRIuSEC")", pos, pp, nn);
+ fflush(stdout);
+#endif
+ n = recovery_read_sectors(r, pos, pp, nn);
+#ifdef DEBUG
+ printf(" -> %zd\n", n);
+#endif
+
+ /* See whether it worked. */
+ if (n != nn) {
+ /* We didn't get everything we wanted. */
+
+ /* If we got more than the caller asked for then technically this is
+ * good; but there must be some problem lurking up ahead, and the
+ * caller will want to skip past that. So we don't update the tracking
+ * information to reflect our new data; even though this /looks/ like a
+ * success, it isn't really.
+ */
+ if (n >= 0 && n > want) n = want;
+
+ /* We're done. */
+ goto end;
+ }
+
+ /* Extend the region to include the new piece. */
+ r->start = pp;
+#ifdef DEBUG
+ show_recovery_buffer_map(r, "joined new region");
+#endif
+ }
+
+ if (pos + want > r->pos + r->end) {
+ /* The requested region extends beyond the current region, so we'll need
+ * to read some stuff there.
+ */
+
+ /* Determine the place in the buffer where this data will be placed, and
+ * how long it will need to be. Note that pos <= r->pos + r->end, so
+ * there won't be a gap between the old good region and the material
+ * we're trying to read.
+ */
+ pp = r->end; nn = (pos + want) - (r->pos + r->end);
+
+ /* Read the data. */
+#ifdef DEBUG
+ printf(";; read high (%"PRIuSEC"@%"PRIuSEC", %"PRIuSEC")",
+ r->pos + pp, pp, nn);
+ fflush(stdout);
+#endif
+ n = recovery_read_sectors(r, r->pos + pp, pp, nn);
+#ifdef DEBUG
+ printf(" -> %zd\n", n);
+#endif
+
+ /* See whether it worked. */
+ if (n > 0) {
+ /* We read something, so add it onto the existing region. */
+
+ r->end += n;
+#ifdef DEBUG
+ show_recovery_buffer_map(r, "joined new region");
+#endif
+ }
+ }
+
+ /* Work out the return value to pass back to the caller. The newly read
+ * material has been merged with the existing region (the case where we
+ * didn't manage to join the two together has been handled already), so we
+ * can easily work out how much stuff is available by looking at the
+ * tracking information. It only remains to bound the region size by the
+ * requested length.
+ */
+ n = r->pos + r->end - pos;
+ if (!n && want) n = -1;
+ else if (n > want) n = want;
+
+end:
+ /* Done. */
+#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)
+ /* 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);