fs_set_times/
set_times.rs

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/// Set the last access timestamp of a file or other filesystem object.
26#[inline]
27pub fn set_atime<P: AsRef<Path>>(path: P, atime: SystemTimeSpec) -> io::Result<()> {
28    set_times(path, Some(atime), None)
29}
30
31/// Set the last modification timestamp of a file or other filesystem object.
32#[inline]
33pub fn set_mtime<P: AsRef<Path>>(path: P, mtime: SystemTimeSpec) -> io::Result<()> {
34    set_times(path, None, Some(mtime))
35}
36
37/// Set the last access and last modification timestamps of a file or other
38/// filesystem object.
39#[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, &times, 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/// Like `set_times`, but never follows symlinks.
98#[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/// Like `set_times`, but never follows symlinks.
109#[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, &times, AtFlags::SYMLINK_NOFOLLOW)?)
120}
121
122/// Like `set_times`, but never follows symlinks.
123#[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
158/// An extension trait for `std::fs::File`, `cap_std::fs::File`, and similar
159/// types.
160pub 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
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, &times)?)
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    // 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}