Skip to content

Commit 302c315

Browse files
authored
Merge pull request #90 from JRF63/cp-fixes
Cp fixes
2 parents d9f1b73 + 566ca8a commit 302c315

File tree

9 files changed

+1757
-357
lines changed

9 files changed

+1757
-357
lines changed

file/Cargo.toml

-4
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@ path = "src/cat.rs"
2222
name = "cmp"
2323
path = "src/cmp.rs"
2424

25-
[[bin]]
26-
name = "cp"
27-
path = "src/cp.rs"
28-
2925
[[bin]]
3026
name = "dd"
3127
path = "src/dd.rs"

file/src/cp.rs

-221
This file was deleted.

tree/Cargo.toml

+5-3
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ regex.workspace = true
2020

2121
[features]
2222
posixutils_test_all = []
23-
24-
# This test must be run alone
25-
test_mv_sticky_to_xpart = []
23+
requires_root = []
2624

2725
[[bin]]
2826
name = "chgrp"
@@ -36,6 +34,10 @@ path = "src/chmod.rs"
3634
name = "chown"
3735
path = "src/chown.rs"
3836

37+
[[bin]]
38+
name = "cp"
39+
path = "src/cp.rs"
40+
3941
[[bin]]
4042
name = "du"
4143
path = "src/du.rs"

tree/src/common/mod.rs

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use gettextrs::gettext;
2+
use std::ffi::{CStr, CString};
3+
use std::os::unix::{ffi::OsStrExt, fs::MetadataExt};
4+
use std::path::Path;
5+
use std::{fs, io};
6+
7+
/// Return the error message.
8+
///
9+
/// This is for compatibility with coreutils mv. `format!("{e}")` will append
10+
/// the error code after the error message which we do not want.
11+
pub fn error_string(e: &io::Error) -> String {
12+
let s = match e.raw_os_error() {
13+
// Like `format!("{e}")` except without the error code.
14+
//
15+
// `std` doesn't expose `sys::os::error_string` so this was copied from:
16+
// https://github.com/rust-lang/rust/blob/72f616273cbbacc06918ef50470d052d39d9b514/library/std/src/sys/pal/unix/os.rs#L124-L149
17+
Some(errno) => {
18+
let mut buf = [0; 128];
19+
20+
unsafe {
21+
if libc::strerror_r(errno as _, buf.as_mut_ptr(), buf.len()) == 0 {
22+
String::from_utf8_lossy(CStr::from_ptr(buf.as_ptr()).to_bytes()).to_string()
23+
} else {
24+
// `std` just panics here
25+
String::from("Unknown error")
26+
}
27+
}
28+
}
29+
None => format!("{e}"),
30+
};
31+
32+
// Translate the error string
33+
gettext(s)
34+
}
35+
36+
/// Copy the metadata in `source_md` to `target`.
37+
///
38+
/// This copies the last access time, last modification time, user ownership,
39+
/// group ownership, and permissions.
40+
pub fn copy_characteristics(source_md: &fs::Metadata, target: &Path) -> io::Result<()> {
41+
let target_cstr = CString::new(target.as_os_str().as_bytes())?;
42+
43+
// [last_access_time, last_modified_time]
44+
let times = [
45+
libc::timespec {
46+
tv_sec: source_md.atime(),
47+
tv_nsec: source_md.atime_nsec(),
48+
},
49+
libc::timespec {
50+
tv_sec: source_md.mtime(),
51+
tv_nsec: source_md.mtime_nsec(),
52+
},
53+
];
54+
55+
unsafe {
56+
// Copy last access and last modified times
57+
let ret = libc::utimensat(
58+
libc::AT_FDCWD, // Relative to the cwd of the process
59+
target_cstr.as_ptr(),
60+
times.as_ptr(),
61+
libc::AT_SYMLINK_NOFOLLOW, // Update the file itself if a symlink
62+
);
63+
if ret != 0 {
64+
return Err(io::Error::last_os_error());
65+
}
66+
67+
// Copy user and group
68+
let ret = libc::chown(target_cstr.as_ptr(), source_md.uid(), source_md.gid());
69+
if ret != 0 {
70+
return Err(io::Error::last_os_error());
71+
}
72+
73+
// Copy permissions
74+
let ret = libc::chmod(target_cstr.as_ptr(), source_md.mode() as libc::mode_t);
75+
if ret != 0 {
76+
return Err(io::Error::last_os_error());
77+
}
78+
}
79+
Ok(())
80+
}
81+
82+
/// Check if the file is writable for the current process.
83+
pub fn is_file_writable(md: &Option<fs::Metadata>) -> bool {
84+
match md {
85+
Some(md) => {
86+
// These are "effective" IDs and not "real" to allow for things like
87+
// sudo
88+
let uid = unsafe { libc::geteuid() };
89+
let gid = unsafe { libc::getegid() };
90+
91+
let same_user = md.uid() == uid;
92+
let same_group = md.gid() == gid;
93+
94+
// `libc::mode_t` is not the same for all platforms while
95+
// `unix::fs::MetadataExt::mode` is always a `u32`.
96+
let mode = md.mode() as libc::mode_t;
97+
98+
if same_user && (mode & libc::S_IWUSR != 0) {
99+
true
100+
} else if same_group && (mode & libc::S_IWGRP != 0) {
101+
true
102+
} else {
103+
mode & libc::S_IWOTH != 0
104+
}
105+
}
106+
None => false,
107+
}
108+
}

0 commit comments

Comments
 (0)