Skip to main content

fez/
lib.rs

1//! fez: an agent-friendly front end for systemd operations over local and SSH
2//! transports.
3//!
4//! The crate is structured as a library plus thin binaries so the fake bridge
5//! and integration tests can reuse the protocol modules.
6
7#![deny(missing_docs)]
8
9/// JSON-lines audit logging of attempted and completed mutations.
10pub mod audit;
11/// Concrete capability implementations (the commands fez runs).
12pub mod capabilities;
13/// Machine-readable descriptors of the capability surface.
14pub mod capability;
15/// Command-line argument definitions.
16pub mod cli;
17mod dispatch;
18/// The `fez/v1` JSON response envelope.
19pub mod envelope;
20/// Crate-wide error type and exit-code mapping.
21pub mod error;
22/// The agent bootstrap contract printed by `fez guide`.
23pub mod guide;
24/// Model Context Protocol server support.
25pub mod mcp;
26/// Wire protocol between fez and the bridge.
27pub mod protocol;
28/// Guardrails around destructive operations (protected units, confirmations).
29pub mod safety;
30/// Local and SSH transports for reaching the bridge.
31pub mod transport;
32
33/// Restore the default `SIGPIPE` disposition on Unix.
34///
35/// Rust installs `SIG_IGN` for `SIGPIPE` at startup, which turns a closed
36/// downstream pipe (`fez ... | head`) into an `EPIPE` write error. The
37/// `print!`/`println!` macros then `panic!`, so fez exits 101 with a backtrace
38/// hint instead of stopping quietly. Resetting the disposition to `SIG_DFL`
39/// makes the process die from `SIGPIPE` the Unix way, matching coreutils, git,
40/// and most other CLIs. This is a no-op on non-Unix targets.
41///
42/// Call this once, as early as possible in `main`, before any stdout writes.
43pub fn reset_sigpipe() {
44    #[cfg(unix)]
45    unix_sigpipe::reset();
46}
47
48/// Unix `SIGPIPE` handling, isolated so the FFI stays in one place.
49#[cfg(unix)]
50mod unix_sigpipe {
51    /// `SIGPIPE` signal number (13 on Linux and macOS).
52    const SIGPIPE: i32 = 13;
53    /// `SIG_DFL` handler sentinel: take the default action.
54    const SIG_DFL: usize = 0;
55    /// `SIG_IGN` handler sentinel: ignore the signal (Rust's startup default).
56    #[cfg(test)]
57    const SIG_IGN: usize = 1;
58
59    extern "C" {
60        /// libc `signal(2)`: install `handler` for `signum`, returning the
61        /// previous handler.
62        fn signal(signum: i32, handler: usize) -> usize;
63    }
64
65    /// Set `SIGPIPE` to `SIG_DFL`, returning the previous disposition.
66    pub fn reset() -> usize {
67        // SAFETY: `signal` has a stable C ABI. We pass the well-known `SIGPIPE`
68        // number and the `SIG_DFL` sentinel; the call has no memory effects we
69        // rely on, and we simply forward its return value (the old handler).
70        unsafe { signal(SIGPIPE, SIG_DFL) }
71    }
72
73    #[cfg(test)]
74    mod tests {
75        use super::{reset, SIG_DFL, SIG_IGN};
76
77        #[test]
78        fn reset_moves_disposition_from_ignore_to_default() {
79            // Rust installs SIG_IGN at startup, so the first reset reports that
80            // as the previous disposition; a second reset then sees SIG_DFL,
81            // proving the first call took effect.
82            let previous = reset();
83            let after = reset();
84            assert!(previous == SIG_IGN || previous == SIG_DFL);
85            assert_eq!(after, SIG_DFL);
86        }
87
88        #[test]
89        fn public_reset_sigpipe_is_callable() {
90            // Exercise the public wrapper so it stays covered; idempotent and
91            // safe to call repeatedly within the test process.
92            crate::reset_sigpipe();
93            assert_eq!(reset(), SIG_DFL);
94        }
95    }
96}
97
98/// Entry point: parse-to-exit. Returns the process exit code.
99pub fn run(cli: cli::Cli) -> i32 {
100    dispatch::run(cli)
101}