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.
This commit is contained in:
parent
21e9336fe8
commit
61b45c670b
@ -184,6 +184,11 @@ pub struct DirEntry(fs_imp::DirEntry);
|
|||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
pub struct OpenOptions(fs_imp::OpenOptions);
|
pub struct OpenOptions(fs_imp::OpenOptions);
|
||||||
|
|
||||||
|
/// Representation of the various timestamps on a file.
|
||||||
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
|
#[unstable(feature = "file_set_times", issue = "98245")]
|
||||||
|
pub struct FileTimes(fs_imp::FileTimes);
|
||||||
|
|
||||||
/// Representation of the various permissions on a file.
|
/// Representation of the various permissions on a file.
|
||||||
///
|
///
|
||||||
/// This module only currently provides one bit of information,
|
/// This module only currently provides one bit of information,
|
||||||
@ -590,6 +595,49 @@ impl File {
|
|||||||
pub fn set_permissions(&self, perm: Permissions) -> io::Result<()> {
|
pub fn set_permissions(&self, perm: Permissions) -> io::Result<()> {
|
||||||
self.inner.set_permissions(perm.0)
|
self.inner.set_permissions(perm.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Changes the timestamps of the underlying file.
|
||||||
|
///
|
||||||
|
/// # Platform-specific behavior
|
||||||
|
///
|
||||||
|
/// This function currently corresponds to the `futimens` function on Unix (falling back to
|
||||||
|
/// `futimes` on macOS before 10.13) and the `SetFileTime` function on Windows. Note that this
|
||||||
|
/// [may change in the future][changes].
|
||||||
|
///
|
||||||
|
/// [changes]: io#platform-specific-behavior
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// This function will return an error if the user lacks permission to change timestamps on the
|
||||||
|
/// underlying file. It may also return an error in other os-specific unspecified cases.
|
||||||
|
///
|
||||||
|
/// This function may return an error if the operating system lacks support to change one or
|
||||||
|
/// more of the timestamps set in the `FileTimes` structure.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// #![feature(file_set_times)]
|
||||||
|
///
|
||||||
|
/// fn main() -> std::io::Result<()> {
|
||||||
|
/// use std::fs::{self, File, FileTimes};
|
||||||
|
///
|
||||||
|
/// let src = fs::metadata("src")?;
|
||||||
|
/// let dest = File::options().write(true).open("dest")?;
|
||||||
|
/// let times = FileTimes::new()
|
||||||
|
/// .set_accessed(src.accessed()?)
|
||||||
|
/// .set_modified(src.modified()?);
|
||||||
|
/// dest.set_times(times)?;
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[unstable(feature = "file_set_times", issue = "98245")]
|
||||||
|
#[doc(alias = "futimens")]
|
||||||
|
#[doc(alias = "futimes")]
|
||||||
|
#[doc(alias = "SetFileTime")]
|
||||||
|
pub fn set_times(&self, times: FileTimes) -> io::Result<()> {
|
||||||
|
self.inner.set_times(times.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In addition to the `impl`s here, `File` also has `impl`s for
|
// In addition to the `impl`s here, `File` also has `impl`s for
|
||||||
@ -1246,6 +1294,30 @@ impl FromInner<fs_imp::FileAttr> for Metadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FileTimes {
|
||||||
|
/// Create a new `FileTimes` with no times set.
|
||||||
|
///
|
||||||
|
/// Using the resulting `FileTimes` in [`File::set_times`] will not modify any timestamps.
|
||||||
|
#[unstable(feature = "file_set_times", issue = "98245")]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the last access time of a file.
|
||||||
|
#[unstable(feature = "file_set_times", issue = "98245")]
|
||||||
|
pub fn set_accessed(mut self, t: SystemTime) -> Self {
|
||||||
|
self.0.set_accessed(t.into_inner());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the last modified time of a file.
|
||||||
|
#[unstable(feature = "file_set_times", issue = "98245")]
|
||||||
|
pub fn set_modified(mut self, t: SystemTime) -> Self {
|
||||||
|
self.0.set_modified(t.into_inner());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Permissions {
|
impl Permissions {
|
||||||
/// Returns `true` if these permissions describe a readonly (unwritable) file.
|
/// Returns `true` if these permissions describe a readonly (unwritable) file.
|
||||||
///
|
///
|
||||||
|
@ -311,6 +311,9 @@ pub struct FilePermissions {
|
|||||||
mode: mode_t,
|
mode: mode_t,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct FileTimes([libc::timespec; 2]);
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
pub struct FileType {
|
pub struct FileType {
|
||||||
mode: mode_t,
|
mode: mode_t,
|
||||||
@ -503,6 +506,43 @@ impl FilePermissions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FileTimes {
|
||||||
|
pub fn set_accessed(&mut self, t: SystemTime) {
|
||||||
|
self.0[0] = t.t.to_timespec().expect("Invalid system time");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_modified(&mut self, t: SystemTime) {
|
||||||
|
self.0[1] = t.t.to_timespec().expect("Invalid system time");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TimespecDebugAdapter<'a>(&'a libc::timespec);
|
||||||
|
|
||||||
|
impl fmt::Debug for TimespecDebugAdapter<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("timespec")
|
||||||
|
.field("tv_sec", &self.0.tv_sec)
|
||||||
|
.field("tv_nsec", &self.0.tv_nsec)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for FileTimes {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("FileTimes")
|
||||||
|
.field("accessed", &TimespecDebugAdapter(&self.0[0]))
|
||||||
|
.field("modified", &TimespecDebugAdapter(&self.0[1]))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FileTimes {
|
||||||
|
fn default() -> Self {
|
||||||
|
let omit = libc::timespec { tv_sec: 0, tv_nsec: libc::UTIME_OMIT as _ };
|
||||||
|
Self([omit; 2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FileType {
|
impl FileType {
|
||||||
pub fn is_dir(&self) -> bool {
|
pub fn is_dir(&self) -> bool {
|
||||||
self.is(libc::S_IFDIR)
|
self.is(libc::S_IFDIR)
|
||||||
@ -1021,6 +1061,30 @@ impl File {
|
|||||||
cvt_r(|| unsafe { libc::fchmod(self.as_raw_fd(), perm.mode) })?;
|
cvt_r(|| unsafe { libc::fchmod(self.as_raw_fd(), perm.mode) })?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_times(&self, times: FileTimes) -> io::Result<()> {
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
// futimens requires macOS 10.13
|
||||||
|
if #[cfg(target_os = "macos")] {
|
||||||
|
fn ts_to_tv(ts: &libc::timespec) -> libc::timeval {
|
||||||
|
libc::timeval { tv_sec: ts.tv_sec, tv_usec: (ts.tv_nsec / 1000) as _ }
|
||||||
|
}
|
||||||
|
cvt(unsafe {
|
||||||
|
weak!(fn futimens(c_int, *const libc::timespec) -> c_int);
|
||||||
|
futimens.get()
|
||||||
|
.map(|futimens| futimens(self.as_raw_fd(), times.0.as_ptr()))
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
let timevals = [ts_to_tv(×.0[0]), ts_to_tv(×.0[1])];
|
||||||
|
libc::futimes(self.as_raw_fd(), timevals.as_ptr())
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
} else {
|
||||||
|
cvt(unsafe { libc::futimens(self.as_raw_fd(), times.0.as_ptr()) })?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DirBuilder {
|
impl DirBuilder {
|
||||||
|
@ -17,6 +17,9 @@ pub struct DirEntry(!);
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct OpenOptions {}
|
pub struct OpenOptions {}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
|
pub struct FileTimes {}
|
||||||
|
|
||||||
pub struct FilePermissions(!);
|
pub struct FilePermissions(!);
|
||||||
|
|
||||||
pub struct FileType(!);
|
pub struct FileType(!);
|
||||||
@ -86,6 +89,11 @@ impl fmt::Debug for FilePermissions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FileTimes {
|
||||||
|
pub fn set_accessed(&mut self, _t: SystemTime) {}
|
||||||
|
pub fn set_modified(&mut self, _t: SystemTime) {}
|
||||||
|
}
|
||||||
|
|
||||||
impl FileType {
|
impl FileType {
|
||||||
pub fn is_dir(&self) -> bool {
|
pub fn is_dir(&self) -> bool {
|
||||||
self.0
|
self.0
|
||||||
@ -237,6 +245,10 @@ impl File {
|
|||||||
pub fn set_permissions(&self, _perm: FilePermissions) -> io::Result<()> {
|
pub fn set_permissions(&self, _perm: FilePermissions) -> io::Result<()> {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_times(&self, _times: FileTimes) -> io::Result<()> {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DirBuilder {
|
impl DirBuilder {
|
||||||
|
@ -63,6 +63,12 @@ pub struct FilePermissions {
|
|||||||
readonly: bool,
|
readonly: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
|
pub struct FileTimes {
|
||||||
|
accessed: Option<wasi::Timestamp>,
|
||||||
|
modified: Option<wasi::Timestamp>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
|
#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
|
||||||
pub struct FileType {
|
pub struct FileType {
|
||||||
bits: wasi::Filetype,
|
bits: wasi::Filetype,
|
||||||
@ -112,6 +118,16 @@ impl FilePermissions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FileTimes {
|
||||||
|
pub fn set_accessed(&mut self, t: SystemTime) {
|
||||||
|
self.accessed = Some(t.to_wasi_timestamp_or_panic());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_modified(&mut self, t: SystemTime) {
|
||||||
|
self.modified = Some(t.to_wasi_timestamp_or_panic());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FileType {
|
impl FileType {
|
||||||
pub fn is_dir(&self) -> bool {
|
pub fn is_dir(&self) -> bool {
|
||||||
self.bits == wasi::FILETYPE_DIRECTORY
|
self.bits == wasi::FILETYPE_DIRECTORY
|
||||||
@ -459,6 +475,15 @@ impl File {
|
|||||||
unsupported()
|
unsupported()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_times(&self, times: FileTimes) -> io::Result<()> {
|
||||||
|
self.fd.filestat_set_times(
|
||||||
|
times.accessed.unwrap_or(0),
|
||||||
|
times.modified.unwrap_or(0),
|
||||||
|
times.accessed.map_or(0, |_| wasi::FSTFLAGS_ATIM)
|
||||||
|
| times.modified.map_or(0, |_| wasi::FSTFLAGS_MTIM),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read_link(&self, file: &Path) -> io::Result<PathBuf> {
|
pub fn read_link(&self, file: &Path) -> io::Result<PathBuf> {
|
||||||
read_link(&self.fd, file)
|
read_link(&self.fd, file)
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,10 @@ impl SystemTime {
|
|||||||
SystemTime(Duration::from_nanos(ts))
|
SystemTime(Duration::from_nanos(ts))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_wasi_timestamp_or_panic(&self) -> wasi::Timestamp {
|
||||||
|
self.0.as_nanos().try_into().expect("time does not fit in WASI timestamp")
|
||||||
|
}
|
||||||
|
|
||||||
pub fn sub_time(&self, other: &SystemTime) -> Result<Duration, Duration> {
|
pub fn sub_time(&self, other: &SystemTime) -> Result<Duration, Duration> {
|
||||||
self.0.checked_sub(other.0).ok_or_else(|| other.0 - self.0)
|
self.0.checked_sub(other.0).ok_or_else(|| other.0 - self.0)
|
||||||
}
|
}
|
||||||
|
@ -607,7 +607,7 @@ pub struct SOCKADDR {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
pub struct FILETIME {
|
pub struct FILETIME {
|
||||||
pub dwLowDateTime: DWORD,
|
pub dwLowDateTime: DWORD,
|
||||||
pub dwHighDateTime: DWORD,
|
pub dwHighDateTime: DWORD,
|
||||||
@ -875,6 +875,12 @@ extern "system" {
|
|||||||
pub fn GetSystemDirectoryW(lpBuffer: LPWSTR, uSize: UINT) -> UINT;
|
pub fn GetSystemDirectoryW(lpBuffer: LPWSTR, uSize: UINT) -> UINT;
|
||||||
pub fn RemoveDirectoryW(lpPathName: LPCWSTR) -> BOOL;
|
pub fn RemoveDirectoryW(lpPathName: LPCWSTR) -> BOOL;
|
||||||
pub fn SetFileAttributesW(lpFileName: LPCWSTR, dwFileAttributes: DWORD) -> BOOL;
|
pub fn SetFileAttributesW(lpFileName: LPCWSTR, dwFileAttributes: DWORD) -> BOOL;
|
||||||
|
pub fn SetFileTime(
|
||||||
|
hFile: BorrowedHandle<'_>,
|
||||||
|
lpCreationTime: Option<&FILETIME>,
|
||||||
|
lpLastAccessTime: Option<&FILETIME>,
|
||||||
|
lpLastWriteTime: Option<&FILETIME>,
|
||||||
|
) -> BOOL;
|
||||||
pub fn SetLastError(dwErrCode: DWORD);
|
pub fn SetLastError(dwErrCode: DWORD);
|
||||||
pub fn GetCommandLineW() -> LPWSTR;
|
pub fn GetCommandLineW() -> LPWSTR;
|
||||||
pub fn GetTempPathW(nBufferLength: DWORD, lpBuffer: LPCWSTR) -> DWORD;
|
pub fn GetTempPathW(nBufferLength: DWORD, lpBuffer: LPCWSTR) -> DWORD;
|
||||||
|
@ -82,6 +82,12 @@ pub struct FilePermissions {
|
|||||||
attrs: c::DWORD,
|
attrs: c::DWORD,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
|
pub struct FileTimes {
|
||||||
|
accessed: c::FILETIME,
|
||||||
|
modified: c::FILETIME,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DirBuilder;
|
pub struct DirBuilder;
|
||||||
|
|
||||||
@ -550,6 +556,14 @@ impl File {
|
|||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_times(&self, times: FileTimes) -> io::Result<()> {
|
||||||
|
cvt(unsafe {
|
||||||
|
c::SetFileTime(self.as_handle(), None, Some(×.accessed), Some(×.modified))
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Get only basic file information such as attributes and file times.
|
/// Get only basic file information such as attributes and file times.
|
||||||
fn basic_info(&self) -> io::Result<c::FILE_BASIC_INFO> {
|
fn basic_info(&self) -> io::Result<c::FILE_BASIC_INFO> {
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -895,6 +909,16 @@ impl FilePermissions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FileTimes {
|
||||||
|
pub fn set_accessed(&mut self, t: SystemTime) {
|
||||||
|
self.accessed = t.into_inner();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_modified(&mut self, t: SystemTime) {
|
||||||
|
self.modified = t.into_inner();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FileType {
|
impl FileType {
|
||||||
fn new(attrs: c::DWORD, reparse_tag: c::DWORD) -> FileType {
|
fn new(attrs: c::DWORD, reparse_tag: c::DWORD) -> FileType {
|
||||||
FileType { attributes: attrs, reparse_tag }
|
FileType { attributes: attrs, reparse_tag }
|
||||||
|
@ -2,6 +2,7 @@ use crate::cmp::Ordering;
|
|||||||
use crate::fmt;
|
use crate::fmt;
|
||||||
use crate::mem;
|
use crate::mem;
|
||||||
use crate::sys::c;
|
use crate::sys::c;
|
||||||
|
use crate::sys_common::IntoInner;
|
||||||
use crate::time::Duration;
|
use crate::time::Duration;
|
||||||
|
|
||||||
use core::hash::{Hash, Hasher};
|
use core::hash::{Hash, Hasher};
|
||||||
@ -136,6 +137,12 @@ impl From<c::FILETIME> for SystemTime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IntoInner<c::FILETIME> for SystemTime {
|
||||||
|
fn into_inner(self) -> c::FILETIME {
|
||||||
|
self.t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Hash for SystemTime {
|
impl Hash for SystemTime {
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
self.intervals().hash(state)
|
self.intervals().hash(state)
|
||||||
|
@ -38,7 +38,7 @@ use crate::error::Error;
|
|||||||
use crate::fmt;
|
use crate::fmt;
|
||||||
use crate::ops::{Add, AddAssign, Sub, SubAssign};
|
use crate::ops::{Add, AddAssign, Sub, SubAssign};
|
||||||
use crate::sys::time;
|
use crate::sys::time;
|
||||||
use crate::sys_common::FromInner;
|
use crate::sys_common::{FromInner, IntoInner};
|
||||||
|
|
||||||
#[stable(feature = "time", since = "1.3.0")]
|
#[stable(feature = "time", since = "1.3.0")]
|
||||||
pub use core::time::Duration;
|
pub use core::time::Duration;
|
||||||
@ -686,3 +686,9 @@ impl FromInner<time::SystemTime> for SystemTime {
|
|||||||
SystemTime(time)
|
SystemTime(time)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IntoInner<time::SystemTime> for SystemTime {
|
||||||
|
fn into_inner(self) -> time::SystemTime {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user