Skip to content

Commit 1440cfb

Browse files
committed
Implement fs api set_times and set_times_nofollow
1 parent 2cb4e7d commit 1440cfb

File tree

10 files changed

+221
-18
lines changed

10 files changed

+221
-18
lines changed

library/std/src/fs.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,78 @@ pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result
387387
inner(path.as_ref(), contents.as_ref())
388388
}
389389

390+
/// Changes the timestamps of the file or directory at the specified path.
391+
///
392+
/// This function will attempt to set the access and modification times
393+
/// to the times specified. If the path refers to a symbolic link, this function
394+
/// will follow the link and change the timestamps of the target file.
395+
///
396+
/// # Platform-specific behavior
397+
///
398+
/// This function currently corresponds to the `utimensat` function on Unix platforms
399+
/// and the `SetFileTime` function on Windows.
400+
///
401+
/// # Errors
402+
///
403+
/// This function will return an error if the user lacks permission to change timestamps on the
404+
/// target file or symlink. It may also return an error if the OS does not support it.
405+
///
406+
/// # Examples
407+
///
408+
/// ```no_run
409+
/// use std::fs::{self, FileTimes};
410+
/// use std::time::SystemTime;
411+
///
412+
/// fn main() -> std::io::Result<()> {
413+
/// let now = SystemTime::now();
414+
/// let times = FileTimes::new()
415+
/// .set_accessed(now)
416+
/// .set_modified(now);
417+
/// fs::set_times("foo.txt", times)?;
418+
/// Ok(())
419+
/// }
420+
/// ```
421+
#[unstable(feature = "fs_set_times", issue = "147455")]
422+
pub fn set_times<P: AsRef<Path>>(path: P, times: FileTimes) -> io::Result<()> {
423+
fs_imp::set_times(path.as_ref(), times.0)
424+
}
425+
426+
/// Changes the timestamps of the file or symlink at the specified path.
427+
///
428+
/// This function will attempt to set the access and modification times
429+
/// to the times specified. Differ from `set_times`, if the path refers to a symbolic link,
430+
/// this function will change the timestamps of the symlink itself, not the target file.
431+
///
432+
/// # Platform-specific behavior
433+
///
434+
/// This function currently corresponds to the `utimensat` function with `AT_SYMLINK_NOFOLLOW`
435+
/// on Unix platforms and the `SetFileTime` function on Windows after opening the symlink.
436+
///
437+
/// # Errors
438+
///
439+
/// This function will return an error if the user lacks permission to change timestamps on the
440+
/// target file or symlink. It may also return an error if the OS does not support it.
441+
///
442+
/// # Examples
443+
///
444+
/// ```no_run
445+
/// use std::fs::{self, FileTimes};
446+
/// use std::time::SystemTime;
447+
///
448+
/// fn main() -> std::io::Result<()> {
449+
/// let now = SystemTime::now();
450+
/// let times = FileTimes::new()
451+
/// .set_accessed(now)
452+
/// .set_modified(now);
453+
/// fs::set_times_nofollow("symlink.txt", times)?;
454+
/// Ok(())
455+
/// }
456+
/// ```
457+
#[unstable(feature = "fs_set_times", issue = "147455")]
458+
pub fn set_times_nofollow<P: AsRef<Path>>(path: P, times: FileTimes) -> io::Result<()> {
459+
fs_imp::set_times_nofollow(path.as_ref(), times.0)
460+
}
461+
390462
#[stable(feature = "file_lock", since = "1.89.0")]
391463
impl error::Error for TryLockError {}
392464

library/std/src/sys/fs/hermit.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,14 @@ pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> {
566566
Err(Error::from_raw_os_error(22))
567567
}
568568

569+
pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> {
570+
Err(Error::from_raw_os_error(22))
571+
}
572+
573+
pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> {
574+
Err(Error::from_raw_os_error(22))
575+
}
576+
569577
pub fn rmdir(path: &Path) -> io::Result<()> {
570578
run_path_with_cstr(path, &|path| cvt(unsafe { hermit_abi::rmdir(path.as_ptr()) }).map(|_| ()))
571579
}

library/std/src/sys/fs/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,11 @@ pub fn exists(path: &Path) -> io::Result<bool> {
161161
#[cfg(windows)]
162162
with_native_path(path, &imp::exists)
163163
}
164+
165+
pub fn set_times(path: &Path, times: FileTimes) -> io::Result<()> {
166+
with_native_path(path, &|path| imp::set_times(path, times.clone()))
167+
}
168+
169+
pub fn set_times_nofollow(path: &Path, times: FileTimes) -> io::Result<()> {
170+
with_native_path(path, &|path| imp::set_times_nofollow(path, times.clone()))
171+
}

