mirror of
https://github.com/uutils/coreutils.git
synced 2026-05-06 07:26:38 -04:00
cp: handle special files (#11163)
This commit is contained in:
Generated
+1
@@ -3293,6 +3293,7 @@ dependencies = [
|
||||
"fluent",
|
||||
"indicatif",
|
||||
"libc",
|
||||
"nix",
|
||||
"selinux",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
|
||||
@@ -41,6 +41,7 @@ fluent = { workspace = true }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
exacl = { workspace = true, optional = true }
|
||||
nix = { workspace = true, features = ["fs"] }
|
||||
|
||||
[[bin]]
|
||||
name = "cp"
|
||||
|
||||
@@ -87,6 +87,7 @@ cp-error-selinux-get-context = failed to get security context of { $path }
|
||||
cp-error-selinux-error = SELinux error: { $error }
|
||||
cp-error-selinux-context-conflict = cannot combine --context (-Z) with --preserve=context
|
||||
cp-error-cannot-create-fifo = cannot create fifo { $path }: File exists
|
||||
cp-error-cannot-create-special-file = cannot create special file { $path }: { $error }
|
||||
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 }
|
||||
|
||||
@@ -87,6 +87,7 @@ cp-error-selinux-get-context = échec de l'obtention du contexte de sécurité d
|
||||
cp-error-selinux-error = Erreur SELinux : { $error }
|
||||
cp-error-selinux-context-conflict = impossible de combiner --context (-Z) avec --preserve=context
|
||||
cp-error-cannot-create-fifo = impossible de créer le fifo { $path } : Le fichier existe
|
||||
cp-error-cannot-create-special-file = impossible de créer le fichier spécial { $path } : { $error }
|
||||
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 }
|
||||
|
||||
+46
-53
@@ -2,7 +2,7 @@
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
// spell-checker:ignore (ToDO) copydir ficlone fiemap ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked deduplicated advcpmv nushell IRWXG IRWXO IRWXU IRWXUGO IRWXU IRWXG IRWXO IRWXUGO
|
||||
// spell-checker:ignore (ToDO) copydir ficlone fiemap ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked deduplicated advcpmv nushell IRWXG IRWXO IRWXU IRWXUGO IRWXU IRWXG IRWXO IRWXUGO sflag
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
@@ -10,7 +10,7 @@ use std::ffi::OsString;
|
||||
use std::fmt::Display;
|
||||
use std::fs::{self, Metadata, OpenOptions, Permissions};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::{FileTypeExt, PermissionsExt};
|
||||
use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::net::UnixListener;
|
||||
use std::path::{Path, PathBuf, StripPrefixError};
|
||||
@@ -22,6 +22,8 @@ use uucore::translate;
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command, builder::ValueParser, value_parser};
|
||||
use filetime::FileTime;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
#[cfg(unix)]
|
||||
use nix::sys::stat::{Mode, SFlag, dev_t, mknod as nix_mknod, mode_t};
|
||||
use thiserror::Error;
|
||||
|
||||
use platform::copy_on_write;
|
||||
@@ -2227,13 +2229,8 @@ fn handle_copy_mode(
|
||||
source_metadata: &Metadata,
|
||||
symlinked_files: &mut HashSet<FileInformation>,
|
||||
source_in_command_line: bool,
|
||||
source_is_fifo: bool,
|
||||
source_is_socket: bool,
|
||||
created_parent_dirs: &mut HashSet<PathBuf>,
|
||||
#[cfg(unix)] source_is_stream: bool,
|
||||
) -> CopyResult<PerformedAction> {
|
||||
let source_is_symlink = source_metadata.is_symlink();
|
||||
|
||||
match options.copy_mode {
|
||||
CopyMode::Link => {
|
||||
if dest.exists() {
|
||||
@@ -2267,13 +2264,9 @@ fn handle_copy_mode(
|
||||
dest,
|
||||
options,
|
||||
context,
|
||||
source_is_symlink,
|
||||
source_is_fifo,
|
||||
source_is_socket,
|
||||
source_metadata,
|
||||
symlinked_files,
|
||||
created_parent_dirs,
|
||||
#[cfg(unix)]
|
||||
source_is_stream,
|
||||
)?;
|
||||
}
|
||||
CopyMode::SymLink => {
|
||||
@@ -2291,13 +2284,9 @@ fn handle_copy_mode(
|
||||
dest,
|
||||
options,
|
||||
context,
|
||||
source_is_symlink,
|
||||
source_is_fifo,
|
||||
source_is_socket,
|
||||
source_metadata,
|
||||
symlinked_files,
|
||||
created_parent_dirs,
|
||||
#[cfg(unix)]
|
||||
source_is_stream,
|
||||
)?;
|
||||
}
|
||||
UpdateMode::None => {
|
||||
@@ -2328,13 +2317,9 @@ fn handle_copy_mode(
|
||||
dest,
|
||||
options,
|
||||
context,
|
||||
source_is_symlink,
|
||||
source_is_fifo,
|
||||
source_is_socket,
|
||||
source_metadata,
|
||||
symlinked_files,
|
||||
created_parent_dirs,
|
||||
#[cfg(unix)]
|
||||
source_is_stream,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
@@ -2344,13 +2329,9 @@ fn handle_copy_mode(
|
||||
dest,
|
||||
options,
|
||||
context,
|
||||
source_is_symlink,
|
||||
source_is_fifo,
|
||||
source_is_socket,
|
||||
source_metadata,
|
||||
symlinked_files,
|
||||
created_parent_dirs,
|
||||
#[cfg(unix)]
|
||||
source_is_stream,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
@@ -2582,15 +2563,6 @@ fn copy_file(
|
||||
context,
|
||||
);
|
||||
|
||||
#[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);
|
||||
|
||||
let performed_action = handle_copy_mode(
|
||||
@@ -2601,11 +2573,7 @@ fn copy_file(
|
||||
&source_metadata,
|
||||
symlinked_files,
|
||||
source_in_command_line,
|
||||
source_is_fifo,
|
||||
source_is_socket,
|
||||
created_parent_dirs,
|
||||
#[cfg(unix)]
|
||||
source_is_stream,
|
||||
)?;
|
||||
|
||||
if options.verbose && performed_action != PerformedAction::Skipped {
|
||||
@@ -2741,18 +2709,14 @@ fn handle_no_preserve_mode(options: &Options, org_mode: u32) -> u32 {
|
||||
|
||||
/// Copy the file from `source` to `dest` either using the normal `fs::copy` or a
|
||||
/// copy-on-write scheme if --reflink is specified and the filesystem supports it.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn copy_helper(
|
||||
source: &Path,
|
||||
dest: &Path,
|
||||
options: &Options,
|
||||
context: &str,
|
||||
source_is_symlink: bool,
|
||||
source_is_fifo: bool,
|
||||
source_is_socket: bool,
|
||||
source_metadata: &Metadata,
|
||||
symlinked_files: &mut HashSet<FileInformation>,
|
||||
created_parent_dirs: &mut HashSet<PathBuf>,
|
||||
#[cfg(unix)] source_is_stream: bool,
|
||||
) -> CopyResult<()> {
|
||||
if options.parents {
|
||||
let parent = dest.parent().unwrap_or(dest);
|
||||
@@ -2765,13 +2729,21 @@ fn copy_helper(
|
||||
return Err(CpError::NotADirectory(dest.to_path_buf()));
|
||||
}
|
||||
|
||||
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 {
|
||||
#[cfg(unix)]
|
||||
if options.recursive && !options.copy_contents {
|
||||
let ft = source_metadata.file_type();
|
||||
if ft.is_socket() {
|
||||
return copy_socket(dest, options.overwrite, options.debug);
|
||||
}
|
||||
if ft.is_fifo() {
|
||||
return copy_fifo(dest, options.overwrite, options.debug);
|
||||
}
|
||||
if ft.is_char_device() || ft.is_block_device() {
|
||||
return copy_node(dest, source_metadata, options.overwrite, options.debug);
|
||||
}
|
||||
}
|
||||
|
||||
if source_metadata.is_symlink() {
|
||||
copy_link(source, dest, symlinked_files, options)?;
|
||||
} else {
|
||||
let copy_debug = copy_on_write(
|
||||
@@ -2781,7 +2753,7 @@ fn copy_helper(
|
||||
options.sparse_mode,
|
||||
context,
|
||||
#[cfg(unix)]
|
||||
source_is_stream,
|
||||
is_stream(source_metadata),
|
||||
)?;
|
||||
|
||||
if !options.attributes_only && options.debug {
|
||||
@@ -2816,6 +2788,27 @@ fn copy_socket(dest: &Path, overwrite: OverwriteMode, debug: bool) -> CopyResult
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn copy_node(
|
||||
dest: &Path,
|
||||
source_metadata: &Metadata,
|
||||
overwrite: OverwriteMode,
|
||||
debug: bool,
|
||||
) -> CopyResult<()> {
|
||||
if dest.exists() {
|
||||
overwrite.verify(dest, debug)?;
|
||||
fs::remove_file(dest)?;
|
||||
}
|
||||
let sflag = if source_metadata.file_type().is_char_device() {
|
||||
SFlag::S_IFCHR
|
||||
} else {
|
||||
SFlag::S_IFBLK
|
||||
};
|
||||
let mode = Mode::from_bits_truncate(source_metadata.mode() as mode_t);
|
||||
nix_mknod(dest, sflag, mode, source_metadata.rdev() as dev_t)
|
||||
.map_err(|e| translate!("cp-error-cannot-create-special-file", "path" => dest.quote(), "error" => e.desc()).into())
|
||||
}
|
||||
|
||||
fn copy_link(
|
||||
source: &Path,
|
||||
dest: &Path,
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
// 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 mksocket srwx
|
||||
#[cfg(unix)]
|
||||
use rstest::rstest;
|
||||
use uucore::display::Quotable;
|
||||
#[cfg(feature = "feat_selinux")]
|
||||
use uucore::selinux::get_getfattr_output;
|
||||
@@ -3171,6 +3173,133 @@ fn test_cp_fifo() {
|
||||
assert_eq!(permission, "prwx-wx--x".to_string());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::recursive("-R")]
|
||||
#[case::archive("-a")]
|
||||
#[cfg(unix)]
|
||||
fn test_cp_recursive_char_device(#[case] flag: &str) {
|
||||
use nix::sys::stat::{Mode, SFlag, mknod as nix_mknod};
|
||||
use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};
|
||||
use uutests::util::run_ucmd_as_root;
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = &ts.fixtures;
|
||||
let mode = Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IRGRP;
|
||||
if nix_mknod(
|
||||
at.plus("null").as_path(),
|
||||
SFlag::S_IFCHR,
|
||||
mode,
|
||||
uucore::fs::makedev(1, 3),
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
print!("Test skipped; creating a char device node requires CAP_MKNOD");
|
||||
return;
|
||||
}
|
||||
if let Ok(result) = run_ucmd_as_root(&ts, &[flag, "null", "null2"]) {
|
||||
result.success();
|
||||
let null2_metadata = at.plus("null2").metadata().unwrap();
|
||||
assert!(null2_metadata.file_type().is_char_device());
|
||||
assert_eq!(
|
||||
null2_metadata.rdev(),
|
||||
at.plus("null").metadata().unwrap().rdev()
|
||||
);
|
||||
assert_eq!(
|
||||
null2_metadata.permissions().mode() & 0o777,
|
||||
mode.bits() as _
|
||||
);
|
||||
} else {
|
||||
print!("Test skipped; copying a char device node requires CAP_MKNOD");
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::recursive("-R")]
|
||||
#[case::archive("-a")]
|
||||
#[cfg(unix)]
|
||||
fn test_cp_recursive_char_device_no_permission(#[case] flag: &str) {
|
||||
new_ucmd!()
|
||||
.args(&[flag, "/dev/null", "null2"])
|
||||
.fails()
|
||||
.stderr_is("cp: cannot create special file 'null2': Operation not permitted\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_cp_recursive_char_device_copy_contents() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
ucmd.args(&["-R", "--copy-contents", "/dev/null", "null2"])
|
||||
.succeeds()
|
||||
.no_stderr();
|
||||
assert!(at.plus("null2").metadata().unwrap().file_type().is_file());
|
||||
assert_eq!(at.read("null2"), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_cp_char_device() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
ucmd.args(&["/dev/null", "null2"]).succeeds().no_stderr();
|
||||
assert!(at.plus("null2").metadata().unwrap().file_type().is_file());
|
||||
assert_eq!(at.read("null2"), "");
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::recursive("-R")]
|
||||
#[case::archive("-a")]
|
||||
#[cfg(unix)]
|
||||
fn test_cp_recursive_block_device(#[case] flag: &str) {
|
||||
use nix::sys::stat::{Mode, SFlag, mknod as nix_mknod};
|
||||
use std::os::unix::fs::{FileTypeExt, MetadataExt};
|
||||
use uutests::util::run_ucmd_as_root;
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = &ts.fixtures;
|
||||
if nix_mknod(
|
||||
at.plus("sda").as_path(),
|
||||
SFlag::S_IFBLK,
|
||||
Mode::S_IRUSR | Mode::S_IWUSR,
|
||||
uucore::fs::makedev(8, 0),
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
print!("Test skipped; creating a block device node requires CAP_MKNOD");
|
||||
return;
|
||||
}
|
||||
if let Ok(result) = run_ucmd_as_root(&ts, &[flag, "sda", "sda2"]) {
|
||||
result.success();
|
||||
let sda2_metadata = at.plus("sda2").metadata().unwrap();
|
||||
assert!(sda2_metadata.file_type().is_block_device());
|
||||
assert_eq!(
|
||||
sda2_metadata.rdev(),
|
||||
at.plus("sda").metadata().unwrap().rdev()
|
||||
);
|
||||
} else {
|
||||
print!("Test skipped; copying a block device node requires CAP_MKNOD");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_cp_block_device_no_permission() {
|
||||
use nix::sys::stat::{Mode, SFlag, mknod as nix_mknod};
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = &ts.fixtures;
|
||||
if nix_mknod(
|
||||
at.plus("sda").as_path(),
|
||||
SFlag::S_IFBLK,
|
||||
Mode::S_IRUSR | Mode::S_IWUSR,
|
||||
uucore::fs::makedev(8, 0),
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
print!("Test skipped; creating a block device node requires CAP_MKNOD");
|
||||
return;
|
||||
}
|
||||
ts.ucmd()
|
||||
.args(&["-R", "sda", "sda2"])
|
||||
.fails()
|
||||
.stderr_is("cp: cannot create special file 'sda2': Operation not permitted\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_cp_socket() {
|
||||
|
||||
Reference in New Issue
Block a user