Skip to content

Add creation time support to FileTimes on apple and windows #109773

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions library/std/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::ffi::OsString;
use crate::fmt;
use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
use crate::path::{Path, PathBuf};
use crate::sealed::Sealed;
use crate::sys::fs as fs_imp;
use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
use crate::time::SystemTime;
Expand Down Expand Up @@ -1391,6 +1392,16 @@ impl FileTimes {
}
}

impl AsInnerMut<fs_imp::FileTimes> for FileTimes {
fn as_inner_mut(&mut self) -> &mut fs_imp::FileTimes {
&mut self.0
}
}

// For implementing OS extension traits in `std::os`
#[unstable(feature = "file_set_times", issue = "98245")]
impl Sealed for FileTimes {}

impl Permissions {
/// Returns `true` if these permissions describe a readonly (unwritable) file.
///
Expand Down
54 changes: 52 additions & 2 deletions library/std/src/fs/tests.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use crate::io::prelude::*;

use crate::env;
use crate::fs::{self, File, OpenOptions};
use crate::fs::{self, File, FileTimes, OpenOptions};
use crate::io::{BorrowedBuf, ErrorKind, SeekFrom};
use crate::mem::MaybeUninit;
use crate::path::Path;
use crate::str;
use crate::sync::Arc;
use crate::sys_common::io::test::{tmpdir, TempDir};
use crate::thread;
use crate::time::{Duration, Instant};
use crate::time::{Duration, Instant, SystemTime};

use rand::RngCore;

Expand Down Expand Up @@ -1633,3 +1633,53 @@ fn rename_directory() {
assert!(new_path.join("newdir").is_dir());
assert!(new_path.join("newdir/temp.txt").exists());
}

#[test]
fn test_file_times() {
#[cfg(target_os = "ios")]
use crate::os::ios::fs::FileTimesExt;
#[cfg(target_os = "macos")]
use crate::os::macos::fs::FileTimesExt;
#[cfg(target_os = "watchos")]
use crate::os::watchos::fs::FileTimesExt;
#[cfg(windows)]
use crate::os::windows::fs::FileTimesExt;

let tmp = tmpdir();
let file = File::create(tmp.join("foo")).unwrap();
let mut times = FileTimes::new();
let accessed = SystemTime::UNIX_EPOCH + Duration::from_secs(12345);
let modified = SystemTime::UNIX_EPOCH + Duration::from_secs(54321);
times = times.set_accessed(accessed).set_modified(modified);
#[cfg(any(windows, target_os = "macos", target_os = "ios", target_os = "watchos"))]
let created = SystemTime::UNIX_EPOCH + Duration::from_secs(32123);
#[cfg(any(windows, target_os = "macos", target_os = "ios", target_os = "watchos"))]
{
times = times.set_created(created);
}
match file.set_times(times) {
// Allow unsupported errors on platforms which don't support setting times.
#[cfg(not(any(
windows,
all(
unix,
not(any(
target_os = "android",
target_os = "redox",
target_os = "espidf",
target_os = "horizon"
))
)
)))]
Err(e) if e.kind() == ErrorKind::Unsupported => return,
Err(e) => panic!("error setting file times: {e:?}"),
Ok(_) => {}
}
let metadata = file.metadata().unwrap();
assert_eq!(metadata.accessed().unwrap(), accessed);
assert_eq!(metadata.modified().unwrap(), modified);
#[cfg(any(windows, target_os = "macos", target_os = "ios", target_os = "watchos"))]
{
assert_eq!(metadata.created().unwrap(), created);
}
}
22 changes: 20 additions & 2 deletions library/std/src/os/ios/fs.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#![stable(feature = "metadata_ext", since = "1.1.0")]

use crate::fs::Metadata;
use crate::sys_common::AsInner;
use crate::fs::{self, Metadata};
use crate::sealed::Sealed;
use crate::sys_common::{AsInner, AsInnerMut, IntoInner};
use crate::time::SystemTime;

#[allow(deprecated)]
use crate::os::ios::raw;
Expand Down Expand Up @@ -140,3 +142,19 @@ impl MetadataExt for Metadata {
self.as_inner().as_inner().st_lspare as u32
}
}

/// OS-specific extensions to [`fs::FileTimes`].
#[unstable(feature = "file_set_times", issue = "98245")]
pub trait FileTimesExt: Sealed {
/// Set the creation time of a file.
#[unstable(feature = "file_set_times", issue = "98245")]
fn set_created(self, t: SystemTime) -> Self;
}

