Skip to content

Commit 88e0040

Browse files
committed
Auto merge of #147468 - chenyukang:yukang-api-set-times, r=joshtriplett
Implement fs api set_times and set_times_nofollow implementation of #147455 r? `@joshtriplett`
2 parents 9725c4b + d2f590a commit 88e0040

File tree

11 files changed

+561
-52
lines changed

11 files changed

+561
-52
lines changed

library/std/src/fs.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,87 @@ 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, the
399+
/// `setattrlist` function on Apple platforms, 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+
/// #![feature(fs_set_times)]
410+
/// use std::fs::{self, FileTimes};
411+
/// use std::time::SystemTime;
412+
///
413+
/// fn main() -> std::io::Result<()> {
414+
/// let now = SystemTime::now();
415+
/// let times = FileTimes::new()
416+
/// .set_accessed(now)
417+
/// .set_modified(now);
418+
/// fs::set_times("foo.txt", times)?;
419+
/// Ok(())
420+
/// }
421+
/// ```
422+
#[unstable(feature = "fs_set_times", issue = "147455")]
423+
#[doc(alias = "utimens")]
424+
#[doc(alias = "utimes")]
425+
#[doc(alias = "utime")]
426+
pub fn set_times<P: AsRef<Path>>(path: P, times: FileTimes) -> io::Result<()> {
427+
fs_imp::set_times(path.as_ref(), times.0)
428+
}
429+
430+
/// Changes the timestamps of the file or symlink at the specified path.
431+
///
432+
/// This function will attempt to set the access and modification times
433+
/// to the times specified. Differ from `set_times`, if the path refers to a symbolic link,
434+
/// this function will change the timestamps of the symlink itself, not the target file.
435+
///
436+
/// # Platform-specific behavior
437+
///
438+
/// This function currently corresponds to the `utimensat` function with `AT_SYMLINK_NOFOLLOW` on
439+
/// Unix platforms, the `setattrlist` function with `FSOPT_NOFOLLOW` on Apple platforms, and the
440+
/// `SetFileTime` function on Windows.
441+
///
442+
/// # Errors
443+
///
444+
/// This function will return an error if the user lacks permission to change timestamps on the
445+
/// target file or symlink. It may also return an error if the OS does not support it.
446+
///
447+
/// # Examples
448+
///
449+
/// ```no_run
450+
/// #![feature(fs_set_times)]
451+
/// use std::fs::{self, FileTimes};
452+
/// use std::time::SystemTime;
453+
///
454+
/// fn main() -> std::io::Result<()> {
455+
/// let now = SystemTime::now();
456+
/// let times = FileTimes::new()
457+
/// .set_accessed(now)
458+
/// .set_modified(now);
459+
/// fs::set_times_nofollow("symlink.txt", times)?;
460+
/// Ok(())
461+
/// }
462+
/// ```
463+
#[unstable(feature = "fs_set_times", issue = "147455")]
464+
#[doc(alias = "utimensat")]
465+
#[doc(alias = "lutimens")]
466+
#[doc(alias = "lutimes")]
467+
pub fn set_times_nofollow<P: AsRef<Path>>(path: P, times: FileTimes) -> io::Result<()> {
468+
fs_imp::set_times_nofollow(path.as_ref(), times.0)
469+
}
470+
390471
#[stable(feature = "file_lock", since = "1.89.0")]
391472
impl error::Error for TryLockError {}
392473

