precisej_printable_errno/
lib.rs

1//! Printable system call errors for `nix`. **CURRENTLY IN DEVELOPMENT**
2//!
3//! # What?
4//! `precisej-printable-errno` is a simple library that adds the
5//! possibility of attaching a printable error message to every [Errno].
6//! It additionally lets you add an integer error code that can be used
7//! to exit the application.
8//!
9//! The library is intended to be used by lower-level applications that
10//! intend to use `nix`'s Rust-friendly bindings to libc system functions.
11//!
12//! **Note**: `precisej-printable-errno`'s authors have no relationship
13//! with the `nix-rust` maintainers.
14//!
15//! # Where?
16//! Any system that `nix` supports should be supported by
17//! `precisej-printable-errno`. To use this library, add
18//! `precisej-printable-errno = "$LIB_VERSION"` (replacing `$LIB_VERSION`
19//! with the latest version available in [crates.io](https://crates.io/)).
20//!
21//! Projects currently using `precisej-printable-errno`:
22//! * initd
23//!
24//! If you are the author of another Rust project, are using the library,
25//! and would like to be mentioned here, please contact me.
26//!
27//! # Why?
28//! When writing initd, I found that there wasn't a straightforward way
29//! to bubble up an exit code, and resorted to having a main() function
30//! call a function which would return an `i32`, and then call
31//! [std::process::exit] with the resulting error code. This was
32//! unergonomic and bypassed Rust's excellent Result handling. Therefore
33//! I decided to create special Error structs containing an error message
34//! and an exit code, and since I found it useful I decided to turn it
35//! into a library crate.
36//!
37//! I didn't find out how to do anything similar with other libraries
38//! such as `anyhow`. If someone finds a better, equally lightweight
39//! alternative please contact me.
40//!
41//! ## Why not Termination?
42//! As of 2021-12-10, [std::process::Termination] is unstable and requires
43//! the `termination_trait_lib` feature, which can only be activated in
44//! nightly versions of Rust. Not all programs can make use of nightly (some,
45//! such as `initd`, deny the use of unstable features in its codebase),
46//! for which this crate exists.
47//!
48//! Not all of this library's functionality can be replicated with
49//! [std::process::Termination], so this library can be of use even for users
50//! of nightly Rust, albeit somewhat awkwardly. Future versions of
51//! `precisej-printable-errno` will optionally include an implementation of
52//! [std::process::Termination] for [ExitError] as a non-default feature for
53//! interested nightly users.
54//!
55//! # How?
56//! ```no_run
57//! # use nix::fcntl::{OFlag, open};
58//! # use nix::sys::stat::Mode;
59//! # use nix::unistd::read;
60//! # use precisej_printable_errno::{ErrnoResult, ExitError, PrintableResult};
61//! /* use ... */
62//!
63//! const PROGRAM_NAME: &'static str = "example-program";
64//!
65//! pub fn main() {
66//!     if let Err(e) = start() {
67//!         e.eprint_and_exit()
68//!     }
69//! }
70//!
71//! pub fn start() -> Result<(), ExitError<String>> {
72//!     let init_file = open("/sbin/init", OFlag::O_RDONLY, Mode::empty())
73//!         .printable(PROGRAM_NAME, "unable to open /sbin/init")
74//!         .bail(1)?;
75//!
76//!     let mut buf = [0; 1024];
77//!     read(init_file, &mut buf)
78//!         .printable(PROGRAM_NAME, "unable to read first KB of /sbin/init")
79//!         .bail(2)?;
80//!
81//!     drop(buf);
82//!
83//!     open("/path/to/nonexistent/file", OFlag::O_RDONLY, Mode::empty())
84//!         .printable(PROGRAM_NAME, format!("unable to open {}", "/path/to/nonexistent/file"))
85//!         .bail(3)?;
86//!
87//!     // That last call should have caused the process to exit with
88//!     // code 3 and the following error message:
89//!     //
90//!     // example-program: unable to open /path/to/nonexistent/file: No such file or directory
91//!
92//!     Ok(())
93//! }
94//! ```
95#![crate_name = "precisej_printable_errno"]
96#![cfg_attr(test, deny(warnings))]
97#![deny(unused)]
98#![deny(unstable_features)]
99#![warn(missing_docs)]
100
101use std::{
102    error::Error,
103    ffi::c_void,
104    fmt::{
105        Debug,
106        Display,
107        Formatter,
108    },
109    process::exit,
110};
111use nix::{
112    errno::Errno,
113    libc::{STDERR_FILENO, _exit, write},
114};
115
116/// This is the base struct containing basic error information. Unless
117/// you call [printable_error] or you unwrap the error on a [Result]
118/// containing a `PrintableErrno`, you probably won't use this directly.
119///
120/// When printed, the error message will look like the following:
121/// * If there is an associated errno:
122///   `$program_name: $message: ${errno.desc()}`
123/// * If there is no errno (called from [printable_error]):
124///   `$program_name: $message`
125///
126/// Printed error tries to follow `perror(3p)`'s format with the
127/// addition of the program name.
128#[derive(Debug)]
129pub struct PrintableErrno<S: AsRef<str>> {
130    /// The name of the program responsible for generating this Error.
131    /// This is normally the name of the final binary crate, usually
132    /// saved as a const in the main.rs file.
133    program_name: &'static str,
134
135    /// The error message to be printed. While `PrintableErrno` can hold any valid String,
136    /// we recommend you specify the name of the function you tried to call or the action
137    /// you tried to perform. For example, if you are trying to call
138    /// `open("/path/to/file", OFlag::O_RDONLY, Mode::empty())`, the message could be as
139    /// simple as `open`, or more detailed like `unable to open /path/to/file`.
140    /// `PrintableErrno` will fill in the description of the [Errno] if it was available.
141    message: S,
142
143    /// The originating [Errno] value. This is always present unless this `PrintableErrno`
144    /// originates from a call to [printable_error].
145    errno: Option<Errno>,
146}
147impl <S: AsRef<str>> PrintableErrno<S> {
148    /// Attach an exit code to the Error. Useful for bubbling up errors from a deep
149    /// function using the `?` operator.
150    pub fn bail(self, exit_code: i32) -> ExitError<S> {
151        ExitError {
152            exit_code,
153            errno: self,
154        }
155    }
156
157    /// Print the error to stderr.
158    pub fn eprint(self) {
159        eprintln!("{}", self);
160    }
161
162    /// Same as [PrintableErrno::eprint], but attempts to `write()` directly to stderr
163    /// and not to call any non-signal-safe functions. Useful when one is the child
164    /// in a multi-threaded program after a call to `fork()`.
165    ///
166    /// # Note
167    /// No allocations should be made in this function in order to comply with `fork()`s
168    /// contract of signal-safety.
169    ///
170    /// # Safety
171    /// The caller must ensure that stderr (fd 2) remains open for the entirety of this
172    /// function call. A naive attempt at detecting a closed file descriptor is made on
173    /// first write. However, any subsequent writes may silently fail if stderr is closed
174    /// while this function is running.
175    pub fn eprint_signalsafe(&self) {
176        // XXX: Is writev signal-safe?
177        // signal-safety(7) lists write(2) as signal-safe, but not writev(2).
178        // However writev(2) states:
179        //     The writev() system call works just like write(2) except that
180        //     multiple buffers are written out.
181        // Since write(2) is signal-safe, and writev(2) claims to work like
182        // write(2), is its omission from signal-safety(7) an oversight? Or
183        // is there some other undocumented, unexplained difference between
184        // them? Furthermore, since writev(2)'s signal-safety is unclear, can
185        // we rely on POSIX-compliant OSes and libc/library authors to not
186        // introduce any point of unsafety in this regard? i.e. even if the
187        // CSRG issues a revision to the POSIX standard tomorrow that explicitly
188        // requires writev(2) to be signal-safe, can we be sure that all
189        // supported Linux versions comply, as well as all supported glibc/musl
190        // versions in use?
191        // Further reading:
192        // https://groups.google.com/g/comp.unix.programmer/c/IfHr5I8NwW0
193        // https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html
194        // https://austingroupbugs.net/view.php?id=1455
195        const CONST_COLON: &'static [u8] = b": ";
196        const CONST_NEWLINE: &'static [u8] = b"\n";
197
198        let program_name = &self.program_name.as_bytes();
199        let message = &self.message.as_ref().as_bytes();
200        let res = write_stderr(program_name);
201        if !res {
202            // error writing to stderr: there is nothing left to do
203            return;
204        }
205
206        write_stderr(CONST_COLON);
207        write_stderr(message);
208
209        let errno = self.errno.as_ref();
210        if let Some(errno) = errno {
211            let desc = errno.desc().as_bytes();
212            write_stderr(CONST_COLON);
213            write_stderr(desc);
214        }
215        write_stderr(CONST_NEWLINE);
216
217        fn write_stderr(buf: &[u8]) -> bool {
218            let mut pos = buf.as_ptr() as usize;
219            let mut len = buf.len();
220            loop {
221                let res = unsafe { write(STDERR_FILENO, pos as *const c_void, len) };
222                match res {
223                    0 => break true,
224                    e if e < 0 => {
225                        let err = Errno::last();
226                        if !matches!(err, Errno::EINTR) {
227                            break false;
228                        }
229                        // EINTR means we can try again, so continue loop
230                    }
231                    // written will always be positive
232                    w if (w as usize) < len => {
233                        let written = w as usize;
234                        pos += written;
235                        len -= written;
236                        // try again with remaining
237                    }
238                    _ => break true,
239                }
240            }
241        }
242    }
243}
244impl <S: AsRef<str>> Display for PrintableErrno<S> {
245    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
246        match self.errno {
247            Some(errno) => write!(f, "{}: {}: {}", self.program_name, self.message.as_ref(), errno.desc()),
248            None => write!(f, "{}: {}", self.program_name, self.message.as_ref()),
249        }
250    }
251}
252impl <S: AsRef<str> + Debug> Error for PrintableErrno<S> {
253    fn source(&self) -> Option<&(dyn Error + 'static)> {
254        match self.errno {
255            Some(ref x) => Some(x),
256            None => None,
257        }
258    }
259}
260
261// ***
262
263/// This struct contains an exit code and basic error information (from
264/// [PrintableErrno]). Unless you unwrap the error on a [Result] containing
265/// an `ExitError`, you probably won't use this directly.
266///
267/// When printed, the error message will look like the following:
268/// * If there is an associated errno:
269///   `$program_name: $message: ${errno.desc()}`
270/// * If there is no errno (originally called from [printable_error]):
271///   `$program_name: $message`
272///
273/// Printed error tries to follow `perror(3p)`'s format with the
274/// addition of the program name.
275///
276/// `eprint_and_exit` and `eprint_signal_safe_exit` also call `exit(exit_code)`
277/// and `_exit(exit_code)` respectively, exiting the process with the supplied
278/// exit code.
279#[derive(Debug)]
280pub struct ExitError<S: AsRef<str>> {
281    exit_code: i32,
282    errno: PrintableErrno<S>,
283}
284impl <S: AsRef<str>> ExitError<S> {
285    /// Print the error to stderr and exit with the supplied code.
286    ///
287    /// # Safety
288    /// The caller must ensure that stderr (fd 2) is open.
289    pub fn eprint_and_exit(self) -> ! {
290        eprintln!("{}", self);
291        exit(self.exit_code)
292    }
293
294    /// Same as [ExitError::eprint_and_exit], but attempts to `write()` directly to stderr
295    /// and not to call any non-signal-safe functions. Useful when one is the child
296    /// in a multi-threaded program after a call to `fork()`.
297    ///
298    /// # Safety
299    /// The caller must ensure that stderr (fd 2) remains open for the entirety of this
300    /// function call.
301    ///
302    /// `_exit` is safe to call from the fork()-ed child as it's signal-safe. However, it
303    /// doesn't call destructors (as opposed to exiting from main) nor does it call any exit
304    /// functions if they are set, so caller should only call this from a fork()-ed child.
305    #[allow(unused_unsafe)]
306    pub unsafe fn eprint_signal_safe_exit(self) -> ! {
307        self.errno.eprint_signalsafe();
308        unsafe {
309            _exit(self.exit_code);
310        }
311    }
312}
313impl <S: AsRef<str>> Display for ExitError<S> {
314    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
315        self.errno.fmt(f)
316    }
317}
318impl <S: AsRef<str> + Debug> Error for ExitError<S> {
319    fn source(&self) -> Option<&(dyn Error + 'static)> {
320        self.errno.source()
321    }
322}
323
324// ***
325
326/// This trait defines a [Result] with a nix [Errno], facilitating the conversion
327/// of the error to [PrintableErrno].
328pub trait ErrnoResult<T> {
329    /// Maps the nix [Errno] to a [PrintableErrno] with the program name and the
330    /// supplied message. This allows [nix::Result]s to be easily used with this
331    /// library's error handling and Rust's `?` operator.
332    fn printable<S: AsRef<str>>(self, program_name: &'static str, message: S) -> Result<T, PrintableErrno<S>>;
333}
334impl <T> ErrnoResult<T> for Result<T, Errno> {
335    fn printable<S: AsRef<str>>(self, program_name: &'static str, message: S) -> Result<T, PrintableErrno<S>> {
336        self.map_err(|e| PrintableErrno {
337            program_name,
338            message: message.into(),
339            errno: Some(e),
340        })
341    }
342}
343
344// ***
345
346/// This trait defines a [Result] with our own [PrintableErrno], allowing further conversion
347/// to [ExitError], or printing and returning and Option.
348pub trait PrintableResult<T, S: AsRef<str>> {
349    /// Maps the [PrintableErrno] to an [ExitError] in order to facilitate
350    /// bubbling up the error to the appropriate place to quit the program.
351    fn bail(self, exit_code: i32) -> Result<T, ExitError<S>>;
352
353    /// Consumes the result, prints the error (if present) to stderr, and returns
354    /// an [Option] of the underlying value.
355    ///
356    /// # Safety
357    /// The caller must ensure that stderr (fd 2) is open.
358    fn ok_or_eprint(self) -> Option<T>;
359
360    /// Same as [PrintableResult::ok_or_eprint], but attempts to `write()` directly
361    /// to stderr and not to call any non-signal-safe functions. Useful when one is
362    /// the child in a multi-threaded program after a call to `fork()`.
363    ///
364    /// # Safety
365    /// The caller must ensure that stderr (fd 2) is open.
366    fn ok_or_eprint_signal_safe(self) -> Option<T>;
367}
368impl <T, S: AsRef<str>> PrintableResult<T, S> for Result<T, PrintableErrno<S>> {
369    fn bail(self, exit_code: i32) -> Result<T, ExitError<S>> {
370        self.map_err(|e| e.bail(exit_code))
371    }
372    fn ok_or_eprint(self) -> Option<T> {
373        match self {
374            Ok(t) => Some(t),
375            Err(e) => {
376                e.eprint();
377                None
378            }
379        }
380    }
381
382    fn ok_or_eprint_signal_safe(self) -> Option<T> {
383        match self {
384            Ok(t) => Some(t),
385            Err(e) => {
386                e.eprint_signalsafe();
387                None
388            }
389        }
390    }
391}
392
393// ***
394
395/// This trait defines a [Result] with our own [ExitError]. This allows exiting the program
396/// with the error code specified by the [ExitError], as well as consuming itself and
397/// returning an [Option] after printing the Error to stderr.
398///
399/// **Note**: Normally, you should handle the error in the main function as in the provided
400/// example (see **`How?`** in crate documentation), but these functions can be useful at
401/// times when that might not be practical (for example, the child process failing an
402/// [execve][nix::unistd::execve] after a successful [fork][nix::unistd::fork].
403pub trait ExitErrorResult<T> {
404    /// Similar to [Result::unwrap], but prints the error (if present) to stderr and exits with
405    /// the specified exit code instead of causing the program to panic.
406    ///
407    /// **Note**: Normally, you should handle the error in the main function as in the provided
408    /// example (see **`How?`** in crate documentation).
409    ///
410    /// # Safety
411    /// The caller must ensure that stderr (fd 2) is open.
412    fn unwrap_or_eprint_exit(self) -> T;
413
414    /// Same as [ExitErrorResult::unwrap_or_eprint_exit], but attempts to `write()` directly
415    /// to stderr and not to call any non-signal-safe functions. Useful when one is the child
416    /// in a multi-threaded program after a call to `fork()`.
417    ///
418    /// # Safety
419    /// The caller must ensure that stderr (fd 2) is open.
420    ///
421    /// `_exit` is safe to call from the fork()-ed child as it's signal-safe. However, it
422    /// doesn't call destructors (as opposed to exiting from main) nor does it call any exit
423    /// functions if they are set, so caller should only call this from a fork()-ed child.
424    unsafe fn unwrap_or_eprint_signal_safe_exit(self) -> T;
425
426    /// Consumes the result, prints the error (if present) to stderr, and returns
427    /// an [Option] of the underlying value.
428    ///
429    /// # Safety
430    /// The caller must ensure that stderr (fd 2) is open.
431    fn ok_or_eprint(self) -> Option<T>;
432
433    /// Same as [ExitErrorResult::ok_or_eprint], but attempts to `write()` directly
434    /// to stderr and not to call any non-signal-safe functions. Useful when one is
435    /// the child in a multi-threaded program after a call to `fork()`.
436    ///
437    /// # Safety
438    /// The caller must ensure that stderr (fd 2) is open.
439    fn ok_or_eprint_signal_safe(self) -> Option<T>;
440}
441impl <T, S: AsRef<str>> ExitErrorResult<T> for Result<T, ExitError<S>> {
442    fn unwrap_or_eprint_exit(self) -> T {
443        match self {
444            Ok(t) => t,
445            Err(e) => e.eprint_and_exit()
446        }
447    }
448
449    #[allow(unused_unsafe)]
450    unsafe fn unwrap_or_eprint_signal_safe_exit(self) -> T {
451        match self {
452            Ok(t) => t,
453            Err(e) => unsafe { e.eprint_signal_safe_exit() }
454        }
455    }
456
457    fn ok_or_eprint(self) -> Option<T> {
458        match self {
459            Ok(t) => Some(t),
460            Err(e) => {
461                e.errno.eprint();
462                None
463            }
464        }
465    }
466
467    fn ok_or_eprint_signal_safe(self) -> Option<T> {
468        match self {
469            Ok(t) => Some(t),
470            Err(e) => {
471                e.errno.eprint_signalsafe();
472                None
473            }
474        }
475    }
476}
477
478// ***
479
480/// Creates an error out of the program name and the supplied message. Useful
481/// when an Errno isn't present, but the program wants to signal a problem to
482/// the user and possibly exit if it's fatal.
483pub fn printable_error<S: AsRef<str>>(program_name: &'static str, message: S) -> PrintableErrno<S> {
484    PrintableErrno {
485        program_name,
486        message: message.into(),
487        errno: None
488    }
489}
490
491// ***
492
493impl From<PrintableErrno<&'_ str>> for PrintableErrno<String> {
494    fn from(item: PrintableErrno<&'_ str>) -> Self {
495        PrintableErrno {
496            program_name: item.program_name,
497            message: item.message.to_string(),
498            errno: item.errno,
499        }
500    }
501}
502
503impl From<ExitError<&'_ str>> for ExitError<String> {
504    fn from(item: ExitError<&'_ str>) -> Self {
505        ExitError {
506            exit_code: item.exit_code,
507            errno: item.errno.into(),
508        }
509    }
510}
511
512// ***
513
514#[cfg(test)]
515mod tests {
516    use nix::errno::Errno;
517    use nix::fcntl::{open, OFlag};
518    use nix::sys::stat::Mode;
519    use crate::{ErrnoResult, PrintableResult, printable_error, PrintableErrno, ExitError, ExitErrorResult};
520
521    const TEST_NAME: &str = "precisej-printable-errno";
522
523    #[test]
524    fn printable_message_witherrno() {
525        println!();
526        println!("START TEST 1");
527
528        const MSG1_NAME: &str = "test1: unable to open /pathto/nonexistent/file";
529
530        let result1 = open("/pathto/nonexistent/file", OFlag::O_RDONLY, Mode::empty())
531            .printable(TEST_NAME, MSG1_NAME);
532
533        let result2 = open("/pathto/nonexistent/file", OFlag::O_RDONLY, Mode::empty())
534            .printable(TEST_NAME, MSG1_NAME);
535
536        result1.ok_or_eprint();
537        assert_eq!(
538            format!("{}", result2.unwrap_err()),
539            format!("{}: {}: {}", TEST_NAME, MSG1_NAME, Errno::ENOENT.desc())
540        );
541
542        println!("END TEST 1");
543    }
544
545    #[test]
546    fn printable_message_noerrno() {
547        println!();
548        println!("START TEST 2");
549
550        const MSG2_NAME: &str = "test2: expected value 1, got 30";
551
552        let result1 = printable_error(TEST_NAME, MSG2_NAME);
553        let result2 = printable_error(TEST_NAME, MSG2_NAME);
554
555        result1.eprint();
556        assert_eq!(
557            format!("{}", result2),
558            format!("{}: {}", TEST_NAME, MSG2_NAME)
559        );
560
561        println!("END TEST 2");
562    }
563
564    #[test]
565    fn bail_correct() {
566        println!();
567        println!("START TEST 3");
568
569        const MSG3_NAME: &str = "test3: unable to open /pathto/nonexistent/file";
570        const MSG3_EXIT_CODE: i32 = 1;
571
572        let result1 = open("/pathto/nonexistent/file", OFlag::O_RDONLY, Mode::empty())
573            .printable(TEST_NAME, MSG3_NAME)
574            .bail(MSG3_EXIT_CODE);
575
576        let result2 = open("/pathto/nonexistent/file", OFlag::O_RDONLY, Mode::empty())
577            .printable(TEST_NAME, MSG3_NAME)
578            .bail(MSG3_EXIT_CODE);
579
580        let result1 = result1.unwrap_err();
581        eprintln!("{}", result1.errno);
582        eprintln!("Process finished with exit code {}.", result1.exit_code);
583        assert_eq!(
584            result2.unwrap_err().exit_code,
585            MSG3_EXIT_CODE
586        );
587
588        println!("END TEST 3");
589    }
590
591    #[test]
592    fn eprint_signal_safe() {
593        println!();
594        println!("START TEST 4");
595
596        const MSG4_NAME_1: &str = "test4: unable to open /pathto/nonexistent/file";
597        const MSG4_NAME_2: &str = "test4: expected value 1, got 30";
598
599        let result1 = open("/pathto/nonexistent/file", OFlag::O_RDONLY, Mode::empty())
600            .printable(TEST_NAME, MSG4_NAME_1);
601        result1.ok_or_eprint_signal_safe();
602
603        let result2 = printable_error(TEST_NAME, MSG4_NAME_2);
604        let result3 = printable_error(TEST_NAME, MSG4_NAME_2);
605        result2.eprint_signalsafe();
606        result3.eprint_signalsafe();
607
608        println!("END TEST 4");
609    }
610
611    #[test]
612    fn from_str_into_string() {
613        println!();
614        println!("START TEST 5");
615
616        const MSG5_NAME: &str = "test5: expected value 1, got 30";
617
618        let result1 = printable_error(TEST_NAME, MSG5_NAME);
619        let result2 = printable_error(TEST_NAME, MSG5_NAME).bail(2);
620        let result3: PrintableErrno<String> = result1.into();
621        let _: ExitError<String> = result2.into();
622
623        result3.eprint();
624
625        internal_test_5_fn_result_1().ok_or_eprint();
626        internal_test_5_fn_result_2().ok_or_eprint();
627
628        println!("END TEST 5");
629
630        fn internal_test_5_fn_result_1() -> Result<(), PrintableErrno<String>> {
631            Err::<(), PrintableErrno<&'static str>>(printable_error(TEST_NAME, MSG5_NAME))?;
632            Ok(())
633        }
634        fn internal_test_5_fn_result_2() -> Result<(), ExitError<String>> {
635            Err::<(), PrintableErrno<&'static str>>(printable_error(TEST_NAME, MSG5_NAME)).bail(2)?;
636            Ok(())
637        }
638    }
639}