#[unstable(feature = "file_set_times", issue = "98245")]
impl FileTimesExt for fs::FileTimes {
fn set_created(mut self, t: SystemTime) -> Self {
self.as_inner_mut().set_created(t.into_inner());
self
}
}
22 changes: 20 additions & 2 deletions library/std/src/os/macos/fs.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#![stable(feature = "metadata_ext", since = "1.1.0")]

use crate::fs::Metadata;
use crate::sys_common::AsInner;
use crate::fs::{self, Metadata};
use crate::sealed::Sealed;
use crate::sys_common::{AsInner, AsInnerMut, IntoInner};
use crate::time::SystemTime;

#[allow(deprecated)]
use crate::os::macos::raw;
Expand Down Expand Up @@ -146,3 +148,19 @@ impl MetadataExt for Metadata {
[qspare[0] as u64, qspare[1] as u64]
}
}

/// OS-specific extensions to [`fs::FileTimes`].
#[unstable(feature = "file_set_times", issue = "98245")]
pub trait FileTimesExt: Sealed {
/// Set the creation time of a file.
#[unstable(feature = "file_set_times", issue = "98245")]
fn set_created(self, t: SystemTime) -> Self;
}

#[unstable(feature = "file_set_times", issue = "98245")]
impl FileTimesExt for fs::FileTimes {
fn set_created(mut self, t: SystemTime) -> Self {
self.as_inner_mut().set_created(t.into_inner());
self
}
}
22 changes: 20 additions & 2 deletions library/std/src/os/watchos/fs.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#![stable(feature = "metadata_ext", since = "1.1.0")]

use crate::fs::Metadata;
use crate::sys_common::AsInner;
use crate::fs::{self, Metadata};
use crate::sealed::Sealed;
use crate::sys_common::{AsInner, AsInnerMut, IntoInner};
use crate::time::SystemTime;

#[allow(deprecated)]
use crate::os::watchos::raw;
Expand Down Expand Up @@ -140,3 +142,19 @@ impl MetadataExt for Metadata {
self.as_inner().as_inner().st_lspare as u32
}
}

/// OS-specific extensions to [`fs::FileTimes`].
#[unstable(feature = "file_set_times", issue = "98245")]
pub trait FileTimesExt: Sealed {
/// Set the creation time of a file.
#[unstable(feature = "file_set_times", issue = "98245")]
fn set_created(self, t: SystemTime) -> Self;
}

#[unstable(feature = "file_set_times", issue = "98245")]
impl FileTimesExt for fs::FileTimes {
fn set_created(mut self, t: SystemTime) -> Self {
self.as_inner_mut().set_created(t.into_inner());
self
}
}
19 changes: 18 additions & 1 deletion library/std/src/os/windows/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use crate::io;
use crate::path::Path;
use crate::sealed::Sealed;
use crate::sys;
use crate::sys_common::{AsInner, AsInnerMut};
use crate::sys_common::{AsInner, AsInnerMut, IntoInner};
use crate::time::SystemTime;

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

/// Windows-specific extensions to [`fs::FileTimes`].
#[unstable(feature = "file_set_times", issue = "98245")]
pub trait FileTimesExt: Sealed {
/// Set the creation time of a file.
#[unstable(feature = "file_set_times", issue = "98245")]
fn set_created(self, t: SystemTime) -> Self;
}

#[unstable(feature = "file_set_times", issue = "98245")]
impl FileTimesExt for fs::FileTimes {
fn set_created(mut self, t: SystemTime) -> Self {
self.as_inner_mut().set_created(t.into_inner());
self
}
}

/// Creates a new symlink to a non-directory file on the filesystem.
///
/// The `link` path will be a file symbolic link pointing to the `original`
Expand Down
52 changes: 37 additions & 15 deletions library/std/src/sys/unix/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@ pub struct FilePermissions {
pub struct FileTimes {
accessed: Option<SystemTime>,
modified: Option<SystemTime>,
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))]
created: Option<SystemTime>,
}

#[derive(Copy, Clone, Eq, Debug)]
Expand Down Expand Up @@ -591,6 +593,11 @@ impl FileTimes {
pub fn set_modified(&mut self, t: SystemTime) {
self.modified = Some(t);
}

#[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))]
pub fn set_created(&mut self, t: SystemTime) {
self.created = Some(t);
}
}

