Skip to content

Commit 521f4da

Browse files
committed
Auto merge of #109773 - beetrees:set-file-time-improvements, r=Amanieu
Add creation time support to `FileTimes` on apple and windows Adds support for setting file creation times on platforms which support changing it directly (currently only Apple and Windows). Based on top of #110093 (which was split from this PR). ACP: rust-lang/libs-team#199 (currently still in progress) Tracking issue: #98245 `@rustbot` label +T-libs-api -T-libs
2 parents 8a281f9 + 246dcbc commit 521f4da

File tree

8 files changed

+196
-28
lines changed

8 files changed

+196
-28
lines changed

library/std/src/fs.rs

+11
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::ffi::OsString;
1515
use crate::fmt;
1616
use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
1717
use crate::path::{Path, PathBuf};
18+
use crate::sealed::Sealed;
1819
use crate::sys::fs as fs_imp;
1920
use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
2021
use crate::time::SystemTime;
@@ -1391,6 +1392,16 @@ impl FileTimes {
13911392
}
13921393
}
13931394

1395+
impl AsInnerMut<fs_imp::FileTimes> for FileTimes {
1396+
fn as_inner_mut(&mut self) -> &mut fs_imp::FileTimes {
1397+
&mut self.0
1398+
}
1399+
}
1400+
1401+
// For implementing OS extension traits in `std::os`
1402+
#[unstable(feature = "file_set_times", issue = "98245")]
1403+
impl Sealed for FileTimes {}
1404+
13941405
impl Permissions {
13951406
/// Returns `true` if these permissions describe a readonly (unwritable) file.
13961407
///

library/std/src/fs/tests.rs

+52-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
use crate::io::prelude::*;
22

33
use crate::env;
4-
use crate::fs::{self, File, OpenOptions};
4+
use crate::fs::{self, File, FileTimes, OpenOptions};
55
use crate::io::{BorrowedBuf, ErrorKind, SeekFrom};
66
use crate::mem::MaybeUninit;
77
use crate::path::Path;
88
use crate::str;
99
use crate::sync::Arc;
1010
use crate::sys_common::io::test::{tmpdir, TempDir};
1111
use crate::thread;
12-
use crate::time::{Duration, Instant};
12+
use crate::time::{Duration, Instant, SystemTime};
1313

1414
use rand::RngCore;
1515

@@ -1633,3 +1633,53 @@ fn rename_directory() {
16331633
assert!(new_path.join("newdir").is_dir());
16341634
assert!(new_path.join("newdir/temp.txt").exists());
16351635
}
1636+
1637+
#[test]
1638+
fn test_file_times() {
1639+
#[cfg(target_os = "ios")]
1640+
use crate::os::ios::fs::FileTimesExt;
1641+
#[cfg(target_os = "macos")]
1642+
use crate::os::macos::fs::FileTimesExt;
1643+
#[cfg(target_os = "watchos")]
1644+
use crate::os::watchos::fs::FileTimesExt;
1645+
#[cfg(windows)]
1646+
use crate::os::windows::fs::FileTimesExt;
1647+
1648+
let tmp = tmpdir();
1649+
let file = File::create(tmp.join("foo")).unwrap();
1650+
let mut times = FileTimes::new();
1651+
let accessed = SystemTime::UNIX_EPOCH + Duration::from_secs(12345);
1652+
let modified = SystemTime::UNIX_EPOCH + Duration::from_secs(54321);
1653+
times = times.set_accessed(accessed).set_modified(modified);
1654+
#[cfg(any(windows, target_os = "macos", target_os = "ios", target_os = "watchos"))]
1655+
let created = SystemTime::UNIX_EPOCH + Duration::from_secs(32123);
1656+
#[cfg(any(windows, target_os = "macos", target_os = "ios", target_os = "watchos"))]
1657+
{
1658+
times = times.set_created(created);
1659+
}
1660+
match file.set_times(times) {
1661+
// Allow unsupported errors on platforms which don't support setting times.
1662+
#[cfg(not(any(
1663+
windows,
1664+
all(
1665+
unix,
1666+
not(any(
1667+
target_os = "android",
1668+
target_os = "redox",
1669+
target_os = "espidf",
1670+
target_os = "horizon"
1671+
))
1672+
)
1673+
)))]
1674+
Err(e) if e.kind() == ErrorKind::Unsupported => return,
1675+
Err(e) => panic!("error setting file times: {e:?}"),
1676+
Ok(_) => {}
1677+
}
1678+
let metadata = file.metadata().unwrap();
1679+
assert_eq!(metadata.accessed().unwrap(), accessed);
1680+
assert_eq!(metadata.modified().unwrap(), modified);
1681+
#[cfg(any(windows, target_os = "macos", target_os = "ios", target_os = "watchos"))]
1682+
{
1683+
assert_eq!(metadata.created().unwrap(), created);
1684+
}
1685+
}

