copy_metadata/
lib.rs

1#![warn(clippy::cargo)]
2
3#[cfg(unix)]
4use std::os::unix::fs::{chown, MetadataExt as _, PermissionsExt as _};
5use std::{io, path::Path};
6
7use filetime::{set_file_times, FileTime};
8
9/// from: https://github.com/helix-editor/helix/blob/d6eb10d9f907139597ededa38a2cab44b26f5da6/helix-stdx/src/faccess.rs#L60
10///
11/// uses MPL-2.0 license, link: https://github.com/helix-editor/helix/blob/master/LICENSE
12#[cfg(unix)]
13fn copy_permission_inner(
14    to: &Path,
15    from_meta: &std::fs::Metadata,
16    to_meta: &std::fs::Metadata,
17) -> io::Result<()> {
18    let from_gid = from_meta.gid();
19    let to_gid = to_meta.gid();
20
21    let mut perms = from_meta.permissions();
22    perms.set_mode(perms.mode() & 0o0777);
23    if from_gid != to_gid && chown(to, None, Some(from_gid)).is_err() {
24        let new_perms = (perms.mode() & 0o0707) | ((perms.mode() & 0o07) << 3);
25        perms.set_mode(new_perms);
26    }
27    std::fs::set_permissions(to, perms)?;
28    Ok(())
29}
30
31#[cfg(windows)]
32fn copy_permission_inner(
33    to: &Path,
34    from_meta: &std::fs::Metadata,
35    _to_meta: &std::fs::Metadata,
36) -> io::Result<()> {
37    let permissions = from_meta.permissions();
38    std::fs::set_permissions(to, permissions)?;
39    Ok(())
40}
41
42fn copy_time_inner(to: &Path, from_meta: &std::fs::Metadata) -> io::Result<()> {
43    let atime = FileTime::from_last_access_time(from_meta);
44    let mtime = FileTime::from_last_modification_time(from_meta);
45    set_file_times(to, atime, mtime)
46}
47
48/// copy metadata from one file to another, including permissions and time.
49pub fn copy_metadata(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
50    let (from, to) = (from.as_ref(), to.as_ref());
51    let from_meta = std::fs::metadata(from)?;
52    let to_meta = std::fs::metadata(to)?;
53
54    // try to copy time first, because it might be refused if permission changes to
55    // read-only.
56    let res = copy_time_inner(to, &from_meta);
57    copy_permission_inner(to, &from_meta, &to_meta)?;
58    if let Err(err) = res {
59        if err.kind() == io::ErrorKind::PermissionDenied {
60            copy_time_inner(to, &from_meta)?;
61        }
62    }
63    Ok(())
64}
65
66/// Copy permission from one file to another.
67pub fn copy_permission(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
68    let (from, to) = (from.as_ref(), to.as_ref());
69    let from_meta = std::fs::metadata(from)?;
70    let to_meta = std::fs::metadata(to)?;
71    copy_permission_inner(to, &from_meta, &to_meta)
72}
73
74/// Copy time stamp from one file to another.
75///
76/// Including last_access_time (atime) and last_modification_time (mtime).
77pub fn copy_time(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
78    let (from, to) = (from.as_ref(), to.as_ref());
79    let from_meta = std::fs::metadata(from)?;
80    copy_time_inner(to, &from_meta)
81}