Skip to content

Improve File::set_times error handling #101675

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
Oct 1, 2022
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
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