From bfbdf10d3fba4516887b26270db3141e26acf062 Mon Sep 17 00:00:00 2001 From: Anonymous-AAA Date: Wed, 13 Aug 2025 08:25:34 +0530 Subject: [PATCH] cp: fix recursive socket file copy --- src/uu/cp/src/cp.rs | 29 ++++++++++++++++++++++++++++- tests/by-util/test_cp.rs | 27 ++++++++++++++++++++++++--- tests/uutests/src/lib/util.rs | 11 ++++++++++- 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index e826ac3c470..8f2d4b930a5 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -11,6 +11,8 @@ use std::fmt::Display; use std::fs::{self, Metadata, OpenOptions, Permissions}; #[cfg(unix)] use std::os::unix::fs::{FileTypeExt, PermissionsExt}; +#[cfg(unix)] +use std::os::unix::net::UnixListener; use std::path::{Path, PathBuf, StripPrefixError}; use std::{fmt, io}; #[cfg(all(unix, not(target_os = "android")))] @@ -2071,6 +2073,7 @@ fn handle_copy_mode( symlinked_files: &mut HashSet, source_in_command_line: bool, source_is_fifo: bool, + source_is_socket: bool, #[cfg(unix)] source_is_stream: bool, ) -> CopyResult { let source_is_symlink = source_metadata.is_symlink(); @@ -2110,6 +2113,7 @@ fn handle_copy_mode( context, source_is_symlink, source_is_fifo, + source_is_socket, symlinked_files, #[cfg(unix)] source_is_stream, @@ -2132,6 +2136,7 @@ fn handle_copy_mode( context, source_is_symlink, source_is_fifo, + source_is_socket, symlinked_files, #[cfg(unix)] source_is_stream, @@ -2167,6 +2172,7 @@ fn handle_copy_mode( context, source_is_symlink, source_is_fifo, + source_is_socket, symlinked_files, #[cfg(unix)] source_is_stream, @@ -2181,6 +2187,7 @@ fn handle_copy_mode( context, source_is_symlink, source_is_fifo, + source_is_socket, symlinked_files, #[cfg(unix)] source_is_stream, @@ -2407,8 +2414,12 @@ fn copy_file( #[cfg(unix)] let source_is_fifo = source_metadata.file_type().is_fifo(); + #[cfg(unix)] + let source_is_socket = source_metadata.file_type().is_socket(); #[cfg(not(unix))] let source_is_fifo = false; + #[cfg(not(unix))] + let source_is_socket = false; let source_is_stream = is_stream(&source_metadata); @@ -2421,6 +2432,7 @@ fn copy_file( symlinked_files, source_in_command_line, source_is_fifo, + source_is_socket, #[cfg(unix)] source_is_stream, )?; @@ -2547,6 +2559,7 @@ fn copy_helper( context: &str, source_is_symlink: bool, source_is_fifo: bool, + source_is_socket: bool, symlinked_files: &mut HashSet, #[cfg(unix)] source_is_stream: bool, ) -> CopyResult<()> { @@ -2559,7 +2572,10 @@ fn copy_helper( return Err(CpError::NotADirectory(dest.to_path_buf())); } - if source_is_fifo && options.recursive && !options.copy_contents { + if source_is_socket && options.recursive && !options.copy_contents { + #[cfg(unix)] + copy_socket(dest, options.overwrite, options.debug)?; + } else if source_is_fifo && options.recursive && !options.copy_contents { #[cfg(unix)] copy_fifo(dest, options.overwrite, options.debug)?; } else if source_is_symlink { @@ -2596,6 +2612,17 @@ fn copy_fifo(dest: &Path, overwrite: OverwriteMode, debug: bool) -> CopyResult<( .map_err(|_| translate!("cp-error-cannot-create-fifo", "path" => dest.quote()).into()) } +#[cfg(unix)] +fn copy_socket(dest: &Path, overwrite: OverwriteMode, debug: bool) -> CopyResult<()> { + if dest.exists() { + overwrite.verify(dest, debug)?; + fs::remove_file(dest)?; + } + + UnixListener::bind(dest)?; + Ok(()) +} + fn copy_link( source: &Path, dest: &Path, diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 62cf5e1b9a9..b40e8c918b3 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -4,7 +4,7 @@ // file that was distributed with this source code. // spell-checker:ignore (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs neve ROOTDIR USERDIR outfile uufs xattrs -// spell-checker:ignore bdfl hlsl IRWXO IRWXG nconfined matchpathcon libselinux-devel prwx doesnotexist reftests subdirs +// spell-checker:ignore bdfl hlsl IRWXO IRWXG nconfined matchpathcon libselinux-devel prwx doesnotexist reftests subdirs mksocket srwx use uucore::display::Quotable; #[cfg(feature = "feat_selinux")] use uucore::selinux::get_getfattr_output; @@ -18,10 +18,10 @@ use std::io::Write; #[cfg(not(windows))] use std::os::unix::fs; -#[cfg(unix)] -use std::os::unix::fs::MetadataExt; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; +#[cfg(unix)] +use std::os::unix::fs::{FileTypeExt, MetadataExt}; #[cfg(windows)] use std::os::windows::fs::symlink_file; #[cfg(not(windows))] @@ -3105,6 +3105,27 @@ fn test_cp_fifo() { assert_eq!(permission, "prwx-wx--x".to_string()); } +#[test] +#[cfg(unix)] +fn test_cp_socket() { + let (at, mut ucmd) = at_and_ucmd!(); + at.mksocket("socket"); + // Also test that permissions are preserved + at.set_mode("socket", 0o731); + ucmd.arg("--preserve=mode") + .arg("-r") + .arg("socket") + .arg("socket2") + .succeeds() + .no_stderr() + .no_stdout(); + + let metadata = std::fs::metadata(at.subdir.join("socket2")).unwrap(); + let permission = uucore::fs::display_permissions(&metadata, true); + assert!(metadata.file_type().is_socket()); + assert_eq!(permission, "srwx-wx--x".to_string()); +} + #[cfg(all(unix, not(target_vendor = "apple")))] fn find_other_group(current: u32) -> Option { // Get the first group that doesn't match current diff --git a/tests/uutests/src/lib/util.rs b/tests/uutests/src/lib/util.rs index 40784e3df1b..93b3fd7d98f 100644 --- a/tests/uutests/src/lib/util.rs +++ b/tests/uutests/src/lib/util.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. //spell-checker: ignore (linux) rlimit prlimit coreutil ggroups uchild uncaptured scmd SHLVL canonicalized openpty -//spell-checker: ignore (linux) winsize xpixel ypixel setrlimit FSIZE SIGBUS SIGSEGV sigbus tmpfs +//spell-checker: ignore (linux) winsize xpixel ypixel setrlimit FSIZE SIGBUS SIGSEGV sigbus tmpfs mksocket #![allow(dead_code)] #![allow( @@ -34,6 +34,8 @@ use std::os::fd::OwnedFd; #[cfg(unix)] use std::os::unix::fs::{PermissionsExt, symlink as symlink_dir, symlink as symlink_file}; #[cfg(unix)] +use std::os::unix::net::UnixListener; +#[cfg(unix)] use std::os::unix::process::CommandExt; #[cfg(unix)] use std::os::unix::process::ExitStatusExt; @@ -1132,6 +1134,13 @@ impl AtPath { } } + #[cfg(unix)] + pub fn mksocket(&self, socket: &str) { + let full_path = self.plus_as_string(socket); + log_info("mksocket", &full_path); + UnixListener::bind(full_path).expect("Socket file creation failed."); + } + #[cfg(not(windows))] pub fn is_fifo(&self, fifo: &str) -> bool { unsafe {