1#![warn(clippy::cargo)]
2
3#[cfg(unix)]
4use std::os::unix::fs::PermissionsExt as _;
5#[cfg(unix)]
6use std::os::unix::io::AsRawFd;
7use std::{fs::File, io, path::Path};
8
9#[cfg(feature = "copy-time")]
10use filetime::{set_file_handle_times, FileTime};
11
12const FILE_FLAG_BACKUP_SEMANTICS: u32 = 0x2000000;
13const FILE_FLAG_OPEN_REPARSE_POINT: u32 = 0x00200000;
14
15#[inline]
17fn open_file_for_metadata(path: &Path, is_source: bool) -> io::Result<File> {
18 let mut opts = std::fs::OpenOptions::new();
19
20 #[cfg(windows)]
21 {
22 use std::os::windows::fs::{MetadataExt, OpenOptionsExt};
23 opts.read(true);
24 let access = if is_source { 0x0080 } else { 0x0100 };
27 opts.access_mode(access)
28 .share_mode(0x7) .custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT); let f = opts.open(path)?;
33
34 if f.metadata()?.file_attributes() & 0x400 != 0 {
37 return Err(io::Error::new(
39 io::ErrorKind::InvalidInput,
40 "Symlinks not supported",
41 ));
42 }
43
44 Ok(f)
45 }
46
47 #[cfg(unix)]
48 {
49 use std::os::unix::fs::OpenOptionsExt;
50
51 let flags = libc::O_NONBLOCK | libc::O_NOFOLLOW;
56 opts.custom_flags(flags);
57
58 opts.read(true);
60 match opts.open(path) {
61 Ok(f) => Ok(f),
62 Err(e) if e.raw_os_error() == Some(libc::ELOOP) => Err(io::Error::new(
63 io::ErrorKind::InvalidInput,
64 "Symlinks not supported",
65 )),
66 Err(e) if e.kind() == io::ErrorKind::PermissionDenied && !is_source => {
67 let mut write_opts = std::fs::OpenOptions::new();
72 write_opts.write(true).custom_flags(flags);
73 match write_opts.open(path) {
74 Ok(f) => Ok(f),
75 Err(we) if we.raw_os_error() == Some(libc::ELOOP) => Err(io::Error::new(
76 io::ErrorKind::InvalidInput,
77 "Symlinks not supported",
78 )),
79 Err(we) => Err(we),
80 }
81 }
82 Err(e) => Err(e),
83 }
84 }
85}
86
87#[cfg(unix)]
88fn copy_permission_impl(
89 to_file: &File,
90 from_meta: &std::fs::Metadata,
91 to_meta: &std::fs::Metadata,
92) -> io::Result<()> {
93 use std::os::unix::fs::MetadataExt;
94
95 let from_gid = from_meta.gid();
96 let to_gid = to_meta.gid();
97 let from_uid = from_meta.uid();
98
99 let mut perms = from_meta.permissions();
100 perms.set_mode(perms.mode() & 0o0777);
101
102 if from_gid != to_gid {
104 let fd = to_file.as_raw_fd();
105 let res = unsafe { libc::fchown(fd, from_uid, from_gid) };
108
109 if res != 0 {
110 let new_perms = (perms.mode() & 0o0707) | ((perms.mode() & 0o07) << 3);
119 perms.set_mode(new_perms);
120 }
121 }
122
123 to_file.set_permissions(perms)
126}
127
128#[cfg(windows)]
129#[inline]
130fn copy_permission_impl(
131 to_file: &File,
132 from_meta: &std::fs::Metadata,
133 _to_meta: &std::fs::Metadata,
134) -> io::Result<()> {
135 to_file.set_permissions(from_meta.permissions())
138}
139
140pub fn copy_metadata(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
143 let from_file = open_file_for_metadata(from.as_ref(), true)?;
146 let from_meta = from_file.metadata()?;
147
148 let to_file = open_file_for_metadata(to.as_ref(), false)?;
150 let to_meta = to_file.metadata()?;
151
152 #[cfg(feature = "copy-time")]
153 {
154 let atime = FileTime::from_last_access_time(&from_meta);
155 let mtime = FileTime::from_last_modification_time(&from_meta);
156
157 set_file_handle_times(&to_file, Some(atime), Some(mtime))?;
159 }
160
161 copy_permission_impl(&to_file, &from_meta, &to_meta)
163}
164
165pub fn copy_permission(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
167 let from_file = open_file_for_metadata(from.as_ref(), true)?;
168 let from_meta = from_file.metadata()?;
169
170 let to_file = open_file_for_metadata(to.as_ref(), false)?;
171 let to_meta = to_file.metadata()?;
172
173 copy_permission_impl(&to_file, &from_meta, &to_meta)
174}
175
176#[cfg(feature = "copy-time")]
178pub fn copy_time(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
179 let from_file = open_file_for_metadata(from.as_ref(), true)?;
180 let from_meta = from_file.metadata()?;
181
182 let atime = FileTime::from_last_access_time(&from_meta);
183 let mtime = FileTime::from_last_modification_time(&from_meta);
184
185 let to_file = open_file_for_metadata(to.as_ref(), false)?;
186 set_file_handle_times(&to_file, Some(atime), Some(mtime))
187}