Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change std::fs::copy to use copyfile on MacOS and iOS #58901

Merged
merged 3 commits into from
Mar 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion src/libstd/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1581,7 +1581,8 @@ pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()>
/// `O_CLOEXEC` is set for returned file descriptors.
/// On Windows, this function currently corresponds to `CopyFileEx`. Alternate
/// NTFS streams are copied but only the size of the main stream is returned by
/// this function.
/// this function. On MacOS, this function corresponds to `copyfile` with
/// `COPYFILE_ALL`.
/// Note that, this [may change in the future][changes].
///
/// [changes]: ../io/index.html#platform-specific-behavior
Expand Down Expand Up @@ -2836,6 +2837,26 @@ mod tests {
assert_eq!(check!(out_path.metadata()).len(), copied_len);
}

#[test]
fn copy_file_follows_dst_symlink() {
let tmp = tmpdir();
if !got_symlink_permission(&tmp) { return };

let in_path = tmp.join("in.txt");
let out_path = tmp.join("out.txt");
let out_path_symlink = tmp.join("out_symlink.txt");

check!(fs::write(&in_path, "foo"));
check!(fs::write(&out_path, "bar"));
check!(symlink_file(&out_path, &out_path_symlink));

check!(fs::copy(&in_path, &out_path_symlink));

assert!(check!(out_path_symlink.symlink_metadata()).file_type().is_symlink());
assert_eq!(check!(fs::read(&out_path_symlink)), b"foo".to_vec());
assert_eq!(check!(fs::read(&out_path)), b"foo".to_vec());
}

#[test]
fn symlinks_work() {
let tmpdir = tmpdir();
Expand Down
87 changes: 86 additions & 1 deletion src/libstd/sys/unix/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,10 @@ pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
Ok(PathBuf::from(OsString::from_vec(buf)))
}

#[cfg(not(any(target_os = "linux", target_os = "android")))]
#[cfg(not(any(target_os = "linux",
target_os = "android",
target_os = "macos",
target_os = "ios")))]
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
use crate::fs::File;
if !from.is_file() {
Expand Down Expand Up @@ -937,3 +940,85 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
writer.set_permissions(perm)?;
Ok(written)
}

#[cfg(any(target_os = "macos", target_os = "ios"))]
pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
const COPYFILE_ACL: u32 = 1 << 0;
const COPYFILE_STAT: u32 = 1 << 1;
const COPYFILE_XATTR: u32 = 1 << 2;
const COPYFILE_DATA: u32 = 1 << 3;

const COPYFILE_SECURITY: u32 = COPYFILE_STAT | COPYFILE_ACL;
const COPYFILE_METADATA: u32 = COPYFILE_SECURITY | COPYFILE_XATTR;
const COPYFILE_ALL: u32 = COPYFILE_METADATA | COPYFILE_DATA;

const COPYFILE_STATE_COPIED: u32 = 8;

#[allow(non_camel_case_types)]
type copyfile_state_t = *mut libc::c_void;
#[allow(non_camel_case_types)]
type copyfile_flags_t = u32;

extern "C" {
fn copyfile(
from: *const libc::c_char,
to: *const libc::c_char,
state: copyfile_state_t,
flags: copyfile_flags_t,
) -> libc::c_int;
fn copyfile_state_alloc() -> copyfile_state_t;
fn copyfile_state_free(state: copyfile_state_t) -> libc::c_int;
fn copyfile_state_get(
state: copyfile_state_t,
flag: u32,
dst: *mut libc::c_void,
) -> libc::c_int;
}

struct FreeOnDrop(copyfile_state_t);
impl Drop for FreeOnDrop {
fn drop(&mut self) {
// The code below ensures that `FreeOnDrop` is never a null pointer
unsafe {
// `copyfile_state_free` returns -1 if the `to` or `from` files
// cannot be closed. However, this is not considerd this an
// error.
copyfile_state_free(self.0);
}
}
}

if !from.is_file() {
return Err(Error::new(ErrorKind::InvalidInput,
"the source path is not an existing regular file"))
}

// We ensure that `FreeOnDrop` never contains a null pointer so it is
// always safe to call `copyfile_state_free`
let state = unsafe {
let state = copyfile_state_alloc();
if state.is_null() {
return Err(crate::io::Error::last_os_error());
}
FreeOnDrop(state)
};

cvt(unsafe {
copyfile(
cstr(from)?.as_ptr(),
cstr(to)?.as_ptr(),
state.0,
COPYFILE_ALL,
)
})?;

let mut bytes_copied: libc::off_t = 0;
cvt(unsafe {
copyfile_state_get(
state.0,
COPYFILE_STATE_COPIED,
&mut bytes_copied as *mut libc::off_t as *mut libc::c_void,
)
})?;
Ok(bytes_copied as u64)
}