Skip to content

Commit 223d7f8

Browse files
authored
Rollup merge of rust-lang#98246 - joshtriplett:times, r=m-ou-se
Support setting file accessed/modified timestamps Add `struct FileTimes` to contain the relevant file timestamps, since most platforms require setting all of them at once. (This also allows for future platform-specific extensions such as setting creation time.) Add `File::set_file_time` to set the timestamps for a `File`. Implement the `sys` backends for UNIX, macOS (which needs to fall back to `futimes` before macOS 10.13 because it lacks `futimens`), Windows, and WASI.
2 parents b5a2a80 + 585767d commit 223d7f8

File tree

9 files changed

+231
-2
lines changed

9 files changed

+231
-2
lines changed

library/std/src/fs.rs

+81
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,11 @@ pub struct DirEntry(fs_imp::DirEntry);
184184
#[stable(feature = "rust1", since = "1.0.0")]
185185
pub struct OpenOptions(fs_imp::OpenOptions);
186186

187+
/// Representation of the various timestamps on a file.
188+
#[derive(Copy, Clone, Debug, Default)]
189+
#[unstable(feature = "file_set_times", issue = "98245")]
190+
pub struct FileTimes(fs_imp::FileTimes);
191+
187192
/// Representation of the various permissions on a file.
188193
///
189194
/// This module only currently provides one bit of information,
@@ -590,6 +595,58 @@ impl File {
590595
pub fn set_permissions(&self, perm: Permissions) -> io::Result<()> {
591596
self.inner.set_permissions(perm.0)
592597
}
598+
599+
/// Changes the timestamps of the underlying file.
600+
///
601+
/// # Platform-specific behavior
602+
///
603+
/// This function currently corresponds to the `futimens` function on Unix (falling back to
604+
/// `futimes` on macOS before 10.13) and the `SetFileTime` function on Windows. Note that this
605+
/// [may change in the future][changes].
606+
///
607+
/// [changes]: io#platform-specific-behavior
608+
///
609+
/// # Errors
610+
///
611+
/// This function will return an error if the user lacks permission to change timestamps on the
612+
/// underlying file. It may also return an error in other os-specific unspecified cases.
613+
///
614+
/// This function may return an error if the operating system lacks support to change one or
615+
/// more of the timestamps set in the `FileTimes` structure.
616+
///
617+
/// # Examples
618+
///
619+
/// ```no_run
620+
/// #![feature(file_set_times)]
621+
///
622+
/// fn main() -> std::io::Result<()> {
623+
/// use std::fs::{self, File, FileTimes};
624+
///
625+
/// let src = fs::metadata("src")?;
626+
/// let dest = File::options().write(true).open("dest")?;
627+
/// let times = FileTimes::new()
628+
/// .set_accessed(src.accessed()?)
629+
/// .set_modified(src.modified()?);
630+
/// dest.set_times(times)?;
631+
/// Ok(())
632+
/// }
633+
/// ```
634+
#[unstable(feature = "file_set_times", issue = "98245")]
635+
#[doc(alias = "futimens")]
636+
#[doc(alias = "futimes")]
637+
#[doc(alias = "SetFileTime")]
638+
pub fn set_times(&self, times: FileTimes) -> io::Result<()> {
639+
self.inner.set_times(times.0)
640+
}
641+
642+
/// Changes the modification time of the underlying file.
643+
///
644+
/// This is an alias for `set_times(FileTimes::new().set_modified(time))`.
645+
#[unstable(feature = "file_set_times", issue = "98245")]
646+
#[inline]
647+
pub fn set_modified(&self, time: SystemTime) -> io::Result<()> {
648+
self.set_times(FileTimes::new().set_modified(time))
649+
}
593650
}
594651

595652
// In addition to the `impl`s here, `File` also has `impl`s for
@@ -1246,6 +1303,30 @@ impl FromInner<fs_imp::FileAttr> for Metadata {
12461303
}
12471304
}
12481305

