Stop documenting the recovery API that 1.6.0 deleted

Disc::sweep, Disc::patch, Disc::copy, SweepOptions and PatchOptions have
zero occurrences in src/ — recovery moved to freemkv-engine — but they were
still documented in 30 places across README.md, TROUBLESHOOTING.md, six
files under docs/, seven src/ doc comments and a Cargo.toml comment.
README.md is the crate's GitHub front page and carried a full multi-pass
code example that cannot compile.

Two of the src/ references were intra-doc LINKS to deleted items
([`disc::Disc::copy`], [`disc::Disc::patch`] in scsi/mod.rs). They produced
no warning on a normal `cargo doc` only because they sit on pub(crate)
items; `--document-private-items` reports both, and they are gone now.

The README example is deleted rather than rewritten against the engine's
API: libfreemkv documenting a downstream crate's API on its own front page
is the drift that produced this, and it cannot even depend on it. The src/
references become plain code spans naming freemkv_engine::recovery::* —
deliberately not links, for the same reason.

docs/rip-recovery.md was 202 lines about relocated code. It now documents
only what this crate owns — Drive::read, SenseFamily, DiscStream's adaptive
batch halving — plus the read-path design constraints, which belong with the
code that enforces them, and points at freemkv-engine/src/recovery/ for the
strategy. api-design.md's module tree is regenerated from the real src/disc/
and src/drive/ layouts instead of hand-patched; it had listed sweep.rs,
patch.rs, mapfile.rs and read_error.rs, none of which exist.

Three stale facts surfaced while rewriting and are corrected: the read
timeouts are 10 s / 60 s, not the documented 1.5 s / 30 s; Drive::reset and
SgIoTransport::reset no longer exist at all, so "no SCSI reset from any read
path" is now stated as the stronger fact it has become; and verify_title,
listed as a progress-emitting operation, was removed entirely.

