mirror of
https://github.com/freemkv/libfreemkv.git
synced 2026-10-07 18:13:10 +00:00
Feature-gate the crate: scsi (transport-only) vs rip (full)
Split the crate into two additive features so a transport-only consumer (freemkv-flash) can depend on the SCSI passthrough layer without pulling the ripping dependency tree (serde, zip, rayon, aes, freemkv-unlock, …): - scsi = the SCSI passthrough transport (src/scsi/ + error): ScsiTransport, CDB builders, per-OS backends + the macOS cc shim, sense parsing. Pulls only tracing + the always-on libc target dep. - rip = the full ripping tree (drive/disc/UDF/MPLS/CLPI/AACS/CSS/mux/keydb + the freemkv-unlock seam) and all its heavy deps. rip implies scsi. - default = ["rip", "scsi"], so the full build stays byte-for-byte identical for the existing consumers; transport-only users set default-features = false, features = ["scsi"]. All ripping modules are gated #[cfg(feature = "rip")]; error + scsi stay always compiled. Two read-path timeout consts are rip-gated so a transport-only build does not warn on unused.
This commit is contained in:
+48
-14
@@ -18,45 +18,79 @@ publish = false
|
||||
lto = "thin"
|
||||
codegen-units = 1
|
||||
|
||||
# ── Feature layout ───────────────────────────────────────────────────────────
|
||||
# `scsi` — the SCSI passthrough transport layer ONLY (`src/scsi/` + `error`):
|
||||
# ScsiTransport/execute, ScsiResult/ScsiSense/parse_sense, DriveInfo,
|
||||
# list_drives, drive_has_disc, CDB builders, the per-OS backends and
|
||||
# the raw-`cc` macOS shim (built by build.rs). Pulls only `tracing`
|
||||
# (transport instrumentation) plus the always-on `libc` target dep —
|
||||
# none of the ripping dependency tree.
|
||||
# `rip` — the full ripping tree (drive lifecycle, disc/UDF/MPLS/CLPI parsing,
|
||||
# AACS/CSS decrypt, PES/mux pipeline, keydb, freemkv-unlock seam). All
|
||||
# heavy deps (serde, zip, rayon, roxmltree, rand, crossbeam, memchr,
|
||||
# sha1, aes, base64, freemkv-unlock, serde_json) live here.
|
||||
# `default = ["rip", "scsi"]` preserves today's full build byte-for-byte;
|
||||
# consumers wanting transport only use `default-features = false, features =
|
||||
# ["scsi"]`. `rip` implies `scsi` because the ripping tree reads through it.
|
||||
[features]
|
||||
default = ["rip", "scsi"]
|
||||
scsi = ["dep:tracing"]
|
||||
rip = [
|
||||
"scsi",
|
||||
"dep:serde",
|
||||
"dep:serde_json",
|
||||
"dep:sha1",
|
||||
"dep:aes",
|
||||
"dep:freemkv-unlock",
|
||||
"dep:rand",
|
||||
"dep:zip",
|
||||
"dep:base64",
|
||||
"dep:roxmltree",
|
||||
"dep:crossbeam-channel",
|
||||
"dep:rayon",
|
||||
"dep:memchr",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
sha1 = "0.10"
|
||||
aes = "0.9"
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
serde_json = { version = "1", optional = true }
|
||||
sha1 = { version = "0.10", optional = true }
|
||||
aes = { version = "0.9", optional = true }
|
||||
# Interim path dep for local cross-repo dev; the release script re-pins this to
|
||||
# `{ git = ".../freemkv-unlock", tag = "vX.Y.Z" }` before tagging libfreemkv (so
|
||||
# the released tag resolves freemkv-unlock from git, not a sibling path).
|
||||
freemkv-unlock = { path = "../freemkv-unlock" }
|
||||
rand = "0.10"
|
||||
zip = { version = "8", default-features = false, features = ["deflate"] }
|
||||
base64 = "0.23"
|
||||
freemkv-unlock = { path = "../freemkv-unlock", optional = true }
|
||||
rand = { version = "0.10", optional = true }
|
||||
zip = { version = "8", default-features = false, features = ["deflate"], optional = true }
|
||||
base64 = { version = "0.23", optional = true }
|
||||
# Read-only XML DOM parser (pure Rust, forbid(unsafe_code), entity-expansion
|
||||
# bounded). Parses the HD-DVD Advanced-Content playlist `ADV_OBJ/VPLST000.XPL`
|
||||
# — untrusted disc bytes — into authoritative titles/clips/chapters. A real
|
||||
# parser, not a hand-rolled scanner: the XPL is genuine XML (comments, varied
|
||||
# attribute order, self-closing tags).
|
||||
roxmltree = "0.20"
|
||||
roxmltree = { version = "0.20", optional = true }
|
||||
# Trace-level instrumentation for the read/transport path (SgIoTransport::execute
|
||||
# and friends). Permitted
|
||||
# and friends). Part of the `scsi` feature — the transport layer traces here.
|
||||
# Permitted
|
||||
# under CLAUDE.md ("Acceptable strings: debug/trace logging"). Consumers (autorip)
|
||||
# wire a tracing subscriber and pipe events into the JSONL debug log.
|
||||
tracing = "0.1"
|
||||
tracing = { version = "0.1", optional = true }
|
||||
# Bounded MPSC channel with kernel-wakeup send_timeout. Used by `io::pipeline`
|
||||
# so the halt-aware send/finish loops can BLOCK on consumer drain instead of
|
||||
# polling — the 50 ms poll cadence of the previous mpsc-based impl capped mux
|
||||
# throughput at ~1 MB/s (0.21.7).
|
||||
crossbeam-channel = "0.5"
|
||||
crossbeam-channel = { version = "0.5", optional = true }
|
||||
# Persistent work-stealing thread pool for parallel AACS unit
|
||||
# decryption. Per-call std::thread::scope spawned fresh OS threads
|
||||
# and that overhead dominated for typical batch sizes (60 units).
|
||||
# rayon's global pool initialises once on first use.
|
||||
rayon = "1"
|
||||
rayon = { version = "1", optional = true }
|
||||
# SIMD-accelerated bytestring search. Drives the HEVC/H.264 start-code
|
||||
# scan in `mux::codec::h264::find_start_code` — naive byte-by-byte
|
||||
# walk is ~500 MB/s single-thread on x86_64; memchr's vectorised
|
||||
# `memmem::find` for the 3-byte `00 00 01` needle hits ~5 GB/s on
|
||||
# AVX2-capable hosts.
|
||||
memchr = "2"
|
||||
memchr = { version = "2", optional = true }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
+100
-28
@@ -97,55 +97,99 @@
|
||||
pub const VERSION_LABEL: &str = concat!(env!("FREEMKV_VERSION"), env!("GIT_SUFFIX"));
|
||||
|
||||
/// The muxing/writing-application string written into MKV output
|
||||
/// (`"freemkv <version> (g<hash>)"`).
|
||||
/// (`"freemkv <version> (g<hash>)"`). Only the ripping tree muxes, so this is
|
||||
/// gated with it — a transport-only build has no MKV writer to stamp.
|
||||
#[cfg(feature = "rip")]
|
||||
pub(crate) const MUX_APP: &str = concat!("freemkv ", env!("FREEMKV_VERSION"), env!("GIT_SUFFIX"));
|
||||
|
||||
pub mod aacs;
|
||||
pub(crate) mod bdnav;
|
||||
pub(crate) mod clpi;
|
||||
pub mod consts;
|
||||
pub mod css;
|
||||
pub mod decrypt;
|
||||
pub mod diag;
|
||||
pub mod dirimage;
|
||||
pub mod disc;
|
||||
pub mod drive;
|
||||
pub(crate) mod dvdnav;
|
||||
// ─── Always compiled ────────────────────────────────────────────────────────
|
||||
// `error` and `scsi` form the transport-only surface (`--features scsi`,
|
||||
// `default-features = false`). `error` carries the SCSI error variants the
|
||||
// transport layer returns; `scsi` is the SCSI passthrough layer itself. Neither
|
||||
// pulls the ripping dependency tree. Everything below is gated behind `rip`.
|
||||
pub mod error;
|
||||
pub mod event;
|
||||
pub mod halt;
|
||||
#[cfg(test)]
|
||||
mod harness;
|
||||
pub mod hex;
|
||||
pub(crate) mod identity;
|
||||
pub(crate) mod ifo;
|
||||
pub mod io;
|
||||
pub mod keysource;
|
||||
pub mod labels;
|
||||
pub(crate) mod mpls;
|
||||
pub mod mux;
|
||||
pub mod pes;
|
||||
pub(crate) mod platform;
|
||||
pub mod progress;
|
||||
pub mod scsi;
|
||||
|
||||
// ─── Ripping tree (feature = "rip") ─────────────────────────────────────────
|
||||
// Drive lifecycle, disc/UDF/MPLS/CLPI parsing, AACS/CSS decrypt, PES/mux
|
||||
// pipeline, keydb, and the freemkv-unlock seam — plus their heavy deps (serde,
|
||||
// zip, rayon, roxmltree, rand, crossbeam, memchr, …). Gated so a transport-only
|
||||
// consumer compiles `scsi` alone.
|
||||
#[cfg(feature = "rip")]
|
||||
pub mod aacs;
|
||||
#[cfg(feature = "rip")]
|
||||
pub(crate) mod bdnav;
|
||||
#[cfg(feature = "rip")]
|
||||
pub(crate) mod clpi;
|
||||
#[cfg(feature = "rip")]
|
||||
pub mod consts;
|
||||
#[cfg(feature = "rip")]
|
||||
pub mod css;
|
||||
#[cfg(feature = "rip")]
|
||||
pub mod decrypt;
|
||||
#[cfg(feature = "rip")]
|
||||
pub mod diag;
|
||||
#[cfg(feature = "rip")]
|
||||
pub mod dirimage;
|
||||
#[cfg(feature = "rip")]
|
||||
pub mod disc;
|
||||
#[cfg(feature = "rip")]
|
||||
pub mod drive;
|
||||
#[cfg(feature = "rip")]
|
||||
pub(crate) mod dvdnav;
|
||||
#[cfg(feature = "rip")]
|
||||
pub mod event;
|
||||
#[cfg(feature = "rip")]
|
||||
pub mod halt;
|
||||
#[cfg(all(test, feature = "rip"))]
|
||||
mod harness;
|
||||
#[cfg(feature = "rip")]
|
||||
pub mod hex;
|
||||
#[cfg(feature = "rip")]
|
||||
pub(crate) mod identity;
|
||||
#[cfg(feature = "rip")]
|
||||
pub(crate) mod ifo;
|
||||
#[cfg(feature = "rip")]
|
||||
pub mod io;
|
||||
#[cfg(feature = "rip")]
|
||||
pub mod keysource;
|
||||
#[cfg(feature = "rip")]
|
||||
pub mod labels;
|
||||
#[cfg(feature = "rip")]
|
||||
pub(crate) mod mpls;
|
||||
#[cfg(feature = "rip")]
|
||||
pub mod mux;
|
||||
#[cfg(feature = "rip")]
|
||||
pub mod pes;
|
||||
#[cfg(feature = "rip")]
|
||||
pub(crate) mod platform;
|
||||
#[cfg(feature = "rip")]
|
||||
pub mod progress;
|
||||
#[cfg(feature = "rip")]
|
||||
pub mod sector;
|
||||
#[cfg(feature = "rip")]
|
||||
pub mod session;
|
||||
#[cfg(test)]
|
||||
#[cfg(all(test, feature = "rip"))]
|
||||
pub(crate) mod testlog;
|
||||
#[cfg(feature = "rip")]
|
||||
pub(crate) mod udf;
|
||||
#[cfg(feature = "rip")]
|
||||
pub(crate) mod unlock_bridge;
|
||||
|
||||
// ─── Drive lifecycle ────────────────────────────────────────────────────────
|
||||
// `Drive::open(path)` → `wait_ready()` → `init()` → `Disc::scan()`. `Drive` owns
|
||||
// the SCSI session; `DriveCapture` etc. expose drive identity/profile data.
|
||||
#[cfg(feature = "rip")]
|
||||
pub use drive::capture::{
|
||||
CapturedFeature, DriveCapture, capture_drive_data, mask_bytes, mask_string,
|
||||
};
|
||||
#[cfg(feature = "rip")]
|
||||
pub use drive::{Drive, DriveStatus, extract_scsi_context, find_drive};
|
||||
|
||||
// ─── Disc session (drive open + SCSI bring-up hoist) ─────────────────────────
|
||||
// One entry point opens a drive, brings transport up, and forwards caller-built
|
||||
// key material into `ScanOptions` (the library derives no certs; see `KeySpec`).
|
||||
#[cfg(feature = "rip")]
|
||||
pub use session::{
|
||||
DeviceTarget, DiscSession, KeySourceFactory, KeySpec, ResolvedKeys, resolve_keys_for, scan_dir,
|
||||
scan_iso,
|
||||
@@ -161,11 +205,13 @@ pub use error::{
|
||||
// ─── Cooperative cancellation ───────────────────────────────────────────────
|
||||
// One-bit cancellation token shared by every long-running loop (mux, and the
|
||||
// engine's sweep/patch passes); clone cheaply, poll `is_cancelled()` in loops.
|
||||
#[cfg(feature = "rip")]
|
||||
pub use halt::Halt;
|
||||
|
||||
// Bounded producer/consumer primitive used by mux (and engine's sweep/patch) to
|
||||
// overlap reads with writes via a consumer thread. `Sink::apply` defines
|
||||
// per-item behavior; `Flow::Stop` ends the consumer cleanly, still calling `close()`.
|
||||
#[cfg(feature = "rip")]
|
||||
pub use io::pipeline::{
|
||||
DEFAULT_PIPELINE_DEPTH, Flow, Pipeline, Sink, WRITE_PIPELINE_DEPTH, WRITE_THROUGH_DEPTH,
|
||||
};
|
||||
@@ -173,15 +219,19 @@ pub use io::pipeline::{
|
||||
// ─── Bounded-cache buffered file writer ─────────────────────────────────────
|
||||
// Drop-in `std::fs::File` replacement for large sequential output (mux, extract,
|
||||
// sweep, patch); drains dirty pages continuously. `pub` for freemkv-engine reuse.
|
||||
#[cfg(feature = "rip")]
|
||||
pub use io::WritebackFile;
|
||||
/// Write an image-level source out as a sector image — what an `iso://`
|
||||
/// DESTINATION means for any source that is not a physical drive. Drive sources
|
||||
/// go through `freemkv_engine::copy`, which is the recovery path; see
|
||||
/// [`io::image_writer`] for why the two are deliberately separate.
|
||||
#[cfg(feature = "rip")]
|
||||
pub use io::image_writer::write_image;
|
||||
|
||||
// ─── Drive events (low-level callbacks) ─────────────────────────────────────
|
||||
#[cfg(feature = "rip")]
|
||||
pub use event::{BatchSizeReason, Event, EventKind};
|
||||
#[cfg(feature = "rip")]
|
||||
pub use identity::DriveId;
|
||||
|
||||
// ─── Unlock seam ────────────────────────────────────────────────────────────
|
||||
@@ -191,46 +241,68 @@ pub use identity::DriveId;
|
||||
// ─── Decryption (AACS / CSS) ────────────────────────────────────────────────
|
||||
// `Disc::scan()` resolves keys onto `Disc`; `DiscStream::new(...)` consumes
|
||||
// them directly. `decrypt_sectors()` is for raw sector buffers (ISO patching).
|
||||
#[cfg(feature = "rip")]
|
||||
pub use decrypt::{AacsKeyMap, DecryptKeys, decrypt_sectors, decrypt_threads, set_decrypt_threads};
|
||||
|
||||
// ─── Disc structure ─────────────────────────────────────────────────────────
|
||||
// `Disc::scan()` fully populates `Disc`; `Disc::identify()` is a UDF-only fast path
|
||||
// for name/format display. Codec enums are canonical; never compare display strings.
|
||||
#[cfg(feature = "rip")]
|
||||
pub use dirimage::DirImage;
|
||||
#[cfg(feature = "rip")]
|
||||
pub use disc::{
|
||||
AacsState, AudioChannels, AudioStream, Clip, Codec, ColorSpace, ContentFormat, Disc,
|
||||
DiscFormat, DiscId, DiscTitle, DriveCredentials, Extent, ExtractOptions, ExtractResult,
|
||||
FileResult, FrameRate, HdrFormat, Key, KeyOrigin, LabelPurpose, LabelQualifier, Resolution,
|
||||
SampleRate, ScanOptions, Stream, SubtitleStream, VideoStream,
|
||||
};
|
||||
#[cfg(feature = "rip")]
|
||||
pub use keysource::{DiscInputs, KeySource, read_encrypted_units, resolve_and_apply};
|
||||
|
||||
// ─── Streams ────────────────────────────────────────────────────────────────
|
||||
// All types implement `pes::Stream` (re-exported `PesStream` to avoid colliding
|
||||
// with `disc::Stream`). Prefer `input()`/`output()` URL resolvers over direct construction.
|
||||
#[cfg(feature = "rip")]
|
||||
pub use pes::PesFrame;
|
||||
#[cfg(feature = "rip")]
|
||||
pub use pes::Stream as PesStream;
|
||||
|
||||
#[cfg(feature = "rip")]
|
||||
pub use mux::DiscStream;
|
||||
#[cfg(feature = "rip")]
|
||||
pub use mux::M2tsStream;
|
||||
#[cfg(feature = "rip")]
|
||||
pub use mux::MkvStream;
|
||||
#[cfg(feature = "rip")]
|
||||
pub use mux::NetworkStream;
|
||||
#[cfg(feature = "rip")]
|
||||
pub use mux::NullStream;
|
||||
#[cfg(feature = "rip")]
|
||||
pub use mux::StdioStream;
|
||||
#[cfg(feature = "rip")]
|
||||
pub use mux::WriteSeek;
|
||||
#[cfg(feature = "rip")]
|
||||
pub use mux::{InputOptions, StreamUrl, input, output, parse_url};
|
||||
#[cfg(feature = "rip")]
|
||||
pub use mux::{Medium, SourceInfo};
|
||||
#[cfg(feature = "rip")]
|
||||
pub use mux::{Mp4FitReport, Mp4Sink, Mp4SkipReason, mp4_fit_report};
|
||||
|
||||
// ─── Lower-level surfaces ───────────────────────────────────────────────────
|
||||
// `ScsiTransport` is the platform trait Drive uses, exposed for out-of-tree
|
||||
// backends. `DecryptingSectorSource` wraps any `SectorSource` to decrypt (AACS/CSS).
|
||||
#[cfg(feature = "rip")]
|
||||
pub use mux::build_iso_pipeline;
|
||||
#[cfg(feature = "rip")]
|
||||
pub use mux::resolve_mux_key_map;
|
||||
#[cfg(feature = "rip")]
|
||||
pub use mux::select::{PidFilter, StreamSelection};
|
||||
#[cfg(feature = "rip")]
|
||||
pub use mux::{MuxEvents, MuxInput, MuxOptions, MuxOutcome, mux_stream};
|
||||
pub use scsi::{DriveInfo, ScsiSense, ScsiTransport, SenseFamily, drive_has_disc, list_drives};
|
||||
#[cfg(feature = "rip")]
|
||||
pub use sector::{
|
||||
DecryptingSectorSource, FileSectorSource, KeyFetch, PrefetchedSectorSource, SectorSource,
|
||||
};
|
||||
#[cfg(feature = "rip")]
|
||||
pub use udf::{UdfFs, read_filesystem};
|
||||
|
||||
@@ -69,6 +69,9 @@ pub(crate) const TUR_TIMEOUT_MS: u32 = 5_000;
|
||||
/// kept submitting fresh reads. The Initio bridge couldn't drain the
|
||||
/// resulting command queue and entered a wedge state that only physical
|
||||
/// replug recovered — proven by the v0.13.18 + v0.13.20 live tests.
|
||||
// Consumed only by the ripping read path (`drive::read`); gated so a
|
||||
// transport-only build doesn't warn on an unused const.
|
||||
#[cfg(feature = "rip")]
|
||||
pub(crate) const READ_TIMEOUT_MS: u32 = 10_000;
|
||||
|
||||
/// Timeout for content READ commands on the recovery path —
|
||||
@@ -91,6 +94,7 @@ pub(crate) const READ_TIMEOUT_MS: u32 = 10_000;
|
||||
/// at 60 s, since that at least visited every range). Reverted; the
|
||||
/// kernel-auto-retry approach is being pursued via a `/dev/sr0` pread
|
||||
/// fallback instead.
|
||||
#[cfg(feature = "rip")]
|
||||
pub(crate) const READ_RECOVERY_TIMEOUT_MS: u32 = 60_000;
|
||||
|
||||
// ── SCSI status bytes (SPC-4 §4.5.5) ────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user