1use crate::SystemTimeSpec;
2use io_lifetimes::AsFilelike;
3#[cfg(not(windows))]
4use rustix::{
5 fs::{futimens, utimensat, AtFlags, Timestamps, CWD},
6 fs::{UTIME_NOW, UTIME_OMIT},
7 time::Timespec,
8};
9use std::path::Path;
10use std::time::SystemTime;
11use std::{fs, io};
12#[cfg(windows)]
13use {
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#[inline]
27pub fn set_atime<P: AsRef<Path>>(path: P, atime: SystemTimeSpec) -> io::Result<()> {
28 set_times(path, Some(atime), None)
29}
30
31#[inline]
33pub fn set_mtime<P: AsRef<Path>>(path: P, mtime: SystemTimeSpec) -> io::Result<()> {
34 set_times(path, None, Some(mtime))
35}
36
37#[inline]
40pub fn set_times<P: AsRef<Path>>(
41 path: P,
42 atime: Option<SystemTimeSpec>,
43 mtime: Option<SystemTimeSpec>,
44) -> io::Result<()> {
45 let path = path.as_ref();
46 _set_times(path, atime, mtime)
47}
48
49#[cfg(not(windows))]
50fn _set_times(
51 path: &Path,
52 atime: Option<SystemTimeSpec>,
53 mtime: Option<SystemTimeSpec>,
54) -> io::Result<()> {
55 let times = Timestamps {
56 last_access: to_timespec(atime)?,
57 last_modification: to_timespec(mtime)?,
58 };
59 Ok(utimensat(CWD, path, ×, AtFlags::empty())?)
60}
61
62#[cfg(windows)]
63fn _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#[inline]
99pub fn set_symlink_times<P: AsRef<Path>>(
100 path: P,
101 atime: Option<SystemTimeSpec>,
102 mtime: Option<SystemTimeSpec>,
103) -> io::Result<()> {
104 let path = path.as_ref();
105 _set_symlink_times(path, atime, mtime)
106}
107
108#[cfg(not(windows))]
110fn _set_symlink_times(
111 path: &Path,
112 atime: Option<SystemTimeSpec>,
113 mtime: Option<SystemTimeSpec>,
114) -> io::Result<()> {
115 let times = Timestamps {
116 last_access: to_timespec(atime)?,
117 last_modification: to_timespec(mtime)?,
118 };
119 Ok(utimensat(CWD, path, ×, AtFlags::SYMLINK_NOFOLLOW)?)
120}
121
122#[cfg(windows)]
124fn _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
158pub trait SetTimes {
161 fn set_times(
168 &self,
169 atime: Option<SystemTimeSpec>,
170 mtime: Option<SystemTimeSpec>,
171 ) -> io::Result<()>;
172}
173
174impl<T: AsFilelike> SetTimes for T {
175 #[inline]
176 fn set_times(
177 &self,
178 atime: Option<SystemTimeSpec>,
179 mtime: Option<SystemTimeSpec>,
180 ) -> io::Result<()> {
181 _set_file_times(&self.as_filelike_view::<fs::File>(), atime, mtime)
182 }
183}
184
185#[cfg(not(windows))]
186fn _set_file_times(
187 file: &fs::File,
188 atime: Option<SystemTimeSpec>,
189 mtime: Option<SystemTimeSpec>,
190) -> io::Result<()> {
191 let times = Timestamps {
192 last_access: to_timespec(atime)?,
193 last_modification: to_timespec(mtime)?,
194 };
195 Ok(futimens(file, ×)?)
196}
197
198#[cfg(not(windows))]
199#[allow(clippy::useless_conversion)]
200pub(crate) fn to_timespec(ft: Option<SystemTimeSpec>) -> io::Result<Timespec> {
201 Ok(match ft {
202 None => Timespec {
203 tv_sec: 0,
204 tv_nsec: UTIME_OMIT.into(),
205 },
206 Some(SystemTimeSpec::SymbolicNow) => Timespec {
207 tv_sec: 0,
208 tv_nsec: UTIME_NOW.into(),
209 },
210 Some(SystemTimeSpec::Absolute(ft)) => {
211 let duration = ft.duration_since(SystemTime::UNIX_EPOCH).unwrap();
212 let nanoseconds = duration.subsec_nanos();
213 assert_ne!(i64::from(nanoseconds), i64::from(UTIME_OMIT));
214 assert_ne!(i64::from(nanoseconds), i64::from(UTIME_NOW));
215 Timespec {
216 tv_sec: duration
217 .as_secs()
218 .try_into()
219 .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?,
220 tv_nsec: nanoseconds.try_into().unwrap(),
221 }
222 }
223 })
224}
225
226#[cfg(windows)]
227fn _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)]
273fn to_filetime(ft: SystemTime) -> io::Result<FILETIME> {
274 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 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}