library/std/src/sys/fs/solid.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,17 @@ pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> {
538538
Ok(())
539539
}
540540

541+
pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> {
542+
Err(io::const_error!(io::ErrorKind::Unsupported, "setting file times not supported",))
543+
}
544+
545+
pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> {
546+
Err(io::const_error!(
547+
io::ErrorKind::Unsupported,
548+
"setting file times on symlinks not supported",
549+
))
550+
}
551+
541552
pub fn rmdir(p: &Path) -> io::Result<()> {
542553
if stat(p)?.file_type().is_dir() {
543554
error::SolidError::err_if_negative(unsafe { abi::SOLID_FS_Unlink(cstr(p)?.as_ptr()) })

library/std/src/sys/fs/uefi.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,14 @@ pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> {
333333
unsupported()
334334
}
335335

336+
pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> {
337+
unsupported()
338+
}
339+
340+
pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> {
341+
unsupported()
342+
}
343+
336344
pub fn rmdir(_p: &Path) -> io::Result<()> {
337345
unsupported()
338346
}

library/std/src/sys/fs/unix.rs

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,27 @@ impl fmt::Debug for OpenOptions {
11951195
}
11961196
}
11971197

1198+
#[cfg(not(any(
1199+
target_os = "redox",
1200+
target_os = "espidf",
1201+
target_os = "horizon",
1202+
target_os = "nuttx",
1203+
)))]
1204+
fn to_timespec(time: Option<SystemTime>) -> io::Result<libc::timespec> {
1205+
match time {
1206+
Some(time) if let Some(ts) = time.t.to_timespec() => Ok(ts),
1207+
Some(time) if time > crate::sys::time::UNIX_EPOCH => Err(io::const_error!(
1208+
io::ErrorKind::InvalidInput,
1209+
"timestamp is too large to set as a file time",
1210+
)),
1211+
Some(_) => Err(io::const_error!(
1212+
io::ErrorKind::InvalidInput,
1213+
"timestamp is too small to set as a file time",
1214+
)),
1215+
None => Ok(libc::timespec { tv_sec: 0, tv_nsec: libc::UTIME_OMIT as _ }),
1216+
}
1217+
}
1218+
11981219
impl File {
11991220
pub fn open(path: &Path, opts: &OpenOptions) -> io::Result<File> {
12001221
run_path_with_cstr(path, &|path| File::open_c(path, opts))
@@ -1604,24 +1625,6 @@ impl File {
16041625
}
16051626

16061627
pub fn set_times(&self, times: FileTimes) -> io::Result<()> {
1607-
#[cfg(not(any(
1608-
target_os = "redox",
1609-
target_os = "espidf",
1610-
target_os = "horizon",
1611-
target_os = "nuttx",
1612-
)))]
1613-
let to_timespec = |time: Option<SystemTime>| match time {
1614-
Some(time) if let Some(ts) = time.t.to_timespec() => Ok(ts),
1615-
Some(time) if time > crate::sys::time::UNIX_EPOCH => Err(io::const_error!(
1616-
io::ErrorKind::InvalidInput,
1617-
"timestamp is too large to set as a file time",
1618-
)),
1619-
Some(_) => Err(io::const_error!(
1620-
io::ErrorKind::InvalidInput,
1621-
"timestamp is too small to set as a file time",
1622-
)),
1623-
None => Ok(libc::timespec { tv_sec: 0, tv_nsec: libc::UTIME_OMIT as _ }),
1624-
};
16251628
cfg_select! {
16261629
any(target_os = "redox", target_os = "espidf", target_os = "horizon", target_os = "nuttx") => {
16271630
// Redox doesn't appear to support `UTIME_OMIT`.
@@ -1970,6 +1973,54 @@ pub fn set_perm(p: &CStr, perm: FilePermissions) -> io::Result<()> {
19701973
cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) }).map(|_| ())
19711974
}
19721975

