/rust/registry/src/index.crates.io-6f17d22bba15001f/fs-set-times-0.20.3/src/set_times.rs
Line | Count | Source (jump to first uncovered line) |
1 | | use crate::SystemTimeSpec; |
2 | | use io_lifetimes::AsFilelike; |
3 | | #[cfg(not(windows))] |
4 | | use rustix::{ |
5 | | fs::{futimens, utimensat, AtFlags, Timestamps, CWD}, |
6 | | fs::{UTIME_NOW, UTIME_OMIT}, |
7 | | time::Timespec, |
8 | | }; |
9 | | use std::path::Path; |
10 | | use std::time::SystemTime; |
11 | | use std::{fs, io}; |
12 | | #[cfg(windows)] |
13 | | use { |
14 | | std::{ |
15 | | os::windows::{fs::OpenOptionsExt, io::AsRawHandle}, |
16 | | ptr, |
17 | | time::Duration, |
18 | | }, |
19 | | windows_sys::Win32::Foundation::{ERROR_NOT_SUPPORTED, FILETIME, HANDLE}, |
20 | | windows_sys::Win32::Storage::FileSystem::{ |
21 | | SetFileTime, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, |
22 | | }, |
23 | | }; |
24 | | |
25 | | /// Set the last access timestamp of a file or other filesystem object. |
26 | | #[inline] |
27 | 0 | pub fn set_atime<P: AsRef<Path>>(path: P, atime: SystemTimeSpec) -> io::Result<()> { |
28 | 0 | set_times(path, Some(atime), None) |
29 | 0 | } |
30 | | |
31 | | /// Set the last modification timestamp of a file or other filesystem object. |
32 | | #[inline] |
33 | 0 | pub fn set_mtime<P: AsRef<Path>>(path: P, mtime: SystemTimeSpec) -> io::Result<()> { |
34 | 0 | set_times(path, None, Some(mtime)) |
35 | 0 | } |
36 | | |
37 | | /// Set the last access and last modification timestamps of a file or other |
38 | | /// filesystem object. |
39 | | #[inline] |
40 | 0 | pub fn set_times<P: AsRef<Path>>( |
41 | 0 | path: P, |
42 | 0 | atime: Option<SystemTimeSpec>, |
43 | 0 | mtime: Option<SystemTimeSpec>, |
44 | 0 | ) -> io::Result<()> { |
45 | 0 | let path = path.as_ref(); |
46 | 0 | _set_times(path, atime, mtime) |
47 | 0 | } |
48 | | |
49 | | #[cfg(not(windows))] |
50 | 0 | fn _set_times( |
51 | 0 | path: &Path, |
52 | 0 | atime: Option<SystemTimeSpec>, |
53 | 0 | mtime: Option<SystemTimeSpec>, |
54 | 0 | ) -> io::Result<()> { |
55 | 0 | let times = Timestamps { |
56 | 0 | last_access: to_timespec(atime)?, |
57 | 0 | last_modification: to_timespec(mtime)?, |
58 | | }; |
59 | 0 | Ok(utimensat(CWD, path, ×, AtFlags::empty())?) |
60 | 0 | } |
61 | | |
62 | | #[cfg(windows)] |
63 | | fn _set_times( |
64 | | path: &Path, |
65 | | atime: Option<SystemTimeSpec>, |
66 | | mtime: Option<SystemTimeSpec>, |
67 | | ) -> io::Result<()> { |
68 | | let custom_flags = FILE_FLAG_BACKUP_SEMANTICS; |
69 | | |
70 | | match fs::OpenOptions::new() |
71 | | .write(true) |
72 | | .custom_flags(custom_flags) |
73 | | .open(path) |
74 | | { |
75 | | Ok(file) => return _set_file_times(&file, atime, mtime), |
76 | | Err(err) => match err.kind() { |
77 | | io::ErrorKind::PermissionDenied => (), |
78 | | _ => return Err(err), |
79 | | }, |
80 | | } |
81 | | |
82 | | match fs::OpenOptions::new() |
83 | | .read(true) |
84 | | .custom_flags(custom_flags) |
85 | | .open(path) |
86 | | { |
87 | | Ok(file) => return _set_file_times(&file, atime, mtime), |
88 | | Err(err) => match err.kind() { |
89 | | io::ErrorKind::PermissionDenied => (), |
90 | | _ => return Err(err), |
91 | | }, |
92 | | } |
93 | | |
94 | | Err(io::Error::from_raw_os_error(ERROR_NOT_SUPPORTED as i32)) |
95 | | } |
96 | | |
97 | | /// Like `set_times`, but never follows symlinks. |
98 | | #[inline] |
99 | 0 | pub fn set_symlink_times<P: AsRef<Path>>( |
100 | 0 | path: P, |
101 | 0 | atime: Option<SystemTimeSpec>, |
102 | 0 | mtime: Option<SystemTimeSpec>, |
103 | 0 | ) -> io::Result<()> { |
104 | 0 | let path = path.as_ref(); |
105 | 0 | _set_symlink_times(path, atime, mtime) |
106 | 0 | } |
107 | | |
108 | | /// Like `set_times`, but never follows symlinks. |
109 | | #[cfg(not(windows))] |
110 | 0 | fn _set_symlink_times( |
111 | 0 | path: &Path, |
112 | 0 | atime: Option<SystemTimeSpec>, |
113 | 0 | mtime: Option<SystemTimeSpec>, |
114 | 0 | ) -> io::Result<()> { |
115 | 0 | let times = Timestamps { |
116 | 0 | last_access: to_timespec(atime)?, |
117 | 0 | last_modification: to_timespec(mtime)?, |
118 | | }; |
119 | 0 | Ok(utimensat(CWD, path, ×, AtFlags::SYMLINK_NOFOLLOW)?) |
120 | 0 | } |
121 | | |
122 | | /// Like `set_times`, but never follows symlinks. |
123 | | #[cfg(windows)] |
124 | | fn _set_symlink_times( |
125 | | path: &Path, |
126 | | atime: Option<SystemTimeSpec>, |
127 | | mtime: Option<SystemTimeSpec>, |
128 | | ) -> io::Result<()> { |
129 | | let custom_flags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT; |
130 | | |
131 | | match fs::OpenOptions::new() |
132 | | .write(true) |
133 | | .custom_flags(custom_flags) |
134 | | .open(path) |
135 | | { |
136 | | Ok(file) => return _set_file_times(&file, atime, mtime), |
137 | | Err(err) => match err.kind() { |
138 | | io::ErrorKind::PermissionDenied => (), |
139 | | _ => return Err(err), |
140 | | }, |
141 | | } |
142 | | |
143 | | match fs::OpenOptions::new() |
144 | | .read(true) |
145 | | .custom_flags(custom_flags) |
146 | | .open(path) |
147 | | { |
148 | | Ok(file) => return _set_file_times(&file, atime, mtime), |
149 | | Err(err) => match err.kind() { |
150 | | io::ErrorKind::PermissionDenied => (), |
151 | | _ => return Err(err), |
152 | | }, |
153 | | } |
154 | | |
155 | | Err(io::Error::from_raw_os_error(ERROR_NOT_SUPPORTED as i32)) |
156 | | } |
157 | | |
158 | | /// An extension trait for `std::fs::File`, `cap_std::fs::File`, and similar |
159 | | /// types. |
160 | | pub trait SetTimes { |
161 | | /// Set the last access and last modification timestamps of an open file |
162 | | /// handle. |
163 | | /// |
164 | | /// This corresponds to [`filetime::set_file_handle_times`]. |
165 | | /// |
166 | | /// [`filetime::set_file_handle_times`]: https://docs.rs/filetime/latest/filetime/fn.set_file_handle_times.html |
167 | | fn set_times( |
168 | | &self, |
169 | | atime: Option<SystemTimeSpec>, |
170 | | mtime: Option<SystemTimeSpec>, |
171 | | ) -> io::Result<()>; |
172 | | } |
173 | | |
174 | | impl<T: AsFilelike> SetTimes for T { |
175 | | #[inline] |
176 | 0 | fn set_times( |
177 | 0 | &self, |
178 | 0 | atime: Option<SystemTimeSpec>, |
179 | 0 | mtime: Option<SystemTimeSpec>, |
180 | 0 | ) -> io::Result<()> { |
181 | 0 | _set_file_times(&self.as_filelike_view::<fs::File>(), atime, mtime) |
182 | 0 | } Unexecuted instantiation: <std::fs::File as fs_set_times::set_times::SetTimes>::set_times Unexecuted instantiation: <_ as fs_set_times::set_times::SetTimes>::set_times |
183 | | } |
184 | | |
185 | | #[cfg(not(windows))] |
186 | 0 | fn _set_file_times( |
187 | 0 | file: &fs::File, |
188 | 0 | atime: Option<SystemTimeSpec>, |
189 | 0 | mtime: Option<SystemTimeSpec>, |
190 | 0 | ) -> io::Result<()> { |
191 | 0 | let times = Timestamps { |
192 | 0 | last_access: to_timespec(atime)?, |
193 | 0 | last_modification: to_timespec(mtime)?, |
194 | | }; |
195 | 0 | Ok(futimens(file, ×)?) |
196 | 0 | } |
197 | | |
198 | | #[cfg(not(windows))] |
199 | | #[allow(clippy::useless_conversion)] |
200 | 0 | pub(crate) fn to_timespec(ft: Option<SystemTimeSpec>) -> io::Result<Timespec> { |
201 | 0 | Ok(match ft { |
202 | 0 | None => Timespec { |
203 | 0 | tv_sec: 0, |
204 | 0 | tv_nsec: UTIME_OMIT.into(), |
205 | 0 | }, |
206 | 0 | Some(SystemTimeSpec::SymbolicNow) => Timespec { |
207 | 0 | tv_sec: 0, |
208 | 0 | tv_nsec: UTIME_NOW.into(), |
209 | 0 | }, |
210 | 0 | Some(SystemTimeSpec::Absolute(ft)) => { |
211 | 0 | let duration = ft.duration_since(SystemTime::UNIX_EPOCH).unwrap(); |
212 | 0 | let nanoseconds = duration.subsec_nanos(); |
213 | 0 | assert_ne!(i64::from(nanoseconds), i64::from(UTIME_OMIT)); |
214 | 0 | assert_ne!(i64::from(nanoseconds), i64::from(UTIME_NOW)); |
215 | | Timespec { |
216 | 0 | tv_sec: duration |
217 | 0 | .as_secs() |
218 | 0 | .try_into() |
219 | 0 | .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?, |
220 | 0 | tv_nsec: nanoseconds.try_into().unwrap(), |
221 | | } |
222 | | } |
223 | | }) |
224 | 0 | } |
225 | | |
226 | | #[cfg(windows)] |
227 | | fn _set_file_times( |
228 | | file: &fs::File, |
229 | | atime: Option<SystemTimeSpec>, |
230 | | mtime: Option<SystemTimeSpec>, |
231 | | ) -> io::Result<()> { |
232 | | let mut now = None; |
233 | | |
234 | | let atime = match atime { |
235 | | None => None, |
236 | | Some(SystemTimeSpec::SymbolicNow) => { |
237 | | let right_now = SystemTime::now(); |
238 | | now = Some(right_now); |
239 | | Some(right_now) |
240 | | } |
241 | | Some(SystemTimeSpec::Absolute(time)) => Some(time), |
242 | | }; |
243 | | let mtime = match mtime { |
244 | | None => None, |
245 | | Some(SystemTimeSpec::SymbolicNow) => { |
246 | | if let Some(prev_now) = now { |
247 | | Some(prev_now) |
248 | | } else { |
249 | | Some(SystemTime::now()) |
250 | | } |
251 | | } |
252 | | Some(SystemTimeSpec::Absolute(time)) => Some(time), |
253 | | }; |
254 | | |
255 | | let atime = atime.map(to_filetime).transpose()?; |
256 | | let mtime = mtime.map(to_filetime).transpose()?; |
257 | | if unsafe { |
258 | | SetFileTime( |
259 | | file.as_raw_handle() as HANDLE, |
260 | | ptr::null(), |
261 | | atime.as_ref().map(|r| r as *const _).unwrap_or(ptr::null()), |
262 | | mtime.as_ref().map(|r| r as *const _).unwrap_or(ptr::null()), |
263 | | ) |
264 | | } != 0 |
265 | | { |
266 | | Ok(()) |
267 | | } else { |
268 | | Err(io::Error::last_os_error()) |
269 | | } |
270 | | } |
271 | | |
272 | | #[cfg(windows)] |
273 | | fn to_filetime(ft: SystemTime) -> io::Result<FILETIME> { |
274 | | // To convert a `SystemTime` to absolute seconds and nanoseconds, we need |
275 | | // a reference point. The `UNIX_EPOCH` is the only reference point provided |
276 | | // by the standard library. But we know that Windows' time stamps are |
277 | | // relative to January 1, 1601 so adjust by the difference between that and |
278 | | // the Unix epoch. |
279 | | let epoch = SystemTime::UNIX_EPOCH - Duration::from_secs(11644473600); |
280 | | let ft = ft |
281 | | .duration_since(epoch) |
282 | | .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; |
283 | | |
284 | | let intervals = ft.as_secs() * (1_000_000_000 / 100) + u64::from(ft.subsec_nanos() / 100); |
285 | | |
286 | | // On Windows, a zero time is silently ignored, so issue an error instead. |
287 | | if intervals == 0 { |
288 | | return Err(io::Error::from_raw_os_error(ERROR_NOT_SUPPORTED as i32)); |
289 | | } |
290 | | |
291 | | Ok(FILETIME { |
292 | | dwLowDateTime: intervals as u32, |
293 | | dwHighDateTime: (intervals >> 32) as u32, |
294 | | }) |
295 | | } |