Skip to content

Commit 74d80ea

Browse files
authored
Merge pull request #7061 from DaringCuteSeal/cp-stream-2
cp: implement copying from streams
2 parents 40511f6 + c6d1923 commit 74d80ea

File tree

7 files changed

+189
-50
lines changed

7 files changed

+189
-50
lines changed

src/uu/cp/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ quick-error = { workspace = true }
2828
selinux = { workspace = true, optional = true }
2929
uucore = { workspace = true, features = [
3030
"backup-control",
31+
"buf-copy",
3132
"entries",
3233
"fs",
3334
"fsxattr",

src/uu/cp/src/cp.rs

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::collections::{HashMap, HashSet};
1010
#[cfg(not(windows))]
1111
use std::ffi::CString;
1212
use std::ffi::OsString;
13-
use std::fs::{self, File, Metadata, OpenOptions, Permissions};
13+
use std::fs::{self, Metadata, OpenOptions, Permissions};
1414
use std::io;
1515
#[cfg(unix)]
1616
use std::os::unix::ffi::OsStrExt;
@@ -1963,6 +1963,7 @@ fn print_paths(parents: bool, source: &Path, dest: &Path) {
19631963
///
19641964
/// * `Ok(())` - The file was copied successfully.
19651965
/// * `Err(CopyError)` - An error occurred while copying the file.
1966+
#[allow(clippy::too_many_arguments)]
19661967
fn handle_copy_mode(
19671968
source: &Path,
19681969
dest: &Path,
@@ -1971,15 +1972,10 @@ fn handle_copy_mode(
19711972
source_metadata: &Metadata,
19721973
symlinked_files: &mut HashSet<FileInformation>,
19731974
source_in_command_line: bool,
1975+
source_is_fifo: bool,
1976+
#[cfg(unix)] source_is_stream: bool,
19741977
) -> CopyResult<()> {
1975-
let source_file_type = source_metadata.file_type();
1976-
1977-
let source_is_symlink = source_file_type.is_symlink();
1978-
1979-
#[cfg(unix)]
1980-
let source_is_fifo = source_file_type.is_fifo();
1981-
#[cfg(not(unix))]
1982-
let source_is_fifo = false;
1978+
let source_is_symlink = source_metadata.is_symlink();
19831979

19841980
match options.copy_mode {
19851981
CopyMode::Link => {
@@ -2016,6 +2012,8 @@ fn handle_copy_mode(
20162012
source_is_symlink,
20172013
source_is_fifo,
20182014
symlinked_files,
2015+
#[cfg(unix)]
2016+
source_is_stream,
20192017
)?;
20202018
}
20212019
CopyMode::SymLink => {
@@ -2036,6 +2034,8 @@ fn handle_copy_mode(
20362034
source_is_symlink,
20372035
source_is_fifo,
20382036
symlinked_files,
2037+
#[cfg(unix)]
2038+
source_is_stream,
20392039
)?;
20402040
}
20412041
update_control::UpdateMode::ReplaceNone => {
@@ -2066,6 +2066,8 @@ fn handle_copy_mode(
20662066
source_is_symlink,
20672067
source_is_fifo,
20682068
symlinked_files,
2069+
#[cfg(unix)]
2070+
source_is_stream,
20692071
)?;
20702072
}
20712073
}
@@ -2079,6 +2081,8 @@ fn handle_copy_mode(
20792081
source_is_symlink,
20802082
source_is_fifo,
20812083
symlinked_files,
2084+
#[cfg(unix)]
2085+
source_is_stream,
20822086
)?;
20832087
}
20842088
}
@@ -2305,6 +2309,18 @@ fn copy_file(
23052309

23062310
let dest_permissions = calculate_dest_permissions(dest, &source_metadata, options, context)?;
23072311

2312+
#[cfg(unix)]
2313+
let source_is_fifo = source_metadata.file_type().is_fifo();
2314+
#[cfg(not(unix))]
2315+
let source_is_fifo = false;
2316+
2317+
#[cfg(unix)]
2318+
let source_is_stream = source_is_fifo
2319+
|| source_metadata.file_type().is_char_device()
2320+
|| source_metadata.file_type().is_block_device();
2321+
#[cfg(not(unix))]
2322+
let source_is_stream = false;
2323+
23082324
handle_copy_mode(
23092325
source,
23102326
dest,
@@ -2313,6 +2329,9 @@ fn copy_file(
23132329
&source_metadata,
23142330
symlinked_files,
23152331
source_in_command_line,
2332+
source_is_fifo,
2333+
#[cfg(unix)]
2334+
source_is_stream,
23162335
)?;
23172336

23182337
// TODO: implement something similar to gnu's lchown
@@ -2328,8 +2347,16 @@ fn copy_file(
23282347

23292348
if options.dereference(source_in_command_line) {
23302349
if let Ok(src) = canonicalize(source, MissingHandling::Normal, ResolveMode::Physical) {
2331-
copy_attributes(&src, dest, &options.attributes)?;
2350+
if src.exists() {
2351+
copy_attributes(&src, dest, &options.attributes)?;
2352+
}
23322353
}
2354+
} else if source_is_stream && source.exists() {
2355+
// Some stream files may not exist after we have copied it,
2356+
// like anonymous pipes. Thus, we can't really copy its
2357+
// attributes. However, this is already handled in the stream
2358+
// copy function (see `copy_stream` under platform/linux.rs).
2359+
copy_attributes(source, dest, &options.attributes)?;
23332360
} else {
23342361
copy_attributes(source, dest, &options.attributes)?;
23352362
}
@@ -2393,6 +2420,7 @@ fn handle_no_preserve_mode(options: &Options, org_mode: u32) -> u32 {
23932420

23942421
/// Copy the file from `source` to `dest` either using the normal `fs::copy` or a
23952422
/// copy-on-write scheme if --reflink is specified and the filesystem supports it.
2423+
#[allow(clippy::too_many_arguments)]
23962424
fn copy_helper(
23972425
source: &Path,
23982426
dest: &Path,
@@ -2401,6 +2429,7 @@ fn copy_helper(
24012429
source_is_symlink: bool,
24022430
source_is_fifo: bool,
24032431
symlinked_files: &mut HashSet<FileInformation>,
2432+
#[cfg(unix)] source_is_stream: bool,
24042433
) -> CopyResult<()> {
24052434
if options.parents {
24062435
let parent = dest.parent().unwrap_or(dest);
@@ -2411,12 +2440,7 @@ fn copy_helper(
24112440
return Err(Error::NotADirectory(dest.to_path_buf()));
24122441
}
24132442

2414-
if source.as_os_str() == "/dev/null" {
2415-
/* workaround a limitation of fs::copy
2416-
* https://github.com/rust-lang/rust/issues/79390
2417-
*/
2418-
File::create(dest).context(dest.display().to_string())?;
2419-
} else if source_is_fifo && options.recursive && !options.copy_contents {
2443+
if source_is_fifo && options.recursive && !options.copy_contents {
24202444
#[cfg(unix)]
24212445
copy_fifo(dest, options.overwrite, options.debug)?;
24222446
} else if source_is_symlink {
@@ -2428,8 +2452,10 @@ fn copy_helper(
24282452
options.reflink_mode,
24292453
options.sparse_mode,
24302454
context,
2431-
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
2455+
#[cfg(unix)]
24322456
source_is_fifo,
2457+
#[cfg(unix)]
2458+
source_is_stream,
24332459
)?;
24342460

24352461
if !options.attributes_only && options.debug {

src/uu/cp/src/platform/linux.rs

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use std::os::unix::fs::MetadataExt;
1212
use std::os::unix::fs::{FileTypeExt, OpenOptionsExt};
1313
use std::os::unix::io::AsRawFd;
1414
use std::path::Path;
15+
use uucore::buf_copy;
1516

1617
use quick_error::ResultExt;
1718

@@ -220,8 +221,9 @@ fn check_dest_is_fifo(dest: &Path) -> bool {
220221
}
221222
}
222223

223-
/// Copy the contents of the given source FIFO to the given file.
224-
fn copy_fifo_contents<P>(source: P, dest: P) -> std::io::Result<u64>
224+
/// Copy the contents of a stream from `source` to `dest`. The `if_fifo` argument is used to
225+
/// determine if we need to modify the file's attributes before and after copying.
226+
fn copy_stream<P>(source: P, dest: P, is_fifo: bool) -> std::io::Result<u64>
225227
where
226228
P: AsRef<Path>,
227229
{
@@ -250,8 +252,14 @@ where
250252
.write(true)
251253
.mode(mode)
252254
.open(&dest)?;
253-
let num_bytes_copied = std::io::copy(&mut src_file, &mut dst_file)?;
254-
dst_file.set_permissions(src_file.metadata()?.permissions())?;
255+
256+
let num_bytes_copied = buf_copy::copy_stream(&mut src_file, &mut dst_file)
257+
.map_err(|_| std::io::Error::from(std::io::ErrorKind::Other))?;
258+
259+
if is_fifo {
260+
dst_file.set_permissions(src_file.metadata()?.permissions())?;
261+
}
262+
255263
Ok(num_bytes_copied)
256264
}
257265

@@ -268,6 +276,7 @@ pub(crate) fn copy_on_write(
268276
sparse_mode: SparseMode,
269277
context: &str,
270278
source_is_fifo: bool,
279+
source_is_stream: bool,
271280
) -> CopyResult<CopyDebug> {
272281
let mut copy_debug = CopyDebug {
273282
offload: OffloadReflinkDebug::Unknown,
@@ -279,10 +288,9 @@ pub(crate) fn copy_on_write(
279288
copy_debug.sparse_detection = SparseDebug::Zeros;
280289
// Default SparseDebug val for SparseMode::Always
281290
copy_debug.reflink = OffloadReflinkDebug::No;
282-
if source_is_fifo {
291+
if source_is_stream {
283292
copy_debug.offload = OffloadReflinkDebug::Avoided;
284-
285-
copy_fifo_contents(source, dest).map(|_| ())
293+
copy_stream(source, dest, source_is_fifo).map(|_| ())
286294
} else {
287295
let mut copy_method = CopyMethod::Default;
288296
let result = handle_reflink_never_sparse_always(source, dest);
@@ -300,10 +308,9 @@ pub(crate) fn copy_on_write(
300308
(ReflinkMode::Never, SparseMode::Never) => {
301309
copy_debug.reflink = OffloadReflinkDebug::No;
302310

303-
if source_is_fifo {
311+
if source_is_stream {
304312
copy_debug.offload = OffloadReflinkDebug::Avoided;
305-
306-
copy_fifo_contents(source, dest).map(|_| ())
313+
copy_stream(source, dest, source_is_fifo).map(|_| ())
307314
} else {
308315
let result = handle_reflink_never_sparse_never(source);
309316
if let Ok(debug) = result {
@@ -315,9 +322,9 @@ pub(crate) fn copy_on_write(
315322
(ReflinkMode::Never, SparseMode::Auto) => {
316323
copy_debug.reflink = OffloadReflinkDebug::No;
317324

318-
if source_is_fifo {
325+
if source_is_stream {
319326
copy_debug.offload = OffloadReflinkDebug::Avoided;
320-
copy_fifo_contents(source, dest).map(|_| ())
327+
copy_stream(source, dest, source_is_fifo).map(|_| ())
321328
} else {
322329
let mut copy_method = CopyMethod::Default;
323330
let result = handle_reflink_never_sparse_auto(source, dest);
@@ -335,10 +342,9 @@ pub(crate) fn copy_on_write(
335342
(ReflinkMode::Auto, SparseMode::Always) => {
336343
copy_debug.sparse_detection = SparseDebug::Zeros; // Default SparseDebug val for
337344
// SparseMode::Always
338-
if source_is_fifo {
345+
if source_is_stream {
339346
copy_debug.offload = OffloadReflinkDebug::Avoided;
340-
341-
copy_fifo_contents(source, dest).map(|_| ())
347+
copy_stream(source, dest, source_is_fifo).map(|_| ())
342348
} else {
343349
let mut copy_method = CopyMethod::Default;
344350
let result = handle_reflink_auto_sparse_always(source, dest);
@@ -356,9 +362,9 @@ pub(crate) fn copy_on_write(
356362

357363
(ReflinkMode::Auto, SparseMode::Never) => {
358364
copy_debug.reflink = OffloadReflinkDebug::No;
359-
if source_is_fifo {
365+
if source_is_stream {
360366
copy_debug.offload = OffloadReflinkDebug::Avoided;
361-
copy_fifo_contents(source, dest).map(|_| ())
367+
copy_stream(source, dest, source_is_fifo).map(|_| ())
362368
} else {
363369
let result = handle_reflink_auto_sparse_never(source);
364370
if let Ok(debug) = result {
@@ -369,9 +375,9 @@ pub(crate) fn copy_on_write(
369375
}
370376
}
371377
(ReflinkMode::Auto, SparseMode::Auto) => {
372-
if source_is_fifo {
378+
if source_is_stream {
373379
copy_debug.offload = OffloadReflinkDebug::Unsupported;
374-
copy_fifo_contents(source, dest).map(|_| ())
380+
copy_stream(source, dest, source_is_fifo).map(|_| ())
375381
} else {
376382
let mut copy_method = CopyMethod::Default;
377383
let result = handle_reflink_auto_sparse_auto(source, dest);

src/uu/cp/src/platform/macos.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
// file that was distributed with this source code.
55
// spell-checker:ignore reflink
66
use std::ffi::CString;
7-
use std::fs::{self, File};
8-
use std::io;
7+
use std::fs::{self, File, OpenOptions};
98
use std::os::unix::ffi::OsStrExt;
9+
use std::os::unix::fs::OpenOptionsExt;
1010
use std::path::Path;
1111

1212
use quick_error::ResultExt;
13+
use uucore::buf_copy;
14+
use uucore::mode::get_umask;
1315

1416
use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode};
1517

@@ -24,6 +26,7 @@ pub(crate) fn copy_on_write(
2426
sparse_mode: SparseMode,
2527
context: &str,
2628
source_is_fifo: bool,
29+
source_is_stream: bool,
2730
) -> CopyResult<CopyDebug> {
2831
if sparse_mode != SparseMode::Auto {
2932
return Err("--sparse is only supported on linux".to_string().into());
@@ -85,10 +88,23 @@ pub(crate) fn copy_on_write(
8588
}
8689
_ => {
8790
copy_debug.reflink = OffloadReflinkDebug::Yes;
88-
if source_is_fifo {
91+
if source_is_stream {
8992
let mut src_file = File::open(source)?;
90-
let mut dst_file = File::create(dest)?;
91-
io::copy(&mut src_file, &mut dst_file).context(context)?
93+
let mode = 0o622 & !get_umask();
94+
let mut dst_file = OpenOptions::new()
95+
.create(true)
96+
.write(true)
97+
.mode(mode)
98+
.open(dest)?;
99+
100+
let context = buf_copy::copy_stream(&mut src_file, &mut dst_file)
101+
.map_err(|_| std::io::Error::from(std::io::ErrorKind::Other))
102+
.context(context)?;
103+
104+
if source_is_fifo {
105+
dst_file.set_permissions(src_file.metadata()?.permissions())?;
106+
}
107+
context
92108
} else {
93109
fs::copy(source, dest).context(context)?
94110
}

src/uu/cp/src/platform/mod.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22
//
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
5+
6+
#[cfg(all(
7+
unix,
8+
not(any(target_os = "macos", target_os = "linux", target_os = "android"))
9+
))]
10+
mod other_unix;
11+
#[cfg(all(
12+
unix,
13+
not(any(target_os = "macos", target_os = "linux", target_os = "android"))
14+
))]
15+
pub(crate) use self::other_unix::copy_on_write;
16+
517
#[cfg(target_os = "macos")]
618
mod macos;
719
#[cfg(target_os = "macos")]
@@ -12,7 +24,13 @@ mod linux;
1224
#[cfg(any(target_os = "linux", target_os = "android"))]
1325
pub(crate) use self::linux::copy_on_write;
1426

15-
#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "macos")))]
27+
#[cfg(not(any(
28+
unix,
29+
any(target_os = "macos", target_os = "linux", target_os = "android")
30+
)))]
1631
mod other;
17-
#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "macos")))]
32+
#[cfg(not(any(
33+
unix,
34+
any(target_os = "macos", target_os = "linux", target_os = "android")
35+
)))]
1836
pub(crate) use self::other::copy_on_write;

0 commit comments

Comments
 (0)