uucore/mods/error.rs
1// This file is part of the uutils coreutils package.
2//
3// For the full copyright and license information, please view the LICENSE
4// file that was distributed with this source code.
5//! All utils return exit with an exit code. Usually, the following scheme is used:
6//! * `0`: succeeded
7//! * `1`: minor problems
8//! * `2`: major problems
9//!
10//! This module provides types to reconcile these exit codes with idiomatic Rust error
11//! handling. This has a couple advantages over manually using [`std::process::exit`]:
12//! 1. It enables the use of `?`, `map_err`, `unwrap_or`, etc. in `uumain`.
13//! 1. It encourages the use of [`UResult`]/[`Result`] in functions in the utils.
14//! 1. The error messages are largely standardized across utils.
15//! 1. Standardized error messages can be created from external result types
16//! (i.e. [`std::io::Result`] & `clap::ClapResult`).
17//! 1. [`set_exit_code`] takes away the burden of manually tracking exit codes for non-fatal errors.
18//!
19//! # Usage
20//! The signature of a typical util should be:
21//! ```ignore
22//! fn uumain(args: impl uucore::Args) -> UResult<()> {
23//! ...
24//! }
25//! ```
26//! [`UResult`] is a simple wrapper around [`Result`] with a custom error trait: [`UError`]. The
27//! most important difference with types implementing [`std::error::Error`] is that [`UError`]s
28//! can specify the exit code of the program when they are returned from `uumain`:
29//! * When `Ok` is returned, the code set with [`set_exit_code`] is used as exit code. If
30//! [`set_exit_code`] was not used, then `0` is used.
31//! * When `Err` is returned, the code corresponding with the error is used as exit code and the
32//! error message is displayed.
33//!
34//! Additionally, the errors can be displayed manually with the [`crate::show`] and [`crate::show_if_err`] macros:
35//! ```ignore
36//! let res = Err(USimpleError::new(1, "Error!!"));
37//! show_if_err!(res);
38//! // or
39//! if let Err(e) = res {
40//! show!(e);
41//! }
42//! ```
43//!
44//! **Note**: The [`crate::show`] and [`crate::show_if_err`] macros set the exit code of the program using
45//! [`set_exit_code`]. See the documentation on that function for more information.
46//!
47//! # Guidelines
48//! * Use error types from `uucore` where possible.
49//! * Add error types to `uucore` if an error appears in multiple utils.
50//! * Prefer proper custom error types over [`ExitCode`] and [`USimpleError`].
51//! * [`USimpleError`] may be used in small utils with simple error handling.
52//! * Using [`ExitCode`] is not recommended but can be useful for converting utils to use
53//! [`UResult`].
54
55// spell-checker:ignore uioerror rustdoc
56
57use std::{
58 cell::Cell,
59 error::Error,
60 fmt::{Display, Formatter},
61 io::Write,
62 sync::atomic::{AtomicI32, Ordering},
63};
64
65static EXIT_CODE: AtomicI32 = AtomicI32::new(0);
66
67/// Get the last exit code set with [`set_exit_code`].
68/// The default value is `0`.
69pub fn get_exit_code() -> i32 {
70 EXIT_CODE.load(Ordering::SeqCst)
71}
72
73/// Set the exit code for the program if `uumain` returns `Ok(())`.
74///
75/// This function is most useful for non-fatal errors, for example when applying an operation to
76/// multiple files:
77/// ```ignore
78/// use uucore::error::{UResult, set_exit_code};
79///
80/// fn uumain(args: impl uucore::Args) -> UResult<()> {
81/// ...
82/// for file in files {
83/// let res = some_operation_that_might_fail(file);
84/// match res {
85/// Ok() => {},
86/// Err(_) => set_exit_code(1),
87/// }
88/// }
89/// Ok(()) // If any of the operations failed, 1 is returned.
90/// }
91/// ```
92pub fn set_exit_code(code: i32) {
93 EXIT_CODE.store(code, Ordering::SeqCst);
94}
95
96/// Result type that should be returned by all utils.
97pub type UResult<T> = Result<T, Box<dyn UError>>;
98
99/// Custom errors defined by the utils and `uucore`.
100///
101/// All errors should implement [`std::error::Error`], [`std::fmt::Display`] and
102/// [`std::fmt::Debug`] and have an additional `code` method that specifies the
103/// exit code of the program if the error is returned from `uumain`.
104///
105/// An example of a custom error from `ls`:
106///
107/// ```
108/// use uucore::{
109/// display::Quotable,
110/// error::{UError, UResult}
111/// };
112/// use std::{
113/// error::Error,
114/// fmt::{Display, Debug},
115/// path::PathBuf
116/// };
117///
118/// #[derive(Debug)]
119/// enum LsError {
120/// InvalidLineWidth(String),
121/// NoMetadata(PathBuf),
122/// }
123///
124/// impl UError for LsError {
125/// fn code(&self) -> i32 {
126/// match self {
127/// LsError::InvalidLineWidth(_) => 2,
128/// LsError::NoMetadata(_) => 1,
129/// }
130/// }
131/// }
132///
133/// impl Error for LsError {}
134///
135/// impl Display for LsError {
136/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137/// match self {
138/// LsError::InvalidLineWidth(s) => write!(f, "invalid line width: {}", s.quote()),
139/// LsError::NoMetadata(p) => write!(f, "could not open file: {}", p.quote()),
140/// }
141/// }
142/// }
143/// ```
144///
145/// The main routine would look like this:
146///
147/// ```ignore
148/// #[uucore::main]
149/// pub fn uumain(args: impl uucore::Args) -> UResult<()> {
150/// // Perform computations here ...
151/// return Err(LsError::InvalidLineWidth(String::from("test")).into())
152/// }
153/// ```
154///
155/// The call to `into()` is required to convert the `LsError` to
156/// [`Box<dyn UError>`]. The implementation for `From` is provided automatically.
157///
158/// A crate like [`quick_error`](https://crates.io/crates/quick-error) might
159/// also be used, but will still require an `impl` for the `code` method.
160pub trait UError: Error + Send {
161 /// Error code of a custom error.
162 ///
163 /// Set a return value for each variant of an enum-type to associate an
164 /// error code (which is returned to the system shell) with an error
165 /// variant.
166 ///
167 /// # Example
168 ///
169 /// ```
170 /// use uucore::{
171 /// display::Quotable,
172 /// error::UError
173 /// };
174 /// use std::{
175 /// error::Error,
176 /// fmt::{Display, Debug},
177 /// path::PathBuf
178 /// };
179 ///
180 /// #[derive(Debug)]
181 /// enum MyError {
182 /// Foo(String),
183 /// Bar(PathBuf),
184 /// Bing(),
185 /// }
186 ///
187 /// impl UError for MyError {
188 /// fn code(&self) -> i32 {
189 /// match self {
190 /// MyError::Foo(_) => 2,
191 /// // All other errors yield the same error code, there's no
192 /// // need to list them explicitly.
193 /// _ => 1,
194 /// }
195 /// }
196 /// }
197 ///
198 /// impl Error for MyError {}
199 ///
200 /// impl Display for MyError {
201 /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202 /// use MyError as ME;
203 /// match self {
204 /// ME::Foo(s) => write!(f, "Unknown Foo: {}", s.quote()),
205 /// ME::Bar(p) => write!(f, "Couldn't find Bar: {}", p.quote()),
206 /// ME::Bing() => write!(f, "Exterminate!"),
207 /// }
208 /// }
209 /// }
210 /// ```
211 fn code(&self) -> i32 {
212 1
213 }
214
215 /// Print usage help to a custom error.
216 ///
217 /// Return true or false to control whether a short usage help is printed
218 /// below the error message. The usage help is in the format: "Try `{name}
219 /// --help` for more information." and printed only if `true` is returned.
220 ///
221 /// # Example
222 ///
223 /// ```
224 /// use uucore::{
225 /// display::Quotable,
226 /// error::UError
227 /// };
228 /// use std::{
229 /// error::Error,
230 /// fmt::{Display, Debug},
231 /// path::PathBuf
232 /// };
233 ///
234 /// #[derive(Debug)]
235 /// enum MyError {
236 /// Foo(String),
237 /// Bar(PathBuf),
238 /// Bing(),
239 /// }
240 ///
241 /// impl UError for MyError {
242 /// fn usage(&self) -> bool {
243 /// match self {
244 /// // This will have a short usage help appended
245 /// MyError::Bar(_) => true,
246 /// // These matches won't have a short usage help appended
247 /// _ => false,
248 /// }
249 /// }
250 /// }
251 ///
252 /// impl Error for MyError {}
253 ///
254 /// impl Display for MyError {
255 /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256 /// use MyError as ME;
257 /// match self {
258 /// ME::Foo(s) => write!(f, "Unknown Foo: {}", s.quote()),
259 /// ME::Bar(p) => write!(f, "Couldn't find Bar: {}", p.quote()),
260 /// ME::Bing() => write!(f, "Exterminate!"),
261 /// }
262 /// }
263 /// }
264 /// ```
265 fn usage(&self) -> bool {
266 false
267 }
268}
269
270impl<T> From<T> for Box<dyn UError>
271where
272 T: UError + 'static,
273{
274 fn from(t: T) -> Self {
275 Box::new(t)
276 }
277}
278
279/// A simple error type with an exit code and a message that implements [`UError`].
280///
281/// ```
282/// use uucore::error::{UResult, USimpleError};
283/// let err = USimpleError { code: 1, message: "error!".into()};
284/// let res: UResult<()> = Err(err.into());
285/// // or using the `new` method:
286/// let res: UResult<()> = Err(USimpleError::new(1, "error!"));
287/// ```
288#[derive(Debug)]
289pub struct USimpleError {
290 /// Exit code of the error.
291 pub code: i32,
292
293 /// Error message.
294 pub message: String,
295}
296
297impl USimpleError {
298 /// Create a new `USimpleError` with a given exit code and message.
299 #[allow(clippy::new_ret_no_self)]
300 pub fn new<S: Into<String>>(code: i32, message: S) -> Box<dyn UError> {
301 Box::new(Self {
302 code,
303 message: message.into(),
304 })
305 }
306}
307
308impl Error for USimpleError {}
309
310impl Display for USimpleError {
311 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
312 self.message.fmt(f)
313 }
314}
315
316impl UError for USimpleError {
317 fn code(&self) -> i32 {
318 self.code
319 }
320}
321
322/// Wrapper type around [`std::io::Error`].
323#[derive(Debug)]
324pub struct UUsageError {
325 /// Exit code of the error.
326 pub code: i32,
327
328 /// Error message.
329 pub message: String,
330}
331
332impl UUsageError {
333 #[allow(clippy::new_ret_no_self)]
334 /// Create a new `UUsageError` with a given exit code and message.
335 pub fn new<S: Into<String>>(code: i32, message: S) -> Box<dyn UError> {
336 Box::new(Self {
337 code,
338 message: message.into(),
339 })
340 }
341}
342
343impl Error for UUsageError {}
344
345impl Display for UUsageError {
346 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
347 self.message.fmt(f)
348 }
349}
350
351impl UError for UUsageError {
352 fn code(&self) -> i32 {
353 self.code
354 }
355
356 fn usage(&self) -> bool {
357 true
358 }
359}
360
361/// Wrapper type around [`std::io::Error`].
362///
363/// The messages displayed by [`UIoError`] should match the error messages displayed by GNU
364/// coreutils.
365///
366/// There are two ways to construct this type: with [`UIoError::new`] or by calling the
367/// [`FromIo::map_err_context`] method on a [`std::io::Result`] or [`std::io::Error`].
368/// ```
369/// use uucore::{
370/// display::Quotable,
371/// error::{FromIo, UResult, UIoError, UError}
372/// };
373/// use std::fs::File;
374/// use std::path::Path;
375/// let path = Path::new("test.txt");
376///
377/// // Manual construction
378/// let e: Box<dyn UError> = UIoError::new(
379/// std::io::ErrorKind::NotFound,
380/// format!("cannot access {}", path.quote())
381/// );
382/// let res: UResult<()> = Err(e.into());
383///
384/// // Converting from an `std::io::Error`.
385/// let res: UResult<File> = File::open(path).map_err_context(|| format!("cannot access {}", path.quote()));
386/// ```
387#[derive(Debug)]
388pub struct UIoError {
389 context: Option<String>,
390 inner: std::io::Error,
391}
392
393impl UIoError {
394 #[allow(clippy::new_ret_no_self)]
395 /// Create a new `UIoError` with a given exit code and message.
396 pub fn new<S: Into<String>>(kind: std::io::ErrorKind, context: S) -> Box<dyn UError> {
397 Box::new(Self {
398 context: Some(context.into()),
399 inner: kind.into(),
400 })
401 }
402}
403
404impl UError for UIoError {}
405
406impl Error for UIoError {}
407
408impl Display for UIoError {
409 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
410 use std::io::ErrorKind::*;
411
412 let message;
413 let message = if self.inner.raw_os_error().is_some() {
414 // These are errors that come directly from the OS.
415 // We want to normalize their messages across systems,
416 // and we want to strip the "(os error X)" suffix.
417 match self.inner.kind() {
418 NotFound => "No such file or directory",
419 PermissionDenied => "Permission denied",
420 ConnectionRefused => "Connection refused",
421 ConnectionReset => "Connection reset",
422 ConnectionAborted => "Connection aborted",
423 NotConnected => "Not connected",
424 AddrInUse => "Address in use",
425 AddrNotAvailable => "Address not available",
426 BrokenPipe => "Broken pipe",
427 AlreadyExists => "Already exists",
428 WouldBlock => "Would block",
429 InvalidInput => "Invalid input",
430 InvalidData => "Invalid data",
431 TimedOut => "Timed out",
432 WriteZero => "Write zero",
433 Interrupted => "Interrupted",
434 UnexpectedEof => "Unexpected end of file",
435 _ => {
436 // TODO: When the new error variants
437 // (https://github.com/rust-lang/rust/issues/86442)
438 // are stabilized, we should add them to the match statement.
439 message = strip_errno(&self.inner);
440 &message
441 }
442 }
443 } else {
444 // These messages don't need as much normalization, and the above
445 // messages wouldn't always be a good substitute.
446 // For example, ErrorKind::NotFound doesn't necessarily mean it was
447 // a file that was not found.
448 // There are also errors with entirely custom messages.
449 message = self.inner.to_string();
450 &message
451 };
452 if let Some(ctx) = &self.context {
453 write!(f, "{ctx}: {message}")
454 } else {
455 write!(f, "{message}")
456 }
457 }
458}
459
460/// Strip the trailing " (os error XX)" from io error strings.
461pub fn strip_errno(err: &std::io::Error) -> String {
462 let mut msg = err.to_string();
463 if let Some(pos) = msg.find(" (os error ") {
464 msg.truncate(pos);
465 }
466 msg
467}
468
469/// Enables the conversion from [`std::io::Error`] to [`UError`] and from [`std::io::Result`] to
470/// [`UResult`].
471pub trait FromIo<T> {
472 /// Map the error context of an [`std::io::Error`] or [`std::io::Result`] to a custom error
473 fn map_err_context(self, context: impl FnOnce() -> String) -> T;
474}
475
476impl FromIo<Box<UIoError>> for std::io::Error {
477 fn map_err_context(self, context: impl FnOnce() -> String) -> Box<UIoError> {
478 Box::new(UIoError {
479 context: Some(context()),
480 inner: self,
481 })
482 }
483}
484
485impl<T> FromIo<UResult<T>> for std::io::Result<T> {
486 fn map_err_context(self, context: impl FnOnce() -> String) -> UResult<T> {
487 self.map_err(|e| e.map_err_context(context) as Box<dyn UError>)
488 }
489}
490
491impl FromIo<Box<UIoError>> for std::io::ErrorKind {
492 fn map_err_context(self, context: impl FnOnce() -> String) -> Box<UIoError> {
493 Box::new(UIoError {
494 context: Some(context()),
495 inner: std::io::Error::new(self, ""),
496 })
497 }
498}
499
500impl From<std::io::Error> for UIoError {
501 fn from(f: std::io::Error) -> Self {
502 Self {
503 context: None,
504 inner: f,
505 }
506 }
507}
508
509impl From<std::io::Error> for Box<dyn UError> {
510 fn from(f: std::io::Error) -> Self {
511 let u_error: UIoError = f.into();
512 Box::new(u_error) as Self
513 }
514}
515
516/// Enables the conversion from [`Result<T, nix::Error>`] to [`UResult<T>`].
517///
518/// # Examples
519///
520/// ```
521/// use uucore::error::FromIo;
522/// use nix::errno::Errno;
523///
524/// let nix_err = Err::<(), nix::Error>(Errno::EACCES);
525/// let uio_result = nix_err.map_err_context(|| String::from("fix me please!"));
526///
527/// // prints "fix me please!: Permission denied"
528/// println!("{}", uio_result.unwrap_err());
529/// ```
530#[cfg(unix)]
531impl<T> FromIo<UResult<T>> for Result<T, nix::Error> {
532 fn map_err_context(self, context: impl FnOnce() -> String) -> UResult<T> {
533 self.map_err(|e| {
534 Box::new(UIoError {
535 context: Some(context()),
536 inner: std::io::Error::from_raw_os_error(e as i32),
537 }) as Box<dyn UError>
538 })
539 }
540}
541
542#[cfg(unix)]
543impl<T> FromIo<UResult<T>> for nix::Error {
544 fn map_err_context(self, context: impl FnOnce() -> String) -> UResult<T> {
545 Err(Box::new(UIoError {
546 context: Some(context()),
547 inner: std::io::Error::from_raw_os_error(self as i32),
548 }) as Box<dyn UError>)
549 }
550}
551
552#[cfg(unix)]
553impl From<nix::Error> for UIoError {
554 fn from(f: nix::Error) -> Self {
555 Self {
556 context: None,
557 inner: std::io::Error::from_raw_os_error(f as i32),
558 }
559 }
560}
561
562#[cfg(unix)]
563impl From<nix::Error> for Box<dyn UError> {
564 fn from(f: nix::Error) -> Self {
565 let u_error: UIoError = f.into();
566 Box::new(u_error) as Self
567 }
568}
569
570/// Shorthand to construct [`UIoError`]-instances.
571///
572/// This macro serves as a convenience call to quickly construct instances of
573/// [`UIoError`]. It takes:
574///
575/// - An instance of [`std::io::Error`]
576/// - A `format!`-compatible string and
577/// - An arbitrary number of arguments to the format string
578///
579/// In exactly this order. It is equivalent to the more verbose code seen in the
580/// example.
581///
582/// # Examples
583///
584/// ```
585/// use uucore::error::UIoError;
586/// use uucore::uio_error;
587///
588/// let io_err = std::io::Error::new(
589/// std::io::ErrorKind::PermissionDenied, "fix me please!"
590/// );
591///
592/// let uio_err = UIoError::new(
593/// io_err.kind(),
594/// format!("Error code: {}", 2)
595/// );
596///
597/// let other_uio_err = uio_error!(io_err, "Error code: {}", 2);
598///
599/// // prints "fix me please!: Permission denied"
600/// println!("{uio_err}");
601/// // prints "Error code: 2: Permission denied"
602/// println!("{other_uio_err}");
603/// ```
604///
605/// The [`std::fmt::Display`] impl of [`UIoError`] will then ensure that an
606/// appropriate error message relating to the actual error kind of the
607/// [`std::io::Error`] is appended to whatever error message is defined in
608/// addition (as secondary argument).
609///
610/// If you want to show only the error message for the [`std::io::ErrorKind`]
611/// that's contained in [`UIoError`], pass the second argument as empty string:
612///
613/// ```
614/// use uucore::error::UIoError;
615/// use uucore::uio_error;
616///
617/// let io_err = std::io::Error::new(
618/// std::io::ErrorKind::PermissionDenied, "fix me please!"
619/// );
620///
621/// let other_uio_err = uio_error!(io_err, "");
622///
623/// // prints: ": Permission denied"
624/// println!("{other_uio_err}");
625/// ```
626//#[macro_use]
627#[macro_export]
628macro_rules! uio_error(
629 ($err:expr, $($args:tt)+) => ({
630 UIoError::new(
631 $err.kind(),
632 format!($($args)+)
633 )
634 })
635);
636
637/// A special error type that does not print any message when returned from
638/// `uumain`. Especially useful for porting utilities to using [`UResult`].
639///
640/// There are two ways to construct an [`ExitCode`]:
641/// ```
642/// use uucore::error::{ExitCode, UResult};
643/// // Explicit
644/// let res: UResult<()> = Err(ExitCode(1).into());
645///
646/// // Using into on `i32`:
647/// let res: UResult<()> = Err(1.into());
648/// ```
649/// This type is especially useful for a trivial conversion from utils returning [`i32`] to
650/// returning [`UResult`].
651#[derive(Debug)]
652pub struct ExitCode(pub i32);
653
654impl ExitCode {
655 #[allow(clippy::new_ret_no_self)]
656 /// Create a new `ExitCode` with a given exit code.
657 pub fn new(code: i32) -> Box<dyn UError> {
658 Box::new(Self(code))
659 }
660}
661
662impl Error for ExitCode {}
663
664impl Display for ExitCode {
665 fn fmt(&self, _: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
666 Ok(())
667 }
668}
669
670impl UError for ExitCode {
671 fn code(&self) -> i32 {
672 self.0
673 }
674}
675
676impl From<i32> for Box<dyn UError> {
677 fn from(i: i32) -> Self {
678 ExitCode::new(i)
679 }
680}
681
682/// A wrapper for `clap::Error` that implements [`UError`]
683///
684/// Contains a custom error code. When `Display::fmt` is called on this struct
685/// the [`clap::Error`] will be printed _directly to `stdout` or `stderr`_.
686/// This is because `clap` only supports colored output when it prints directly.
687///
688/// [`ClapErrorWrapper`] is generally created by calling the
689/// [`UClapError::with_exit_code`] method on [`clap::Error`] or using the [`From`]
690/// implementation from [`clap::Error`] to `Box<dyn UError>`, which constructs
691/// a [`ClapErrorWrapper`] with an exit code of `1`.
692///
693/// ```rust
694/// use uucore::error::{ClapErrorWrapper, UError, UClapError};
695/// let command = clap::Command::new("test");
696/// let result: Result<_, ClapErrorWrapper> = command.try_get_matches().with_exit_code(125);
697///
698/// let command = clap::Command::new("test");
699/// let result: Result<_, Box<dyn UError>> = command.try_get_matches().map_err(Into::into);
700/// ```
701#[derive(Debug)]
702pub struct ClapErrorWrapper {
703 code: i32,
704 error: clap::Error,
705 print_failed: Cell<bool>,
706}
707
708/// Extension trait for `clap::Error` to adjust the exit code.
709pub trait UClapError<T> {
710 /// Set the exit code for the program if `uumain` returns `Ok(())`.
711 fn with_exit_code(self, code: i32) -> T;
712}
713
714impl From<clap::Error> for Box<dyn UError> {
715 fn from(e: clap::Error) -> Self {
716 Box::new(ClapErrorWrapper {
717 code: 1,
718 error: e,
719 print_failed: Cell::new(false),
720 })
721 }
722}
723
724impl UClapError<ClapErrorWrapper> for clap::Error {
725 fn with_exit_code(self, code: i32) -> ClapErrorWrapper {
726 ClapErrorWrapper {
727 code,
728 error: self,
729 print_failed: Cell::new(false),
730 }
731 }
732}
733
734impl UClapError<Result<clap::ArgMatches, ClapErrorWrapper>>
735 for Result<clap::ArgMatches, clap::Error>
736{
737 fn with_exit_code(self, code: i32) -> Result<clap::ArgMatches, ClapErrorWrapper> {
738 self.map_err(|e| e.with_exit_code(code))
739 }
740}
741
742impl UError for ClapErrorWrapper {
743 fn code(&self) -> i32 {
744 // If the error is a DisplayHelp or DisplayVersion variant,
745 // check if printing failed. If it did, return 1, otherwise 0.
746 if let clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion =
747 self.error.kind()
748 {
749 i32::from(self.print_failed.get())
750 } else {
751 self.code
752 }
753 }
754}
755
756impl Error for ClapErrorWrapper {}
757
758// This is abuse of the Display trait
759impl Display for ClapErrorWrapper {
760 fn fmt(&self, _f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
761 // Check if printing succeeds. For DisplayHelp and DisplayVersion,
762 // error.print() writes to stdout, so we need to detect write failures
763 // (e.g., when stdout is /dev/full).
764 if let Err(print_fail) = self.error.print() {
765 // Mark that printing failed so code() can return the appropriate exit code
766 self.print_failed.set(true);
767 // Try to display this error to stderr, but ignore if that fails too
768 // since we're already in an error state.
769 let _ = writeln!(std::io::stderr(), "{}: {print_fail}", crate::util_name());
770 // Mirror GNU behavior: when failing to print help or version, exit with error code.
771 // This avoids silent failures when stdout is full or closed.
772 set_exit_code(1);
773 }
774 // Always return Ok(()) to satisfy Display's contract and prevent panic
775 Ok(())
776 }
777}
778
779#[cfg(test)]
780mod tests {
781 #[test]
782 #[cfg(unix)]
783 fn test_nix_error_conversion() {
784 use super::{FromIo, UIoError};
785 use nix::errno::Errno;
786 use std::io::ErrorKind;
787
788 for (nix_error, expected_error_kind) in [
789 (Errno::EACCES, ErrorKind::PermissionDenied),
790 (Errno::ENOENT, ErrorKind::NotFound),
791 (Errno::EEXIST, ErrorKind::AlreadyExists),
792 ] {
793 let error = UIoError::from(nix_error);
794 assert_eq!(expected_error_kind, error.inner.kind());
795 }
796 assert_eq!(
797 "test: Permission denied",
798 Err::<(), nix::Error>(Errno::EACCES)
799 .map_err_context(|| String::from("test"))
800 .unwrap_err()
801 .to_string()
802 );
803 }
804}