Repository: uutils/coreutils
Pull request #7775: cp: factor make_fifo() function out to uucore::fs
Author: login: jfinkels
Created: 2025-04-18T00:35:29Z | Merged: 2025-04-19T09:40:29Z

Create a function `uucore::fs::make_fifo()` for creating a named pipe on Unix systems, and use it in `cp`.

This was extracted from pull request #7241. The `make_fifo()` function will be used in `mv` as well.


Diff:
diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs
index 8f254f0c439..190bbde3c1a 100644
--- a/src/uu/cp/src/cp.rs
+++ b/src/uu/cp/src/cp.rs
@@ -7,14 +7,10 @@
 use quick_error::quick_error;
 use std::cmp::Ordering;
 use std::collections::{HashMap, HashSet};
-#[cfg(not(windows))]
-use std::ffi::CString;
 use std::ffi::OsString;
 use std::fs::{self, Metadata, OpenOptions, Permissions};
 use std::io;
 #[cfg(unix)]
-use std::os::unix::ffi::OsStrExt;
-#[cfg(unix)]
 use std::os::unix::fs::{FileTypeExt, PermissionsExt};
 use std::path::{Path, PathBuf, StripPrefixError};
 #[cfg(all(unix, not(target_os = "android")))]
@@ -23,13 +19,13 @@ use uucore::fsxattr::copy_xattrs;
 use clap::{Arg, ArgAction, ArgMatches, Command, builder::ValueParser};
 use filetime::FileTime;
 use indicatif::{ProgressBar, ProgressStyle};
-#[cfg(unix)]
-use libc::mkfifo;
 use quick_error::ResultExt;
 
 use platform::copy_on_write;
 use uucore::display::Quotable;
 use uucore::error::{UClapError, UError, UResult, UUsageError, set_exit_code};