library/std/src/fs/tests.rs

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2226,3 +2226,222 @@ fn test_open_options_invalid_combinations() {
22262226
assert_eq!(err.kind(), ErrorKind::InvalidInput);
22272227
assert_eq!(err.to_string(), "must specify at least one of read, write, or append access");
22282228
}
2229+
2230+
#[test]
2231+
fn test_fs_set_times() {
2232+
#[cfg(target_vendor = "apple")]
2233+
use crate::os::darwin::fs::FileTimesExt;
2234+
#[cfg(windows)]
2235+
use crate::os::windows::fs::FileTimesExt;
2236+
2237+
let tmp = tmpdir();
2238+
let path = tmp.join("foo");
2239+
File::create(&path).unwrap();
2240+
2241+
let mut times = FileTimes::new();
2242+
let accessed = SystemTime::UNIX_EPOCH + Duration::from_secs(12345);
2243+
let modified = SystemTime::UNIX_EPOCH + Duration::from_secs(54321);
2244+
times = times.set_accessed(accessed).set_modified(modified);
2245+
2246+
#[cfg(any(windows, target_vendor = "apple"))]
2247+
let created = SystemTime::UNIX_EPOCH + Duration::from_secs(32123);
2248+
#[cfg(any(windows, target_vendor = "apple"))]
2249+
{
2250+
times = times.set_created(created);
2251+
}
2252+
2253+
match fs::set_times(&path, times) {
2254+
// Allow unsupported errors on platforms which don't support setting times.
2255+
#[cfg(not(any(
2256+
windows,
2257+
all(
2258+
unix,
2259+
not(any(
2260+
target_os = "android",
2261+
target_os = "redox",
2262+
target_os = "espidf",
2263+
target_os = "horizon"
2264+
))
2265+
)
2266+
)))]
2267+
Err(e) if e.kind() == ErrorKind::Unsupported => return,
2268+
Err(e) => panic!("error setting file times: {e:?}"),
2269+
Ok(_) => {}
2270+
}
2271+
2272+
let metadata = fs::metadata(&path).unwrap();
2273+
assert_eq!(metadata.accessed().unwrap(), accessed);
2274+
assert_eq!(metadata.modified().unwrap(), modified);
2275+
#[cfg(any(windows, target_vendor = "apple"))]
2276+
{
2277+
assert_eq!(metadata.created().unwrap(), created);
2278+
}
2279+
}
2280+
2281+
#[test]
2282+
fn test_fs_set_times_follows_symlink() {
2283+
#[cfg(target_vendor = "apple")]
2284+
use crate::os::darwin::fs::FileTimesExt;
2285+
#[cfg(windows)]
2286+
use crate::os::windows::fs::FileTimesExt;
2287+
2288+
let tmp = tmpdir();
2289+
2290+
// Create a target file
2291+
let target = tmp.join("target");
2292+
File::create(&target).unwrap();
2293+
2294+
// Create a symlink to the target
2295+
#[cfg(unix)]
2296+
let link = tmp.join("link");
2297+
#[cfg(unix)]
2298+
crate::os::unix::fs::symlink(&target, &link).unwrap();
2299+
2300+
#[cfg(windows)]
2301+
let link = tmp.join("link.txt");
2302+
#[cfg(windows)]
2303+
crate::os::windows::fs::symlink_file(&target, &link).unwrap();
2304+
2305+
// Get the symlink's own modified time BEFORE calling set_times (to compare later)
2306+
// We don't check accessed time because reading metadata may update atime on some platforms.
2307+
let link_metadata_before = fs::symlink_metadata(&link).unwrap();
2308+
let link_modified_before = link_metadata_before.modified().unwrap();
2309+
2310+
let mut times = FileTimes::new();
2311+
let accessed = SystemTime::UNIX_EPOCH + Duration::from_secs(12345);
2312+
let modified = SystemTime::UNIX_EPOCH + Duration::from_secs(54321);
2313+
times = times.set_accessed(accessed).set_modified(modified);
2314+
2315+
#[cfg(any(windows, target_vendor = "apple"))]
2316+
let created = SystemTime::UNIX_EPOCH + Duration::from_secs(32123);
2317+
#[cfg(any(windows, target_vendor = "apple"))]
2318+
{
2319+
times = times.set_created(created);
2320+
}
2321+
2322+
// Call fs::set_times on the symlink - it should follow the link and modify the target
2323+
match fs::set_times(&link, times) {
2324+
// Allow unsupported errors on platforms which don't support setting times.
2325+
#[cfg(not(any(
2326+
windows,
2327+
all(
2328+
unix,
2329+
not(any(
2330+
target_os = "android",
2331+
target_os = "redox",
2332+
target_os = "espidf",
2333+
target_os = "horizon"
2334+
))
2335+
)
2336+
)))]
2337+
Err(e) if e.kind() == ErrorKind::Unsupported => return,
2338+
Err(e) => panic!("error setting file times through symlink: {e:?}"),
2339+
Ok(_) => {}
2340+
}
2341+
2342+
// Verify that the TARGET file's times were changed (following the symlink)
2343+
let target_metadata = fs::metadata(&target).unwrap();
2344+
assert_eq!(
2345+
target_metadata.accessed().unwrap(),
2346+
accessed,
2347+
"target file accessed time should match"
2348+
);
2349+
assert_eq!(
2350+
target_metadata.modified().unwrap(),
2351+
modified,
2352+
"target file modified time should match"
2353+
);
2354+
#[cfg(any(windows, target_vendor = "apple"))]
2355+
{
2356+
assert_eq!(
2357+
target_metadata.created().unwrap(),
2358+
created,
2359+
"target file created time should match"
2360+
);
2361+
}
2362+
2363+
// Also verify through the symlink (fs::metadata follows symlinks)
2364+
let link_followed_metadata = fs::metadata(&link).unwrap();
2365+
assert_eq!(link_followed_metadata.accessed().unwrap(), accessed);
2366+
assert_eq!(link_followed_metadata.modified().unwrap(), modified);
2367+
2368+
// Verify that the SYMLINK ITSELF was NOT modified
2369+
// Note: We only check modified time, not accessed time, because reading the symlink
2370+
// metadata may update its atime on some platforms (e.g., Linux).
2371+
let link_metadata_after = fs::symlink_metadata(&link).unwrap();
2372+
assert_eq!(
2373+
link_metadata_after.modified().unwrap(),
2374+
link_modified_before,
2375+
"symlink's own modified time should not change"
2376+
);
2377+
}
2378+
2379+
#[test]
2380+
fn test_fs_set_times_nofollow() {
2381+
#[cfg(target_vendor = "apple")]
2382+
use crate::os::darwin::fs::FileTimesExt;
2383+
#[cfg(windows)]
2384+
use crate::os::windows::fs::FileTimesExt;
2385+
2386+
let tmp = tmpdir();
2387+
2388+
// Create a target file and a symlink to it
2389+
let target = tmp.join("target");
2390+
File::create(&target).unwrap();
2391+
2392+
#[cfg(unix)]
2393+
let link = tmp.join("link");
2394+
#[cfg(unix)]
2395+
crate::os::unix::fs::symlink(&target, &link).unwrap();
2396+
2397+
#[cfg(windows)]
2398+
let link = tmp.join("link.txt");
2399+
#[cfg(windows)]
2400+
crate::os::windows::fs::symlink_file(&target, &link).unwrap();
2401+
2402+
let mut times = FileTimes::new();
2403+
let accessed = SystemTime::UNIX_EPOCH + Duration::from_secs(11111);
2404+
let modified = SystemTime::UNIX_EPOCH + Duration::from_secs(22222);
2405+
times = times.set_accessed(accessed).set_modified(modified);
2406+
2407+
#[cfg(any(windows, target_vendor = "apple"))]
2408+
let created = SystemTime::UNIX_EPOCH + Duration::from_secs(33333);
2409+
#[cfg(any(windows, target_vendor = "apple"))]
2410+
{
2411+
times = times.set_created(created);
2412+
}
2413+
2414+
// Set times on the symlink itself (not following it)
2415+
match fs::set_times_nofollow(&link, times) {
2416+
// Allow unsupported errors on platforms which don't support setting times.
2417+
#[cfg(not(any(
2418+
windows,
2419+
all(
2420+
unix,
2421+
not(any(
2422+
target_os = "android",
2423+
target_os = "redox",
2424+
target_os = "espidf",
2425+
target_os = "horizon"
2426+
))
2427+
)
2428+
)))]
2429+
Err(e) if e.kind() == ErrorKind::Unsupported => return,
2430+
Err(e) => panic!("error setting symlink times: {e:?}"),
2431+
Ok(_) => {}
2432+
}
2433+
2434+
// Read symlink metadata (without following)
2435+
let metadata = fs::symlink_metadata(&link).unwrap();
2436+
assert_eq!(metadata.accessed().unwrap(), accessed);
2437+
assert_eq!(metadata.modified().unwrap(), modified);
2438+
#[cfg(any(windows, target_vendor = "apple"))]
2439+
{
2440+
assert_eq!(metadata.created().unwrap(), created);
2441+
}
2442+
2443+
// Verify that the target file's times were NOT changed
2444+
let target_metadata = fs::metadata(&target).unwrap();
2445+
assert_ne!(target_metadata.accessed().unwrap(), accessed);
2446+
assert_ne!(target_metadata.modified().unwrap(), modified);
2447+
}

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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,14 @@ 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+
unsupported()
543+
}
544+
545+
pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> {
546+
unsupported()
547+
}
548+
541549
pub fn rmdir(p: &Path) -> io::Result<()> {
542550
if stat(p)?.file_type().is_dir() {
543551
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
}

0 commit comments

Comments
 (0)