diff --git a/src/uu/cp/locales/en-US.ftl b/src/uu/cp/locales/en-US.ftl index bc39d613289..1a634aca0d5 100644 --- a/src/uu/cp/locales/en-US.ftl +++ b/src/uu/cp/locales/en-US.ftl @@ -86,6 +86,7 @@ cp-error-selinux-set-context = failed to set the security context of { $path }: cp-error-selinux-get-context = failed to get security context of { $path } cp-error-selinux-error = SELinux error: { $error } cp-error-cannot-create-fifo = cannot create fifo { $path }: File exists +cp-error-cannot-create-socket = cannot create socket { $path }: File exists cp-error-invalid-attribute = invalid attribute { $value } cp-error-failed-to-create-whole-tree = failed to create whole tree cp-error-failed-to-create-directory = Failed to create directory: { $error } diff --git a/src/uu/cp/locales/fr-FR.ftl b/src/uu/cp/locales/fr-FR.ftl index 2fea7cf4d7a..530ca3401e2 100644 --- a/src/uu/cp/locales/fr-FR.ftl +++ b/src/uu/cp/locales/fr-FR.ftl @@ -86,6 +86,7 @@ cp-error-selinux-set-context = échec de la définition du contexte de sécurit cp-error-selinux-get-context = échec de l'obtention du contexte de sécurité de { $path } cp-error-selinux-error = Erreur SELinux : { $error } cp-error-cannot-create-fifo = impossible de créer le fifo { $path } : Le fichier existe +cp-error-cannot-create-socket = impossible de créer le socket { $path } : Le fichier existe cp-error-invalid-attribute = attribut invalide { $value } cp-error-failed-to-create-whole-tree = échec de la création de l'arborescence complète cp-error-failed-to-create-directory = Échec de la création du répertoire : { $error } diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index e826ac3c470..5e6ac5985b3 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -25,13 +25,13 @@ use thiserror::Error; use platform::copy_on_write; use uucore::display::Quotable; use uucore::error::{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, paths_refer_to_same_file, }; +#[cfg(unix)] +use uucore::fs::{make_fifo, make_socket}; use uucore::{backup_control, update_control}; // These are exposed for projects (e.g. nushell) that want to create an `Options` value, which // requires these enum. @@ -2071,6 +2071,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 +2111,7 @@ fn handle_copy_mode( context, source_is_symlink, source_is_fifo, + source_is_socket, symlinked_files, #[cfg(unix)] source_is_stream, @@ -2132,6 +2134,7 @@ fn handle_copy_mode( context, source_is_symlink, source_is_fifo, + source_is_socket, symlinked_files, #[cfg(unix)] source_is_stream, @@ -2167,6 +2170,7 @@ fn handle_copy_mode( context, source_is_symlink, source_is_fifo, + source_is_socket, symlinked_files, #[cfg(unix)] source_is_stream, @@ -2181,6 +2185,7 @@ fn handle_copy_mode( context, source_is_symlink, source_is_fifo, + source_is_socket, symlinked_files, #[cfg(unix)] source_is_stream, @@ -2407,8 +2412,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 +2430,7 @@ fn copy_file( symlinked_files, source_in_command_line, source_is_fifo, + source_is_socket, #[cfg(unix)] source_is_stream, )?; @@ -2547,6 +2557,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 +2570,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 +2610,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)?; + } + + make_socket(dest) + .map_err(|_| translate!("cp-error-cannot-create-socket", "path" => dest.quote()).into()) +} + fn copy_link( source: &Path, dest: &Path, diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index e112bf730e2..58f960802e1 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -11,7 +11,7 @@ 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, - mkfifo, mode_t, + mkfifo, mknod, mode_t, }; use std::collections::HashSet; use std::collections::VecDeque; @@ -837,6 +837,17 @@ pub fn make_fifo(path: &Path) -> std::io::Result<()> { } } +#[cfg(unix)] +pub fn make_socket(path: &Path) -> std::io::Result<()> { + let name = CString::new(path.to_str().unwrap()).unwrap(); + let err = unsafe { mknod(name.as_ptr(), S_IFSOCK, 0) }; + 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. @@ -1101,4 +1112,16 @@ mod tests { std::thread::spawn(move || assert!(std::fs::write(&path2, b"foo").is_ok())); assert_eq!(std::fs::read(&path).unwrap(), b"foo"); } + + #[cfg(unix)] + #[test] + fn test_make_socket() { + // Create the socket in a temporary directory. + let tempdir = tempdir().unwrap(); + let path = tempdir.path().join("f"); + assert!(make_socket(&path).is_ok()); + + // Check that it is indeed a socket + assert!(std::fs::metadata(&path).unwrap().file_type().is_socket()); + } } diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 62cf5e1b9a9..9ce522d966f 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 srwx mksocket 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..9298a4db9c1 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( @@ -1132,6 +1132,16 @@ impl AtPath { } } + #[cfg(unix)] + pub fn mksocket(&self, socket: &str) { + let full_path = self.plus_as_string(socket); + log_info("mksocket", &full_path); + unsafe { + let socket_name: CString = CString::new(full_path).expect("CString creation failed."); + libc::mknod(socket_name.as_ptr(), libc::S_IFSOCK, 0); + } + } + #[cfg(not(windows))] pub fn is_fifo(&self, fifo: &str) -> bool { unsafe {