Skip to content

Commit

Permalink
Auto merge of #109773 - beetrees:set-file-time-improvements, r=Amanieu
Browse files Browse the repository at this point in the history
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
  • Loading branch information
bors committed May 19, 2023
2 parents 8a281f9 + 246dcbc commit 521f4da
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 28 deletions.
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

0 comments on commit 521f4da

Please sign in to comment.