+#[cfg(unix)]
+use uucore::fs::make_fifo;
 use uucore::fs::{
     FileInformation, MissingHandling, ResolveMode, are_hardlinks_to_same_file, canonicalize,
     get_filename, is_symlink_loop, normalize_path, path_ends_with_terminator,
@@ -2534,12 +2530,7 @@ fn copy_fifo(dest: &Path, overwrite: OverwriteMode, debug: bool) -> CopyResult<(
         fs::remove_file(dest)?;
     }
 
-    let name = CString::new(dest.as_os_str().as_bytes()).unwrap();
-    let err = unsafe { mkfifo(name.as_ptr(), 0o666) };
-    if err == -1 {
-        return Err(format!("cannot create fifo {}: File exists", dest.quote()).into());
-    }
-    Ok(())
+    make_fifo(dest).map_err(|_| format!("cannot create fifo {}: File exists", dest.quote()).into())
 }
 
 fn copy_link(
diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs
index d70fb78224f..5ad1399efae 100644
--- a/src/uucore/src/lib/features/fs.rs
+++ b/src/uucore/src/lib/features/fs.rs
@@ -3,7 +3,7 @@
 // For the full copyright and license information, please view the LICENSE
 // file that was distributed with this source code.
 
-//! Set of functions to manage files and symlinks
+//! Set of functions to manage regular files, special files, and links.
 
 // spell-checker:ignore backport
 
@@ -11,11 +11,13 @@
 use libc::{
     S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK, S_IRGRP, S_IROTH,
     S_IRUSR, S_ISGID, S_ISUID, S_ISVTX, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR,
-    mode_t,
+    mkfifo, mode_t,
 };
 use std::collections::HashSet;
 use std::collections::VecDeque;
 use std::env;
+#[cfg(unix)]
+use std::ffi::CString;
 use std::ffi::{OsStr, OsString};
 use std::fs;
 use std::fs::read_dir;
@@ -798,6 +800,37 @@ pub fn get_filename(file: &Path) -> Option<&str> {
     file.file_name().and_then(|filename| filename.to_str())
 }
 
+/// Make a FIFO, also known as a named pipe.
+///
+/// This is a safe wrapper for the unsafe [`libc::mkfifo`] function,
+/// which makes a [named
+/// pipe](https://en.wikipedia.org/wiki/Named_pipe) on Unix systems.
+///
+/// # Errors
+///
+/// If the named pipe cannot be created.
+///
+/// # Examples
+///
+/// ```ignore
+/// use uucore::fs::make_fifo;
+///
+/// make_fifo("my-pipe").expect("failed to create the named pipe");
+///
+/// std::thread::spawn(|| { std::fs::write("my-pipe", b"hello").unwrap(); });
+/// assert_eq!(std::fs::read("my-pipe").unwrap(), b"hello");
+/// ```
+#[cfg(unix)]
+pub fn make_fifo(path: &Path) -> std::io::Result<()> {
+    let name = CString::new(path.to_str().unwrap()).unwrap();
+    let err = unsafe { mkfifo(name.as_ptr(), 0o666) };
+    if err == -1 {
+        Err(std::io::Error::from_raw_os_error(err))
+    } else {
+        Ok(())
+    }
+}
+
 #[cfg(test)]
 mod tests {
     // Note this useful idiom: importing names from outer (for mod tests) scope.
@@ -807,6 +840,8 @@ mod tests {
     #[cfg(unix)]
     use std::os::unix;
     #[cfg(unix)]
+    use std::os::unix::fs::FileTypeExt;
+    #[cfg(unix)]
     use tempfile::{NamedTempFile, tempdir};
 
     struct NormalizePathTestCase<'a> {
@@ -1039,4 +1074,25 @@ mod tests {
         let file_path = PathBuf::from("~/foo.txt");
         assert!(matches!(get_filename(&file_path), Some("foo.txt")));
     }
+
+    #[cfg(unix)]
+    #[test]
+    fn test_make_fifo() {
+        // Create the FIFO in a temporary directory.
+        let tempdir = tempdir().unwrap();
+        let path = tempdir.path().join("f");
+        assert!(make_fifo(&path).is_ok());
+
+        // Check that it is indeed a FIFO.
+        assert!(std::fs::metadata(&path).unwrap().file_type().is_fifo());
+
+        // Check that we can write to it and read from it.
+        //
+        // Write and read need to happen in different threads,
+        // otherwise `write` would block indefinitely while waiting
+        // for the `read`.
+        let path2 = path.clone();
+        std::thread::spawn(move || assert!(std::fs::write(&path2, b"foo").is_ok()));
+        assert_eq!(std::fs::read(&path).unwrap(), b"foo");
+    }
 }


Reviews:
user: login: sylvestre
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T08:04:20Z
user: login: sylvestre
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T08:04:39Z
user: login: jfinkels
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-19T01:09:24Z

Review comments:
diff_hunk: @@ -798,6 +800,20 @@ pub fn get_filename(file: &Path) -> Option<&str> {
     file.file_name().and_then(|filename| filename.to_str())
 }
 
+/// Make a FIFO, also known as a named pipe.
+///
+/// This is a safe wrapper for the `mkfifo` function from `libc`.
+#[cfg(unix)]
+pub fn make_fifo(path: &Path) -> std::io::Result<()> {
path: src/uucore/src/lib/features/fs.rs
user: login: sylvestre
body: given that the doc will be published here:
https://docs.rs/uucore/latest/uucore/fs/index.html

please add some rustdoc (with an example :)
thanks

created_at: 2025-04-18T08:04:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -798,6 +800,20 @@ pub fn get_filename(file: &Path) -> Option<&str> {
     file.file_name().and_then(|filename| filename.to_str())
 }
 
+/// Make a FIFO, also known as a named pipe.
+///
+/// This is a safe wrapper for the `mkfifo` function from `libc`.
+#[cfg(unix)]
+pub fn make_fifo(path: &Path) -> std::io::Result<()> {
path: src/uucore/src/lib/features/fs.rs
user: login: sylvestre
body: also, please add an unit test 
created_at: 2025-04-18T08:04:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -798,6 +800,20 @@ pub fn get_filename(file: &Path) -> Option<&str> {
     file.file_name().and_then(|filename| filename.to_str())
 }
 
+/// Make a FIFO, also known as a named pipe.
+///
+/// This is a safe wrapper for the `mkfifo` function from `libc`.
+#[cfg(unix)]
+pub fn make_fifo(path: &Path) -> std::io::Result<()> {
path: src/uucore/src/lib/features/fs.rs
user: login: jfinkels
body: I added a test and a bit more detail to the docs. Not sure how much more I could say about it, but let me know if more is needed. Thanks
created_at: 2025-04-19T01:09:24Z
author_association: COLLABORATOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-18T01:11:34Z
body: GNU testsuite comparison:
```
Skip an intermittent issue tests/misc/stdbuf (fails in this run but passes in the 'main' branch)
Skipping an intermittent issue tests/tail/inotify-dir-recreate (passes in this run but fails in the 'main' branch)
```
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-19T01:47:58Z
body: GNU testsuite comparison:
```
Skip an intermittent issue tests/misc/stdbuf (fails in this run but passes in the 'main' branch)
```
author_association: NONE