library/std/src/os/ios/fs.rs

+20-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#![stable(feature = "metadata_ext", since = "1.1.0")]
22

3-
use crate::fs::Metadata;
4-
use crate::sys_common::AsInner;
3+
use crate::fs::{self, Metadata};
4+
use crate::sealed::Sealed;
5+
use crate::sys_common::{AsInner, AsInnerMut, IntoInner};
6+
use crate::time::SystemTime;
57

68
#[allow(deprecated)]
79
use crate::os::ios::raw;
@@ -140,3 +142,19 @@ impl MetadataExt for Metadata {
140142
self.as_inner().as_inner().st_lspare as u32
141143
}
142144
}
145+
146+
/// OS-specific extensions to [`fs::FileTimes`].
147+
#[unstable(feature = "file_set_times", issue = "98245")]
148+
pub trait FileTimesExt: Sealed {
149+
/// Set the creation time of a file.
150+
#[unstable(feature = "file_set_times", issue = "98245")]
151+
fn set_created(self, t: SystemTime) -> Self;
152+
}
153+
154+
#[unstable(feature = "file_set_times", issue = "98245")]
155+
impl FileTimesExt for fs::FileTimes {
156+
fn set_created(mut self, t: SystemTime) -> Self {
157+
self.as_inner_mut().set_created(t.into_inner());
158+
self
159+
}
160+
}

library/std/src/os/macos/fs.rs

+20-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#![stable(feature = "metadata_ext", since = "1.1.0")]
22

3-
use crate::fs::Metadata;
4-
use crate::sys_common::AsInner;
3+
use crate::fs::{self, Metadata};
4+
use crate::sealed::Sealed;
5+
use crate::sys_common::{AsInner, AsInnerMut, IntoInner};
6+
use crate::time::SystemTime;
57

68
#[allow(deprecated)]
79
use crate::os::macos::raw;
@@ -146,3 +148,19 @@ impl MetadataExt for Metadata {
146148
[qspare[0] as u64, qspare[1] as u64]
147149
}
148150
}
151+
152+
/// OS-specific extensions to [`fs::FileTimes`].
153+
#[unstable(feature = "file_set_times", issue = "98245")]
154+
pub trait FileTimesExt: Sealed {
155+
/// Set the creation time of a file.
156+
#[unstable(feature = "file_set_times", issue = "98245")]
157+
fn set_created(self, t: SystemTime) -> Self;
158+
}
159+
160+
#[unstable(feature = "file_set_times", issue = "98245")]
161+
impl FileTimesExt for fs::FileTimes {
162+
fn set_created(mut self, t: SystemTime) -> Self {
163+
self.as_inner_mut().set_created(t.into_inner());
164+
self
165+
}
166+
}

library/std/src/os/watchos/fs.rs

+20-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#![stable(feature = "metadata_ext", since = "1.1.0")]
22

3-
use crate::fs::Metadata;
4-
use crate::sys_common::AsInner;
3+
use crate::fs::{self, Metadata};
4+
use crate::sealed::Sealed;
5+
use crate::sys_common::{AsInner, AsInnerMut, IntoInner};
6+
use crate::time::SystemTime;
57

68
#[allow(deprecated)]
79
use crate::os::watchos::raw;
@@ -140,3 +142,19 @@ impl MetadataExt for Metadata {
140142
self.as_inner().as_inner().st_lspare as u32
141143
}
142144
}
145+
146+
/// OS-specific extensions to [`fs::FileTimes`].
147+
#[unstable(feature = "file_set_times", issue = "98245")]
148+
pub trait FileTimesExt: Sealed {
149+
/// Set the creation time of a file.
150+
#[unstable(feature = "file_set_times", issue = "98245")]
151+
fn set_created(self, t: SystemTime) -> Self;
152+
}
153+
154+
#[unstable(feature = "file_set_times", issue = "98245")]
155+
impl FileTimesExt for fs::FileTimes {
156+
fn set_created(mut self, t: SystemTime) -> Self {
157+
self.as_inner_mut().set_created(t.into_inner());
158+
self
159+
}
160+
}

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