CHANGELOG.md keeps its references — those are the historical record of the
releases that shipped the API.
This commit is contained in:
Matthew Jackson
2026-07-29 18:04:51 -07:00
parent d34979ac57
commit 3db4106253
16 changed files with 114 additions and 245 deletions
+4 -4
View File
@@ -715,19 +715,19 @@ impl Drive {
/// SCSI reset.
///
/// `recovery=true` uses [`crate::scsi::READ_RECOVERY_TIMEOUT_MS`] (60 s,
/// matches sg_dd) for the `Disc::patch` pass; `recovery=false` uses
/// [`crate::scsi::READ_TIMEOUT_MS`] (10 s) for `Disc::copy`'s fast
/// matches sg_dd) for the `freemkv_engine::recovery::patch` pass; `recovery=false` uses
/// [`crate::scsi::READ_TIMEOUT_MS`] (10 s) for `freemkv_engine::recovery::copy`'s fast
/// skip-forward sweep. Both budgets are generous enough that the drive
/// can finish ECC recovery on a marginal sector — pre-0.13.21 this was
/// 1.5 s on the fast path which forced the kernel mid-layer to time
/// out and escalate while we waited anyway. On any failure returns
/// `Err(DiscRead)` immediately; orchestration (`Disc::patch` multi-pass,
/// `Err(DiscRead)` immediately; orchestration (`freemkv_engine::recovery::patch` multi-pass,
/// `DiscStream` adaptive batch halving) handles retry policy.
///
/// Inline retry phases (5× gentle + reset+reopen + 5× more) were
/// removed in 0.13.6: on some USB-SATA bridges the inline reset wedged
/// drive firmware without ever recovering a sector. The remaining
/// recovery layers (Disc::patch multi-pass, DiscStream batch halving)
/// recovery layers (freemkv_engine::recovery::patch multi-pass, DiscStream batch halving)
/// do not touch the wedge-prone reset path.
pub fn read(&mut self, lba: u32, count: u16, buf: &mut [u8], recovery: bool) -> Result<usize> {
// Bulk path: FUA off (the drive cache IS the streaming throughput).
+1 -1
View File
@@ -1032,7 +1032,7 @@ impl Error {
/// REQUEST, BLANK CHECK, kernel `IoError`, and any non-SCSI variant.
/// Caller-agnostic predicate — describes a property of the *error*,
/// not what one specific call site should do with it. Used by
/// `Disc::copy`'s hysteresis dispatch.
/// `freemkv_engine::recovery::copy`'s hysteresis dispatch.
pub fn is_marginal_read(&self) -> bool {
self.scsi_sense()
.map(crate::scsi::ScsiSense::is_marginal)
+1 -1
View File
@@ -3,7 +3,7 @@
//! One stream type for all disc sources. The source is a SectorSource —
//! Drive (hardware) or FileSectorSource (file). DiscStream doesn't care.
//!
//! Read-only. For disc→ISO (raw sector copy), use `Disc::copy()`.
//! Read-only. For disc→ISO (raw sector copy), use `freemkv_engine::recovery::copy`.
use crate::disc::{DiscTitle, Extent};
use crate::drive::extract_scsi_context;
+1 -1
View File
@@ -21,7 +21,7 @@
//! output.finish()?;
//! ```
//!
//! For disc→ISO (raw sector copy), use `Disc::copy()` instead.
//! For disc→ISO (raw sector copy), use `freemkv_engine::recovery::copy` instead.
// Public modules — types here are intentionally part of the consumable API.
pub mod disc;
+1 -1
View File
@@ -16,7 +16,7 @@
//! | fvi:// | -- | Yes | file path (required) — per-picture video index |
//!
//! Bare paths without a scheme are rejected.
//! For disc→ISO (raw sector copy), use `Disc::copy()` instead.
//! For disc→ISO (raw sector copy), use `freemkv_engine::recovery::copy` instead.
//!
//! Note: `disc://` cannot be opened through [`input`]; it returns
//! [`crate::error::Error::DiscUrlNotDirect`]. Live-disc input must go
+4 -4
View File
@@ -1,7 +1,7 @@
//! Pipeline-progress reporting for the rip pipeline.
//!
//! Architecture rule: ONE progress signal type. Every long-running
//! pipeline operation (`Disc::copy`, `Disc::patch`, `verify_title`) emits the
//! pipeline operation (`freemkv_engine::recovery::{copy, patch}`) emits the
//! same [`PassProgress`] shape via the [`Progress`] trait. Consumers (autorip)
//! compute their own single derived view from these fields and never reach
//! into per-pass internals.
@@ -18,12 +18,12 @@
/// (reverse)", "Scrape", "Mux") or just use a generic "Pass N" label.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PassKind {
/// `Disc::copy` — initial sweep across the entire disc.
/// `freemkv_engine::recovery::copy` — initial sweep across the entire disc.
Sweep,
/// `Disc::patch` retry pass with `block_sectors >= 2`. `reverse=true`
/// `freemkv_engine::recovery::patch` retry pass with `block_sectors >= 2`. `reverse=true`
/// means walking bad ranges from highest to lowest LBA.
Trim { reverse: bool },
/// `Disc::patch` final pass at 1 sector per block.
/// `freemkv_engine::recovery::patch` final pass at 1 sector per block.
Scrape { reverse: bool },
/// Demux ISO → output (MKV / M2TS / network). Single phase that runs
/// after all rip passes complete. The library's mux pipeline does not
+6 -6
View File
@@ -44,7 +44,7 @@ pub const AACS_KEY_CLASS: u8 = 0x02;
pub(crate) const TUR_TIMEOUT_MS: u32 = 5_000;
/// Timeout for content READ commands (READ_10 / READ_12) on the fast
/// path — the [`disc::Disc::copy`] sweep that bisects-on-failure.
/// path — the `freemkv_engine::recovery::copy` sweep that bisects-on-failure.
///
/// 10 s is calibrated from live empirical data on an LG BU40N + Initio
/// 1618L bridge ripping a UHD with marginal sectors:
@@ -67,7 +67,7 @@ pub(crate) const TUR_TIMEOUT_MS: u32 = 5_000;
pub(crate) const READ_TIMEOUT_MS: u32 = 10_000;
/// Timeout for content READ commands on the recovery path —
/// [`disc::Disc::patch`]'s targeted retries on bad ranges. Matches
/// `freemkv_engine::recovery::patch`'s targeted retries on bad ranges. Matches
/// `sg_dd`'s 60 s ceiling: long enough that any sector the drive can
/// recover at all gets the time to do so, short enough that an
/// unresponsive bus is detected before the per-range watchdog fires.
@@ -77,7 +77,7 @@ pub(crate) const READ_TIMEOUT_MS: u32 = 10_000;
/// safety ceiling, not a steady-state cost.
///
/// Historical note (2026-05-08): briefly lowered to 2 s with a 5×
/// inline retry loop in `Disc::patch` to mimic the kernel `sr_mod`
/// inline retry loop in `freemkv_engine::recovery::patch` to mimic the kernel `sr_mod`
/// driver's auto-retry pattern. The synthetic logic worked but on the
/// live drive each "2 s" read paid ~1.5 s of kernel SCSI mid-layer
/// error escalation on top, so 5× retries took ~17 s per LBA and
@@ -237,7 +237,7 @@ impl ScsiSense {
///
/// `false` for HARDWARE ERROR, DATA PROTECT, UNIT ATTENTION,
/// ILLEGAL REQUEST, BLANK CHECK, and any unknown key. Used
/// by [`Error::is_marginal_read`] / `Disc::copy`'s hysteresis
/// by [`Error::is_marginal_read`] / `freemkv_engine::recovery::copy`'s hysteresis
/// dispatch.
pub fn is_marginal(&self) -> bool {
matches!(
@@ -1022,8 +1022,8 @@ mod parse_sense_tests {
#[cfg(test)]
mod scsi_sense_predicate_tests {
//! Classification of [`ScsiSense`] predicate methods against SPC-4
//! §4.5.6 Table 28 sense keys. These drive `Disc::copy` hysteresis
//! and `Disc::patch` routing; a misclassification here silently
//! §4.5.6 Table 28 sense keys. These drive `freemkv_engine::recovery::copy` hysteresis
//! and `freemkv_engine::recovery::patch` routing; a misclassification here silently
//! changes which sectors get retried vs. marked unreadable.
use super::*;