From 1ae22d83f97a66f0bbcee083abf3f0386df5332a Mon Sep 17 00:00:00 2001 From: Thalia Archibald Date: Tue, 22 Apr 2025 01:19:14 -0700 Subject: [PATCH 01/22] Implement `Debug` for `EncodeWide` Since `std::os::windows::ffi::EncodeWide` was reexported from `std::sys_common::wtf8::EncodeWide`, which has `#![allow(missing_debug_implementations)]` in the parent module, it did not implement `Debug`. When it was moved to `core`, a placeholder impl was added; fill it in. --- library/alloc/src/wtf8/tests.rs | 11 +++++++++++ library/core/src/wtf8.rs | 29 +++++++++++++++++++++++++---- library/std/src/sys_common/mod.rs | 1 - 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/library/alloc/src/wtf8/tests.rs b/library/alloc/src/wtf8/tests.rs index 291f63f9f9e54..a72ad0837d11e 100644 --- a/library/alloc/src/wtf8/tests.rs +++ b/library/alloc/src/wtf8/tests.rs @@ -579,6 +579,17 @@ fn wtf8_encode_wide_size_hint() { assert!(iter.next().is_none()); } +#[test] +fn wtf8_encode_wide_debug() { + let mut string = Wtf8Buf::from_str("aé "); + string.push(CodePoint::from_u32(0xD83D).unwrap()); + string.push_char('💩'); + assert_eq!( + format!("{:?}", string.encode_wide()), + r#"EncodeWide(['a', 'é', ' ', 0xD83D, 0xD83D, 0xDCA9])"# + ); +} + #[test] fn wtf8_clone_into() { let mut string = Wtf8Buf::new(); diff --git a/library/core/src/wtf8.rs b/library/core/src/wtf8.rs index de0dfa560a3f3..0c03496c5e367 100644 --- a/library/core/src/wtf8.rs +++ b/library/core/src/wtf8.rs @@ -562,15 +562,36 @@ impl Iterator for EncodeWide<'_> { } } +#[stable(feature = "encode_wide_fused_iterator", since = "1.62.0")] +impl FusedIterator for EncodeWide<'_> {} + +#[stable(feature = "encode_wide_debug", since = "CURRENT_RUSTC_VERSION")] impl fmt::Debug for EncodeWide<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("EncodeWide").finish_non_exhaustive() + struct CodeUnit(u16); + impl fmt::Debug for CodeUnit { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // This output attempts to balance readability with precision. + // Render characters which take only one WTF-16 code unit using + // `char` syntax and everything else as code units with hex + // integer syntax (including paired and unpaired surrogate + // halves). Since Rust has no `char`-like type for WTF-16, this + // isn't perfect, so if this output isn't suitable, it is open + // to being changed (see #140153). + match char::from_u32(self.0 as u32) { + Some(c) => write!(f, "{c:?}"), + None => write!(f, "0x{:04X}", self.0), + } + } + } + + write!(f, "EncodeWide(")?; + f.debug_list().entries(self.clone().map(CodeUnit)).finish()?; + write!(f, ")")?; + Ok(()) } } -#[stable(feature = "encode_wide_fused_iterator", since = "1.62.0")] -impl FusedIterator for EncodeWide<'_> {} - impl Hash for CodePoint { #[inline] fn hash(&self, state: &mut H) { diff --git a/library/std/src/sys_common/mod.rs b/library/std/src/sys_common/mod.rs index ec45c723e0de5..c6cb1b006e8c2 100644 --- a/library/std/src/sys_common/mod.rs +++ b/library/std/src/sys_common/mod.rs @@ -15,7 +15,6 @@ //! Progress on this is tracked in #84187. #![allow(missing_docs)] -#![allow(missing_debug_implementations)] #[cfg(test)] mod tests; From 1dd0a01deb61bd8ec132c5f39943af2eb4b2eaf9 Mon Sep 17 00:00:00 2001 From: Ana Hobden Date: Tue, 7 Oct 2025 13:06:56 -0700 Subject: [PATCH 02/22] Fix backtraces with -C panic=abort on qnx; emit unwind tables by default --- compiler/rustc_target/src/spec/base/nto_qnx.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/rustc_target/src/spec/base/nto_qnx.rs b/compiler/rustc_target/src/spec/base/nto_qnx.rs index 3f35b8b801ab9..6498742917545 100644 --- a/compiler/rustc_target/src/spec/base/nto_qnx.rs +++ b/compiler/rustc_target/src/spec/base/nto_qnx.rs @@ -12,6 +12,9 @@ pub(crate) fn opts() -> TargetOptions { has_thread_local: false, linker: Some("qcc".into()), os: "nto".into(), + // We want backtraces to work by default and they rely on unwind tables + // (regardless of `-C panic` strategy). + default_uwtable: true, position_independent_executables: true, static_position_independent_executables: true, relro_level: RelroLevel::Full, From 1e6b444df7f1242ad9b0cc58620f101f34514734 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Tue, 7 Oct 2025 13:07:23 -0700 Subject: [PATCH 03/22] library: fs: Factor out a `file_time_to_timespec` function in preparation for reusing it --- library/std/src/sys/fs/unix.rs | 49 ++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 33a1e7ff5e40e..bcd5ea6494447 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1604,24 +1604,6 @@ impl File { } pub fn set_times(&self, times: FileTimes) -> io::Result<()> { - #[cfg(not(any( - target_os = "redox", - target_os = "espidf", - target_os = "horizon", - target_os = "nuttx", - )))] - let to_timespec = |time: Option| 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_error!( - io::ErrorKind::InvalidInput, - "timestamp is too large to set as a file time", - )), - Some(_) => Err(io::const_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_select! { any(target_os = "redox", target_os = "espidf", target_os = "horizon", target_os = "nuttx") => { // Redox doesn't appear to support `UTIME_OMIT`. @@ -1639,17 +1621,17 @@ impl File { let mut attrlist: libc::attrlist = unsafe { mem::zeroed() }; attrlist.bitmapcount = libc::ATTR_BIT_MAP_COUNT; if times.created.is_some() { - buf[num_times].write(to_timespec(times.created)?); + buf[num_times].write(file_time_to_timespec(times.created)?); num_times += 1; attrlist.commonattr |= libc::ATTR_CMN_CRTIME; } if times.modified.is_some() { - buf[num_times].write(to_timespec(times.modified)?); + buf[num_times].write(file_time_to_timespec(times.modified)?); num_times += 1; attrlist.commonattr |= libc::ATTR_CMN_MODTIME; } if times.accessed.is_some() { - buf[num_times].write(to_timespec(times.accessed)?); + buf[num_times].write(file_time_to_timespec(times.accessed)?); num_times += 1; attrlist.commonattr |= libc::ATTR_CMN_ACCTIME; } @@ -1663,7 +1645,7 @@ impl File { Ok(()) } target_os = "android" => { - let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?]; + let times = [file_time_to_timespec(times.accessed)?, file_time_to_timespec(times.modified)?]; // futimens requires Android API level 19 cvt(unsafe { weak!( @@ -1697,7 +1679,7 @@ impl File { return Ok(()); } } - let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?]; + let times = [file_time_to_timespec(times.accessed)?, file_time_to_timespec(times.modified)?]; cvt(unsafe { libc::futimens(self.as_raw_fd(), times.as_ptr()) })?; Ok(()) } @@ -1705,6 +1687,27 @@ impl File { } } +#[cfg(not(any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "nuttx", +)))] +fn file_time_to_timespec(time: Option) -> io::Result { + 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_error!( + io::ErrorKind::InvalidInput, + "timestamp is too large to set as a file time", + )), + Some(_) => Err(io::const_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 _ }), + } +}; + impl DirBuilder { pub fn new() -> DirBuilder { DirBuilder { mode: 0o777 } From 1bf555c947296016d78de926e989d306b4cfde45 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Tue, 7 Oct 2025 13:18:38 -0700 Subject: [PATCH 04/22] library: fs: Factor out the Apple file time to attrlist code for reuse --- library/std/src/sys/fs/unix.rs | 74 +++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 23 deletions(-) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index bcd5ea6494447..bed9ea9139834 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1616,30 +1616,12 @@ impl File { )) } target_vendor = "apple" => { - let mut buf = [mem::MaybeUninit::::uninit(); 3]; - let mut num_times = 0; - let mut attrlist: libc::attrlist = unsafe { mem::zeroed() }; - attrlist.bitmapcount = libc::ATTR_BIT_MAP_COUNT; - if times.created.is_some() { - buf[num_times].write(file_time_to_timespec(times.created)?); - num_times += 1; - attrlist.commonattr |= libc::ATTR_CMN_CRTIME; - } - if times.modified.is_some() { - buf[num_times].write(file_time_to_timespec(times.modified)?); - num_times += 1; - attrlist.commonattr |= libc::ATTR_CMN_MODTIME; - } - if times.accessed.is_some() { - buf[num_times].write(file_time_to_timespec(times.accessed)?); - num_times += 1; - attrlist.commonattr |= libc::ATTR_CMN_ACCTIME; - } + let ta = TimesAttrlist::from_times(×)?; cvt(unsafe { libc::fsetattrlist( self.as_raw_fd(), - (&raw const attrlist).cast::().cast_mut(), - buf.as_ptr().cast::().cast_mut(), - num_times * size_of::(), + ta.attrlist(), + ta.times_buf(), + ta.times_buf_size(), 0 ) })?; Ok(()) @@ -1706,7 +1688,53 @@ fn file_time_to_timespec(time: Option) -> io::Result )), None => Ok(libc::timespec { tv_sec: 0, tv_nsec: libc::UTIME_OMIT as _ }), } -}; +} + +#[cfg(target_vendor = "apple")] +struct TimesAttrlist { + buf: [mem::MaybeUninit; 3], + attrlist: libc::attrlist, + num_times: usize, +} + +#[cfg(target_vendor = "apple")] +impl TimesAttrlist { + fn from_times(times: &FileTimes) -> io::Result { + let mut this = Self { + buf: [mem::MaybeUninit::::uninit(); 3], + attrlist: unsafe { mem::zeroed() }, + num_times: 0, + }; + this.attrlist.bitmapcount = libc::ATTR_BIT_MAP_COUNT; + if times.created.is_some() { + this.buf[this.num_times].write(file_time_to_timespec(times.created)?); + this.num_times += 1; + attrlist.commonattr |= libc::ATTR_CMN_CRTIME; + } + if times.modified.is_some() { + this.buf[this.num_times].write(file_time_to_timespec(times.modified)?); + this.num_times += 1; + attrlist.commonattr |= libc::ATTR_CMN_MODTIME; + } + if times.accessed.is_some() { + this.buf[this.num_times].write(file_time_to_timespec(times.accessed)?); + this.num_times += 1; + attrlist.commonattr |= libc::ATTR_CMN_ACCTIME; + } + } + + fn attrlist(&self) -> *mut libc::c_void { + (&raw const self.attrlist).cast::().cast_mut() + } + + fn times_buf(&self) -> *mut libc::c_void { + self.buf.as_ptr().cast::().cast_mut() + } + + fn times_buf_size(&self) -> usize { + self.num_times * size_of::() + } +} impl DirBuilder { pub fn new() -> DirBuilder { From 0e2130c2c9726a243cacafae3d0c6fdf8284301f Mon Sep 17 00:00:00 2001 From: Evan Jones Date: Wed, 8 Oct 2025 15:51:21 -0400 Subject: [PATCH 05/22] std::thread spawn: Docs: Link to Builder::spawn; Make same. Replace "use this API instead" with a link to Builder::spawn. Edit the paragraph to make it slightly clearer. The Scope::spawn method already included a. Make the docs for the two nearly the same. --- library/std/src/thread/mod.rs | 5 ++--- library/std/src/thread/scoped.rs | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/library/std/src/thread/mod.rs b/library/std/src/thread/mod.rs index fd7cce3f97db8..78c85c0af6449 100644 --- a/library/std/src/thread/mod.rs +++ b/library/std/src/thread/mod.rs @@ -620,9 +620,8 @@ impl Builder { /// (It is the responsibility of the program to either eventually join threads it /// creates or detach them; otherwise, a resource leak will result.) /// -/// This call will create a thread using default parameters of [`Builder`], if you -/// want to specify the stack size or the name of the thread, use this API -/// instead. +/// This function creates a thread with the default parameters. To specify the +/// new thread's stack size or the name, use [`Builder::spawn`]. /// /// As you can see in the signature of `spawn` there are two constraints on /// both the closure given to `spawn` and its return value, let's explain them: diff --git a/library/std/src/thread/scoped.rs b/library/std/src/thread/scoped.rs index a4c0ca5417d00..2368ce4988d89 100644 --- a/library/std/src/thread/scoped.rs +++ b/library/std/src/thread/scoped.rs @@ -181,9 +181,8 @@ impl<'scope, 'env> Scope<'scope, 'env> { /// end of the scope. In that case, if the spawned thread panics, [`scope`] will /// panic after all threads are joined. /// - /// This call will create a thread using default parameters of [`Builder`]. - /// If you want to specify the stack size or the name of the thread, use - /// [`Builder::spawn_scoped`] instead. + /// This function creates a thread with the default parameters. To specify the + /// new thread's stack size or the name, use [`Builder::spawn_scoped`]. /// /// # Panics /// From 722545427f46d670228c345a03ab9c0c9cf74241 Mon Sep 17 00:00:00 2001 From: yukang Date: Wed, 8 Oct 2025 16:39:53 +0800 Subject: [PATCH 06/22] Implement fs api set_times and set_times_nofollow --- library/std/src/fs.rs | 74 +++++++++ library/std/src/fs/tests.rs | 219 ++++++++++++++++++++++++++ library/std/src/sys/fs/hermit.rs | 8 + library/std/src/sys/fs/mod.rs | 8 + library/std/src/sys/fs/solid.rs | 11 ++ library/std/src/sys/fs/uefi.rs | 8 + library/std/src/sys/fs/unix.rs | 127 +++++++++++++++ library/std/src/sys/fs/unsupported.rs | 8 + library/std/src/sys/fs/vexos.rs | 8 + library/std/src/sys/fs/wasi.rs | 12 ++ library/std/src/sys/fs/windows.rs | 17 ++ 11 files changed, 500 insertions(+) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 28b2c7173d321..e97190e69d68f 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -387,6 +387,80 @@ pub fn write, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result inner(path.as_ref(), contents.as_ref()) } +/// Changes the timestamps of the file or directory at the specified path. +/// +/// This function will attempt to set the access and modification times +/// to the times specified. If the path refers to a symbolic link, this function +/// will follow the link and change the timestamps of the target file. +/// +/// # Platform-specific behavior +/// +/// This function currently corresponds to the `utimensat` function on Unix platforms +/// and the `SetFileTime` function on Windows. +/// +/// # Errors +/// +/// This function will return an error if the user lacks permission to change timestamps on the +/// target file or symlink. It may also return an error if the OS does not support it. +/// +/// # Examples +/// +/// ```no_run +/// #![feature(fs_set_times)] +/// use std::fs::{self, FileTimes}; +/// use std::time::SystemTime; +/// +/// fn main() -> std::io::Result<()> { +/// let now = SystemTime::now(); +/// let times = FileTimes::new() +/// .set_accessed(now) +/// .set_modified(now); +/// fs::set_times("foo.txt", times)?; +/// Ok(()) +/// } +/// ``` +#[unstable(feature = "fs_set_times", issue = "147455")] +pub fn set_times>(path: P, times: FileTimes) -> io::Result<()> { + fs_imp::set_times(path.as_ref(), times.0) +} + +/// Changes the timestamps of the file or symlink at the specified path. +/// +/// This function will attempt to set the access and modification times +/// to the times specified. Differ from `set_times`, if the path refers to a symbolic link, +/// this function will change the timestamps of the symlink itself, not the target file. +/// +/// # Platform-specific behavior +/// +/// This function currently corresponds to the `utimensat` function with `AT_SYMLINK_NOFOLLOW` +/// on Unix platforms and the `SetFileTime` function on Windows after opening the symlink. +/// +/// # Errors +/// +/// This function will return an error if the user lacks permission to change timestamps on the +/// target file or symlink. It may also return an error if the OS does not support it. +/// +/// # Examples +/// +/// ```no_run +/// #![feature(fs_set_times)] +/// use std::fs::{self, FileTimes}; +/// use std::time::SystemTime; +/// +/// fn main() -> std::io::Result<()> { +/// let now = SystemTime::now(); +/// let times = FileTimes::new() +/// .set_accessed(now) +/// .set_modified(now); +/// fs::set_times_nofollow("symlink.txt", times)?; +/// Ok(()) +/// } +/// ``` +#[unstable(feature = "fs_set_times", issue = "147455")] +pub fn set_times_nofollow>(path: P, times: FileTimes) -> io::Result<()> { + fs_imp::set_times_nofollow(path.as_ref(), times.0) +} + #[stable(feature = "file_lock", since = "1.89.0")] impl error::Error for TryLockError {} diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index f8dfb0d633400..4d67ba9248998 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -2226,3 +2226,222 @@ fn test_open_options_invalid_combinations() { assert_eq!(err.kind(), ErrorKind::InvalidInput); assert_eq!(err.to_string(), "must specify at least one of read, write, or append access"); } + +#[test] +fn test_fs_set_times() { + #[cfg(target_vendor = "apple")] + use crate::os::darwin::fs::FileTimesExt; + #[cfg(windows)] + use crate::os::windows::fs::FileTimesExt; + + let tmp = tmpdir(); + let path = tmp.join("foo"); + File::create(&path).unwrap(); + + let mut times = FileTimes::new(); + let accessed = SystemTime::UNIX_EPOCH + Duration::from_secs(12345); + let modified = SystemTime::UNIX_EPOCH + Duration::from_secs(54321); + times = times.set_accessed(accessed).set_modified(modified); + + #[cfg(any(windows, target_vendor = "apple"))] + let created = SystemTime::UNIX_EPOCH + Duration::from_secs(32123); + #[cfg(any(windows, target_vendor = "apple"))] + { + times = times.set_created(created); + } + + match fs::set_times(&path, times) { + // Allow unsupported errors on platforms which don't support setting times. + #[cfg(not(any( + windows, + all( + unix, + not(any( + target_os = "android", + target_os = "redox", + target_os = "espidf", + target_os = "horizon" + )) + ) + )))] + Err(e) if e.kind() == ErrorKind::Unsupported => return, + Err(e) => panic!("error setting file times: {e:?}"), + Ok(_) => {} + } + + let metadata = fs::metadata(&path).unwrap(); + assert_eq!(metadata.accessed().unwrap(), accessed); + assert_eq!(metadata.modified().unwrap(), modified); + #[cfg(any(windows, target_vendor = "apple"))] + { + assert_eq!(metadata.created().unwrap(), created); + } +} + +#[test] +fn test_fs_set_times_follows_symlink() { + #[cfg(target_vendor = "apple")] + use crate::os::darwin::fs::FileTimesExt; + #[cfg(windows)] + use crate::os::windows::fs::FileTimesExt; + + let tmp = tmpdir(); + + // Create a target file + let target = tmp.join("target"); + File::create(&target).unwrap(); + + // Create a symlink to the target + #[cfg(unix)] + let link = tmp.join("link"); + #[cfg(unix)] + crate::os::unix::fs::symlink(&target, &link).unwrap(); + + #[cfg(windows)] + let link = tmp.join("link.txt"); + #[cfg(windows)] + crate::os::windows::fs::symlink_file(&target, &link).unwrap(); + + // Get the symlink's own modified time BEFORE calling set_times (to compare later) + // We don't check accessed time because reading metadata may update atime on some platforms. + let link_metadata_before = fs::symlink_metadata(&link).unwrap(); + let link_modified_before = link_metadata_before.modified().unwrap(); + + let mut times = FileTimes::new(); + let accessed = SystemTime::UNIX_EPOCH + Duration::from_secs(12345); + let modified = SystemTime::UNIX_EPOCH + Duration::from_secs(54321); + times = times.set_accessed(accessed).set_modified(modified); + + #[cfg(any(windows, target_vendor = "apple"))] + let created = SystemTime::UNIX_EPOCH + Duration::from_secs(32123); + #[cfg(any(windows, target_vendor = "apple"))] + { + times = times.set_created(created); + } + + // Call fs::set_times on the symlink - it should follow the link and modify the target + match fs::set_times(&link, times) { + // Allow unsupported errors on platforms which don't support setting times. + #[cfg(not(any( + windows, + all( + unix, + not(any( + target_os = "android", + target_os = "redox", + target_os = "espidf", + target_os = "horizon" + )) + ) + )))] + Err(e) if e.kind() == ErrorKind::Unsupported => return, + Err(e) => panic!("error setting file times through symlink: {e:?}"), + Ok(_) => {} + } + + // Verify that the TARGET file's times were changed (following the symlink) + let target_metadata = fs::metadata(&target).unwrap(); + assert_eq!( + target_metadata.accessed().unwrap(), + accessed, + "target file accessed time should match" + ); + assert_eq!( + target_metadata.modified().unwrap(), + modified, + "target file modified time should match" + ); + #[cfg(any(windows, target_vendor = "apple"))] + { + assert_eq!( + target_metadata.created().unwrap(), + created, + "target file created time should match" + ); + } + + // Also verify through the symlink (fs::metadata follows symlinks) + let link_followed_metadata = fs::metadata(&link).unwrap(); + assert_eq!(link_followed_metadata.accessed().unwrap(), accessed); + assert_eq!(link_followed_metadata.modified().unwrap(), modified); + + // Verify that the SYMLINK ITSELF was NOT modified + // Note: We only check modified time, not accessed time, because reading the symlink + // metadata may update its atime on some platforms (e.g., Linux). + let link_metadata_after = fs::symlink_metadata(&link).unwrap(); + assert_eq!( + link_metadata_after.modified().unwrap(), + link_modified_before, + "symlink's own modified time should not change" + ); +} + +#[test] +fn test_fs_set_times_nofollow() { + #[cfg(target_vendor = "apple")] + use crate::os::darwin::fs::FileTimesExt; + #[cfg(windows)] + use crate::os::windows::fs::FileTimesExt; + + let tmp = tmpdir(); + + // Create a target file and a symlink to it + let target = tmp.join("target"); + File::create(&target).unwrap(); + + #[cfg(unix)] + let link = tmp.join("link"); + #[cfg(unix)] + crate::os::unix::fs::symlink(&target, &link).unwrap(); + + #[cfg(windows)] + let link = tmp.join("link.txt"); + #[cfg(windows)] + crate::os::windows::fs::symlink_file(&target, &link).unwrap(); + + let mut times = FileTimes::new(); + let accessed = SystemTime::UNIX_EPOCH + Duration::from_secs(11111); + let modified = SystemTime::UNIX_EPOCH + Duration::from_secs(22222); + times = times.set_accessed(accessed).set_modified(modified); + + #[cfg(any(windows, target_vendor = "apple"))] + let created = SystemTime::UNIX_EPOCH + Duration::from_secs(33333); + #[cfg(any(windows, target_vendor = "apple"))] + { + times = times.set_created(created); + } + + // Set times on the symlink itself (not following it) + match fs::set_times_nofollow(&link, times) { + // Allow unsupported errors on platforms which don't support setting times. + #[cfg(not(any( + windows, + all( + unix, + not(any( + target_os = "android", + target_os = "redox", + target_os = "espidf", + target_os = "horizon" + )) + ) + )))] + Err(e) if e.kind() == ErrorKind::Unsupported => return, + Err(e) => panic!("error setting symlink times: {e:?}"), + Ok(_) => {} + } + + // Read symlink metadata (without following) + let metadata = fs::symlink_metadata(&link).unwrap(); + assert_eq!(metadata.accessed().unwrap(), accessed); + assert_eq!(metadata.modified().unwrap(), modified); + #[cfg(any(windows, target_vendor = "apple"))] + { + assert_eq!(metadata.created().unwrap(), created); + } + + // Verify that the target file's times were NOT changed + let target_metadata = fs::metadata(&target).unwrap(); + assert_ne!(target_metadata.accessed().unwrap(), accessed); + assert_ne!(target_metadata.modified().unwrap(), modified); +} diff --git a/library/std/src/sys/fs/hermit.rs b/library/std/src/sys/fs/hermit.rs index 175d919c289dd..21235bcfbd8c5 100644 --- a/library/std/src/sys/fs/hermit.rs +++ b/library/std/src/sys/fs/hermit.rs @@ -566,6 +566,14 @@ pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { Err(Error::from_raw_os_error(22)) } +pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> { + Err(Error::from_raw_os_error(22)) +} + +pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> { + Err(Error::from_raw_os_error(22)) +} + pub fn rmdir(path: &Path) -> io::Result<()> { run_path_with_cstr(path, &|path| cvt(unsafe { hermit_abi::rmdir(path.as_ptr()) }).map(|_| ())) } diff --git a/library/std/src/sys/fs/mod.rs b/library/std/src/sys/fs/mod.rs index 64f5a6b36d3db..b498f9cb7ea72 100644 --- a/library/std/src/sys/fs/mod.rs +++ b/library/std/src/sys/fs/mod.rs @@ -161,3 +161,11 @@ pub fn exists(path: &Path) -> io::Result { #[cfg(windows)] with_native_path(path, &imp::exists) } + +pub fn set_times(path: &Path, times: FileTimes) -> io::Result<()> { + with_native_path(path, &|path| imp::set_times(path, times.clone())) +} + +pub fn set_times_nofollow(path: &Path, times: FileTimes) -> io::Result<()> { + with_native_path(path, &|path| imp::set_times_nofollow(path, times.clone())) +} diff --git a/library/std/src/sys/fs/solid.rs b/library/std/src/sys/fs/solid.rs index 808a95829114e..39bd9b3cdd70b 100644 --- a/library/std/src/sys/fs/solid.rs +++ b/library/std/src/sys/fs/solid.rs @@ -538,6 +538,17 @@ pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> { Ok(()) } +pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> { + Err(io::const_error!(io::ErrorKind::Unsupported, "setting file times not supported",)) +} + +pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> { + Err(io::const_error!( + io::ErrorKind::Unsupported, + "setting file times on symlinks not supported", + )) +} + pub fn rmdir(p: &Path) -> io::Result<()> { if stat(p)?.file_type().is_dir() { error::SolidError::err_if_negative(unsafe { abi::SOLID_FS_Unlink(cstr(p)?.as_ptr()) }) diff --git a/library/std/src/sys/fs/uefi.rs b/library/std/src/sys/fs/uefi.rs index 5763d7862f5ae..e4e7274ae8cb3 100644 --- a/library/std/src/sys/fs/uefi.rs +++ b/library/std/src/sys/fs/uefi.rs @@ -333,6 +333,14 @@ pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { unsupported() } +pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> { + unsupported() +} + +pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> { + unsupported() +} + pub fn rmdir(_p: &Path) -> io::Result<()> { unsupported() } diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index bed9ea9139834..578b5f4a1d986 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1195,6 +1195,55 @@ impl fmt::Debug for OpenOptions { } } +#[cfg(not(any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "nuttx", +)))] +fn to_timespec(time: Option) -> io::Result { + 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_error!( + io::ErrorKind::InvalidInput, + "timestamp is too large to set as a file time", + )), + Some(_) => Err(io::const_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(target_vendor = "apple")] +fn set_attrlist_with_times( + times: &FileTimes, +) -> io::Result<(libc::attrlist, [mem::MaybeUninit; 3], usize)> { + let mut buf = [mem::MaybeUninit::::uninit(); 3]; + let mut num_times = 0; + let mut attrlist: libc::attrlist = unsafe { mem::zeroed() }; + attrlist.bitmapcount = libc::ATTR_BIT_MAP_COUNT; + + if times.created.is_some() { + buf[num_times].write(to_timespec(times.created)?); + num_times += 1; + attrlist.commonattr |= libc::ATTR_CMN_CRTIME; + } + if times.modified.is_some() { + buf[num_times].write(to_timespec(times.modified)?); + num_times += 1; + attrlist.commonattr |= libc::ATTR_CMN_MODTIME; + } + if times.accessed.is_some() { + buf[num_times].write(to_timespec(times.accessed)?); + num_times += 1; + attrlist.commonattr |= libc::ATTR_CMN_ACCTIME; + } + + Ok((attrlist, buf, num_times)) +} + impl File { pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { run_path_with_cstr(path, &|path| File::open_c(path, opts)) @@ -2112,6 +2161,84 @@ fn open_from(from: &Path) -> io::Result<(crate::fs::File, crate::fs::Metadata)> Ok((reader, metadata)) } +fn set_times_impl(p: &CStr, times: FileTimes, flags: c_int) -> io::Result<()> { + cfg_select! { + any(target_os = "redox", target_os = "espidf", target_os = "horizon", target_os = "nuttx") => { + let _ = (p, times, flags); + Err(io::const_error!( + io::ErrorKind::Unsupported, + "setting file times not supported", + )) + } + target_vendor = "apple" => { + // Apple platforms use setattrlist which supports setting times on symlinks + let (attrlist, buf, num_times) = set_attrlist_with_times(×)?; + let options = if flags == libc::AT_SYMLINK_NOFOLLOW { + libc::FSOPT_NOFOLLOW + } else { + 0 + }; + + cvt(unsafe { libc::setattrlist( + p.as_ptr(), + (&raw const attrlist).cast::().cast_mut(), + buf.as_ptr().cast::().cast_mut(), + num_times * size_of::(), + options as u32 + ) })?; + Ok(()) + } + target_os = "android" => { + let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?]; + // utimensat requires Android API level 19 + cvt(unsafe { + weak!( + fn utimensat(dirfd: c_int, path: *const c_char, times: *const libc::timespec, flags: c_int) -> c_int; + ); + match utimensat.get() { + Some(utimensat) => utimensat(libc::AT_FDCWD, p.as_ptr(), times.as_ptr(), flags), + None => return Err(io::const_error!( + io::ErrorKind::Unsupported, + "setting file times requires Android API level >= 19", + )), + } + })?; + Ok(()) + } + _ => { + #[cfg(all(target_os = "linux", target_env = "gnu", target_pointer_width = "32", not(target_arch = "riscv32")))] + { + use crate::sys::{time::__timespec64, weak::weak}; + + // Added in glibc 2.34 + weak!( + fn __utimensat64(dirfd: c_int, path: *const c_char, times: *const __timespec64, flags: c_int) -> c_int; + ); + + if let Some(utimensat64) = __utimensat64.get() { + let to_timespec = |time: Option| time.map(|time| time.t.to_timespec64()) + .unwrap_or(__timespec64::new(0, libc::UTIME_OMIT as _)); + let times = [to_timespec(times.accessed), to_timespec(times.modified)]; + cvt(unsafe { utimensat64(libc::AT_FDCWD, p.as_ptr(), times.as_ptr(), flags) })?; + return Ok(()); + } + } + let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?]; + cvt(unsafe { libc::utimensat(libc::AT_FDCWD, p.as_ptr(), times.as_ptr(), flags) })?; + Ok(()) + } + } +} + +pub fn set_times(p: &CStr, times: FileTimes) -> io::Result<()> { + // flags = 0 means follow symlinks + set_times_impl(p, times, 0) +} + +pub fn set_times_nofollow(p: &CStr, times: FileTimes) -> io::Result<()> { + set_times_impl(p, times, libc::AT_SYMLINK_NOFOLLOW) +} + #[cfg(target_os = "espidf")] fn open_to_and_set_permissions( to: &Path, diff --git a/library/std/src/sys/fs/unsupported.rs b/library/std/src/sys/fs/unsupported.rs index efaddb51b3751..659ea2a8fc276 100644 --- a/library/std/src/sys/fs/unsupported.rs +++ b/library/std/src/sys/fs/unsupported.rs @@ -312,6 +312,14 @@ pub fn set_perm(_p: &Path, perm: FilePermissions) -> io::Result<()> { match perm.0 {} } +pub fn set_times(_p: &Path, times: FileTimes) -> io::Result<()> { + match times {} +} + +pub fn set_times_nofollow(_p: &Path, times: FileTimes) -> io::Result<()> { + match times {} +} + pub fn rmdir(_p: &Path) -> io::Result<()> { unsupported() } diff --git a/library/std/src/sys/fs/vexos.rs b/library/std/src/sys/fs/vexos.rs index f642e7cb074ec..99b156d535768 100644 --- a/library/std/src/sys/fs/vexos.rs +++ b/library/std/src/sys/fs/vexos.rs @@ -492,6 +492,14 @@ pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { unsupported() } +pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> { + unsupported() +} + +pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> { + unsupported() +} + pub fn exists(path: &Path) -> io::Result { run_path_with_cstr(path, &|path| Ok(unsafe { vex_sdk::vexFileStatus(path.as_ptr()) } != 0)) } diff --git a/library/std/src/sys/fs/wasi.rs b/library/std/src/sys/fs/wasi.rs index 0b65b9cb389df..1e6c0fad5b830 100644 --- a/library/std/src/sys/fs/wasi.rs +++ b/library/std/src/sys/fs/wasi.rs @@ -643,6 +643,18 @@ pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { unsupported() } +pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> { + // File times haven't been fully figured out in wasi yet, so this is + // likely temporary + unsupported() +} + +pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> { + // File times haven't been fully figured out in wasi yet, so this is + // likely temporary + unsupported() +} + pub fn rmdir(p: &Path) -> io::Result<()> { let (dir, file) = open_parent(p)?; dir.remove_directory(osstr2str(file.as_ref())?) diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index ccfe410627f70..f2d325da35c7d 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -1514,6 +1514,23 @@ pub fn set_perm(p: &WCStr, perm: FilePermissions) -> io::Result<()> { } } +pub fn set_times(p: &WCStr, times: FileTimes) -> io::Result<()> { + let mut opts = OpenOptions::new(); + opts.write(true); + opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS); + let file = File::open_native(p, &opts)?; + file.set_times(times) +} + +pub fn set_times_nofollow(p: &WCStr, times: FileTimes) -> io::Result<()> { + let mut opts = OpenOptions::new(); + opts.write(true); + // `FILE_FLAG_OPEN_REPARSE_POINT` for no_follow behavior + opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS | c::FILE_FLAG_OPEN_REPARSE_POINT); + let file = File::open_native(p, &opts)?; + file.set_times(times) +} + fn get_path(f: &File) -> io::Result { fill_utf16_buf( |buf, sz| unsafe { From 3d40fa69587c60d8ba7bcf3d2a39e503a5ded07c Mon Sep 17 00:00:00 2001 From: Yukang Date: Thu, 9 Oct 2025 10:20:48 +0800 Subject: [PATCH 07/22] Update library/std/src/fs.rs Co-authored-by: Josh Triplett --- library/std/src/fs.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index e97190e69d68f..f374f4155cfd2 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -432,8 +432,9 @@ pub fn set_times>(path: P, times: FileTimes) -> io::Result<()> { /// /// # Platform-specific behavior /// -/// This function currently corresponds to the `utimensat` function with `AT_SYMLINK_NOFOLLOW` -/// on Unix platforms and the `SetFileTime` function on Windows after opening the symlink. +/// This function currently corresponds to the `utimensat` function with `AT_SYMLINK_NOFOLLOW` on +/// Unix platforms, the `setattrlist` function with `FSOPT_NOFOLLOW` on Apple platforms, and the +/// `SetFileTime` function on Windows. /// /// # Errors /// From 6308e76cd7d9d6b4b3912c875eabd3eb695afc5d Mon Sep 17 00:00:00 2001 From: Yukang Date: Thu, 9 Oct 2025 10:21:06 +0800 Subject: [PATCH 08/22] Update library/std/src/fs.rs Co-authored-by: Josh Triplett --- library/std/src/fs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index f374f4155cfd2..60eefc36ae2be 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -395,8 +395,8 @@ pub fn write, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result /// /// # Platform-specific behavior /// -/// This function currently corresponds to the `utimensat` function on Unix platforms -/// and the `SetFileTime` function on Windows. +/// This function currently corresponds to the `utimensat` function on Unix platforms, the +/// `setattrlist` function on Apple platforms, and the `SetFileTime` function on Windows. /// /// # Errors /// From 1c5c8caad2401c923c1c8c515f004c52c8e74ed8 Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 9 Oct 2025 10:23:34 +0800 Subject: [PATCH 09/22] use proper unsupported --- library/std/src/sys/fs/solid.rs | 7 ++----- library/std/src/sys/fs/unix.rs | 2 ++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/library/std/src/sys/fs/solid.rs b/library/std/src/sys/fs/solid.rs index 39bd9b3cdd70b..f6d5d3b784d3b 100644 --- a/library/std/src/sys/fs/solid.rs +++ b/library/std/src/sys/fs/solid.rs @@ -539,14 +539,11 @@ pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> { } pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> { - Err(io::const_error!(io::ErrorKind::Unsupported, "setting file times not supported",)) + unsupported() } pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> { - Err(io::const_error!( - io::ErrorKind::Unsupported, - "setting file times on symlinks not supported", - )) + unsupported() } pub fn rmdir(p: &Path) -> io::Result<()> { diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 578b5f4a1d986..014b10f06176c 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1201,6 +1201,7 @@ impl fmt::Debug for OpenOptions { target_os = "horizon", target_os = "nuttx", )))] +#[inline(always)] fn to_timespec(time: Option) -> io::Result { match time { Some(time) if let Some(ts) = time.t.to_timespec() => Ok(ts), @@ -1217,6 +1218,7 @@ fn to_timespec(time: Option) -> io::Result { } #[cfg(target_vendor = "apple")] +#[inline(always)] fn set_attrlist_with_times( times: &FileTimes, ) -> io::Result<(libc::attrlist, [mem::MaybeUninit; 3], usize)> { From 46c6f0aadee016c28a0eb3d3fa977f33111e38ab Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 9 Oct 2025 10:49:13 +0800 Subject: [PATCH 10/22] rebase #147504 --- library/std/src/sys/fs/unix.rs | 73 ++++++---------------------------- 1 file changed, 13 insertions(+), 60 deletions(-) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 014b10f06176c..63a6db77324a9 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1195,57 +1195,6 @@ impl fmt::Debug for OpenOptions { } } -#[cfg(not(any( - target_os = "redox", - target_os = "espidf", - target_os = "horizon", - target_os = "nuttx", -)))] -#[inline(always)] -fn to_timespec(time: Option) -> io::Result { - 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_error!( - io::ErrorKind::InvalidInput, - "timestamp is too large to set as a file time", - )), - Some(_) => Err(io::const_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(target_vendor = "apple")] -#[inline(always)] -fn set_attrlist_with_times( - times: &FileTimes, -) -> io::Result<(libc::attrlist, [mem::MaybeUninit; 3], usize)> { - let mut buf = [mem::MaybeUninit::::uninit(); 3]; - let mut num_times = 0; - let mut attrlist: libc::attrlist = unsafe { mem::zeroed() }; - attrlist.bitmapcount = libc::ATTR_BIT_MAP_COUNT; - - if times.created.is_some() { - buf[num_times].write(to_timespec(times.created)?); - num_times += 1; - attrlist.commonattr |= libc::ATTR_CMN_CRTIME; - } - if times.modified.is_some() { - buf[num_times].write(to_timespec(times.modified)?); - num_times += 1; - attrlist.commonattr |= libc::ATTR_CMN_MODTIME; - } - if times.accessed.is_some() { - buf[num_times].write(to_timespec(times.accessed)?); - num_times += 1; - attrlist.commonattr |= libc::ATTR_CMN_ACCTIME; - } - - Ok((attrlist, buf, num_times)) -} - impl File { pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { run_path_with_cstr(path, &|path| File::open_c(path, opts)) @@ -1760,18 +1709,19 @@ impl TimesAttrlist { if times.created.is_some() { this.buf[this.num_times].write(file_time_to_timespec(times.created)?); this.num_times += 1; - attrlist.commonattr |= libc::ATTR_CMN_CRTIME; + this.attrlist.commonattr |= libc::ATTR_CMN_CRTIME; } if times.modified.is_some() { this.buf[this.num_times].write(file_time_to_timespec(times.modified)?); this.num_times += 1; - attrlist.commonattr |= libc::ATTR_CMN_MODTIME; + this.attrlist.commonattr |= libc::ATTR_CMN_MODTIME; } if times.accessed.is_some() { this.buf[this.num_times].write(file_time_to_timespec(times.accessed)?); this.num_times += 1; - attrlist.commonattr |= libc::ATTR_CMN_ACCTIME; + this.attrlist.commonattr |= libc::ATTR_CMN_ACCTIME; } + Ok(this) } fn attrlist(&self) -> *mut libc::c_void { @@ -2174,7 +2124,8 @@ fn set_times_impl(p: &CStr, times: FileTimes, flags: c_int) -> io::Result<()> { } target_vendor = "apple" => { // Apple platforms use setattrlist which supports setting times on symlinks - let (attrlist, buf, num_times) = set_attrlist_with_times(×)?; + //let (attrlist, buf, num_times) = set_attrlist_with_times(×)?; + let ta = TimesAttrlist::from_times(×)?; let options = if flags == libc::AT_SYMLINK_NOFOLLOW { libc::FSOPT_NOFOLLOW } else { @@ -2183,15 +2134,15 @@ fn set_times_impl(p: &CStr, times: FileTimes, flags: c_int) -> io::Result<()> { cvt(unsafe { libc::setattrlist( p.as_ptr(), - (&raw const attrlist).cast::().cast_mut(), - buf.as_ptr().cast::().cast_mut(), - num_times * size_of::(), + ta.attrlist(), + ta.times_buf(), + ta.times_buf_size(), options as u32 ) })?; Ok(()) } target_os = "android" => { - let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?]; + let times = [file_time_to_timespec(times.accessed)?, file_time_to_timespec(times.modified)?]; // utimensat requires Android API level 19 cvt(unsafe { weak!( @@ -2225,18 +2176,20 @@ fn set_times_impl(p: &CStr, times: FileTimes, flags: c_int) -> io::Result<()> { return Ok(()); } } - let times = [to_timespec(times.accessed)?, to_timespec(times.modified)?]; + let times = [file_time_to_timespec(times.accessed)?, file_time_to_timespec(times.modified)?]; cvt(unsafe { libc::utimensat(libc::AT_FDCWD, p.as_ptr(), times.as_ptr(), flags) })?; Ok(()) } } } +#[inline(always)] pub fn set_times(p: &CStr, times: FileTimes) -> io::Result<()> { // flags = 0 means follow symlinks set_times_impl(p, times, 0) } +#[inline(always)] pub fn set_times_nofollow(p: &CStr, times: FileTimes) -> io::Result<()> { set_times_impl(p, times, libc::AT_SYMLINK_NOFOLLOW) } From 2438df75fe8a954f54479ce1a79cb9863f05d926 Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 9 Oct 2025 11:08:30 +0800 Subject: [PATCH 11/22] support fs::set_times for wasi --- library/std/src/sys/fs/wasi.rs | 55 +++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/library/std/src/sys/fs/wasi.rs b/library/std/src/sys/fs/wasi.rs index 1e6c0fad5b830..92eb35317415f 100644 --- a/library/std/src/sys/fs/wasi.rs +++ b/library/std/src/sys/fs/wasi.rs @@ -536,17 +536,9 @@ impl File { } pub fn set_times(&self, times: FileTimes) -> io::Result<()> { - let to_timestamp = |time: Option| match time { - Some(time) if let Some(ts) = time.to_wasi_timestamp() => Ok(ts), - Some(_) => Err(io::const_error!( - io::ErrorKind::InvalidInput, - "timestamp is too large to set as a file time", - )), - None => Ok(0), - }; self.fd.filestat_set_times( - to_timestamp(times.accessed)?, - to_timestamp(times.modified)?, + to_wasi_timestamp_or_now(times.accessed)?, + to_wasi_timestamp_or_now(times.modified)?, times.accessed.map_or(0, |_| wasi::FSTFLAGS_ATIM) | times.modified.map_or(0, |_| wasi::FSTFLAGS_MTIM), ) @@ -643,16 +635,43 @@ pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { unsupported() } -pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> { - // File times haven't been fully figured out in wasi yet, so this is - // likely temporary - unsupported() +#[inline(always)] +pub fn set_times(p: &Path, times: FileTimes) -> io::Result<()> { + let (dir, file) = open_parent(p)?; + set_times_impl(&dir, &file, times, wasi::LOOKUPFLAGS_SYMLINK_FOLLOW) } -pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> { - // File times haven't been fully figured out in wasi yet, so this is - // likely temporary - unsupported() +#[inline(always)] +pub fn set_times_nofollow(p: &Path, times: FileTimes) -> io::Result<()> { + let (dir, file) = open_parent(p)?; + set_times_impl(&dir, &file, times, 0) +} + +fn to_wasi_timestamp_or_now(time: Option) -> io::Result { + match time { + Some(time) if let Some(ts) = time.to_wasi_timestamp() => Ok(ts), + Some(_) => Err(io::const_error!( + io::ErrorKind::InvalidInput, + "timestamp is too large to set as a file time", + )), + None => Ok(0), + } +} + +fn set_times_impl( + fd: &WasiFd, + path: &Path, + times: FileTimes, + flags: wasi::Lookupflags, +) -> io::Result<()> { + fd.path_filestat_set_times( + flags, + osstr2str(path.as_ref())?, + to_wasi_timestamp_or_now(times.accessed)?, + to_wasi_timestamp_or_now(times.modified)?, + times.accessed.map_or(0, |_| wasi::FSTFLAGS_ATIM) + | times.modified.map_or(0, |_| wasi::FSTFLAGS_MTIM), + ) } pub fn rmdir(p: &Path) -> io::Result<()> { From 1dd5641d74ca6d0fd873e7de9f5a63d12046d870 Mon Sep 17 00:00:00 2001 From: Yukang Date: Thu, 9 Oct 2025 11:37:48 +0800 Subject: [PATCH 12/22] add doc alias for set_times_nofollow Co-authored-by: Josh Triplett --- library/std/src/fs.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 60eefc36ae2be..9841a246d6a5d 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -458,6 +458,9 @@ pub fn set_times>(path: P, times: FileTimes) -> io::Result<()> { /// } /// ``` #[unstable(feature = "fs_set_times", issue = "147455")] +#[doc(alias = "utimensat")] +#[doc(alias = "lutimens")] +#[doc(alias = "lutimes")] pub fn set_times_nofollow>(path: P, times: FileTimes) -> io::Result<()> { fs_imp::set_times_nofollow(path.as_ref(), times.0) } From 6fd1c2bd9456cfa69812c7b35132b6f4ce7569f0 Mon Sep 17 00:00:00 2001 From: Yukang Date: Thu, 9 Oct 2025 11:38:19 +0800 Subject: [PATCH 13/22] add doc alias for set_times Co-authored-by: Josh Triplett --- library/std/src/fs.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 9841a246d6a5d..b548eb4939d42 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -420,6 +420,9 @@ pub fn write, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result /// } /// ``` #[unstable(feature = "fs_set_times", issue = "147455")] +#[doc(alias = "utimens")] +#[doc(alias = "utimes")] +#[doc(alias = "utime")] pub fn set_times>(path: P, times: FileTimes) -> io::Result<()> { fs_imp::set_times(path.as_ref(), times.0) } From 901366af1e673a260d1a20551b0199540ae4b455 Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 9 Oct 2025 16:42:54 +0800 Subject: [PATCH 14/22] fix c_char error in Android --- library/std/src/sys/fs/unix.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 63a6db77324a9..51849a31f61b5 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -2146,7 +2146,7 @@ fn set_times_impl(p: &CStr, times: FileTimes, flags: c_int) -> io::Result<()> { // utimensat requires Android API level 19 cvt(unsafe { weak!( - fn utimensat(dirfd: c_int, path: *const c_char, times: *const libc::timespec, flags: c_int) -> c_int; + fn utimensat(dirfd: c_int, path: *const libc::c_char, times: *const libc::timespec, flags: c_int) -> c_int; ); match utimensat.get() { Some(utimensat) => utimensat(libc::AT_FDCWD, p.as_ptr(), times.as_ptr(), flags), From f8118d88d7bdb5f697cf4712e01f6b46fe2bc742 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Thu, 9 Oct 2025 06:36:51 -0700 Subject: [PATCH 15/22] unsupported: Use `unsupported()` for `set_times` --- library/std/src/sys/fs/unsupported.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/std/src/sys/fs/unsupported.rs b/library/std/src/sys/fs/unsupported.rs index 659ea2a8fc276..7901bf5624d74 100644 --- a/library/std/src/sys/fs/unsupported.rs +++ b/library/std/src/sys/fs/unsupported.rs @@ -312,8 +312,8 @@ pub fn set_perm(_p: &Path, perm: FilePermissions) -> io::Result<()> { match perm.0 {} } -pub fn set_times(_p: &Path, times: FileTimes) -> io::Result<()> { - match times {} +pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> { + unsupported() } pub fn set_times_nofollow(_p: &Path, times: FileTimes) -> io::Result<()> { From d2f590a6d598dc75b09fd9d97c5bb32d23a6971a Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Thu, 9 Oct 2025 06:37:09 -0700 Subject: [PATCH 16/22] unsupported: Use `unsupported()` for `set_times_nofollow` --- library/std/src/sys/fs/unsupported.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/std/src/sys/fs/unsupported.rs b/library/std/src/sys/fs/unsupported.rs index 7901bf5624d74..f222151d18e25 100644 --- a/library/std/src/sys/fs/unsupported.rs +++ b/library/std/src/sys/fs/unsupported.rs @@ -316,8 +316,8 @@ pub fn set_times(_p: &Path, _times: FileTimes) -> io::Result<()> { unsupported() } -pub fn set_times_nofollow(_p: &Path, times: FileTimes) -> io::Result<()> { - match times {} +pub fn set_times_nofollow(_p: &Path, _times: FileTimes) -> io::Result<()> { + unsupported() } pub fn rmdir(_p: &Path) -> io::Result<()> { From 8182085617878610473f0b88f07fc9803f4b4960 Mon Sep 17 00:00:00 2001 From: yukang Date: Wed, 15 Oct 2025 08:02:32 +0800 Subject: [PATCH 17/22] Fix compiling error for redox etc --- library/std/src/sys/fs/unix.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 51849a31f61b5..d9a7fcb0e2d39 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -2113,10 +2113,10 @@ fn open_from(from: &Path) -> io::Result<(crate::fs::File, crate::fs::Metadata)> Ok((reader, metadata)) } -fn set_times_impl(p: &CStr, times: FileTimes, flags: c_int) -> io::Result<()> { +fn set_times_impl(p: &CStr, times: FileTimes, follow_symlinks: bool) -> io::Result<()> { cfg_select! { any(target_os = "redox", target_os = "espidf", target_os = "horizon", target_os = "nuttx") => { - let _ = (p, times, flags); + let _ = (p, times, follow_symlinks); Err(io::const_error!( io::ErrorKind::Unsupported, "setting file times not supported", @@ -2124,12 +2124,11 @@ fn set_times_impl(p: &CStr, times: FileTimes, flags: c_int) -> io::Result<()> { } target_vendor = "apple" => { // Apple platforms use setattrlist which supports setting times on symlinks - //let (attrlist, buf, num_times) = set_attrlist_with_times(×)?; let ta = TimesAttrlist::from_times(×)?; - let options = if flags == libc::AT_SYMLINK_NOFOLLOW { - libc::FSOPT_NOFOLLOW - } else { + let options = if follow_symlinks { 0 + } else { + libc::FSOPT_NOFOLLOW }; cvt(unsafe { libc::setattrlist( @@ -2143,6 +2142,7 @@ fn set_times_impl(p: &CStr, times: FileTimes, flags: c_int) -> io::Result<()> { } target_os = "android" => { let times = [file_time_to_timespec(times.accessed)?, file_time_to_timespec(times.modified)?]; + let flags = if follow_symlinks { 0 } else { libc::AT_SYMLINK_NOFOLLOW }; // utimensat requires Android API level 19 cvt(unsafe { weak!( @@ -2159,6 +2159,7 @@ fn set_times_impl(p: &CStr, times: FileTimes, flags: c_int) -> io::Result<()> { Ok(()) } _ => { + let flags = if follow_symlinks { 0 } else { libc::AT_SYMLINK_NOFOLLOW }; #[cfg(all(target_os = "linux", target_env = "gnu", target_pointer_width = "32", not(target_arch = "riscv32")))] { use crate::sys::{time::__timespec64, weak::weak}; @@ -2185,13 +2186,12 @@ fn set_times_impl(p: &CStr, times: FileTimes, flags: c_int) -> io::Result<()> { #[inline(always)] pub fn set_times(p: &CStr, times: FileTimes) -> io::Result<()> { - // flags = 0 means follow symlinks - set_times_impl(p, times, 0) + set_times_impl(p, times, true) } #[inline(always)] pub fn set_times_nofollow(p: &CStr, times: FileTimes) -> io::Result<()> { - set_times_impl(p, times, libc::AT_SYMLINK_NOFOLLOW) + set_times_impl(p, times, false) } #[cfg(target_os = "espidf")] From c4dc39be0505ef6eae4cea0d337bbe0b25593fa4 Mon Sep 17 00:00:00 2001 From: Evan Jones Date: Thu, 16 Oct 2025 09:07:57 -0400 Subject: [PATCH 18/22] add link to Builder (code review improvement) --- library/std/src/thread/mod.rs | 4 ++-- library/std/src/thread/scoped.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/library/std/src/thread/mod.rs b/library/std/src/thread/mod.rs index 78c85c0af6449..16313da8e178c 100644 --- a/library/std/src/thread/mod.rs +++ b/library/std/src/thread/mod.rs @@ -620,8 +620,8 @@ impl Builder { /// (It is the responsibility of the program to either eventually join threads it /// creates or detach them; otherwise, a resource leak will result.) /// -/// This function creates a thread with the default parameters. To specify the -/// new thread's stack size or the name, use [`Builder::spawn`]. +/// This function creates a thread with the default parameters of [`Builder`]. +/// To specify the new thread's stack size or the name, use [`Builder::spawn`]. /// /// As you can see in the signature of `spawn` there are two constraints on /// both the closure given to `spawn` and its return value, let's explain them: diff --git a/library/std/src/thread/scoped.rs b/library/std/src/thread/scoped.rs index 2368ce4988d89..75a5303fc321a 100644 --- a/library/std/src/thread/scoped.rs +++ b/library/std/src/thread/scoped.rs @@ -181,8 +181,8 @@ impl<'scope, 'env> Scope<'scope, 'env> { /// end of the scope. In that case, if the spawned thread panics, [`scope`] will /// panic after all threads are joined. /// - /// This function creates a thread with the default parameters. To specify the - /// new thread's stack size or the name, use [`Builder::spawn_scoped`]. + /// This function creates a thread with the default parameters of [`Builder`]. + /// To specify the new thread's stack size or the name, use [`Builder::spawn_scoped`]. /// /// # Panics /// From 10a533417bf30c395f8355bbe9e1b1a10511b559 Mon Sep 17 00:00:00 2001 From: Augie Fackler Date: Tue, 17 Jun 2025 14:49:53 -0400 Subject: [PATCH 19/22] bootstrap: migrate to object 0.37 I noticed we had a mix of 0.37 and 0.36 at work, and this was why. As far as I can tell everything still works fine. --- src/bootstrap/Cargo.lock | 4 ++-- src/bootstrap/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bootstrap/Cargo.lock b/src/bootstrap/Cargo.lock index fe832652d25ce..5a2adb206b856 100644 --- a/src/bootstrap/Cargo.lock +++ b/src/bootstrap/Cargo.lock @@ -491,9 +491,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.5" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] diff --git a/src/bootstrap/Cargo.toml b/src/bootstrap/Cargo.toml index 9a76a7dda2ac3..e1725db60cfca 100644 --- a/src/bootstrap/Cargo.toml +++ b/src/bootstrap/Cargo.toml @@ -41,7 +41,7 @@ clap_complete = "4.4" home = "0.5" ignore = "0.4" libc = "0.2" -object = { version = "0.36.3", default-features = false, features = ["archive", "coff", "read_core", "std", "unaligned"] } +object = { version = "0.37", default-features = false, features = ["archive", "coff", "read_core", "std", "unaligned"] } opener = "0.8" semver = "1.0" serde = "1.0" From fcc47d07a3bbe65450d212fadc044131fd292309 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 17 Oct 2025 14:44:59 +0200 Subject: [PATCH 20/22] rustdoc: Fix passes order so intra-doc links are collected after stripping passes --- src/librustdoc/passes/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs index f45df8d2d0d5c..856581e0d033e 100644 --- a/src/librustdoc/passes/mod.rs +++ b/src/librustdoc/passes/mod.rs @@ -94,11 +94,11 @@ pub(crate) const DEFAULT_PASSES: &[ConditionalPass] = &[ ConditionalPass::always(COLLECT_TRAIT_IMPLS), ConditionalPass::always(CHECK_DOC_TEST_VISIBILITY), ConditionalPass::always(CHECK_DOC_CFG), - ConditionalPass::always(COLLECT_INTRA_DOC_LINKS), ConditionalPass::always(STRIP_ALIASED_NON_LOCAL), ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden), ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate), ConditionalPass::new(STRIP_PRIV_IMPORTS, WhenDocumentPrivate), + ConditionalPass::always(COLLECT_INTRA_DOC_LINKS), ConditionalPass::always(PROPAGATE_DOC_CFG), ConditionalPass::always(PROPAGATE_STABILITY), ConditionalPass::always(RUN_LINTS), From e67d502d4e813da278ac5f3696d873598ea8a16b Mon Sep 17 00:00:00 2001 From: Osama Abdelkader Date: Fri, 17 Oct 2025 00:33:45 +0300 Subject: [PATCH 21/22] Fix autodiff incorrectly applying fat-lto to proc-macro crates When -Z autodiff=Enable is used, the compiler automatically enables fat-lto for all crates. However, proc-macro crates cannot use fat-lto without the -Zdylib-lto flag, causing compilation errors. This commit modifies the lto() method in Session to exclude proc-macro crates from fat-lto when autodiff is enabled, while preserving the existing behavior for all other crate types. The fix ensures that: - Non-proc-macro crates still get fat-lto when autodiff is enabled - Proc-macro crates are excluded from fat-lto when autodiff is enabled - Existing autodiff functionality remains unchanged for regular crates Signed-off-by: Osama Abdelkader --- compiler/rustc_session/src/session.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 172672a80fbd7..085983c523262 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -603,7 +603,8 @@ impl Session { // Autodiff currently requires fat-lto to have access to the llvm-ir of all (indirectly) used functions and types. // fat-lto is the easiest solution to this requirement, but quite expensive. // FIXME(autodiff): Make autodiff also work with embed-bc instead of fat-lto. - if self.opts.autodiff_enabled() { + // Don't apply fat-lto to proc-macro crates as they cannot use fat-lto without -Zdylib-lto + if self.opts.autodiff_enabled() && !self.opts.crate_types.contains(&CrateType::ProcMacro) { return config::Lto::Fat; } From b3bb7500b6ae0c47ec5f62ba3d104ec4f6f1f5e0 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 17 Oct 2025 14:45:09 +0200 Subject: [PATCH 22/22] Add regression tests for intra-doc links --- tests/rustdoc-ui/intra-doc/hidden-check.rs | 14 ++++++++++++++ tests/rustdoc-ui/intra-doc/hidden-check.stderr | 14 ++++++++++++++ tests/rustdoc-ui/intra-doc/private-check.rs | 14 ++++++++++++++ tests/rustdoc-ui/intra-doc/private-check.stderr | 14 ++++++++++++++ tests/rustdoc-ui/issues/issue-91713.stdout | 2 +- 5 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 tests/rustdoc-ui/intra-doc/hidden-check.rs create mode 100644 tests/rustdoc-ui/intra-doc/hidden-check.stderr create mode 100644 tests/rustdoc-ui/intra-doc/private-check.rs create mode 100644 tests/rustdoc-ui/intra-doc/private-check.stderr diff --git a/tests/rustdoc-ui/intra-doc/hidden-check.rs b/tests/rustdoc-ui/intra-doc/hidden-check.rs new file mode 100644 index 0000000000000..db2a6dd1dbda2 --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/hidden-check.rs @@ -0,0 +1,14 @@ +// This test ensures that `doc(hidden)` items intra-doc links are checked whereas private +// items are ignored. + +//@ compile-flags: -Zunstable-options --document-hidden-items + +#![deny(rustdoc::broken_intra_doc_links)] + +/// [not::exist] +//~^ ERROR unresolved link to `not::exist` +#[doc(hidden)] +pub struct X; + +/// [not::exist] +struct Y; diff --git a/tests/rustdoc-ui/intra-doc/hidden-check.stderr b/tests/rustdoc-ui/intra-doc/hidden-check.stderr new file mode 100644 index 0000000000000..4fffb80106405 --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/hidden-check.stderr @@ -0,0 +1,14 @@ +error: unresolved link to `not::exist` + --> $DIR/hidden-check.rs:8:6 + | +LL | /// [not::exist] + | ^^^^^^^^^^ no item named `not` in scope + | +note: the lint level is defined here + --> $DIR/hidden-check.rs:6:9 + | +LL | #![deny(rustdoc::broken_intra_doc_links)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/rustdoc-ui/intra-doc/private-check.rs b/tests/rustdoc-ui/intra-doc/private-check.rs new file mode 100644 index 0000000000000..2f987d1a44cf5 --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/private-check.rs @@ -0,0 +1,14 @@ +// This test ensures that private items intra-doc links are checked whereas `doc(hidden)` +// items are ignored. + +//@ compile-flags: -Zunstable-options --document-private-items + +#![deny(rustdoc::broken_intra_doc_links)] + +/// [not::exist] +#[doc(hidden)] +pub struct X; + +/// [not::exist] +//~^ ERROR unresolved link to `not::exist` +struct Y; diff --git a/tests/rustdoc-ui/intra-doc/private-check.stderr b/tests/rustdoc-ui/intra-doc/private-check.stderr new file mode 100644 index 0000000000000..2ec162809eae7 --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/private-check.stderr @@ -0,0 +1,14 @@ +error: unresolved link to `not::exist` + --> $DIR/private-check.rs:12:6 + | +LL | /// [not::exist] + | ^^^^^^^^^^ no item named `not` in scope + | +note: the lint level is defined here + --> $DIR/private-check.rs:6:9 + | +LL | #![deny(rustdoc::broken_intra_doc_links)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/rustdoc-ui/issues/issue-91713.stdout b/tests/rustdoc-ui/issues/issue-91713.stdout index d34714be6c942..7254708157f0e 100644 --- a/tests/rustdoc-ui/issues/issue-91713.stdout +++ b/tests/rustdoc-ui/issues/issue-91713.stdout @@ -16,11 +16,11 @@ Default passes for rustdoc: collect-trait-impls check_doc_test_visibility check-doc-cfg -collect-intra-doc-links strip-aliased-non-local strip-hidden (when not --document-hidden-items) strip-private (when not --document-private-items) strip-priv-imports (when --document-private-items) +collect-intra-doc-links propagate-doc-cfg propagate-stability run-lints