+18-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ use crate::io;
99
use crate::path::Path;
1010
use crate::sealed::Sealed;
1111
use crate::sys;
12-
use crate::sys_common::{AsInner, AsInnerMut};
12+
use crate::sys_common::{AsInner, AsInnerMut, IntoInner};
13+
use crate::time::SystemTime;
1314

1415
/// Windows-specific extensions to [`fs::File`].
1516
#[stable(feature = "file_offset", since = "1.15.0")]
@@ -526,6 +527,22 @@ impl FileTypeExt for fs::FileType {
526527
}
527528
}
528529

530+
/// Windows-specific extensions to [`fs::FileTimes`].
531+
#[unstable(feature = "file_set_times", issue = "98245")]
532+
pub trait FileTimesExt: Sealed {
533+
/// Set the creation time of a file.
534+
#[unstable(feature = "file_set_times", issue = "98245")]
535+
fn set_created(self, t: SystemTime) -> Self;
536+
}
537+
538+
#[unstable(feature = "file_set_times", issue = "98245")]
539+
impl FileTimesExt for fs::FileTimes {
540+
fn set_created(mut self, t: SystemTime) -> Self {
541+
self.as_inner_mut().set_created(t.into_inner());
542+
self
543+
}
544+
}
545+
529546
/// Creates a new symlink to a non-directory file on the filesystem.
530547
///
531548
/// The `link` path will be a file symbolic link pointing to the `original`

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

+37-15
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,8 @@ pub struct FilePermissions {
349349
pub struct FileTimes {
350350
accessed: Option<SystemTime>,
351351
modified: Option<SystemTime>,
352+
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))]
353+
created: Option<SystemTime>,
352354
}
353355

354356
#[derive(Copy, Clone, Eq, Debug)]
@@ -591,6 +593,11 @@ impl FileTimes {
591593
pub fn set_modified(&mut self, t: SystemTime) {
592594
self.modified = Some(t);
593595
}
596+
597+
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))]
598+
pub fn set_created(&mut self, t: SystemTime) {
599+
self.created = Some(t);
600+
}
594601
}
595602

