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}