1306+
impl FileTimes {
1307+
/// Create a new `FileTimes` with no times set.
1308+
///
1309+
/// Using the resulting `FileTimes` in [`File::set_times`] will not modify any timestamps.
1310+
#[unstable(feature = "file_set_times", issue = "98245")]
1311+
pub fn new() -> Self {
1312+
Self::default()
1313+
}
1314+
1315+
/// Set the last access time of a file.
1316+
#[unstable(feature = "file_set_times", issue = "98245")]
1317+
pub fn set_accessed(mut self, t: SystemTime) -> Self {
1318+
self.0.set_accessed(t.into_inner());
1319+
self
1320+
}
1321+
1322+
/// Set the last modified time of a file.
1323+
#[unstable(feature = "file_set_times", issue = "98245")]
1324+
pub fn set_modified(mut self, t: SystemTime) -> Self {
1325+
self.0.set_modified(t.into_inner());
1326+
self
1327+
}
1328+
}
1329+
12491330
impl Permissions {
12501331
/// Returns `true` if these permissions describe a readonly (unwritable) file.
12511332
///

library/std/src/sys/unix/fs.rs

+64
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,9 @@ pub struct FilePermissions {
311311
mode: mode_t,
312312
}
313313

314+
#[derive(Copy, Clone)]
315+
pub struct FileTimes([libc::timespec; 2]);
316+
314317
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
315318
pub struct FileType {
316319
mode: mode_t,
@@ -503,6 +506,43 @@ impl FilePermissions {
503506
}
504507
}
505508

509+
impl FileTimes {
510+
pub fn set_accessed(&mut self, t: SystemTime) {
511+
self.0[0] = t.t.to_timespec().expect("Invalid system time");
512+
}
513+
514+
pub fn set_modified(&mut self, t: SystemTime) {
515+
self.0[1] = t.t.to_timespec().expect("Invalid system time");
516+
}
517+
}
518+
519+
struct TimespecDebugAdapter<'a>(&'a libc::timespec);
520+
521+
impl fmt::Debug for TimespecDebugAdapter<'_> {
522+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
523+
f.debug_struct("timespec")
524+
.field("tv_sec", &self.0.tv_sec)
525+
.field("tv_nsec", &self.0.tv_nsec)
526+
.finish()
527+
}
528+
}
529+
530+
impl fmt::Debug for FileTimes {
531+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
532+
f.debug_struct("FileTimes")
533+
.field("accessed", &TimespecDebugAdapter(&self.0[0]))
534+
.field("modified", &TimespecDebugAdapter(&self.0[1]))
535+
.finish()
536+
}
537+
}
538+
539+
impl Default for FileTimes {
540+
fn default() -> Self {
541+
let omit = libc::timespec { tv_sec: 0, tv_nsec: libc::UTIME_OMIT };
542+
Self([omit; 2])
543+
}
544+
}
545+
506546
impl FileType {
507547
pub fn is_dir(&self) -> bool {
508548
self.is(libc::S_IFDIR)
@@ -1021,6 +1061,30 @@ impl File {
10211061
cvt_r(|| unsafe { libc::fchmod(self.as_raw_fd(), perm.mode) })?;
10221062
Ok(())
10231063
}
1064+
1065+
pub fn set_times(&self, times: FileTimes) -> io::Result<()> {
1066+
cfg_if::cfg_if! {
1067+
// futimens requires macOS 10.13
1068+
if #[cfg(target_os = "macos")] {
1069+
fn ts_to_tv(ts: &libc::timespec) -> libc::timeval {
1070+
libc::timeval { tv_sec: ts.tv_sec, tv_usec: ts.tv_nsec / 1000 }
1071+
}
1072+
cvt(unsafe {
1073+
let futimens = weak!(fn futimens(c_int, *const libc::timespec) -> c_int);
1074+
futimens.get()
1075+
.map(|futimens| futimens(self.as_raw_fd(), times.0.as_ptr()))
1076+
.unwrap_or_else(|| {
1077+
let timevals = [ts_to_tv(times.0[0]), ts_to_tv(times.0[1])];
1078+
libc::futimes(self.as_raw_fd(), timevals.as_ptr())
1079+
})
1080+
})?;
1081+
} else {
1082+
cvt(unsafe { libc::futimens(self.as_raw_fd(), times.0.as_ptr()) })?;
1083+
}
1084+
}
1085+
1086+
Ok(())
1087+
}
10241088
}
10251089

10261090
impl DirBuilder {

library/std/src/sys/unsupported/fs.rs

+12
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ pub struct DirEntry(!);
1717
#[derive(Clone, Debug)]
1818
pub struct OpenOptions {}
1919

20+
#[derive(Copy, Clone, Debug, Default)]
21+
pub struct FileTimes {}
22+
2023
pub struct FilePermissions(!);
2124

2225
pub struct FileType(!);
@@ -86,6 +89,11 @@ impl fmt::Debug for FilePermissions {
8689
}
8790
}
8891

92+
impl FileTimes {
93+
pub fn set_accessed(&mut self, _t: SystemTime) {}
94+
pub fn set_modified(&mut self, _t: SystemTime) {}
95+
}
96+
8997
impl FileType {
9098
pub fn is_dir(&self) -> bool {
9199
self.0
@@ -237,6 +245,10 @@ impl File {
237245
pub fn set_permissions(&self, _perm: FilePermissions) -> io::Result<()> {
238246
self.0
239247
}
248+
249+
pub fn set_times(&self, _times: FileTimes) -> io::Result<()> {
250+
self.0
251+
}
240252
}
241253

242254
impl DirBuilder {

library/std/src/sys/wasi/fs.rs

+25
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ pub struct FilePermissions {
6363
readonly: bool,
6464
}
6565

66+
#[derive(Copy, Clone, Debug, Default)]
67+
pub struct FileTimes {
68+
accessed: Option<wasi::Timestamp>,
69+
modified: Option<wasi::Timestamp>,
70+
}
71+
6672
#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
6773
pub struct FileType {
6874
bits: wasi::Filetype,
@@ -112,6 +118,16 @@ impl FilePermissions {
112118
}
113119
}
114120

121+
impl FileTimes {
122+
pub fn set_accessed(&mut self, t: SystemTime) {
123+
self.accessed = t.to_wasi_timestamp_or_panic();
124+
}
125+
126+
pub fn set_modified(&mut self, t: SystemTime) {
127+
self.modified = t.to_wasi_timestamp_or_panic();
128+
}
129+
}
130+
115131
impl FileType {
116132
pub fn is_dir(&self) -> bool {
117133
self.bits == wasi::FILETYPE_DIRECTORY
@@ -459,6 +475,15 @@ impl File {
459475
unsupported()
460476
}
461477

478+
pub fn set_times(&self, times: FileTimes) -> io::Result<()> {
479+
self.fd.filestat_set_times(
480+
times.accessed.unwrap_or(0),
481+
times.modified.unwrap_or(0),
482+
times.accessed.map_or(0, || wasi::FSTFLAGS_ATIM)
483+
| times.modified.map_or(0, || wasi::FSTFLAGS_MTIM),
484+
)
485+
}
486+
462487
pub fn read_link(&self, file: &Path) -> io::Result<PathBuf> {
463488
read_link(&self.fd, file)
464489
}

library/std/src/sys/wasi/time.rs

+4
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ impl SystemTime {
4747
SystemTime(Duration::from_nanos(ts))
4848
}
4949

50+
pub fn to_wasi_timestamp_or_panic(&self) -> wasi::Timestamp {
51+
self.0.as_nanos().try_into().expect("time does not fit in WASI timestamp")
52+
}
53+
5054
pub fn sub_time(&self, other: &SystemTime) -> Result<Duration, Duration> {
5155
self.0.checked_sub(other.0).ok_or_else(|| other.0 - self.0)
5256
}

library/std/src/sys/windows/c.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,7 @@ pub struct SOCKADDR {
607607
}
608608

609609
#[repr(C)]
610-
#[derive(Copy, Clone)]
610+
#[derive(Copy, Clone, Debug, Default)]
611611
pub struct FILETIME {
612612
pub dwLowDateTime: DWORD,
613613
pub dwHighDateTime: DWORD,
@@ -875,6 +875,12 @@ extern "system" {
875875
pub fn GetSystemDirectoryW(lpBuffer: LPWSTR, uSize: UINT) -> UINT;
876876
pub fn RemoveDirectoryW(lpPathName: LPCWSTR) -> BOOL;
877877
pub fn SetFileAttributesW(lpFileName: LPCWSTR, dwFileAttributes: DWORD) -> BOOL;
878+
pub fn SetFileTime(
879+
hFile: BorrowedHandle<'_>,
880+
lpCreationTime: Option<&FILETIME>,
881+
lpLastAccessTime: Option<&FILETIME>,
882+
lpLastWriteTime: Option<&FILETIME>,
883+
) -> BOOL;
878884
pub fn SetLastError(dwErrCode: DWORD);
879885
pub fn GetCommandLineW() -> LPWSTR;
880886
pub fn GetTempPathW(nBufferLength: DWORD, lpBuffer: LPCWSTR) -> DWORD;

library/std/src/sys/windows/fs.rs

+24
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ pub struct FilePermissions {
8282
attrs: c::DWORD,
8383
}
8484

85+
#[derive(Copy, Clone, Debug, Default)]
86+
pub struct FileTimes {
87+
accessed: c::FILETIME,
88+
modified: c::FILETIME,
89+
}
90+
8591
#[derive(Debug)]
8692
pub struct DirBuilder;
8793

@@ -550,6 +556,14 @@ impl File {
550556
})?;
551557
Ok(())
552558
}
559+
560+
pub fn set_times(&self, times: FileTimes) -> io::Result<()> {
561+
cvt(unsafe {
562+
c::SetFileTime(self.as_handle(), None, Some(&times.accessed), Some(&times.modified))
563+
})?;
564+
Ok(())
565+
}
566+
553567
/// Get only basic file information such as attributes and file times.
554568
fn basic_info(&self) -> io::Result<c::FILE_BASIC_INFO> {
555569
unsafe {
@@ -895,6 +909,16 @@ impl FilePermissions {
895909
}
896910
}
897911

912+
impl FileTimes {
913+
pub fn set_accessed(&mut self, t: SystemTime) {
914+
self.accessed = t.into_inner();
915+
}
916+
917+
pub fn set_modified(&mut self, t: SystemTime) {
918+
self.modified = t.into_inner();
919+
}
920+
}
921+
898922
impl FileType {
899923
fn new(attrs: c::DWORD, reparse_tag: c::DWORD) -> FileType {
900924
FileType { attributes: attrs, reparse_tag }

library/std/src/sys/windows/time.rs

+7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::cmp::Ordering;
22
use crate::fmt;
33
use crate::mem;
44
use crate::sys::c;
5+
use crate::sys_common::IntoInner;
56
use crate::time::Duration;
67

78
use core::hash::{Hash, Hasher};
@@ -136,6 +137,12 @@ impl From<c::FILETIME> for SystemTime {
136137
}
137138
}
138139

140+
impl IntoInner<c::FILETIME> for SystemTime {
141+
fn into_inner(self) -> c::FILETIME {
142+
self.t
143+
}
144+
}
145+
139146
impl Hash for SystemTime {
140147
fn hash<H: Hasher>(&self, state: &mut H) {
141148
self.intervals().hash(state)

library/std/src/time.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use crate::error::Error;
3838
use crate::fmt;
3939
use crate::ops::{Add, AddAssign, Sub, SubAssign};
4040
use crate::sys::time;
41-
use crate::sys_common::FromInner;
41+
use crate::sys_common::{FromInner, IntoInner};
4242

4343
#[stable(feature = "time", since = "1.3.0")]
4444
pub use core::time::Duration;
@@ -686,3 +686,9 @@ impl FromInner<time::SystemTime> for SystemTime {
686686
SystemTime(time)
687687
}
688688
}
689+
690+
impl IntoInner<time::SystemTime> for SystemTime {
691+
fn into_inner(self) -> time::SystemTime {
692+
self.0
693+
}
694+
}

0 commit comments

Comments
 (0)