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
This commit is contained in:
commit
521f4dae1b
8 changed files with 196 additions and 28 deletions
|
@ -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;
|
||||
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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;
|
||||
|
@ -9,7 +9,7 @@ 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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")]
|
||||
|
@ -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`
|
||||
|
|
|
@ -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)]
|
||||
|
@ -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 {
|
||||
|
@ -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(×[0]), ts_to_tv(×[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",
|
||||
|
|
|
@ -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()
|
||||
|
@ -582,7 +584,10 @@ 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",
|
||||
|
@ -590,18 +595,23 @@ impl File {
|
|||
}
|
||||
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(())
|
||||
}
|
||||
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue