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/// Command-line argument definitions.
14pub mod cli;
15mod dispatch;
16/// The `fez/v1` JSON response envelope.
17pub mod envelope;
18/// Crate-wide error type and exit-code mapping.
19pub mod error;
20/// The agent bootstrap contract printed by `fez guide`.
21pub mod guide;
22/// Model Context Protocol server support.
23pub mod mcp;
24/// Wire protocol between fez and the bridge.
25pub mod protocol;
26/// Guardrails around destructive operations (protected units, confirmations).
27pub mod safety;
28/// Machine-readable descriptors of the capability surface.
29pub mod schema;
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}