Skip to main content

fs_core/
lib.rs

1//! Pure-Rust block-device framework. The shared substrate every filesystem
2//! driver and disk-image reader plugs into.
3//!
4//! See the crate-level
5//! [`README`](https://github.com/antimatter-studios/rust-fs-core) for the
6//! intended consumers and design.
7
8#![deny(unsafe_op_in_unsafe_fn)]
9
10/// One in-memory device for this crate's own tests.
11#[cfg(test)]
12pub(crate) mod test_device;
13
14pub mod block;
15pub mod caching_device;
16pub mod callback_device;
17// The command-line plumbing the family's tools share, behind the `cli`
18// feature. Its own module docs say what it is.
19#[cfg(feature = "cli")]
20pub mod cli;
21pub mod counting_device;
22pub mod error;
23pub mod ffi;
24#[cfg(any(unix, windows))]
25pub mod file_device;
26pub mod readonly;
27pub mod slice;
28pub mod stream;
29
30pub use block::{BlockDevice, BlockRead};
31pub use caching_device::CachingDevice;
32pub use callback_device::{CallbackDevice, FlushCb, ReadCb, WriteCb};
33pub use counting_device::CountingDevice;
34pub use error::{Error, Result};
35#[cfg(any(unix, windows))]
36pub use file_device::FileDevice;
37pub use readonly::ReadOnlyDevice;
38pub use slice::{OwnedRwSlice, OwnedSlice, SliceReader};
39pub use stream::BlockReadStreamer;
40
41// DOES THIS BUILD ACTUALLY TRAP AN ARITHMETIC OVERFLOW?
42//
43// Inline in `lib.rs` rather than a module of its own, for the reason
44// `tests/ci_profile.rs` explains at length: a separate file under `src/`
45// hangs off one `mod` line, and losing that line leaves the file present,
46// uncompiled, and asserting nothing, with no lint to say so. Inline,
47// there is no declaration to lose. It cannot live in `tests/` at all --
48// it has to be part of the library target that the debug step builds,
49// because the question it answers is about that build.
50#[cfg(test)]
51mod overflow_checks {
52    /// Set by the debug step in `ci.yml`, and by nothing else.
53    ///
54    /// The release step must NOT set it: overflow checks are off there
55    /// deliberately, because that is what ships.
56    const HANDSHAKE: &str = "EXPECT_OVERFLOW_CHECKS";
57
58    /// Perform an overflow and report whether the program was stopped.
59    ///
60    /// This is the only question that matters and the only one that
61    /// cannot be answered by reading a file. `overflow-checks` can be
62    /// turned off by a manifest key in four spellings, by a
63    /// `CARGO_PROFILE_TEST_OVERFLOW_CHECKS` variable set at step or job
64    /// level, by `.cargo/config.toml`, and by whatever cargo adds next.
65    /// Each of those was found separately, all the same shape: a scanner
66    /// asking whether a known spelling of "disabled" appears in the text
67    /// it happens to read. This asks the build instead.
68    fn this_build_traps_an_overflow() -> bool {
69        // The hook is silenced so a deliberate panic does not print a
70        // scary backtrace into a passing job's log. `set_hook` is
71        // process-wide, so for the moment this is installed another
72        // thread's panic message would be swallowed too -- it would
73        // still fail, just less legibly. Narrow, and worth it against a
74        // log line that reads as a failure on every green run.
75        let previous = std::panic::take_hook();
76        std::panic::set_hook(Box::new(|_| {}));
77        let trapped = std::panic::catch_unwind(|| {
78            // `black_box` keeps this out of const evaluation, where it
79            // would be a compile error rather than a runtime trap.
80            let big = std::hint::black_box(u64::MAX);
81            std::hint::black_box(big + 1);
82        })
83        .is_err();
84        std::panic::set_hook(previous);
85        trapped
86    }
87
88    /// When the gate says it built a profile that traps, check that it
89    /// did.
90    ///
91    /// # What guards this test's own relevance
92    ///
93    /// With `HANDSHAKE` unset this asserts nothing, which is the shape
94    /// of a test that passes because its fixture is missing. It is not
95    /// guarded here, because it cannot be: a build cannot tell whether
96    /// it was supposed to be the checking one. It is guarded in
97    /// `tests/ci_profile.rs`, which reads `ci.yml` and refuses if no
98    /// `cargo test` there runs without `--release` while setting this
99    /// variable. Delete the step, or drop the variable from it, and that
100    /// test fails.
101    ///
102    /// The two are not redundant. A runtime check cannot notice its own
103    /// absence; a text scan cannot tell whether the build it describes
104    /// works. One proves the step is there, this proves it can see.
105    #[test]
106    fn the_build_the_gate_asked_to_check_does_check() {
107        let asked = match std::env::var(HANDSHAKE) {
108            Ok(value) if !value.is_empty() => value,
109            _ => return,
110        };
111
112        assert!(
113            this_build_traps_an_overflow(),
114            "{HANDSHAKE}={asked} was set, so this run is the one that is \
115             supposed to panic on arithmetic overflow -- and it did not. \
116             The checks are off in the profile the gate built. Something \
117             turned them off where nothing reading Cargo.toml can see it: \
118             a CARGO_PROFILE_TEST_OVERFLOW_CHECKS variable at step or job \
119             level, a .cargo/config.toml, or a cargo mechanism newer than \
120             this comment. The debug step is running and blind, which is \
121             the exact state it exists to rule out."
122        );
123    }
124}