1976+
fn set_times_impl(p: &CStr, times: FileTimes, flags: c_int) -> io::Result<()> {
1977+
#[cfg(not(any(
1978+
target_os = "redox",
1979+
target_os = "espidf",
1980+
target_os = "horizon",
1981+
target_os = "nuttx",
1982+
)))]
1983+
let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?];
1984+
cfg_select! {
1985+
any(target_os = "redox", target_os = "espidf", target_os = "horizon", target_os = "nuttx") => {
1986+
let _ = (p, times, flags);
1987+
Err(io::const_error!(
1988+
io::ErrorKind::Unsupported,
1989+
"setting file times not supported",
1990+
))
1991+
}
1992+
target_vendor = "apple" => {
1993+
// FIXME: need to implement
1994+
let _ = (p, times, flags);
1995+
Err(io::const_error!(
1996+
io::ErrorKind::Unsupported,
1997+
"setting file times not supported",
1998+
))
1999+
}
2000+
target_os = "android" => {
2001+
// FIXME: need to implement
2002+
let _ = (p, times, flags);
2003+
Err(io::const_error!(
2004+
io::ErrorKind::Unsupported,
2005+
"setting file times not supported",
2006+
))
2007+
}
2008+
_ => {
2009+
cvt(unsafe { libc::utimensat(libc::AT_FDCWD, p.as_ptr(), times.as_ptr(), flags) })?;
2010+
Ok(())
2011+
}
2012+
}
2013+
}
2014+
2015+
pub fn set_times(p: &CStr, times: FileTimes) -> io::Result<()> {
2016+
// flags = 0 means follow symlinks
2017+
set_times_impl(p, times, 0)
2018+
}
2019+
2020+
pub fn set_times_nofollow(p: &CStr, times: FileTimes) -> io::Result<()> {
2021+
set_times_impl(p, times, libc::AT_SYMLINK_NOFOLLOW)
2022+
}
2023+
19732024
pub fn rmdir(p: &CStr) -> io::Result<()> {
19742025
cvt(unsafe { libc::rmdir(p.as_ptr()) }).map(|_| ())
19752026
}

library/std/src/sys/fs/unsupported.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,14 @@ pub fn set_perm(_p: &Path, perm: FilePermissions) -> io::Result<()> {
312312
match perm.0 {}
313313
}
314314

315+
pub fn set_times(_p: &Path, times: FileTimes) -> io::Result<()> {
316+
match times {}
317+
}
318+
319+
pub fn set_times_nofollow(_p: &Path, times: FileTimes) -> io::Result<()> {
320+
match times {}
321+
}
322+
315323
pub fn rmdir(_p: &Path) -> io::Result<()> {
316324
unsupported()
317325
}

library/std/src/sys/fs/vexos.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,14 @@ pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> {
492492
unsupported()
493493
}
494494

495+
pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> {
496+
unsupported()
497+
}
498+
499+
pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> {
500+
unsupported()
501+
}
502+
495503
pub fn exists(path: &Path) -> io::Result<bool> {
496504
run_path_with_cstr(path, &|path| Ok(unsafe { vex_sdk::vexFileStatus(path.as_ptr()) } != 0))
497505
}

library/std/src/sys/fs/wasi.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,18 @@ pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> {
643643
unsupported()
644644
}
645645

646+
pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> {
647+
// File times haven't been fully figured out in wasi yet, so this is
648+
// likely temporary
649+
unsupported()
650+
}
651+
652+
pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> {
653+
// File times haven't been fully figured out in wasi yet, so this is
654+
// likely temporary
655+
unsupported()
656+
}
657+
646658
pub fn rmdir(p: &Path) -> io::Result<()> {
647659
let (dir, file) = open_parent(p)?;
648660
dir.remove_directory(osstr2str(file.as_ref())?)

library/std/src/sys/fs/windows.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,6 +1514,23 @@ pub fn set_perm(p: &WCStr, perm: FilePermissions) -> io::Result<()> {
15141514
}
15151515
}
15161516

1517+
pub fn set_times(p: &WCStr, times: FileTimes) -> io::Result<()> {
1518+
let mut opts = OpenOptions::new();
1519+
opts.write(true);
1520+
opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS);
1521+
let file = File::open_native(p, &opts)?;
1522+
file.set_times(times)
1523+
}
1524+
1525+
pub fn set_times_nofollow(p: &WCStr, times: FileTimes) -> io::Result<()> {
1526+
let mut opts = OpenOptions::new();
1527+
opts.write(true);
1528+
// `FILE_FLAG_OPEN_REPARSE_POINT` for no_follow behavior
1529+
opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS | c::FILE_FLAG_OPEN_REPARSE_POINT);
1530+
let file = File::open_native(p, &opts)?;
1531+
file.set_times(times)
1532+
}
1533+
15171534
fn get_path(f: &File) -> io::Result<PathBuf> {
15181535
fill_utf16_buf(
15191536
|buf, sz| unsafe {

0 commit comments

Comments
 (0)