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}