1#![warn(clippy::cargo)]
2
3#[cfg(unix)]
4use std::os::unix::fs::{chown, MetadataExt as _, PermissionsExt as _};
5use std::{fs::File, io, path::Path};
6
7use filetime::{set_file_handle_times, set_file_times, FileTime};
8
9const FILE_FLAG_BACKUP_SEMANTICS: u32 = 0x2000000;
10
11#[inline]
13fn open_file_for_metadata(path: &Path) -> io::Result<File> {
14 let mut opts = std::fs::OpenOptions::new();
15 opts.read(true);
16
17 #[cfg(windows)]
18 {
19 use std::os::windows::fs::OpenOptionsExt;
20 opts.access_mode(0x0100)
24 .share_mode(0x7)
25 .custom_flags(FILE_FLAG_BACKUP_SEMANTICS);
26 }
27
28 opts.open(path)
29}
30
31#[cfg(unix)]
32fn copy_permission_impl(
33 to_path: &Path,
34 to_file: Option<&File>,
35 from_meta: &std::fs::Metadata,
36 to_meta: &std::fs::Metadata,
37) -> io::Result<()> {
38 let from_gid = from_meta.gid();
39 let to_gid = to_meta.gid();
40
41 let mut perms = from_meta.permissions();
42 perms.set_mode(perms.mode() & 0o0777);
43
44 if from_gid != to_gid && chown(to_path, None, Some(from_gid)).is_err() {
46 let new_perms = (perms.mode() & 0o0707) | ((perms.mode() & 0o07) << 3);
47 perms.set_mode(new_perms);
48 }
49
50 if let Some(file) = to_file {
52 file.set_permissions(perms)
53 } else {
54 std::fs::set_permissions(to_path, perms)
55 }
56}
57
58#[cfg(windows)]
59#[inline]
60fn copy_permission_impl(
61 to_path: &Path,
62 to_file: Option<&File>,
63 from_meta: &std::fs::Metadata,
64 _to_meta: &std::fs::Metadata,
65) -> io::Result<()> {
66 let permissions = from_meta.permissions();
67 if let Some(file) = to_file {
68 file.set_permissions(permissions)
69 } else {
70 std::fs::set_permissions(to_path, permissions)
71 }
72}
73
74#[inline]
75fn copy_time_path(to: &Path, from_meta: &std::fs::Metadata) -> io::Result<()> {
76 let atime = FileTime::from_last_access_time(from_meta);
77 let mtime = FileTime::from_last_modification_time(from_meta);
78 set_file_times(to, atime, mtime)
79}
80
81pub fn copy_metadata(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
83 let (from, to) = (from.as_ref(), to.as_ref());
84 let from_meta = std::fs::metadata(from)?;
85
86 match open_file_for_metadata(to) {
87 Ok(to_file) => {
88 let to_meta = to_file.metadata()?;
89 let atime = FileTime::from_last_access_time(&from_meta);
90 let mtime = FileTime::from_last_modification_time(&from_meta);
91
92 if let Err(e) = set_file_handle_times(&to_file, Some(atime), Some(mtime)) {
93 if e.kind() == io::ErrorKind::PermissionDenied {
94 copy_time_path(to, &from_meta)?;
95 } else {
96 return Err(e);
97 }
98 }
99
100 copy_permission_impl(to, Some(&to_file), &from_meta, &to_meta)
101 }
102 Err(_) => {
103 let to_meta = std::fs::metadata(to)?;
105 let res = copy_time_path(to, &from_meta);
106 copy_permission_impl(to, None, &from_meta, &to_meta)?;
107
108 if let Err(err) = res {
109 if err.kind() == io::ErrorKind::PermissionDenied {
111 copy_time_path(to, &from_meta)?;
112 } else {
113 return Err(err);
114 }
115 }
116 Ok(())
117 }
118 }
119}
120
121pub fn copy_permission(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
123 let (from, to) = (from.as_ref(), to.as_ref());
124 let from_meta = std::fs::metadata(from)?;
125
126 match open_file_for_metadata(to) {
127 Ok(to_file) => {
128 let to_meta = to_file.metadata()?;
129 copy_permission_impl(to, Some(&to_file), &from_meta, &to_meta)
130 }
131 Err(_) => {
132 let to_meta = std::fs::metadata(to)?;
133 copy_permission_impl(to, None, &from_meta, &to_meta)
134 }
135 }
136}
137
138pub fn copy_time(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
142 let (from, to) = (from.as_ref(), to.as_ref());
143 let from_meta = std::fs::metadata(from)?;
144
145 let atime = FileTime::from_last_access_time(&from_meta);
146 let mtime = FileTime::from_last_modification_time(&from_meta);
147
148 if let Ok(to_file) = open_file_for_metadata(to) {
149 if let Err(e) = set_file_handle_times(&to_file, Some(atime), Some(mtime)) {
150 if e.kind() == io::ErrorKind::PermissionDenied {
151 return set_file_times(to, atime, mtime);
152 }
153 return Err(e);
154 }
155 Ok(())
156 } else {
157 set_file_times(to, atime, mtime)
158 }
159}