Coverage Report

Created: 2025-07-18 06:03

/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, &times, 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, &times, 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, &times)?)
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
}