596603
impl FileType {
@@ -1215,26 +1222,41 @@ impl File {
12151222
io::ErrorKind::Unsupported,
12161223
"setting file times not supported",
12171224
))
1218-
} else if #[cfg(any(target_os = "android", target_os = "macos"))] {
1225+
} else if #[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))] {
1226+
let mut buf = [mem::MaybeUninit::<libc::timespec>::uninit(); 3];
1227+
let mut num_times = 0;
1228+
let mut attrlist: libc::attrlist = unsafe { mem::zeroed() };
1229+
attrlist.bitmapcount = libc::ATTR_BIT_MAP_COUNT;
1230+
if times.created.is_some() {
1231+
buf[num_times].write(to_timespec(times.created)?);
1232+
num_times += 1;
1233+
attrlist.commonattr |= libc::ATTR_CMN_CRTIME;
1234+
}
1235+
if times.modified.is_some() {
1236+
buf[num_times].write(to_timespec(times.modified)?);
1237+
num_times += 1;
1238+
attrlist.commonattr |= libc::ATTR_CMN_MODTIME;
1239+
}
1240+
if times.accessed.is_some() {
1241+
buf[num_times].write(to_timespec(times.accessed)?);
1242+
num_times += 1;
1243+
attrlist.commonattr |= libc::ATTR_CMN_ACCTIME;
1244+
}
1245+
cvt(unsafe { libc::fsetattrlist(
1246+
self.as_raw_fd(),
1247+
(&attrlist as *const libc::attrlist).cast::<libc::c_void>().cast_mut(),
1248+
buf.as_ptr().cast::<libc::c_void>().cast_mut(),
1249+
num_times * mem::size_of::<libc::timespec>(),
1250+
0
1251+
) })?;
1252+
Ok(())
1253+
} else if #[cfg(target_os = "android")] {
12191254
let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?];
1220-
// futimens requires macOS 10.13, and Android API level 19
1255+
// futimens requires Android API level 19
12211256
cvt(unsafe {
12221257
weak!(fn futimens(c_int, *const libc::timespec) -> c_int);
12231258
match futimens.get() {
12241259
Some(futimens) => futimens(self.as_raw_fd(), times.as_ptr()),
1225-
#[cfg(target_os = "macos")]
1226-
None => {
1227-
fn ts_to_tv(ts: &libc::timespec) -> libc::timeval {
1228-
libc::timeval {
1229-
tv_sec: ts.tv_sec,
1230-
tv_usec: (ts.tv_nsec / 1000) as _
1231-
}
1232-
}
1233-
let timevals = [ts_to_tv(&times[0]), ts_to_tv(&times[1])];
1234-
libc::futimes(self.as_raw_fd(), timevals.as_ptr())
1235-
}
1236-
// futimes requires even newer Android.
1237-
#[cfg(target_os = "android")]
12381260
None => return Err(io::const_io_error!(
12391261
io::ErrorKind::Unsupported,
12401262
"setting file times requires Android API level >= 19",

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

+18-4
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,10 @@ pub struct FilePermissions {
8888
pub struct FileTimes {
8989
accessed: Option<c::FILETIME>,
9090
modified: Option<c::FILETIME>,
91+
created: Option<c::FILETIME>,
9192
}
92-
impl core::fmt::Debug for c::FILETIME {
93+
94+
impl fmt::Debug for c::FILETIME {
9395
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
9496
let time = ((self.dwHighDateTime as u64) << 32) | self.dwLowDateTime as u64;
9597
f.debug_tuple("FILETIME").field(&time).finish()
@@ -582,26 +584,34 @@ impl File {
582584

583585
pub fn set_times(&self, times: FileTimes) -> io::Result<()> {
584586
let is_zero = |t: c::FILETIME| t.dwLowDateTime == 0 && t.dwHighDateTime == 0;
585-
if times.accessed.map_or(false, is_zero) || times.modified.map_or(false, is_zero) {
587+
if times.accessed.map_or(false, is_zero)
588+
|| times.modified.map_or(false, is_zero)
589+
|| times.created.map_or(false, is_zero)
590+
{
586591
return Err(io::const_io_error!(
587592
io::ErrorKind::InvalidInput,
588593
"Cannot set file timestamp to 0",
589594
));
590595
}
591596
let is_max =
592597
|t: c::FILETIME| t.dwLowDateTime == c::DWORD::MAX && t.dwHighDateTime == c::DWORD::MAX;
593-
if times.accessed.map_or(false, is_max) || times.modified.map_or(false, is_max) {
598+
if times.accessed.map_or(false, is_max)
599+
|| times.modified.map_or(false, is_max)
600+
|| times.created.map_or(false, is_max)
601+
{
594602
return Err(io::const_io_error!(
595603
io::ErrorKind::InvalidInput,
596604
"Cannot set file timestamp to 0xFFFF_FFFF_FFFF_FFFF",
597605
));
598606
}
599607
cvt(unsafe {
608+
let created =
609+
times.created.as_ref().map(|a| a as *const c::FILETIME).unwrap_or(ptr::null());
600610
let accessed =
601611
times.accessed.as_ref().map(|a| a as *const c::FILETIME).unwrap_or(ptr::null());
602612
let modified =
603613
times.modified.as_ref().map(|a| a as *const c::FILETIME).unwrap_or(ptr::null());
604-
c::SetFileTime(self.as_raw_handle(), ptr::null_mut(), accessed, modified)
614+
c::SetFileTime(self.as_raw_handle(), created, accessed, modified)
605615
})?;
606616
Ok(())
607617
}
@@ -1005,6 +1015,10 @@ impl FileTimes {
10051015
pub fn set_modified(&mut self, t: SystemTime) {
10061016
self.modified = Some(t.into_inner());
10071017
}
1018+
1019+
pub fn set_created(&mut self, t: SystemTime) {
1020+
self.created = Some(t.into_inner());
1021+
}
10081022
}
10091023

10101024
impl FileType {

0 commit comments

Comments
 (0)