diff --git a/src/uu/mkfifo/locales/en-US.ftl b/src/uu/mkfifo/locales/en-US.ftl index c6dcae83180..af9f169e4ac 100644 --- a/src/uu/mkfifo/locales/en-US.ftl +++ b/src/uu/mkfifo/locales/en-US.ftl @@ -9,6 +9,6 @@ mkfifo-help-context = like -Z, or if CTX is specified then set the SELinux or SM # Error messages mkfifo-error-invalid-mode = invalid mode: { $error } mkfifo-error-missing-operand = missing operand -mkfifo-error-cannot-create-fifo = cannot create fifo { $path }: File exists +mkfifo-error-cannot-create-fifo = cannot create fifo { $path }: { $error } mkfifo-error-cannot-set-permissions = cannot set permissions on { $path }: { $error } mkfifo-error-non-file-permission = mode must specify only file permission bits diff --git a/src/uu/mkfifo/locales/fr-FR.ftl b/src/uu/mkfifo/locales/fr-FR.ftl index 14cfc6dd582..da91e0c7bc6 100644 --- a/src/uu/mkfifo/locales/fr-FR.ftl +++ b/src/uu/mkfifo/locales/fr-FR.ftl @@ -9,6 +9,6 @@ mkfifo-help-context = comme -Z, ou si CTX est spécifié, définir le contexte d # Messages d'erreur mkfifo-error-invalid-mode = mode invalide : { $error } mkfifo-error-missing-operand = opérande manquant -mkfifo-error-cannot-create-fifo = impossible de créer le fifo { $path } : Le fichier existe +mkfifo-error-cannot-create-fifo = impossible de créer le fifo { $path } : { $error } mkfifo-error-cannot-set-permissions = impossible de définir les permissions sur { $path } : { $error } mkfifo-error-non-file-permission = le mode ne doit spécifier que des bits de permission de fichier diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index 46898ade7b6..a21345e380f 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -6,6 +6,7 @@ use clap::{Arg, ArgAction, Command, value_parser}; use rustix::fs::Mode; use rustix::process::umask; +use std::io; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; use uucore::translate; @@ -55,10 +56,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let mkfifo_result = create_fifo(f.as_str(), mode); umask(prev_umask); - if mkfifo_result.is_err() { + if let Err(e) = mkfifo_result { show!(USimpleError::new( 1, - translate!("mkfifo-error-cannot-create-fifo", "path" => f.quote()), + translate!( + "mkfifo-error-cannot-create-fifo", + "path" => f.quote(), + "error" => os_err_msg(&e) + ), )); } else { // Apply SELinux context if requested @@ -136,19 +141,36 @@ pub fn uu_app() -> Command { // libc's path-based `mkfifo` there. Both rely on the caller having cleared // the umask so the requested mode is applied atomically (see issue #10020). #[cfg(not(target_vendor = "apple"))] -fn create_fifo(path: &str, mode: u32) -> Result<(), ()> { +fn create_fifo(path: &str, mode: u32) -> Result<(), io::Error> { use rustix::fs::{CWD, mkfifoat}; - mkfifoat(CWD, path, Mode::from_bits_truncate(mode)).map_err(|_| ()) + mkfifoat(CWD, path, Mode::from_bits_truncate(mode)) + .map_err(|errno: rustix::io::Errno| io::Error::from_raw_os_error(errno.raw_os_error())) } #[cfg(target_vendor = "apple")] -fn create_fifo(path: &str, mode: u32) -> Result<(), ()> { +fn create_fifo(path: &str, mode: u32) -> Result<(), io::Error> { use std::ffi::CString; - let c_path = CString::new(path).map_err(|_| ())?; + let c_path = CString::new(path).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?; // SAFETY: `c_path` is a valid NUL-terminated C string and `mode` is a // standard mode_t bit pattern. let rc = unsafe { libc::mkfifo(c_path.as_ptr(), mode as libc::mode_t) }; - if rc == 0 { Ok(()) } else { Err(()) } + if rc == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } +} + +/// Returns the OS error message without Rust's appended `(os error N)` suffix, +/// so the output matches the format produced by GNU coreutils (e.g. "File exists" +/// rather than "File exists (os error 17)"). +fn os_err_msg(e: &io::Error) -> String { + let s = e.to_string(); + if let Some(idx) = s.rfind(" (os error ") { + s[..idx].to_string() + } else { + s + } } fn calculate_mode(mode_option: Option<&String>) -> Result { diff --git a/tests/by-util/test_mkfifo.rs b/tests/by-util/test_mkfifo.rs index 02fa91b2a52..e8ab3df9678 100644 --- a/tests/by-util/test_mkfifo.rs +++ b/tests/by-util/test_mkfifo.rs @@ -86,6 +86,16 @@ fn test_create_one_fifo_already_exists() { .stderr_is("mkfifo: cannot create fifo 'abcdef': File exists\n"); } +#[test] +fn test_create_fifo_in_missing_directory() { + // Regression test for #12669: when the parent directory doesn't exist, + // mkfifo used to always report "File exists" regardless of the actual + // OS error. It should report the real reason instead, matching GNU. + new_ucmd!().arg("no-such-directory/fifo").fails().stderr_is( + "mkfifo: cannot create fifo 'no-such-directory/fifo': No such file or directory\n", + ); +} + #[test] fn test_create_fifo_with_mode_and_umask() { use uucore::fs::display_permissions; @@ -158,7 +168,9 @@ fn test_create_fifo_permission_denied() { // We no longer attempt to modify file permission if the file was failed to be created. // Therefore the error message should only contain "cannot create". - let err_msg = format!("mkfifo: cannot create fifo '{named_pipe}': File exists\n"); + // The directory has mode 0o644 (no execute bit), so the real OS error is + // EACCES ("Permission denied"), not EEXIST ("File exists"). + let err_msg = format!("mkfifo: cannot create fifo '{named_pipe}': Permission denied\n"); scene .ucmd()