Skip to content

Commit

Permalink
Rollup merge of rust-lang#101675 - beetrees:set-times-no-panic, r=jos…
Browse files Browse the repository at this point in the history
…htriplett

Improve `File::set_times` error handling

Makes `File::set_times` return an error if the `SystemTime` cannot fit into the required type instead of panicking in `FileTimes::set_{accessed,modified}`. Also makes `File::set_times` return an error on Windows if either of the passed times are `0xFFFF_FFFF_FFFF_FFFF`, as [the documentation for `SetFileTime`](https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfiletime) states that this will prevent operations on the file handle from updating the corresponding file time instead of setting the corresponding file time to that value.

Tracking issue: rust-lang#98245
  • Loading branch information
matthiaskrgr authored Oct 1, 2022
2 parents 744e397 + c66860a commit 21fc218
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 49 deletions.
1 change: 1 addition & 0 deletions library/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@
#![feature(doc_notable_trait)]
#![feature(dropck_eyepatch)]
#![feature(exhaustive_patterns)]
#![feature(if_let_guard)]
#![feature(intra_doc_pointers)]
#![feature(lang_items)]
#![feature(let_chains)]
Expand Down
62 changes: 21 additions & 41 deletions library/std/src/sys/unix/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,8 +313,11 @@ pub struct FilePermissions {
mode: mode_t,
}

#[derive(Copy, Clone)]
pub struct FileTimes([libc::timespec; 2]);
#[derive(Copy, Clone, Debug, Default)]
pub struct FileTimes {
accessed: Option<SystemTime>,
modified: Option<SystemTime>,
}

#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct FileType {
Expand Down Expand Up @@ -512,45 +515,11 @@ impl FilePermissions {

impl FileTimes {
pub fn set_accessed(&mut self, t: SystemTime) {
self.0[0] = t.t.to_timespec().expect("Invalid system time");
self.accessed = Some(t);
}

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 {
// Redox doesn't appear to support `UTIME_OMIT`, so we stub it out here, and always return
// an error in `set_times`.
// ESP-IDF and HorizonOS do not support `futimens` at all and the behavior for those OS is therefore
// the same as for Redox.
#[cfg(any(target_os = "redox", target_os = "espidf", target_os = "horizon"))]
let omit = libc::timespec { tv_sec: 0, tv_nsec: 0 };
#[cfg(not(any(target_os = "redox", target_os = "espidf", target_os = "horizon")))]
let omit = libc::timespec { tv_sec: 0, tv_nsec: libc::UTIME_OMIT as _ };
Self([omit; 2])
self.modified = Some(t);
}
}

Expand Down Expand Up @@ -1084,6 +1053,17 @@ impl File {
}

pub fn set_times(&self, times: FileTimes) -> io::Result<()> {
#[cfg(not(any(target_os = "redox", target_os = "espidf", target_os = "horizon")))]
let to_timespec = |time: Option<SystemTime>| {
match time {
Some(time) if let Some(ts) = time.t.to_timespec() => Ok(ts),
Some(time) if time > crate::sys::time::UNIX_EPOCH => Err(io::const_io_error!(io::ErrorKind::InvalidInput, "timestamp is too large to set as a file time")),
Some(_) => Err(io::const_io_error!(io::ErrorKind::InvalidInput, "timestamp is too small to set as a file time")),
None => Ok(libc::timespec { tv_sec: 0, tv_nsec: libc::UTIME_OMIT as _ }),
}
};
#[cfg(not(any(target_os = "redox", target_os = "espidf", target_os = "horizon")))]
let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?];
cfg_if::cfg_if! {
if #[cfg(any(target_os = "redox", target_os = "espidf", target_os = "horizon"))] {
// Redox doesn't appear to support `UTIME_OMIT`.
Expand All @@ -1099,7 +1079,7 @@ impl File {
cvt(unsafe {
weak!(fn futimens(c_int, *const libc::timespec) -> c_int);
match futimens.get() {
Some(futimens) => futimens(self.as_raw_fd(), times.0.as_ptr()),
Some(futimens) => futimens(self.as_raw_fd(), times.as_ptr()),
#[cfg(target_os = "macos")]
None => {
fn ts_to_tv(ts: &libc::timespec) -> libc::timeval {
Expand All @@ -1108,7 +1088,7 @@ impl File {
tv_usec: (ts.tv_nsec / 1000) as _
}
}
let timevals = [ts_to_tv(&times.0[0]), ts_to_tv(&times.0[1])];
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.
Expand All @@ -1121,7 +1101,7 @@ impl File {
})?;
Ok(())
} else {
cvt(unsafe { libc::futimens(self.as_raw_fd(), times.0.as_ptr()) })?;
cvt(unsafe { libc::futimens(self.as_raw_fd(), times.as_ptr()) })?;
Ok(())
}
}
Expand Down
19 changes: 13 additions & 6 deletions library/std/src/sys/wasi/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ pub struct FilePermissions {

#[derive(Copy, Clone, Debug, Default)]
pub struct FileTimes {
accessed: Option<wasi::Timestamp>,
modified: Option<wasi::Timestamp>,
accessed: Option<SystemTime>,
modified: Option<SystemTime>,
}

#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
Expand Down Expand Up @@ -120,11 +120,11 @@ impl FilePermissions {

impl FileTimes {
pub fn set_accessed(&mut self, t: SystemTime) {
self.accessed = Some(t.to_wasi_timestamp_or_panic());
self.accessed = Some(t);
}

pub fn set_modified(&mut self, t: SystemTime) {
self.modified = Some(t.to_wasi_timestamp_or_panic());
self.modified = Some(t);
}
}

Expand Down Expand Up @@ -476,9 +476,16 @@ impl File {
}

pub fn set_times(&self, times: FileTimes) -> io::Result<()> {
let to_timestamp = |time: Option<SystemTime>| {
match time {
Some(time) if let Some(ts) = time.to_wasi_timestamp() => Ok(ts),
Some(_) => Err(io::const_io_error!(io::ErrorKind::InvalidInput, "timestamp is too large to set as a file time")),
None => Ok(0),
}
};
self.fd.filestat_set_times(
times.accessed.unwrap_or(0),
times.modified.unwrap_or(0),
to_timestamp(times.accessed)?,
to_timestamp(times.modified)?,
times.accessed.map_or(0, |_| wasi::FSTFLAGS_ATIM)
| times.modified.map_or(0, |_| wasi::FSTFLAGS_MTIM),
)
Expand Down
4 changes: 2 additions & 2 deletions library/std/src/sys/wasi/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ impl SystemTime {
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 to_wasi_timestamp(&self) -> Option<wasi::Timestamp> {
self.0.as_nanos().try_into().ok()
}

pub fn sub_time(&self, other: &SystemTime) -> Result<Duration, Duration> {
Expand Down
8 changes: 8 additions & 0 deletions library/std/src/sys/windows/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,14 @@ impl File {
"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) {
return Err(io::const_io_error!(
io::ErrorKind::InvalidInput,
"Cannot set file timestamp to 0xFFFF_FFFF_FFFF_FFFF",
));
}
cvt(unsafe {
c::SetFileTime(self.as_handle(), None, times.accessed.as_ref(), times.modified.as_ref())
})?;
Expand Down

0 comments on commit 21fc218

Please sign in to comment.