impl FileType {
Expand Down Expand Up @@ -1215,26 +1222,41 @@ impl File {
io::ErrorKind::Unsupported,
"setting file times not supported",
))
} else if #[cfg(any(target_os = "android", target_os = "macos"))] {
} else if #[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))] {
let mut buf = [mem::MaybeUninit::<libc::timespec>::uninit(); 3];
let mut num_times = 0;
let mut attrlist: libc::attrlist = unsafe { mem::zeroed() };
attrlist.bitmapcount = libc::ATTR_BIT_MAP_COUNT;
if times.created.is_some() {
buf[num_times].write(to_timespec(times.created)?);
num_times += 1;
attrlist.commonattr |= libc::ATTR_CMN_CRTIME;
}
if times.modified.is_some() {
buf[num_times].write(to_timespec(times.modified)?);
num_times += 1;
attrlist.commonattr |= libc::ATTR_CMN_MODTIME;
}
if times.accessed.is_some() {
buf[num_times].write(to_timespec(times.accessed)?);
num_times += 1;
attrlist.commonattr |= libc::ATTR_CMN_ACCTIME;
}
cvt(unsafe { libc::fsetattrlist(
self.as_raw_fd(),
(&attrlist as *const libc::attrlist).cast::<libc::c_void>().cast_mut(),
buf.as_ptr().cast::<libc::c_void>().cast_mut(),
num_times * mem::size_of::<libc::timespec>(),
0
) })?;
Ok(())
} else if #[cfg(target_os = "android")] {
let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?];
// futimens requires macOS 10.13, and Android API level 19
// futimens requires Android API level 19
cvt(unsafe {
weak!(fn futimens(c_int, *const libc::timespec) -> c_int);
match futimens.get() {
Some(futimens) => futimens(self.as_raw_fd(), times.as_ptr()),
#[cfg(target_os = "macos")]
None => {
fn ts_to_tv(ts: &libc::timespec) -> libc::timeval {
libc::timeval {
tv_sec: ts.tv_sec,
tv_usec: (ts.tv_nsec / 1000) as _
}
}
let timevals = [ts_to_tv(&times[0]), ts_to_tv(&times[1])];
libc::futimes(self.as_raw_fd(), timevals.as_ptr())
}
// futimes requires even newer Android.
#[cfg(target_os = "android")]
None => return Err(io::const_io_error!(
io::ErrorKind::Unsupported,
"setting file times requires Android API level >= 19",
Expand Down
22 changes: 18 additions & 4 deletions library/std/src/sys/windows/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,10 @@ pub struct FilePermissions {
pub struct FileTimes {
accessed: Option<c::FILETIME>,
modified: Option<c::FILETIME>,
created: Option<c::FILETIME>,
}
impl core::fmt::Debug for c::FILETIME {

impl fmt::Debug for c::FILETIME {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let time = ((self.dwHighDateTime as u64) << 32) | self.dwLowDateTime as u64;
f.debug_tuple("FILETIME").field(&time).finish()
Expand Down Expand Up @@ -582,26 +584,34 @@ impl File {

pub fn set_times(&self, times: FileTimes) -> io::Result<()> {
let is_zero = |t: c::FILETIME| t.dwLowDateTime == 0 && t.dwHighDateTime == 0;
if times.accessed.map_or(false, is_zero) || times.modified.map_or(false, is_zero) {
if times.accessed.map_or(false, is_zero)
|| times.modified.map_or(false, is_zero)
|| times.created.map_or(false, is_zero)
{
return Err(io::const_io_error!(
io::ErrorKind::InvalidInput,
"Cannot set file timestamp to 0",
));
}
let is_max =
|t: c::FILETIME| t.dwLowDateTime == c::DWORD::MAX && t.dwHighDateTime == c::DWORD::MAX;
if times.accessed.map_or(false, is_max) || times.modified.map_or(false, is_max) {
if times.accessed.map_or(false, is_max)
|| times.modified.map_or(false, is_max)
|| times.created.map_or(false, is_max)
{
return Err(io::const_io_error!(
io::ErrorKind::InvalidInput,
"Cannot set file timestamp to 0xFFFF_FFFF_FFFF_FFFF",
));
}
cvt(unsafe {
let created =
times.created.as_ref().map(|a| a as *const c::FILETIME).unwrap_or(ptr::null());
let accessed =
times.accessed.as_ref().map(|a| a as *const c::FILETIME).unwrap_or(ptr::null());
let modified =
times.modified.as_ref().map(|a| a as *const c::FILETIME).unwrap_or(ptr::null());
c::SetFileTime(self.as_raw_handle(), ptr::null_mut(), accessed, modified)
c::SetFileTime(self.as_raw_handle(), created, accessed, modified)
})?;
Ok(())
}
Expand Down Expand Up @@ -1005,6 +1015,10 @@ impl FileTimes {
pub fn set_modified(&mut self, t: SystemTime) {
self.modified = Some(t.into_inner());
}

pub fn set_created(&mut self, t: SystemTime) {
self.created = Some(t.into_inner());
}
}

impl FileType {
Expand Down