diff options
Diffstat (limited to 'library/std/src')
72 files changed, 1851 insertions, 585 deletions
diff --git a/library/std/src/collections/hash/map.rs b/library/std/src/collections/hash/map.rs index 109bc394634..56d734ba2fb 100644 --- a/library/std/src/collections/hash/map.rs +++ b/library/std/src/collections/hash/map.rs @@ -1446,6 +1446,11 @@ impl<K, V, const N: usize> From<[(K, V); N]> for HashMap<K, V, RandomState> where K: Eq + Hash, { + /// Converts a `[(K, V); N]` into a `HashMap<K, V>`. + /// + /// If any entries in the array have equal keys, + /// all but one of the corresponding values will be dropped. + /// /// # Examples /// /// ``` @@ -3219,6 +3224,10 @@ where K: Eq + Hash, S: BuildHasher + Default, { + /// Constructs a `HashMap<K, V>` from an iterator of key-value pairs. + /// + /// If the iterator produces any pairs with equal keys, + /// all but one of the corresponding values will be dropped. fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> HashMap<K, V, S> { let mut map = HashMap::with_hasher(Default::default()); map.extend(iter); diff --git a/library/std/src/collections/hash/set.rs b/library/std/src/collections/hash/set.rs index 4c81aaff458..bcf6d6b4ccf 100644 --- a/library/std/src/collections/hash/set.rs +++ b/library/std/src/collections/hash/set.rs @@ -1091,6 +1091,11 @@ impl<T, const N: usize> From<[T; N]> for HashSet<T, RandomState> where T: Eq + Hash, { + /// Converts a `[T; N]` into a `HashSet<T>`. + /// + /// If the array contains any equal values, + /// all but one will be dropped. + /// /// # Examples /// /// ``` diff --git a/library/std/src/env.rs b/library/std/src/env.rs index 043747d0bc5..535236ff89a 100644 --- a/library/std/src/env.rs +++ b/library/std/src/env.rs @@ -597,6 +597,13 @@ impl Error for JoinPathsError { /// Returns the path of the current user's home directory if known. /// +/// This may return `None` if getting the directory fails or if the platform does not have user home directories. +/// +/// For storing user data and configuration it is often preferable to use more specific directories. +/// For example, [XDG Base Directories] on Unix or the `LOCALAPPDATA` and `APPDATA` environment variables on Windows. +/// +/// [XDG Base Directories]: https://specifications.freedesktop.org/basedir-spec/latest/ +/// /// # Unix /// /// - Returns the value of the 'HOME' environment variable if it is set diff --git a/library/std/src/ffi/mod.rs b/library/std/src/ffi/mod.rs index 469136be883..7d7cce09a3f 100644 --- a/library/std/src/ffi/mod.rs +++ b/library/std/src/ffi/mod.rs @@ -179,19 +179,19 @@ pub use core::ffi::{ c_ulong, c_ulonglong, c_ushort, }; -#[doc(no_inline)] +#[doc(inline)] #[stable(feature = "cstr_from_bytes_until_nul", since = "1.69.0")] pub use self::c_str::FromBytesUntilNulError; -#[doc(no_inline)] +#[doc(inline)] #[stable(feature = "cstr_from_bytes", since = "1.10.0")] pub use self::c_str::FromBytesWithNulError; -#[doc(no_inline)] +#[doc(inline)] #[stable(feature = "cstring_from_vec_with_nul", since = "1.58.0")] pub use self::c_str::FromVecWithNulError; -#[doc(no_inline)] +#[doc(inline)] #[stable(feature = "cstring_into", since = "1.7.0")] pub use self::c_str::IntoStringError; -#[doc(no_inline)] +#[doc(inline)] #[stable(feature = "rust1", since = "1.0.0")] pub use self::c_str::NulError; #[doc(inline)] diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 2d5d869630e..9b752ed1443 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -1869,8 +1869,10 @@ impl Permissions { /// /// # Note /// - /// This function does not take Access Control Lists (ACLs) or Unix group - /// membership into account. + /// This function does not take Access Control Lists (ACLs), Unix group + /// membership and other nuances into account. + /// Therefore the return value of this function cannot be relied upon + /// to predict whether attempts to read or write the file will actually succeed. /// /// # Windows /// @@ -1885,10 +1887,13 @@ impl Permissions { /// # Unix (including macOS) /// /// On Unix-based platforms this checks if *any* of the owner, group or others - /// write permission bits are set. It does not check if the current - /// user is in the file's assigned group. It also does not check ACLs. - /// Therefore the return value of this function cannot be relied upon - /// to predict whether attempts to read or write the file will actually succeed. + /// write permission bits are set. It does not consider anything else, including: + /// + /// * Whether the current user is in the file's assigned group. + /// * Permissions granted by ACL. + /// * That `root` user can write to files that do not have any write bits set. + /// * Writable files on a filesystem that is mounted read-only. + /// /// The [`PermissionsExt`] trait gives direct access to the permission bits but /// also does not read ACLs. /// @@ -2397,12 +2402,14 @@ pub fn symlink_metadata<P: AsRef<Path>>(path: P) -> io::Result<Metadata> { /// # Platform-specific behavior /// /// This function currently corresponds to the `rename` function on Unix -/// and the `MoveFileEx` function with the `MOVEFILE_REPLACE_EXISTING` flag on Windows. +/// and the `SetFileInformationByHandle` function on Windows. /// /// Because of this, the behavior when both `from` and `to` exist differs. On /// Unix, if `from` is a directory, `to` must also be an (empty) directory. If -/// `from` is not a directory, `to` must also be not a directory. In contrast, -/// on Windows, `from` can be anything, but `to` must *not* be a directory. +/// `from` is not a directory, `to` must also be not a directory. The behavior +/// on Windows is the same on Windows 10 1607 and higher if `FileRenameInfoEx` +/// is supported by the filesystem; otherwise, `from` can be anything, but +/// `to` must *not* be a directory. /// /// Note that, this [may change in the future][changes]. /// diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 018e1958641..28f16da1ed8 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -1912,3 +1912,73 @@ fn test_hidden_file_truncation() { let metadata = file.metadata().unwrap(); assert_eq!(metadata.len(), 0); } + +#[cfg(windows)] +#[test] +fn test_rename_file_over_open_file() { + // Make sure that std::fs::rename works if the target file is already opened with FILE_SHARE_DELETE. See #123985. + let tmpdir = tmpdir(); + + // Create source with test data to read. + let source_path = tmpdir.join("source_file.txt"); + fs::write(&source_path, b"source hello world").unwrap(); + + // Create target file with test data to read; + let target_path = tmpdir.join("target_file.txt"); + fs::write(&target_path, b"target hello world").unwrap(); + + // Open target file + let target_file = fs::File::open(&target_path).unwrap(); + + // Rename source + fs::rename(source_path, &target_path).unwrap(); + + core::mem::drop(target_file); + assert_eq!(fs::read(target_path).unwrap(), b"source hello world"); +} + +#[test] +#[cfg(windows)] +fn test_rename_directory_to_non_empty_directory() { + // Renaming a directory over a non-empty existing directory should fail on Windows. + let tmpdir: TempDir = tmpdir(); + + let source_path = tmpdir.join("source_directory"); + let target_path = tmpdir.join("target_directory"); + + fs::create_dir(&source_path).unwrap(); + fs::create_dir(&target_path).unwrap(); + + fs::write(target_path.join("target_file.txt"), b"target hello world").unwrap(); + + error!(fs::rename(source_path, target_path), 145); // ERROR_DIR_NOT_EMPTY +} + +#[test] +fn test_rename_symlink() { + let tmpdir = tmpdir(); + let original = tmpdir.join("original"); + let dest = tmpdir.join("dest"); + let not_exist = Path::new("does not exist"); + + symlink_file(not_exist, &original).unwrap(); + fs::rename(&original, &dest).unwrap(); + // Make sure that renaming `original` to `dest` preserves the symlink. + assert_eq!(fs::read_link(&dest).unwrap().as_path(), not_exist); +} + +#[test] +#[cfg(windows)] +fn test_rename_junction() { + let tmpdir = tmpdir(); + let original = tmpdir.join("original"); + let dest = tmpdir.join("dest"); + let not_exist = Path::new("does not exist"); + + junction_point(¬_exist, &original).unwrap(); + fs::rename(&original, &dest).unwrap(); + + // Make sure that renaming `original` to `dest` preserves the junction point. + // Junction links are always absolute so we just check the file name is correct. + assert_eq!(fs::read_link(&dest).unwrap().file_name(), Some(not_exist.as_os_str())); +} diff --git a/library/std/src/io/buffered/linewritershim.rs b/library/std/src/io/buffered/linewritershim.rs index 3d04ccd1c7d..5ebeada59bb 100644 --- a/library/std/src/io/buffered/linewritershim.rs +++ b/library/std/src/io/buffered/linewritershim.rs @@ -119,7 +119,14 @@ impl<'a, W: ?Sized + Write> Write for LineWriterShim<'a, W> { // the buffer? // - If not, scan for the last newline that *does* fit in the buffer let tail = if flushed >= newline_idx { - &buf[flushed..] + let tail = &buf[flushed..]; + // Avoid unnecessary short writes by not splitting the remaining + // bytes if they're larger than the buffer. + // They can be written in full by the next call to write. + if tail.len() >= self.buffer.capacity() { + return Ok(flushed); + } + tail } else if newline_idx - flushed <= self.buffer.capacity() { &buf[flushed..newline_idx] } else { diff --git a/library/std/src/io/buffered/tests.rs b/library/std/src/io/buffered/tests.rs index bff0f823c4b..17f6107aa03 100644 --- a/library/std/src/io/buffered/tests.rs +++ b/library/std/src/io/buffered/tests.rs @@ -847,8 +847,7 @@ fn long_line_flushed() { } /// Test that, given a very long partial line *after* successfully -/// flushing a complete line, the very long partial line is buffered -/// unconditionally, and no additional writes take place. This assures +/// flushing a complete line, no additional writes take place. This assures /// the property that `write` should make at-most-one attempt to write /// new data. #[test] @@ -856,13 +855,22 @@ fn line_long_tail_not_flushed() { let writer = ProgrammableSink::default(); let mut writer = LineWriter::with_capacity(5, writer); - // Assert that Line 1\n is flushed, and 01234 is buffered - assert_eq!(writer.write(b"Line 1\n0123456789").unwrap(), 12); + // Assert that Line 1\n is flushed and the long tail isn't. + let bytes = b"Line 1\n0123456789"; + writer.write(bytes).unwrap(); assert_eq!(&writer.get_ref().buffer, b"Line 1\n"); +} + +// Test that appending to a full buffer emits a single write, flushing the buffer. +#[test] +fn line_full_buffer_flushed() { + let writer = ProgrammableSink::default(); + let mut writer = LineWriter::with_capacity(5, writer); + assert_eq!(writer.write(b"01234").unwrap(), 5); // Because the buffer is full, this subsequent write will flush it assert_eq!(writer.write(b"5").unwrap(), 1); - assert_eq!(&writer.get_ref().buffer, b"Line 1\n01234"); + assert_eq!(&writer.get_ref().buffer, b"01234"); } /// Test that, if an attempt to pre-flush buffered data returns Ok(0), diff --git a/library/std/src/io/error.rs b/library/std/src/io/error.rs index 03f38e220a5..476c403c21f 100644 --- a/library/std/src/io/error.rs +++ b/library/std/src/io/error.rs @@ -338,9 +338,9 @@ pub enum ErrorKind { /// example, on Unix, a named pipe opened with `File::open`. #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] NotSeekable, - /// Filesystem quota was exceeded. - #[unstable(feature = "io_error_more", issue = "86442")] - FilesystemQuotaExceeded, + /// Filesystem quota or some other kind of quota was exceeded. + #[stable(feature = "io_error_quota_exceeded", since = "CURRENT_RUSTC_VERSION")] + QuotaExceeded, /// File larger than allowed or supported. /// /// This might arise from a hard limit of the underlying filesystem or file access API, or from @@ -364,7 +364,7 @@ pub enum ErrorKind { #[stable(feature = "io_error_a_bit_more", since = "1.83.0")] Deadlock, /// Cross-device or cross-filesystem (hard) link or rename. - #[unstable(feature = "io_error_more", issue = "86442")] + #[stable(feature = "io_error_crosses_devices", since = "CURRENT_RUSTC_VERSION")] CrossesDevices, /// Too many (hard) links to the same filesystem object. /// @@ -446,8 +446,8 @@ pub enum ErrorKind { impl ErrorKind { pub(crate) fn as_str(&self) -> &'static str { use ErrorKind::*; - // tidy-alphabetical-start match *self { + // tidy-alphabetical-start AddrInUse => "address in use", AddrNotAvailable => "address not available", AlreadyExists => "entity already exists", @@ -460,12 +460,11 @@ impl ErrorKind { Deadlock => "deadlock", DirectoryNotEmpty => "directory not empty", ExecutableFileBusy => "executable file busy", - FileTooLarge => "file too large", FilesystemLoop => "filesystem loop or indirection limit (e.g. symlink loop)", - FilesystemQuotaExceeded => "filesystem quota exceeded", + FileTooLarge => "file too large", HostUnreachable => "host unreachable", - Interrupted => "operation interrupted", InProgress => "in progress", + Interrupted => "operation interrupted", InvalidData => "invalid data", InvalidFilename => "invalid filename", InvalidInput => "invalid input parameter", @@ -479,6 +478,7 @@ impl ErrorKind { Other => "other error", OutOfMemory => "out of memory", PermissionDenied => "permission denied", + QuotaExceeded => "quota exceeded", ReadOnlyFilesystem => "read-only filesystem or storage medium", ResourceBusy => "resource busy", StaleNetworkFileHandle => "stale network file handle", @@ -490,8 +490,8 @@ impl ErrorKind { Unsupported => "unsupported", WouldBlock => "operation would block", WriteZero => "write zero", + // tidy-alphabetical-end } - // tidy-alphabetical-end } } diff --git a/library/std/src/io/error/repr_bitpacked.rs b/library/std/src/io/error/repr_bitpacked.rs index a839a2fbac1..f958a938646 100644 --- a/library/std/src/io/error/repr_bitpacked.rs +++ b/library/std/src/io/error/repr_bitpacked.rs @@ -335,7 +335,7 @@ fn kind_from_prim(ek: u32) -> Option<ErrorKind> { WriteZero, StorageFull, NotSeekable, - FilesystemQuotaExceeded, + QuotaExceeded, FileTooLarge, ResourceBusy, ExecutableFileBusy, diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 4ffb0463006..7912f969bbd 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -1083,7 +1083,7 @@ pub trait Read { /// let f = BufReader::new(File::open("foo.txt")?); /// /// for byte in f.bytes() { - /// println!("{}", byte.unwrap()); + /// println!("{}", byte?); /// } /// Ok(()) /// } @@ -1995,15 +1995,16 @@ pub trait Seek { /// .write(true) /// .read(true) /// .create(true) - /// .open("foo.txt").unwrap(); + /// .open("foo.txt")?; /// /// let hello = "Hello!\n"; - /// write!(f, "{hello}").unwrap(); - /// f.rewind().unwrap(); + /// write!(f, "{hello}")?; + /// f.rewind()?; /// /// let mut buf = String::new(); - /// f.read_to_string(&mut buf).unwrap(); + /// f.read_to_string(&mut buf)?; /// assert_eq!(&buf, hello); + /// # std::io::Result::Ok(()) /// ``` #[stable(feature = "seek_rewind", since = "1.55.0")] fn rewind(&mut self) -> Result<()> { @@ -2212,8 +2213,9 @@ fn skip_until<R: BufRead + ?Sized>(r: &mut R, delim: u8) -> Result<usize> { /// /// let stdin = io::stdin(); /// for line in stdin.lock().lines() { -/// println!("{}", line.unwrap()); +/// println!("{}", line?); /// } +/// # std::io::Result::Ok(()) /// ``` /// /// If you have something that implements [`Read`], you can use the [`BufReader` @@ -2236,7 +2238,8 @@ fn skip_until<R: BufRead + ?Sized>(r: &mut R, delim: u8) -> Result<usize> { /// let f = BufReader::new(f); /// /// for line in f.lines() { -/// println!("{}", line.unwrap()); +/// let line = line?; +/// println!("{line}"); /// } /// /// Ok(()) @@ -2274,7 +2277,7 @@ pub trait BufRead: Read { /// let stdin = io::stdin(); /// let mut stdin = stdin.lock(); /// - /// let buffer = stdin.fill_buf().unwrap(); + /// let buffer = stdin.fill_buf()?; /// /// // work with buffer /// println!("{buffer:?}"); @@ -2282,6 +2285,7 @@ pub trait BufRead: Read { /// // ensure the bytes we worked with aren't returned again later /// let length = buffer.len(); /// stdin.consume(length); + /// # std::io::Result::Ok(()) /// ``` #[stable(feature = "rust1", since = "1.0.0")] fn fill_buf(&mut self) -> Result<&[u8]>; @@ -2327,12 +2331,13 @@ pub trait BufRead: Read { /// let stdin = io::stdin(); /// let mut stdin = stdin.lock(); /// - /// while stdin.has_data_left().unwrap() { + /// while stdin.has_data_left()? { /// let mut line = String::new(); - /// stdin.read_line(&mut line).unwrap(); + /// stdin.read_line(&mut line)?; /// // work with line /// println!("{line:?}"); /// } + /// # std::io::Result::Ok(()) /// ``` #[unstable(feature = "buf_read_has_data_left", reason = "recently added", issue = "86423")] fn has_data_left(&mut self) -> Result<bool> { diff --git a/library/std/src/keyword_docs.rs b/library/std/src/keyword_docs.rs index 4302e24781e..0c526eafdf3 100644 --- a/library/std/src/keyword_docs.rs +++ b/library/std/src/keyword_docs.rs @@ -807,64 +807,6 @@ mod in_keyword {} /// [Reference]: ../reference/statements.html#let-statements mod let_keyword {} -#[doc(keyword = "while")] -// -/// Loop while a condition is upheld. -/// -/// A `while` expression is used for predicate loops. The `while` expression runs the conditional -/// expression before running the loop body, then runs the loop body if the conditional -/// expression evaluates to `true`, or exits the loop otherwise. -/// -/// ```rust -/// let mut counter = 0; -/// -/// while counter < 10 { -/// println!("{counter}"); -/// counter += 1; -/// } -/// ``` -/// -/// Like the [`for`] expression, we can use `break` and `continue`. A `while` expression -/// cannot break with a value and always evaluates to `()` unlike [`loop`]. -/// -/// ```rust -/// let mut i = 1; -/// -/// while i < 100 { -/// i *= 2; -/// if i == 64 { -/// break; // Exit when `i` is 64. -/// } -/// } -/// ``` -/// -/// As `if` expressions have their pattern matching variant in `if let`, so too do `while` -/// expressions with `while let`. The `while let` expression matches the pattern against the -/// expression, then runs the loop body if pattern matching succeeds, or exits the loop otherwise. -/// We can use `break` and `continue` in `while let` expressions just like in `while`. -/// -/// ```rust -/// let mut counter = Some(0); -/// -/// while let Some(i) = counter { -/// if i == 10 { -/// counter = None; -/// } else { -/// println!("{i}"); -/// counter = Some (i + 1); -/// } -/// } -/// ``` -/// -/// For more information on `while` and loops in general, see the [reference]. -/// -/// See also, [`for`], [`loop`]. -/// -/// [`for`]: keyword.for.html -/// [`loop`]: keyword.loop.html -/// [reference]: ../reference/expressions/loop-expr.html#predicate-loops -mod while_keyword {} - #[doc(keyword = "loop")] // /// Loop indefinitely. @@ -1321,10 +1263,10 @@ mod return_keyword {} /// [Reference]: ../reference/items/associated-items.html#methods mod self_keyword {} -// FIXME: Once rustdoc can handle URL conflicts on case insensitive file systems, we can remove the -// three next lines and put back: `#[doc(keyword = "Self")]`. +// FIXME: Once rustdoc can handle URL conflicts on case insensitive file systems, we can replace +// these two lines with `#[doc(keyword = "Self")]` and update `is_doc_keyword` in +// `CheckAttrVisitor`. #[doc(alias = "Self")] -#[allow(rustc::existing_doc_keyword)] #[doc(keyword = "SelfTy")] // /// The implementing type within a [`trait`] or [`impl`] block, or the current type within a type @@ -2343,6 +2285,64 @@ mod use_keyword {} /// [RFC]: https://github.com/rust-lang/rfcs/blob/master/text/0135-where.md mod where_keyword {} +#[doc(keyword = "while")] +// +/// Loop while a condition is upheld. +/// +/// A `while` expression is used for predicate loops. The `while` expression runs the conditional +/// expression before running the loop body, then runs the loop body if the conditional +/// expression evaluates to `true`, or exits the loop otherwise. +/// +/// ```rust +/// let mut counter = 0; +/// +/// while counter < 10 { +/// println!("{counter}"); +/// counter += 1; +/// } +/// ``` +/// +/// Like the [`for`] expression, we can use `break` and `continue`. A `while` expression +/// cannot break with a value and always evaluates to `()` unlike [`loop`]. +/// +/// ```rust +/// let mut i = 1; +/// +/// while i < 100 { +/// i *= 2; +/// if i == 64 { +/// break; // Exit when `i` is 64. +/// } +/// } +/// ``` +/// +/// As `if` expressions have their pattern matching variant in `if let`, so too do `while` +/// expressions with `while let`. The `while let` expression matches the pattern against the +/// expression, then runs the loop body if pattern matching succeeds, or exits the loop otherwise. +/// We can use `break` and `continue` in `while let` expressions just like in `while`. +/// +/// ```rust +/// let mut counter = Some(0); +/// +/// while let Some(i) = counter { +/// if i == 10 { +/// counter = None; +/// } else { +/// println!("{i}"); +/// counter = Some (i + 1); +/// } +/// } +/// ``` +/// +/// For more information on `while` and loops in general, see the [reference]. +/// +/// See also, [`for`], [`loop`]. +/// +/// [`for`]: keyword.for.html +/// [`loop`]: keyword.loop.html +/// [reference]: ../reference/expressions/loop-expr.html#predicate-loops +mod while_keyword {} + // 2018 Edition keywords #[doc(alias = "promise")] diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 6be27b283b2..2f8f5c5c581 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -89,7 +89,7 @@ //! Check out the Rust contribution guidelines [here]( //! https://rustc-dev-guide.rust-lang.org/contributing.html#writing-documentation). //! The source for this documentation can be found on -//! [GitHub](https://github.com/rust-lang/rust). +//! [GitHub](https://github.com/rust-lang/rust) in the 'library/std/' directory. //! To contribute changes, make sure you read the guidelines first, then submit //! pull-requests for your suggested changes. //! @@ -251,7 +251,6 @@ #![allow(explicit_outlives_requirements)] #![allow(unused_lifetimes)] #![allow(internal_features)] -#![deny(rustc::existing_doc_keyword)] #![deny(fuzzy_provenance_casts)] #![deny(unsafe_op_in_unsafe_fn)] #![allow(rustdoc::redundant_explicit_links)] @@ -292,6 +291,7 @@ #![feature(dropck_eyepatch)] #![feature(f128)] #![feature(f16)] +#![feature(formatting_options)] #![feature(if_let_guard)] #![feature(intra_doc_pointers)] #![feature(lang_items)] @@ -347,7 +347,6 @@ #![feature(pin_coerce_unsized_trait)] #![feature(pointer_is_aligned_to)] #![feature(portable_simd)] -#![feature(prelude_2024)] #![feature(ptr_as_uninit)] #![feature(ptr_mask)] #![feature(random)] @@ -373,6 +372,7 @@ #![feature(thin_box)] #![feature(try_reserve_kind)] #![feature(try_with_capacity)] +#![feature(unique_rc_arc)] #![feature(vec_into_raw_parts)] // tidy-alphabetical-end // @@ -544,6 +544,8 @@ pub use core::u64; #[stable(feature = "i128", since = "1.26.0")] #[allow(deprecated, deprecated_in_future)] pub use core::u128; +#[unstable(feature = "unsafe_binders", issue = "130516")] +pub use core::unsafe_binder; #[stable(feature = "rust1", since = "1.0.0")] #[allow(deprecated, deprecated_in_future)] pub use core::usize; diff --git a/library/std/src/os/emscripten/fs.rs b/library/std/src/os/emscripten/fs.rs index 3282b79ac1c..81f9ef331a5 100644 --- a/library/std/src/os/emscripten/fs.rs +++ b/library/std/src/os/emscripten/fs.rs @@ -63,7 +63,7 @@ pub trait MetadataExt { impl MetadataExt for Metadata { #[allow(deprecated)] fn as_raw_stat(&self) -> &raw::stat { - unsafe { &*(self.as_inner().as_inner() as *const libc::stat64 as *const raw::stat) } + unsafe { &*(self.as_inner().as_inner() as *const libc::stat as *const raw::stat) } } fn st_dev(&self) -> u64 { self.as_inner().as_inner().st_dev as u64 diff --git a/library/std/src/os/emscripten/raw.rs b/library/std/src/os/emscripten/raw.rs index d23011c7381..7ae8c45a6f8 100644 --- a/library/std/src/os/emscripten/raw.rs +++ b/library/std/src/os/emscripten/raw.rs @@ -1,6 +1,4 @@ //! Emscripten-specific raw type definitions -//! This is basically exactly the same as the linux definitions, -//! except using the musl-specific stat64 structure in liblibc. #![stable(feature = "raw_ext", since = "1.1.0")] #![deprecated( diff --git a/library/std/src/os/fd/owned.rs b/library/std/src/os/fd/owned.rs index 388b8a88a1a..abb13b75f50 100644 --- a/library/std/src/os/fd/owned.rs +++ b/library/std/src/os/fd/owned.rs @@ -428,6 +428,14 @@ impl<T: AsFd + ?Sized> AsFd for crate::rc::Rc<T> { } } +#[unstable(feature = "unique_rc_arc", issue = "112566")] +impl<T: AsFd + ?Sized> AsFd for crate::rc::UniqueRc<T> { + #[inline] + fn as_fd(&self) -> BorrowedFd<'_> { + (**self).as_fd() + } +} + #[stable(feature = "asfd_ptrs", since = "1.64.0")] impl<T: AsFd + ?Sized> AsFd for Box<T> { #[inline] diff --git a/library/std/src/os/fd/raw.rs b/library/std/src/os/fd/raw.rs index 0d99d5492a2..22f5528248a 100644 --- a/library/std/src/os/fd/raw.rs +++ b/library/std/src/os/fd/raw.rs @@ -266,6 +266,14 @@ impl<T: AsRawFd> AsRawFd for crate::rc::Rc<T> { } } +#[unstable(feature = "unique_rc_arc", issue = "112566")] +impl<T: AsRawFd + ?Sized> AsRawFd for crate::rc::UniqueRc<T> { + #[inline] + fn as_raw_fd(&self) -> RawFd { + (**self).as_raw_fd() + } +} + #[stable(feature = "asrawfd_ptrs", since = "1.63.0")] impl<T: AsRawFd> AsRawFd for Box<T> { #[inline] diff --git a/library/std/src/os/hurd/mod.rs b/library/std/src/os/hurd/mod.rs index aee86c7f616..6cd50aeada1 100644 --- a/library/std/src/os/hurd/mod.rs +++ b/library/std/src/os/hurd/mod.rs @@ -1,6 +1,7 @@ //! Hurd-specific definitions #![stable(feature = "raw_ext", since = "1.1.0")] +#![forbid(unsafe_op_in_unsafe_fn)] pub mod fs; pub mod raw; diff --git a/library/std/src/os/windows/io/handle.rs b/library/std/src/os/windows/io/handle.rs index a4fa94e2b96..76f5f549dd2 100644 --- a/library/std/src/os/windows/io/handle.rs +++ b/library/std/src/os/windows/io/handle.rs @@ -485,6 +485,14 @@ impl<T: AsHandle + ?Sized> AsHandle for crate::rc::Rc<T> { } } +#[unstable(feature = "unique_rc_arc", issue = "112566")] +impl<T: AsHandle + ?Sized> AsHandle for crate::rc::UniqueRc<T> { + #[inline] + fn as_handle(&self) -> BorrowedHandle<'_> { + (**self).as_handle() + } +} + #[stable(feature = "as_windows_ptrs", since = "1.71.0")] impl<T: AsHandle + ?Sized> AsHandle for Box<T> { #[inline] diff --git a/library/std/src/os/windows/io/socket.rs b/library/std/src/os/windows/io/socket.rs index 272641ea6c7..c6d7bad9440 100644 --- a/library/std/src/os/windows/io/socket.rs +++ b/library/std/src/os/windows/io/socket.rs @@ -279,6 +279,14 @@ impl<T: AsSocket> AsSocket for crate::rc::Rc<T> { } } +#[unstable(feature = "unique_rc_arc", issue = "112566")] +impl<T: AsSocket + ?Sized> AsSocket for crate::rc::UniqueRc<T> { + #[inline] + fn as_socket(&self) -> BorrowedSocket<'_> { + (**self).as_socket() + } +} + #[stable(feature = "as_windows_ptrs", since = "1.71.0")] impl<T: AsSocket> AsSocket for Box<T> { #[inline] diff --git a/library/std/src/os/windows/process.rs b/library/std/src/os/windows/process.rs index c2830d2eb61..0277b79b8b6 100644 --- a/library/std/src/os/windows/process.rs +++ b/library/std/src/os/windows/process.rs @@ -4,13 +4,14 @@ #![stable(feature = "process_extensions", since = "1.2.0")] -use crate::ffi::OsStr; +use crate::ffi::{OsStr, c_void}; +use crate::mem::MaybeUninit; use crate::os::windows::io::{ AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, IntoRawHandle, OwnedHandle, RawHandle, }; use crate::sealed::Sealed; use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; -use crate::{process, sys}; +use crate::{io, marker, process, ptr, sys}; #[stable(feature = "process_extensions", since = "1.2.0")] impl FromRawHandle for process::Stdio { @@ -295,41 +296,25 @@ pub trait CommandExt: Sealed { #[unstable(feature = "windows_process_extensions_async_pipes", issue = "98289")] fn async_pipes(&mut self, always_async: bool) -> &mut process::Command; - /// Set a raw attribute on the command, providing extended configuration options for Windows - /// processes. + /// Executes the command as a child process with the given + /// [`ProcThreadAttributeList`], returning a handle to it. /// - /// This method allows you to specify custom attributes for a child process on Windows systems - /// using raw attribute values. Raw attributes provide extended configurability for process - /// creation, but their usage can be complex and potentially unsafe. - /// - /// The `attribute` parameter specifies the raw attribute to be set, while the `value` - /// parameter holds the value associated with that attribute. Please refer to the - /// [`windows-rs` documentation] or the [Win32 API documentation] for detailed information - /// about available attributes and their meanings. - /// - /// [`windows-rs` documentation]: https://microsoft.github.io/windows-docs-rs/doc/windows/ - /// [Win32 API documentation]: https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute + /// This method enables the customization of attributes for the spawned + /// child process on Windows systems. + /// Attributes offer extended configurability for process creation, + /// but their usage can be intricate and potentially unsafe. /// /// # Note /// - /// The maximum number of raw attributes is the value of [`u32::MAX`]. - /// If this limit is exceeded, the call to [`process::Command::spawn`] will return an `Error` - /// indicating that the maximum number of attributes has been exceeded. - /// - /// # Safety - /// - /// The usage of raw attributes is potentially unsafe and should be done with caution. - /// Incorrect attribute values or improper configuration can lead to unexpected behavior or - /// errors. + /// By default, stdin, stdout, and stderr are inherited from the parent + /// process. /// /// # Example /// - /// The following example demonstrates how to create a child process with a specific parent - /// process ID using a raw attribute. - /// - /// ```rust + /// ``` /// #![feature(windows_process_extensions_raw_attribute)] - /// use std::os::windows::{process::CommandExt, io::AsRawHandle}; + /// use std::os::windows::io::AsRawHandle; + /// use std::os::windows::process::{CommandExt, ProcThreadAttributeList}; /// use std::process::Command; /// /// # struct ProcessDropGuard(std::process::Child); @@ -338,36 +323,27 @@ pub trait CommandExt: Sealed { /// # let _ = self.0.kill(); /// # } /// # } - /// + /// # /// let parent = Command::new("cmd").spawn()?; - /// - /// let mut child_cmd = Command::new("cmd"); + /// let parent_process_handle = parent.as_raw_handle(); + /// # let parent = ProcessDropGuard(parent); /// /// const PROC_THREAD_ATTRIBUTE_PARENT_PROCESS: usize = 0x00020000; + /// let mut attribute_list = ProcThreadAttributeList::build() + /// .attribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &parent_process_handle) + /// .finish() + /// .unwrap(); /// - /// unsafe { - /// child_cmd.raw_attribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, parent.as_raw_handle() as isize); - /// } + /// let mut child = Command::new("cmd").spawn_with_attributes(&attribute_list)?; /// # - /// # let parent = ProcessDropGuard(parent); - /// - /// let mut child = child_cmd.spawn()?; - /// /// # child.kill()?; /// # Ok::<(), std::io::Error>(()) /// ``` - /// - /// # Safety Note - /// - /// Remember that improper use of raw attributes can lead to undefined behavior or security - /// vulnerabilities. Always consult the documentation and ensure proper attribute values are - /// used. #[unstable(feature = "windows_process_extensions_raw_attribute", issue = "114854")] - unsafe fn raw_attribute<T: Copy + Send + Sync + 'static>( + fn spawn_with_attributes( &mut self, - attribute: usize, - value: T, - ) -> &mut process::Command; + attribute_list: &ProcThreadAttributeList<'_>, + ) -> io::Result<process::Child>; } #[stable(feature = "windows_process_extensions", since = "1.16.0")] @@ -401,13 +377,13 @@ impl CommandExt for process::Command { self } - unsafe fn raw_attribute<T: Copy + Send + Sync + 'static>( + fn spawn_with_attributes( &mut self, - attribute: usize, - value: T, - ) -> &mut process::Command { - unsafe { self.as_inner_mut().raw_attribute(attribute, value) }; - self + attribute_list: &ProcThreadAttributeList<'_>, + ) -> io::Result<process::Child> { + self.as_inner_mut() + .spawn_with_attributes(sys::process::Stdio::Inherit, true, Some(attribute_list)) + .map(process::Child::from_inner) } } @@ -447,3 +423,245 @@ impl ExitCodeExt for process::ExitCode { process::ExitCode::from_inner(From::from(raw)) } } + +/// A wrapper around windows [`ProcThreadAttributeList`][1]. +/// +/// [1]: <https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-initializeprocthreadattributelist> +#[derive(Debug)] +#[unstable(feature = "windows_process_extensions_raw_attribute", issue = "114854")] +pub struct ProcThreadAttributeList<'a> { + attribute_list: Box<[MaybeUninit<u8>]>, + _lifetime_marker: marker::PhantomData<&'a ()>, +} + +#[unstable(feature = "windows_process_extensions_raw_attribute", issue = "114854")] +impl<'a> ProcThreadAttributeList<'a> { + /// Creates a new builder for constructing a [`ProcThreadAttributeList`]. + pub fn build() -> ProcThreadAttributeListBuilder<'a> { + ProcThreadAttributeListBuilder::new() + } + + /// Returns a pointer to the underling attribute list. + #[doc(hidden)] + pub fn as_ptr(&self) -> *const MaybeUninit<u8> { + self.attribute_list.as_ptr() + } +} + +#[unstable(feature = "windows_process_extensions_raw_attribute", issue = "114854")] +impl<'a> Drop for ProcThreadAttributeList<'a> { + /// Deletes the attribute list. + /// + /// This method calls [`DeleteProcThreadAttributeList`][1] to delete the + /// underlying attribute list. + /// + /// [1]: <https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-deleteprocthreadattributelist> + fn drop(&mut self) { + let lp_attribute_list = self.attribute_list.as_mut_ptr().cast::<c_void>(); + unsafe { sys::c::DeleteProcThreadAttributeList(lp_attribute_list) } + } +} + +/// Builder for constructing a [`ProcThreadAttributeList`]. +#[derive(Clone, Debug)] +#[unstable(feature = "windows_process_extensions_raw_attribute", issue = "114854")] +pub struct ProcThreadAttributeListBuilder<'a> { + attributes: alloc::collections::BTreeMap<usize, ProcThreadAttributeValue>, + _lifetime_marker: marker::PhantomData<&'a ()>, +} + +#[unstable(feature = "windows_process_extensions_raw_attribute", issue = "114854")] +impl<'a> ProcThreadAttributeListBuilder<'a> { + fn new() -> Self { + ProcThreadAttributeListBuilder { + attributes: alloc::collections::BTreeMap::new(), + _lifetime_marker: marker::PhantomData, + } + } + + /// Sets an attribute on the attribute list. + /// + /// The `attribute` parameter specifies the raw attribute to be set, while + /// the `value` parameter holds the value associated with that attribute. + /// Please refer to the [Windows documentation][1] for a list of valid attributes. + /// + /// # Note + /// + /// The maximum number of attributes is the value of [`u32::MAX`]. If this + /// limit is exceeded, the call to [`Self::finish`] will return an `Error` + /// indicating that the maximum number of attributes has been exceeded. + /// + /// # Safety Note + /// + /// Remember that improper use of attributes can lead to undefined behavior + /// or security vulnerabilities. Always consult the documentation and ensure + /// proper attribute values are used. + /// + /// [1]: <https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute#parameters> + pub fn attribute<T>(self, attribute: usize, value: &'a T) -> Self { + unsafe { + self.raw_attribute( + attribute, + ptr::addr_of!(*value).cast::<c_void>(), + crate::mem::size_of::<T>(), + ) + } + } + + /// Sets a raw attribute on the attribute list. + /// + /// This function is useful for setting attributes with pointers or sizes + /// that cannot be derived directly from their values. + /// + /// # Safety + /// + /// This function is marked as `unsafe` because it deals with raw pointers + /// and sizes. It is the responsibility of the caller to ensure the value + /// lives longer than the resulting [`ProcThreadAttributeList`] as well as + /// the validity of the size parameter. + /// + /// # Example + /// + /// ``` + /// #![feature(windows_process_extensions_raw_attribute)] + /// use std::ffi::c_void; + /// use std::os::windows::process::{CommandExt, ProcThreadAttributeList}; + /// use std::os::windows::raw::HANDLE; + /// use std::process::Command; + /// + /// #[repr(C)] + /// pub struct COORD { + /// pub X: i16, + /// pub Y: i16, + /// } + /// + /// extern "system" { + /// fn CreatePipe( + /// hreadpipe: *mut HANDLE, + /// hwritepipe: *mut HANDLE, + /// lppipeattributes: *const c_void, + /// nsize: u32, + /// ) -> i32; + /// fn CreatePseudoConsole( + /// size: COORD, + /// hinput: HANDLE, + /// houtput: HANDLE, + /// dwflags: u32, + /// phpc: *mut isize, + /// ) -> i32; + /// fn CloseHandle(hobject: HANDLE) -> i32; + /// } + /// + /// let [mut input_read_side, mut output_write_side, mut output_read_side, mut input_write_side] = + /// [unsafe { std::mem::zeroed::<HANDLE>() }; 4]; + /// + /// unsafe { + /// CreatePipe(&mut input_read_side, &mut input_write_side, std::ptr::null(), 0); + /// CreatePipe(&mut output_read_side, &mut output_write_side, std::ptr::null(), 0); + /// } + /// + /// let size = COORD { X: 60, Y: 40 }; + /// let mut h_pc = unsafe { std::mem::zeroed() }; + /// unsafe { CreatePseudoConsole(size, input_read_side, output_write_side, 0, &mut h_pc) }; + /// + /// unsafe { CloseHandle(input_read_side) }; + /// unsafe { CloseHandle(output_write_side) }; + /// + /// const PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE: usize = 131094; + /// + /// let attribute_list = unsafe { + /// ProcThreadAttributeList::build() + /// .raw_attribute( + /// PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, + /// h_pc as *const c_void, + /// std::mem::size_of::<isize>(), + /// ) + /// .finish()? + /// }; + /// + /// let mut child = Command::new("cmd").spawn_with_attributes(&attribute_list)?; + /// # + /// # child.kill()?; + /// # Ok::<(), std::io::Error>(()) + /// ``` + pub unsafe fn raw_attribute<T>( + mut self, + attribute: usize, + value_ptr: *const T, + value_size: usize, + ) -> Self { + self.attributes.insert(attribute, ProcThreadAttributeValue { + ptr: value_ptr.cast::<c_void>(), + size: value_size, + }); + self + } + + /// Finalizes the construction of the `ProcThreadAttributeList`. + /// + /// # Errors + /// + /// Returns an error if the maximum number of attributes is exceeded + /// or if there is an I/O error during initialization. + pub fn finish(&self) -> io::Result<ProcThreadAttributeList<'a>> { + // To initialize our ProcThreadAttributeList, we need to determine + // how many bytes to allocate for it. The Windows API simplifies this + // process by allowing us to call `InitializeProcThreadAttributeList` + // with a null pointer to retrieve the required size. + let mut required_size = 0; + let Ok(attribute_count) = self.attributes.len().try_into() else { + return Err(io::const_error!( + io::ErrorKind::InvalidInput, + "maximum number of ProcThreadAttributes exceeded", + )); + }; + unsafe { + sys::c::InitializeProcThreadAttributeList( + ptr::null_mut(), + attribute_count, + 0, + &mut required_size, + ) + }; + + let mut attribute_list = vec![MaybeUninit::uninit(); required_size].into_boxed_slice(); + + // Once we've allocated the necessary memory, it's safe to invoke + // `InitializeProcThreadAttributeList` to properly initialize the list. + sys::cvt(unsafe { + sys::c::InitializeProcThreadAttributeList( + attribute_list.as_mut_ptr().cast::<c_void>(), + attribute_count, + 0, + &mut required_size, + ) + })?; + + // # Add our attributes to the buffer. + // It's theoretically possible for the attribute count to exceed a u32 + // value. Therefore, we ensure that we don't add more attributes than + // the buffer was initialized for. + for (&attribute, value) in self.attributes.iter().take(attribute_count as usize) { + sys::cvt(unsafe { + sys::c::UpdateProcThreadAttribute( + attribute_list.as_mut_ptr().cast::<c_void>(), + 0, + attribute, + value.ptr, + value.size, + ptr::null_mut(), + ptr::null_mut(), + ) + })?; + } + + Ok(ProcThreadAttributeList { attribute_list, _lifetime_marker: marker::PhantomData }) + } +} + +/// Wrapper around the value data to be used as a Process Thread Attribute. +#[derive(Clone, Debug)] +struct ProcThreadAttributeValue { + ptr: *const c_void, + size: usize, +} diff --git a/library/std/src/panicking.rs b/library/std/src/panicking.rs index 97f800dddaa..3b02254548b 100644 --- a/library/std/src/panicking.rs +++ b/library/std/src/panicking.rs @@ -81,7 +81,9 @@ extern "C" fn __rust_foreign_exception() -> ! { rtabort!("Rust cannot catch foreign exceptions"); } +#[derive(Default)] enum Hook { + #[default] Default, Custom(Box<dyn Fn(&PanicHookInfo<'_>) + 'static + Sync + Send>), } @@ -96,13 +98,6 @@ impl Hook { } } -impl Default for Hook { - #[inline] - fn default() -> Hook { - Hook::Default - } -} - static HOOK: RwLock<Hook> = RwLock::new(Hook::Default); /// Registers a custom panic hook, replacing the previously registered hook. @@ -271,7 +266,23 @@ fn default_hook(info: &PanicHookInfo<'_>) { // Use a lock to prevent mixed output in multithreading context. // Some platforms also require it when printing a backtrace, like `SymFromAddr` on Windows. let mut lock = backtrace::lock(); - let _ = writeln!(err, "thread '{name}' panicked at {location}:\n{msg}"); + // Try to write the panic message to a buffer first to prevent other concurrent outputs + // interleaving with it. + let mut buffer = [0u8; 512]; + let mut cursor = crate::io::Cursor::new(&mut buffer[..]); + + let write_msg = |dst: &mut dyn crate::io::Write| { + // We add a newline to ensure the panic message appears at the start of a line. + writeln!(dst, "\nthread '{name}' panicked at {location}:\n{msg}") + }; + + if write_msg(&mut cursor).is_ok() { + let pos = cursor.position() as usize; + let _ = err.write_all(&buffer[0..pos]); + } else { + // The message did not fit into the buffer, write it directly instead. + let _ = write_msg(err); + }; static FIRST_PANIC: AtomicBool = AtomicBool::new(true); @@ -623,7 +634,7 @@ pub fn begin_panic_handler(info: &core::panic::PanicInfo<'_>) -> ! { // Lazily, the first time this gets called, run the actual string formatting. self.string.get_or_insert_with(|| { let mut s = String::new(); - let mut fmt = fmt::Formatter::new(&mut s); + let mut fmt = fmt::Formatter::new(&mut s, fmt::FormattingOptions::new()); let _err = fmt::Display::fmt(&inner, &mut fmt); s }) diff --git a/library/std/src/path.rs b/library/std/src/path.rs index 5b277a982ee..35e920ab344 100644 --- a/library/std/src/path.rs +++ b/library/std/src/path.rs @@ -2327,7 +2327,9 @@ impl Path { // FIXME: Allow Redox prefixes self.has_root() || has_redox_scheme(self.as_u8_slice()) } else { - self.has_root() && (cfg!(any(unix, target_os = "wasi")) || self.prefix().is_some()) + self.has_root() + && (cfg!(any(unix, target_os = "hermit", target_os = "wasi")) + || self.prefix().is_some()) } } diff --git a/library/std/src/pipe.rs b/library/std/src/pipe.rs index 891032e94a6..06f3fd9fdff 100644 --- a/library/std/src/pipe.rs +++ b/library/std/src/pipe.rs @@ -1,20 +1,66 @@ -//! Module for anonymous pipe +//! A cross-platform anonymous pipe. //! -//! ``` -//! #![feature(anonymous_pipe)] +//! This module provides support for anonymous OS pipes, like [pipe] on Linux or [CreatePipe] on +//! Windows. +//! +//! # Behavior +//! +//! A pipe is a synchronous, unidirectional data channel between two or more processes, like an +//! interprocess [`mpsc`](crate::sync::mpsc) provided by the OS. In particular: +//! +//! * A read on a [`PipeReader`] blocks until the pipe is non-empty. +//! * A write on a [`PipeWriter`] blocks when the pipe is full. +//! * When all copies of a [`PipeWriter`] are closed, a read on the corresponding [`PipeReader`] +//! returns EOF. +//! * [`PipeReader`] can be shared, but only one process will consume the data in the pipe. +//! +//! # Capacity +//! +//! Pipe capacity is platform dependent. To quote the Linux [man page]: +//! +//! > Different implementations have different limits for the pipe capacity. Applications should +//! > not rely on a particular capacity: an application should be designed so that a reading process +//! > consumes data as soon as it is available, so that a writing process does not remain blocked. //! +//! # Examples +//! +//! ```no_run +//! #![feature(anonymous_pipe)] //! # #[cfg(miri)] fn main() {} //! # #[cfg(not(miri))] //! # fn main() -> std::io::Result<()> { -//! let (reader, writer) = std::pipe::pipe()?; +//! # use std::process::Command; +//! # use std::io::{Read, Write}; +//! let (ping_rx, mut ping_tx) = std::pipe::pipe()?; +//! let (mut pong_rx, pong_tx) = std::pipe::pipe()?; +//! +//! // Spawn a process that echoes its input. +//! let mut echo_server = Command::new("cat").stdin(ping_rx).stdout(pong_tx).spawn()?; +//! +//! ping_tx.write_all(b"hello")?; +//! // Close to unblock echo_server's reader. +//! drop(ping_tx); +//! +//! let mut buf = String::new(); +//! // Block until echo_server's writer is closed. +//! pong_rx.read_to_string(&mut buf)?; +//! assert_eq!(&buf, "hello"); +//! +//! echo_server.wait()?; //! # Ok(()) //! # } //! ``` - +//! [pipe]: https://man7.org/linux/man-pages/man2/pipe.2.html +//! [CreatePipe]: https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe +//! [man page]: https://man7.org/linux/man-pages/man7/pipe.7.html use crate::io; use crate::sys::anonymous_pipe::{AnonPipe, pipe as pipe_inner}; /// Create anonymous pipe that is close-on-exec and blocking. +/// +/// # Examples +/// +/// See the [module-level](crate::pipe) documentation for examples. #[unstable(feature = "anonymous_pipe", issue = "127154")] #[inline] pub fn pipe() -> io::Result<(PipeReader, PipeWriter)> { @@ -33,6 +79,58 @@ pub struct PipeWriter(pub(crate) AnonPipe); impl PipeReader { /// Create a new [`PipeReader`] instance that shares the same underlying file description. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(anonymous_pipe)] + /// # #[cfg(miri)] fn main() {} + /// # #[cfg(not(miri))] + /// # fn main() -> std::io::Result<()> { + /// # use std::fs; + /// # use std::io::Write; + /// # use std::process::Command; + /// const NUM_SLOT: u8 = 2; + /// const NUM_PROC: u8 = 5; + /// const OUTPUT: &str = "work.txt"; + /// + /// let mut jobs = vec![]; + /// let (reader, mut writer) = std::pipe::pipe()?; + /// + /// // Write NUM_SLOT characters the the pipe. + /// writer.write_all(&[b'|'; NUM_SLOT as usize])?; + /// + /// // Spawn several processes that read a character from the pipe, do some work, then + /// // write back to the pipe. When the pipe is empty, the processes block, so only + /// // NUM_SLOT processes can be working at any given time. + /// for _ in 0..NUM_PROC { + /// jobs.push( + /// Command::new("bash") + /// .args(["-c", + /// &format!( + /// "read -n 1\n\ + /// echo -n 'x' >> '{OUTPUT}'\n\ + /// echo -n '|'", + /// ), + /// ]) + /// .stdin(reader.try_clone()?) + /// .stdout(writer.try_clone()?) + /// .spawn()?, + /// ); + /// } + /// + /// // Wait for all jobs to finish. + /// for mut job in jobs { + /// job.wait()?; + /// } + /// + /// // Check our work and clean up. + /// let xs = fs::read_to_string(OUTPUT)?; + /// fs::remove_file(OUTPUT)?; + /// assert_eq!(xs, "x".repeat(NUM_PROC.into())); + /// # Ok(()) + /// # } + /// ``` #[unstable(feature = "anonymous_pipe", issue = "127154")] pub fn try_clone(&self) -> io::Result<Self> { self.0.try_clone().map(Self) @@ -41,6 +139,38 @@ impl PipeReader { impl PipeWriter { /// Create a new [`PipeWriter`] instance that shares the same underlying file description. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(anonymous_pipe)] + /// # #[cfg(miri)] fn main() {} + /// # #[cfg(not(miri))] + /// # fn main() -> std::io::Result<()> { + /// # use std::process::Command; + /// # use std::io::Read; + /// let (mut reader, writer) = std::pipe::pipe()?; + /// + /// // Spawn a process that writes to stdout and stderr. + /// let mut peer = Command::new("bash") + /// .args([ + /// "-c", + /// "echo -n foo\n\ + /// echo -n bar >&2" + /// ]) + /// .stdout(writer.try_clone()?) + /// .stderr(writer) + /// .spawn()?; + /// + /// // Read and check the result. + /// let mut msg = String::new(); + /// reader.read_to_string(&mut msg)?; + /// assert_eq!(&msg, "foobar"); + /// + /// peer.wait()?; + /// # Ok(()) + /// # } + /// ``` #[unstable(feature = "anonymous_pipe", issue = "127154")] pub fn try_clone(&self) -> io::Result<Self> { self.0.try_clone().map(Self) diff --git a/library/std/src/prelude/common.rs b/library/std/src/prelude/common.rs index e4731280ffe..22a364074c5 100644 --- a/library/std/src/prelude/common.rs +++ b/library/std/src/prelude/common.rs @@ -12,7 +12,8 @@ pub use crate::marker::{Send, Sized, Sync, Unpin}; #[stable(feature = "rust1", since = "1.0.0")] #[doc(no_inline)] pub use crate::ops::{Drop, Fn, FnMut, FnOnce}; -#[unstable(feature = "async_closure", issue = "62290")] +#[cfg_attr(bootstrap, unstable(feature = "async_closure", issue = "62290"))] +#[cfg_attr(not(bootstrap), stable(feature = "async_closure", since = "CURRENT_RUSTC_VERSION"))] #[doc(no_inline)] pub use crate::ops::{AsyncFn, AsyncFnMut, AsyncFnOnce}; diff --git a/library/std/src/prelude/mod.rs b/library/std/src/prelude/mod.rs index 0c610ba67e6..64349987fcf 100644 --- a/library/std/src/prelude/mod.rs +++ b/library/std/src/prelude/mod.rs @@ -25,6 +25,7 @@ //! //! # Prelude contents //! +//! The items included in the prelude depend on the edition of the crate. //! The first version of the prelude is used in Rust 2015 and Rust 2018, //! and lives in [`std::prelude::v1`]. //! [`std::prelude::rust_2015`] and [`std::prelude::rust_2018`] re-export this prelude. @@ -32,8 +33,9 @@ //! //! * <code>[std::marker]::{[Copy], [Send], [Sized], [Sync], [Unpin]}</code>, //! marker traits that indicate fundamental properties of types. -//! * <code>[std::ops]::{[Drop], [Fn], [FnMut], [FnOnce]}</code>, various -//! operations for both destructors and overloading `()`. +//! * <code>[std::ops]::{[Fn], [FnMut], [FnOnce]}</code>, and their analogous +//! async traits, <code>[std::ops]::{[AsyncFn], [AsyncFnMut], [AsyncFnOnce]}</code>. +//! * <code>[std::ops]::[Drop]</code>, for implementing destructors. //! * <code>[std::mem]::[drop]</code>, a convenience function for explicitly //! dropping a value. //! * <code>[std::mem]::{[size_of], [size_of_val]}</code>, to get the size of @@ -67,15 +69,21 @@ //! The prelude used in Rust 2021, [`std::prelude::rust_2021`], includes all of the above, //! and in addition re-exports: //! -//! * <code>[std::convert]::{[TryFrom], [TryInto]}</code>, +//! * <code>[std::convert]::{[TryFrom], [TryInto]}</code>. //! * <code>[std::iter]::[FromIterator]</code>. //! +//! The prelude used in Rust 2024, [`std::prelude::rust_2024`], includes all of the above, +//! and in addition re-exports: +//! +//! * <code>[std::future]::{[Future], [IntoFuture]}</code>. +//! //! [std::borrow]: crate::borrow //! [std::boxed]: crate::boxed //! [std::clone]: crate::clone //! [std::cmp]: crate::cmp //! [std::convert]: crate::convert //! [std::default]: crate::default +//! [std::future]: crate::future //! [std::iter]: crate::iter //! [std::marker]: crate::marker //! [std::mem]: crate::mem @@ -85,6 +93,7 @@ //! [`std::prelude::rust_2015`]: rust_2015 //! [`std::prelude::rust_2018`]: rust_2018 //! [`std::prelude::rust_2021`]: rust_2021 +//! [`std::prelude::rust_2024`]: rust_2024 //! [std::result]: crate::result //! [std::slice]: crate::slice //! [std::string]: crate::string @@ -94,6 +103,8 @@ //! [book-dtor]: ../../book/ch15-03-drop.html //! [book-enums]: ../../book/ch06-01-defining-an-enum.html //! [book-iter]: ../../book/ch13-02-iterators.html +//! [Future]: crate::future::Future +//! [IntoFuture]: crate::future::IntoFuture // No formatting: this file is nothing but re-exports, and their order is worth preserving. #![cfg_attr(rustfmt, rustfmt::skip)] @@ -158,12 +169,12 @@ pub mod rust_2021 { /// The 2024 version of the prelude of The Rust Standard Library. /// /// See the [module-level documentation](self) for more. -#[unstable(feature = "prelude_2024", issue = "121042")] +#[stable(feature = "prelude_2024", since = "CURRENT_RUSTC_VERSION")] pub mod rust_2024 { #[stable(feature = "rust1", since = "1.0.0")] pub use super::common::*; - #[unstable(feature = "prelude_2024", issue = "121042")] + #[stable(feature = "prelude_2024", since = "CURRENT_RUSTC_VERSION")] #[doc(no_inline)] pub use core::prelude::rust_2024::*; } diff --git a/library/std/src/process.rs b/library/std/src/process.rs index 6933528cdbd..929d2b57afe 100644 --- a/library/std/src/process.rs +++ b/library/std/src/process.rs @@ -224,7 +224,7 @@ pub struct Child { /// has been captured. You might find it helpful to do /// /// ```ignore (incomplete) - /// let stdin = child.stdin.take().unwrap(); + /// let stdin = child.stdin.take().expect("handle present"); /// ``` /// /// to avoid partially moving the `child` and thus blocking yourself from calling @@ -236,7 +236,7 @@ pub struct Child { /// has been captured. You might find it helpful to do /// /// ```ignore (incomplete) - /// let stdout = child.stdout.take().unwrap(); + /// let stdout = child.stdout.take().expect("handle present"); /// ``` /// /// to avoid partially moving the `child` and thus blocking yourself from calling @@ -248,7 +248,7 @@ pub struct Child { /// has been captured. You might find it helpful to do /// /// ```ignore (incomplete) - /// let stderr = child.stderr.take().unwrap(); + /// let stderr = child.stderr.take().expect("handle present"); /// ``` /// /// to avoid partially moving the `child` and thus blocking yourself from calling @@ -1052,14 +1052,14 @@ impl Command { /// use std::io::{self, Write}; /// let output = Command::new("/bin/cat") /// .arg("file.txt") - /// .output() - /// .expect("failed to execute process"); + /// .output()?; /// /// println!("status: {}", output.status); - /// io::stdout().write_all(&output.stdout).unwrap(); - /// io::stderr().write_all(&output.stderr).unwrap(); + /// io::stdout().write_all(&output.stdout)?; + /// io::stderr().write_all(&output.stderr)?; /// /// assert!(output.status.success()); + /// # io::Result::Ok(()) /// ``` #[stable(feature = "process", since = "1.0.0")] pub fn output(&mut self) -> io::Result<Output> { @@ -1391,11 +1391,11 @@ impl Stdio { /// let output = Command::new("rev") /// .stdin(Stdio::inherit()) /// .stdout(Stdio::piped()) - /// .output() - /// .expect("Failed to execute command"); + /// .output()?; /// /// print!("You piped in the reverse of: "); - /// io::stdout().write_all(&output.stdout).unwrap(); + /// io::stdout().write_all(&output.stdout)?; + /// # io::Result::Ok(()) /// ``` #[must_use] #[stable(feature = "process", since = "1.0.0")] @@ -1575,14 +1575,14 @@ impl From<fs::File> for Stdio { /// use std::process::Command; /// /// // With the `foo.txt` file containing "Hello, world!" - /// let file = File::open("foo.txt").unwrap(); + /// let file = File::open("foo.txt")?; /// /// let reverse = Command::new("rev") /// .stdin(file) // Implicit File conversion into a Stdio - /// .output() - /// .expect("failed reverse command"); + /// .output()?; /// /// assert_eq!(reverse.stdout, b"!dlrow ,olleH"); + /// # std::io::Result::Ok(()) /// ``` fn from(file: fs::File) -> Stdio { Stdio::from_inner(file.into_inner().into()) @@ -2179,7 +2179,7 @@ impl Child { /// ```no_run /// use std::process::Command; /// - /// let mut child = Command::new("ls").spawn().unwrap(); + /// let mut child = Command::new("ls").spawn()?; /// /// match child.try_wait() { /// Ok(Some(status)) => println!("exited with: {status}"), @@ -2190,6 +2190,7 @@ impl Child { /// } /// Err(e) => println!("error attempting to wait: {e}"), /// } + /// # std::io::Result::Ok(()) /// ``` #[stable(feature = "process_try_wait", since = "1.18.0")] pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> { diff --git a/library/std/src/process/tests.rs b/library/std/src/process/tests.rs index fb0b495961c..e8cbfe337bc 100644 --- a/library/std/src/process/tests.rs +++ b/library/std/src/process/tests.rs @@ -450,7 +450,7 @@ fn test_creation_flags() { fn test_proc_thread_attributes() { use crate::mem; use crate::os::windows::io::AsRawHandle; - use crate::os::windows::process::CommandExt; + use crate::os::windows::process::{CommandExt, ProcThreadAttributeList}; use crate::sys::c::{BOOL, CloseHandle, HANDLE}; use crate::sys::cvt; @@ -490,12 +490,14 @@ fn test_proc_thread_attributes() { let mut child_cmd = Command::new("cmd"); - unsafe { - child_cmd - .raw_attribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, parent.0.as_raw_handle() as isize); - } + let parent_process_handle = parent.0.as_raw_handle(); + + let mut attribute_list = ProcThreadAttributeList::build() + .attribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &parent_process_handle) + .finish() + .unwrap(); - let child = ProcessDropGuard(child_cmd.spawn().unwrap()); + let child = ProcessDropGuard(child_cmd.spawn_with_attributes(&mut attribute_list).unwrap()); let h_snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) }; diff --git a/library/std/src/sync/barrier.rs b/library/std/src/sync/barrier.rs index 82cc13a74b7..862753e4765 100644 --- a/library/std/src/sync/barrier.rs +++ b/library/std/src/sync/barrier.rs @@ -2,6 +2,7 @@ mod tests; use crate::fmt; +// FIXME(nonpoison_mutex,nonpoison_condvar): switch to nonpoison versions once they are available use crate::sync::{Condvar, Mutex}; /// A barrier enables multiple threads to synchronize the beginning @@ -10,26 +11,22 @@ use crate::sync::{Condvar, Mutex}; /// # Examples /// /// ``` -/// use std::sync::{Arc, Barrier}; +/// use std::sync::Barrier; /// use std::thread; /// /// let n = 10; -/// let mut handles = Vec::with_capacity(n); -/// let barrier = Arc::new(Barrier::new(n)); -/// for _ in 0..n { -/// let c = Arc::clone(&barrier); -/// // The same messages will be printed together. -/// // You will NOT see any interleaving. -/// handles.push(thread::spawn(move || { -/// println!("before wait"); -/// c.wait(); -/// println!("after wait"); -/// })); -/// } -/// // Wait for other threads to finish. -/// for handle in handles { -/// handle.join().unwrap(); -/// } +/// let barrier = Barrier::new(n); +/// thread::scope(|s| { +/// for _ in 0..n { +/// // The same messages will be printed together. +/// // You will NOT see any interleaving. +/// s.spawn(|| { +/// println!("before wait"); +/// barrier.wait(); +/// println!("after wait"); +/// }); +/// } +/// }); /// ``` #[stable(feature = "rust1", since = "1.0.0")] pub struct Barrier { @@ -105,26 +102,22 @@ impl Barrier { /// # Examples /// /// ``` - /// use std::sync::{Arc, Barrier}; + /// use std::sync::Barrier; /// use std::thread; /// /// let n = 10; - /// let mut handles = Vec::with_capacity(n); - /// let barrier = Arc::new(Barrier::new(n)); - /// for _ in 0..n { - /// let c = Arc::clone(&barrier); - /// // The same messages will be printed together. - /// // You will NOT see any interleaving. - /// handles.push(thread::spawn(move || { - /// println!("before wait"); - /// c.wait(); - /// println!("after wait"); - /// })); - /// } - /// // Wait for other threads to finish. - /// for handle in handles { - /// handle.join().unwrap(); - /// } + /// let barrier = Barrier::new(n); + /// thread::scope(|s| { + /// for _ in 0..n { + /// // The same messages will be printed together. + /// // You will NOT see any interleaving. + /// s.spawn(|| { + /// println!("before wait"); + /// barrier.wait(); + /// println!("after wait"); + /// }); + /// } + /// }); /// ``` #[stable(feature = "rust1", since = "1.0.0")] pub fn wait(&self) -> BarrierWaitResult { diff --git a/library/std/src/sync/lazy_lock.rs b/library/std/src/sync/lazy_lock.rs index 40510f56134..1e4f9b79e0f 100644 --- a/library/std/src/sync/lazy_lock.rs +++ b/library/std/src/sync/lazy_lock.rs @@ -1,4 +1,4 @@ -use super::once::ExclusiveState; +use super::poison::once::ExclusiveState; use crate::cell::UnsafeCell; use crate::mem::ManuallyDrop; use crate::ops::Deref; @@ -63,6 +63,7 @@ union Data<T, F> { /// ``` #[stable(feature = "lazy_cell", since = "1.80.0")] pub struct LazyLock<T, F = fn() -> T> { + // FIXME(nonpoison_once): if possible, switch to nonpoison version once it is available once: Once, data: UnsafeCell<Data<T, F>>, } diff --git a/library/std/src/sync/mod.rs b/library/std/src/sync/mod.rs index 0fb77331293..5b50a3c6ccf 100644 --- a/library/std/src/sync/mod.rs +++ b/library/std/src/sync/mod.rs @@ -167,6 +167,10 @@ #![stable(feature = "rust1", since = "1.0.0")] +// No formatting: this file is just re-exports, and their order is worth preserving. +#![cfg_attr(rustfmt, rustfmt::skip)] + +// These come from `core` & `alloc` and only in one flavor: no poisoning. #[unstable(feature = "exclusive_wrapper", issue = "98407")] pub use core::sync::Exclusive; #[stable(feature = "rust1", since = "1.0.0")] @@ -175,40 +179,54 @@ pub use core::sync::atomic; #[stable(feature = "rust1", since = "1.0.0")] pub use alloc_crate::sync::{Arc, Weak}; +// FIXME(sync_nonpoison,sync_poison_mod): remove all `#[doc(inline)]` once the modules are stabilized. + +// These exist only in one flavor: no poisoning. #[stable(feature = "rust1", since = "1.0.0")] pub use self::barrier::{Barrier, BarrierWaitResult}; -#[stable(feature = "rust1", since = "1.0.0")] -pub use self::condvar::{Condvar, WaitTimeoutResult}; #[stable(feature = "lazy_cell", since = "1.80.0")] pub use self::lazy_lock::LazyLock; -#[unstable(feature = "mapped_lock_guards", issue = "117108")] -pub use self::mutex::MappedMutexGuard; -#[stable(feature = "rust1", since = "1.0.0")] -pub use self::mutex::{Mutex, MutexGuard}; -#[stable(feature = "rust1", since = "1.0.0")] -#[allow(deprecated)] -pub use self::once::{ONCE_INIT, Once, OnceState}; #[stable(feature = "once_cell", since = "1.70.0")] pub use self::once_lock::OnceLock; -#[stable(feature = "rust1", since = "1.0.0")] -pub use self::poison::{LockResult, PoisonError, TryLockError, TryLockResult}; #[unstable(feature = "reentrant_lock", issue = "121440")] pub use self::reentrant_lock::{ReentrantLock, ReentrantLockGuard}; -#[unstable(feature = "mapped_lock_guards", issue = "117108")] -pub use self::rwlock::{MappedRwLockReadGuard, MappedRwLockWriteGuard}; + +// These make sense and exist only with poisoning. #[stable(feature = "rust1", since = "1.0.0")] -pub use self::rwlock::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +#[doc(inline)] +pub use self::poison::{LockResult, PoisonError}; + +// These (should) exist in both flavors: with and without poisoning. +// FIXME(sync_nonpoison): implement nonpoison versions: +// * Mutex (nonpoison_mutex) +// * Condvar (nonpoison_condvar) +// * Once (nonpoison_once) +// * RwLock (nonpoison_rwlock) +// The historical default is the version with poisoning. +#[stable(feature = "rust1", since = "1.0.0")] +#[doc(inline)] +pub use self::poison::{ + Mutex, MutexGuard, TryLockError, TryLockResult, + Condvar, WaitTimeoutResult, + Once, OnceState, + RwLock, RwLockReadGuard, RwLockWriteGuard, +}; +#[stable(feature = "rust1", since = "1.0.0")] +#[doc(inline)] +#[expect(deprecated)] +pub use self::poison::ONCE_INIT; +#[unstable(feature = "mapped_lock_guards", issue = "117108")] +#[doc(inline)] +pub use self::poison::{MappedMutexGuard, MappedRwLockReadGuard, MappedRwLockWriteGuard}; #[unstable(feature = "mpmc_channel", issue = "126840")] pub mod mpmc; pub mod mpsc; +#[unstable(feature = "sync_poison_mod", issue = "134646")] +pub mod poison; + mod barrier; -mod condvar; mod lazy_lock; -mod mutex; -pub(crate) mod once; mod once_lock; -mod poison; mod reentrant_lock; -mod rwlock; diff --git a/library/std/src/sync/once_lock.rs b/library/std/src/sync/once_lock.rs index 0ae3cf4df36..49f2dafd8fd 100644 --- a/library/std/src/sync/once_lock.rs +++ b/library/std/src/sync/once_lock.rs @@ -101,6 +101,7 @@ use crate::sync::Once; /// ``` #[stable(feature = "once_cell", since = "1.70.0")] pub struct OnceLock<T> { + // FIXME(nonpoison_once): switch to nonpoison version once it is available once: Once, // Whether or not the value is initialized is tracked by `once.is_completed()`. value: UnsafeCell<MaybeUninit<T>>, diff --git a/library/std/src/sync/poison.rs b/library/std/src/sync/poison.rs index da66a088e51..1b8809734b8 100644 --- a/library/std/src/sync/poison.rs +++ b/library/std/src/sync/poison.rs @@ -1,3 +1,78 @@ +//! Synchronization objects that employ poisoning. +//! +//! # Poisoning +//! +//! All synchronization objects in this module implement a strategy called "poisoning" +//! where if a thread panics while holding the exclusive access granted by the primitive, +//! the state of the primitive is set to "poisoned". +//! This information is then propagated to all other threads +//! to signify that the data protected by this primitive is likely tainted +//! (some invariant is not being upheld). +//! +//! The specifics of how this "poisoned" state affects other threads +//! depend on the primitive. See [#Overview] bellow. +//! +//! For the alternative implementations that do not employ poisoning, +//! see `std::sys::nonpoisoning`. +//! +//! # Overview +//! +//! Below is a list of synchronization objects provided by this module +//! with a high-level overview for each object and a description +//! of how it employs "poisoning". +//! +//! - [`Condvar`]: Condition Variable, providing the ability to block +//! a thread while waiting for an event to occur. +//! +//! Condition variables are typically associated with +//! a boolean predicate (a condition) and a mutex. +//! This implementation is associated with [`poison::Mutex`](Mutex), +//! which employs poisoning. +//! For this reason, [`Condvar::wait()`] will return a [`LockResult`], +//! just like [`poison::Mutex::lock()`](Mutex::lock) does. +//! +//! - [`Mutex`]: Mutual Exclusion mechanism, which ensures that at +//! most one thread at a time is able to access some data. +//! +//! [`Mutex::lock()`] returns a [`LockResult`], +//! providing a way to deal with the poisoned state. +//! See [`Mutex`'s documentation](Mutex#poisoning) for more. +//! +//! - [`Once`]: A thread-safe way to run a piece of code only once. +//! Mostly useful for implementing one-time global initialization. +//! +//! [`Once`] is poisoned if the piece of code passed to +//! [`Once::call_once()`] or [`Once::call_once_force()`] panics. +//! When in poisoned state, subsequent calls to [`Once::call_once()`] will panic too. +//! [`Once::call_once_force()`] can be used to clear the poisoned state. +//! +//! - [`RwLock`]: Provides a mutual exclusion mechanism which allows +//! multiple readers at the same time, while allowing only one +//! writer at a time. In some cases, this can be more efficient than +//! a mutex. +//! +//! This implementation, like [`Mutex`], will become poisoned on a panic. +//! Note, however, that an `RwLock` may only be poisoned if a panic occurs +//! while it is locked exclusively (write mode). If a panic occurs in any reader, +//! then the lock will not be poisoned. + +// FIXME(sync_nonpoison) add links to sync::nonpoison to the doc comment above. + +#[stable(feature = "rust1", since = "1.0.0")] +pub use self::condvar::{Condvar, WaitTimeoutResult}; +#[unstable(feature = "mapped_lock_guards", issue = "117108")] +pub use self::mutex::MappedMutexGuard; +#[stable(feature = "rust1", since = "1.0.0")] +pub use self::mutex::{Mutex, MutexGuard}; +#[stable(feature = "rust1", since = "1.0.0")] +#[expect(deprecated)] +pub use self::once::ONCE_INIT; +#[stable(feature = "rust1", since = "1.0.0")] +pub use self::once::{Once, OnceState}; +#[unstable(feature = "mapped_lock_guards", issue = "117108")] +pub use self::rwlock::{MappedRwLockReadGuard, MappedRwLockWriteGuard}; +#[stable(feature = "rust1", since = "1.0.0")] +pub use self::rwlock::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use crate::error::Error; use crate::fmt; #[cfg(panic = "unwind")] @@ -5,7 +80,13 @@ use crate::sync::atomic::{AtomicBool, Ordering}; #[cfg(panic = "unwind")] use crate::thread; -pub struct Flag { +mod condvar; +#[stable(feature = "rust1", since = "1.0.0")] +mod mutex; +pub(crate) mod once; +mod rwlock; + +pub(crate) struct Flag { #[cfg(panic = "unwind")] failed: AtomicBool, } @@ -78,7 +159,7 @@ impl Flag { } #[derive(Clone)] -pub struct Guard { +pub(crate) struct Guard { #[cfg(panic = "unwind")] panicking: bool, } @@ -87,8 +168,8 @@ pub struct Guard { /// /// Both [`Mutex`]es and [`RwLock`]s are poisoned whenever a thread fails while the lock /// is held. The precise semantics for when a lock is poisoned is documented on -/// each lock, but once a lock is poisoned then all future acquisitions will -/// return this error. +/// each lock. For a lock in the poisoned state, unless the state is cleared manually, +/// all future acquisitions will return this error. /// /// # Examples /// @@ -118,7 +199,7 @@ pub struct Guard { /// [`RwLock`]: crate::sync::RwLock #[stable(feature = "rust1", since = "1.0.0")] pub struct PoisonError<T> { - guard: T, + data: T, #[cfg(not(panic = "unwind"))] _never: !, } @@ -147,14 +228,15 @@ pub enum TryLockError<T> { /// A type alias for the result of a lock method which can be poisoned. /// /// The [`Ok`] variant of this result indicates that the primitive was not -/// poisoned, and the `Guard` is contained within. The [`Err`] variant indicates +/// poisoned, and the operation result is contained within. The [`Err`] variant indicates /// that the primitive was poisoned. Note that the [`Err`] variant *also* carries -/// the associated guard, and it can be acquired through the [`into_inner`] -/// method. +/// an associated value assigned by the lock method, and it can be acquired through the +/// [`into_inner`] method. The semantics of the associated value depends on the corresponding +/// lock method. /// /// [`into_inner`]: PoisonError::into_inner #[stable(feature = "rust1", since = "1.0.0")] -pub type LockResult<Guard> = Result<Guard, PoisonError<Guard>>; +pub type LockResult<T> = Result<T, PoisonError<T>>; /// A type alias for the result of a nonblocking locking method. /// @@ -195,8 +277,8 @@ impl<T> PoisonError<T> { /// This method may panic if std was built with `panic="abort"`. #[cfg(panic = "unwind")] #[stable(feature = "sync_poison", since = "1.2.0")] - pub fn new(guard: T) -> PoisonError<T> { - PoisonError { guard } + pub fn new(data: T) -> PoisonError<T> { + PoisonError { data } } /// Creates a `PoisonError`. @@ -208,12 +290,12 @@ impl<T> PoisonError<T> { #[cfg(not(panic = "unwind"))] #[stable(feature = "sync_poison", since = "1.2.0")] #[track_caller] - pub fn new(_guard: T) -> PoisonError<T> { + pub fn new(_data: T) -> PoisonError<T> { panic!("PoisonError created in a libstd built with panic=\"abort\"") } /// Consumes this error indicating that a lock is poisoned, returning the - /// underlying guard to allow access regardless. + /// associated data. /// /// # Examples /// @@ -238,21 +320,21 @@ impl<T> PoisonError<T> { /// ``` #[stable(feature = "sync_poison", since = "1.2.0")] pub fn into_inner(self) -> T { - self.guard + self.data } /// Reaches into this error indicating that a lock is poisoned, returning a - /// reference to the underlying guard to allow access regardless. + /// reference to the associated data. #[stable(feature = "sync_poison", since = "1.2.0")] pub fn get_ref(&self) -> &T { - &self.guard + &self.data } /// Reaches into this error indicating that a lock is poisoned, returning a - /// mutable reference to the underlying guard to allow access regardless. + /// mutable reference to the associated data. #[stable(feature = "sync_poison", since = "1.2.0")] pub fn get_mut(&mut self) -> &mut T { - &mut self.guard + &mut self.data } } @@ -315,13 +397,13 @@ impl<T> Error for TryLockError<T> { } } -pub fn map_result<T, U, F>(result: LockResult<T>, f: F) -> LockResult<U> +pub(crate) fn map_result<T, U, F>(result: LockResult<T>, f: F) -> LockResult<U> where F: FnOnce(T) -> U, { match result { Ok(t) => Ok(f(t)), #[cfg(panic = "unwind")] - Err(PoisonError { guard }) => Err(PoisonError::new(f(guard))), + Err(PoisonError { data }) => Err(PoisonError::new(f(data))), } } diff --git a/library/std/src/sync/condvar.rs b/library/std/src/sync/poison/condvar.rs index 44ffcb528d9..a6e2389c93b 100644 --- a/library/std/src/sync/condvar.rs +++ b/library/std/src/sync/poison/condvar.rs @@ -2,7 +2,7 @@ mod tests; use crate::fmt; -use crate::sync::{LockResult, MutexGuard, PoisonError, mutex, poison}; +use crate::sync::poison::{self, LockResult, MutexGuard, PoisonError, mutex}; use crate::sys::sync as sys; use crate::time::{Duration, Instant}; @@ -16,6 +16,8 @@ use crate::time::{Duration, Instant}; #[stable(feature = "wait_timeout", since = "1.5.0")] pub struct WaitTimeoutResult(bool); +// FIXME(sync_nonpoison): `WaitTimeoutResult` is actually poisoning-agnostic, it seems. +// Should we take advantage of this fact? impl WaitTimeoutResult { /// Returns `true` if the wait was known to have timed out. /// diff --git a/library/std/src/sync/condvar/tests.rs b/library/std/src/sync/poison/condvar/tests.rs index f9e9066bc92..f9e9066bc92 100644 --- a/library/std/src/sync/condvar/tests.rs +++ b/library/std/src/sync/poison/condvar/tests.rs diff --git a/library/std/src/sync/mutex.rs b/library/std/src/sync/poison/mutex.rs index fe2aca031a2..e28c2090afe 100644 --- a/library/std/src/sync/mutex.rs +++ b/library/std/src/sync/poison/mutex.rs @@ -4,10 +4,10 @@ mod tests; use crate::cell::UnsafeCell; use crate::fmt; use crate::marker::PhantomData; -use crate::mem::ManuallyDrop; +use crate::mem::{self, ManuallyDrop}; use crate::ops::{Deref, DerefMut}; use crate::ptr::NonNull; -use crate::sync::{LockResult, TryLockError, TryLockResult, poison}; +use crate::sync::{LockResult, PoisonError, TryLockError, TryLockResult, poison}; use crate::sys::sync as sys; /// A mutual exclusion primitive useful for protecting shared data @@ -273,6 +273,100 @@ impl<T> Mutex<T> { pub const fn new(t: T) -> Mutex<T> { Mutex { inner: sys::Mutex::new(), poison: poison::Flag::new(), data: UnsafeCell::new(t) } } + + /// Returns the contained value by cloning it. + /// + /// # Errors + /// + /// If another user of this mutex panicked while holding the mutex, then + /// this call will return an error instead. + /// + /// # Examples + /// + /// ``` + /// #![feature(lock_value_accessors)] + /// + /// use std::sync::Mutex; + /// + /// let mut mutex = Mutex::new(7); + /// + /// assert_eq!(mutex.get_cloned().unwrap(), 7); + /// ``` + #[unstable(feature = "lock_value_accessors", issue = "133407")] + pub fn get_cloned(&self) -> Result<T, PoisonError<()>> + where + T: Clone, + { + match self.lock() { + Ok(guard) => Ok((*guard).clone()), + Err(_) => Err(PoisonError::new(())), + } + } + + /// Sets the contained value. + /// + /// # Errors + /// + /// If another user of this mutex panicked while holding the mutex, then + /// this call will return an error containing the provided `value` instead. + /// + /// # Examples + /// + /// ``` + /// #![feature(lock_value_accessors)] + /// + /// use std::sync::Mutex; + /// + /// let mut mutex = Mutex::new(7); + /// + /// assert_eq!(mutex.get_cloned().unwrap(), 7); + /// mutex.set(11).unwrap(); + /// assert_eq!(mutex.get_cloned().unwrap(), 11); + /// ``` + #[unstable(feature = "lock_value_accessors", issue = "133407")] + pub fn set(&self, value: T) -> Result<(), PoisonError<T>> { + if mem::needs_drop::<T>() { + // If the contained value has non-trivial destructor, we + // call that destructor after the lock being released. + self.replace(value).map(drop) + } else { + match self.lock() { + Ok(mut guard) => { + *guard = value; + + Ok(()) + } + Err(_) => Err(PoisonError::new(value)), + } + } + } + + /// Replaces the contained value with `value`, and returns the old contained value. + /// + /// # Errors + /// + /// If another user of this mutex panicked while holding the mutex, then + /// this call will return an error containing the provided `value` instead. + /// + /// # Examples + /// + /// ``` + /// #![feature(lock_value_accessors)] + /// + /// use std::sync::Mutex; + /// + /// let mut mutex = Mutex::new(7); + /// + /// assert_eq!(mutex.replace(11).unwrap(), 7); + /// assert_eq!(mutex.get_cloned().unwrap(), 11); + /// ``` + #[unstable(feature = "lock_value_accessors", issue = "133407")] + pub fn replace(&self, value: T) -> LockResult<T> { + match self.lock() { + Ok(mut guard) => Ok(mem::replace(&mut *guard, value)), + Err(_) => Err(PoisonError::new(value)), + } + } } impl<T: ?Sized> Mutex<T> { @@ -290,7 +384,8 @@ impl<T: ?Sized> Mutex<T> { /// # Errors /// /// If another user of this mutex panicked while holding the mutex, then - /// this call will return an error once the mutex is acquired. + /// this call will return an error once the mutex is acquired. The acquired + /// mutex guard will be contained in the returned error. /// /// # Panics /// @@ -331,7 +426,8 @@ impl<T: ?Sized> Mutex<T> { /// /// If another user of this mutex panicked while holding the mutex, then /// this call will return the [`Poisoned`] error if the mutex would - /// otherwise be acquired. + /// otherwise be acquired. An acquired lock guard will be contained + /// in the returned error. /// /// If the mutex could not be acquired because it is already locked, then /// this call will return the [`WouldBlock`] error. @@ -438,7 +534,8 @@ impl<T: ?Sized> Mutex<T> { /// # Errors /// /// If another user of this mutex panicked while holding the mutex, then - /// this call will return an error instead. + /// this call will return an error containing the the underlying data + /// instead. /// /// # Examples /// @@ -465,7 +562,8 @@ impl<T: ?Sized> Mutex<T> { /// # Errors /// /// If another user of this mutex panicked while holding the mutex, then - /// this call will return an error instead. + /// this call will return an error containing a mutable reference to the + /// underlying data instead. /// /// # Examples /// diff --git a/library/std/src/sync/mutex/tests.rs b/library/std/src/sync/poison/mutex/tests.rs index 19ec096c593..395c8aada08 100644 --- a/library/std/src/sync/mutex/tests.rs +++ b/library/std/src/sync/poison/mutex/tests.rs @@ -1,13 +1,34 @@ +use crate::fmt::Debug; +use crate::ops::FnMut; +use crate::panic::{self, AssertUnwindSafe}; use crate::sync::atomic::{AtomicUsize, Ordering}; use crate::sync::mpsc::channel; use crate::sync::{Arc, Condvar, MappedMutexGuard, Mutex, MutexGuard, TryLockError}; -use crate::thread; +use crate::{hint, mem, thread}; struct Packet<T>(Arc<(Mutex<T>, Condvar)>); #[derive(Eq, PartialEq, Debug)] struct NonCopy(i32); +#[derive(Eq, PartialEq, Debug)] +struct NonCopyNeedsDrop(i32); + +impl Drop for NonCopyNeedsDrop { + fn drop(&mut self) { + hint::black_box(()); + } +} + +#[test] +fn test_needs_drop() { + assert!(!mem::needs_drop::<NonCopy>()); + assert!(mem::needs_drop::<NonCopyNeedsDrop>()); +} + +#[derive(Clone, Eq, PartialEq, Debug)] +struct Cloneable(i32); + #[test] fn smoke() { let m = Mutex::new(()); @@ -57,6 +78,21 @@ fn try_lock() { *m.try_lock().unwrap() = (); } +fn new_poisoned_mutex<T>(value: T) -> Mutex<T> { + let mutex = Mutex::new(value); + + let catch_unwind_result = panic::catch_unwind(AssertUnwindSafe(|| { + let _guard = mutex.lock().unwrap(); + + panic!("test panic to poison mutex"); + })); + + assert!(catch_unwind_result.is_err()); + assert!(mutex.is_poisoned()); + + mutex +} + #[test] fn test_into_inner() { let m = Mutex::new(NonCopy(10)); @@ -83,22 +119,32 @@ fn test_into_inner_drop() { #[test] fn test_into_inner_poison() { - let m = Arc::new(Mutex::new(NonCopy(10))); - let m2 = m.clone(); - let _ = thread::spawn(move || { - let _lock = m2.lock().unwrap(); - panic!("test panic in inner thread to poison mutex"); - }) - .join(); + let m = new_poisoned_mutex(NonCopy(10)); - assert!(m.is_poisoned()); - match Arc::try_unwrap(m).unwrap().into_inner() { + match m.into_inner() { Err(e) => assert_eq!(e.into_inner(), NonCopy(10)), Ok(x) => panic!("into_inner of poisoned Mutex is Ok: {x:?}"), } } #[test] +fn test_get_cloned() { + let m = Mutex::new(Cloneable(10)); + + assert_eq!(m.get_cloned().unwrap(), Cloneable(10)); +} + +#[test] +fn test_get_cloned_poison() { + let m = new_poisoned_mutex(Cloneable(10)); + + match m.get_cloned() { + Err(e) => assert_eq!(e.into_inner(), ()), + Ok(x) => panic!("get of poisoned Mutex is Ok: {x:?}"), + } +} + +#[test] fn test_get_mut() { let mut m = Mutex::new(NonCopy(10)); *m.get_mut().unwrap() = NonCopy(20); @@ -107,22 +153,91 @@ fn test_get_mut() { #[test] fn test_get_mut_poison() { - let m = Arc::new(Mutex::new(NonCopy(10))); - let m2 = m.clone(); - let _ = thread::spawn(move || { - let _lock = m2.lock().unwrap(); - panic!("test panic in inner thread to poison mutex"); - }) - .join(); + let mut m = new_poisoned_mutex(NonCopy(10)); - assert!(m.is_poisoned()); - match Arc::try_unwrap(m).unwrap().get_mut() { + match m.get_mut() { Err(e) => assert_eq!(*e.into_inner(), NonCopy(10)), Ok(x) => panic!("get_mut of poisoned Mutex is Ok: {x:?}"), } } #[test] +fn test_set() { + fn inner<T>(mut init: impl FnMut() -> T, mut value: impl FnMut() -> T) + where + T: Debug + Eq, + { + let m = Mutex::new(init()); + + assert_eq!(*m.lock().unwrap(), init()); + m.set(value()).unwrap(); + assert_eq!(*m.lock().unwrap(), value()); + } + + inner(|| NonCopy(10), || NonCopy(20)); + inner(|| NonCopyNeedsDrop(10), || NonCopyNeedsDrop(20)); +} + +#[test] +fn test_set_poison() { + fn inner<T>(mut init: impl FnMut() -> T, mut value: impl FnMut() -> T) + where + T: Debug + Eq, + { + let m = new_poisoned_mutex(init()); + + match m.set(value()) { + Err(e) => { + assert_eq!(e.into_inner(), value()); + assert_eq!(m.into_inner().unwrap_err().into_inner(), init()); + } + Ok(x) => panic!("set of poisoned Mutex is Ok: {x:?}"), + } + } + + inner(|| NonCopy(10), || NonCopy(20)); + inner(|| NonCopyNeedsDrop(10), || NonCopyNeedsDrop(20)); +} + +#[test] +fn test_replace() { + fn inner<T>(mut init: impl FnMut() -> T, mut value: impl FnMut() -> T) + where + T: Debug + Eq, + { + let m = Mutex::new(init()); + + assert_eq!(*m.lock().unwrap(), init()); + assert_eq!(m.replace(value()).unwrap(), init()); + assert_eq!(*m.lock().unwrap(), value()); + } + + inner(|| NonCopy(10), || NonCopy(20)); + inner(|| NonCopyNeedsDrop(10), || NonCopyNeedsDrop(20)); +} + +#[test] +fn test_replace_poison() { + fn inner<T>(mut init: impl FnMut() -> T, mut value: impl FnMut() -> T) + where + T: Debug + Eq, + { + let m = new_poisoned_mutex(init()); + + match m.replace(value()) { + Err(e) => { + assert_eq!(e.into_inner(), value()); + assert_eq!(m.into_inner().unwrap_err().into_inner(), init()); + } + Ok(x) => panic!("replace of poisoned Mutex is Ok: {x:?}"), + } + } + + inner(|| NonCopy(10), || NonCopy(20)); + inner(|| NonCopyNeedsDrop(10), || NonCopyNeedsDrop(20)); +} + +#[test] fn test_mutex_arc_condvar() { let packet = Packet(Arc::new((Mutex::new(false), Condvar::new()))); let packet2 = Packet(packet.0.clone()); @@ -269,7 +384,7 @@ fn test_mapping_mapped_guard() { fn panic_while_mapping_unlocked_poison() { let lock = Mutex::new(()); - let _ = crate::panic::catch_unwind(|| { + let _ = panic::catch_unwind(|| { let guard = lock.lock().unwrap(); let _guard = MutexGuard::map::<(), _>(guard, |_| panic!()); }); @@ -282,7 +397,7 @@ fn panic_while_mapping_unlocked_poison() { Err(TryLockError::Poisoned(_)) => {} } - let _ = crate::panic::catch_unwind(|| { + let _ = panic::catch_unwind(|| { let guard = lock.lock().unwrap(); let _guard = MutexGuard::try_map::<(), _>(guard, |_| panic!()); }); @@ -295,7 +410,7 @@ fn panic_while_mapping_unlocked_poison() { Err(TryLockError::Poisoned(_)) => {} } - let _ = crate::panic::catch_unwind(|| { + let _ = panic::catch_unwind(|| { let guard = lock.lock().unwrap(); let guard = MutexGuard::map::<(), _>(guard, |val| val); let _guard = MappedMutexGuard::map::<(), _>(guard, |_| panic!()); @@ -309,7 +424,7 @@ fn panic_while_mapping_unlocked_poison() { Err(TryLockError::Poisoned(_)) => {} } - let _ = crate::panic::catch_unwind(|| { + let _ = panic::catch_unwind(|| { let guard = lock.lock().unwrap(); let guard = MutexGuard::map::<(), _>(guard, |val| val); let _guard = MappedMutexGuard::try_map::<(), _>(guard, |_| panic!()); diff --git a/library/std/src/sync/once.rs b/library/std/src/sync/poison/once.rs index 27db4b634fb..27db4b634fb 100644 --- a/library/std/src/sync/once.rs +++ b/library/std/src/sync/poison/once.rs diff --git a/library/std/src/sync/once/tests.rs b/library/std/src/sync/poison/once/tests.rs index ce96468aeb6..ce96468aeb6 100644 --- a/library/std/src/sync/once/tests.rs +++ b/library/std/src/sync/poison/once/tests.rs diff --git a/library/std/src/sync/rwlock.rs b/library/std/src/sync/poison/rwlock.rs index d55d1c80dca..1519baf99a8 100644 --- a/library/std/src/sync/rwlock.rs +++ b/library/std/src/sync/poison/rwlock.rs @@ -4,7 +4,7 @@ mod tests; use crate::cell::UnsafeCell; use crate::fmt; use crate::marker::PhantomData; -use crate::mem::{ManuallyDrop, forget}; +use crate::mem::{self, ManuallyDrop, forget}; use crate::ops::{Deref, DerefMut}; use crate::ptr::NonNull; use crate::sync::{LockResult, PoisonError, TryLockError, TryLockResult, poison}; @@ -224,6 +224,103 @@ impl<T> RwLock<T> { pub const fn new(t: T) -> RwLock<T> { RwLock { inner: sys::RwLock::new(), poison: poison::Flag::new(), data: UnsafeCell::new(t) } } + + /// Returns the contained value by cloning it. + /// + /// # Errors + /// + /// This function will return an error if the `RwLock` is poisoned. An + /// `RwLock` is poisoned whenever a writer panics while holding an exclusive + /// lock. + /// + /// # Examples + /// + /// ``` + /// #![feature(lock_value_accessors)] + /// + /// use std::sync::RwLock; + /// + /// let mut lock = RwLock::new(7); + /// + /// assert_eq!(lock.get_cloned().unwrap(), 7); + /// ``` + #[unstable(feature = "lock_value_accessors", issue = "133407")] + pub fn get_cloned(&self) -> Result<T, PoisonError<()>> + where + T: Clone, + { + match self.read() { + Ok(guard) => Ok((*guard).clone()), + Err(_) => Err(PoisonError::new(())), + } + } + + /// Sets the contained value. + /// + /// # Errors + /// + /// This function will return an error containing the provided `value` if + /// the `RwLock` is poisoned. An `RwLock` is poisoned whenever a writer + /// panics while holding an exclusive lock. + /// + /// # Examples + /// + /// ``` + /// #![feature(lock_value_accessors)] + /// + /// use std::sync::RwLock; + /// + /// let mut lock = RwLock::new(7); + /// + /// assert_eq!(lock.get_cloned().unwrap(), 7); + /// lock.set(11).unwrap(); + /// assert_eq!(lock.get_cloned().unwrap(), 11); + /// ``` + #[unstable(feature = "lock_value_accessors", issue = "133407")] + pub fn set(&self, value: T) -> Result<(), PoisonError<T>> { + if mem::needs_drop::<T>() { + // If the contained value has non-trivial destructor, we + // call that destructor after the lock being released. + self.replace(value).map(drop) + } else { + match self.write() { + Ok(mut guard) => { + *guard = value; + + Ok(()) + } + Err(_) => Err(PoisonError::new(value)), + } + } + } + + /// Replaces the contained value with `value`, and returns the old contained value. + /// + /// # Errors + /// + /// This function will return an error containing the provided `value` if + /// the `RwLock` is poisoned. An `RwLock` is poisoned whenever a writer + /// panics while holding an exclusive lock. + /// + /// # Examples + /// + /// ``` + /// #![feature(lock_value_accessors)] + /// + /// use std::sync::RwLock; + /// + /// let mut lock = RwLock::new(7); + /// + /// assert_eq!(lock.replace(11).unwrap(), 7); + /// assert_eq!(lock.get_cloned().unwrap(), 11); + /// ``` + #[unstable(feature = "lock_value_accessors", issue = "133407")] + pub fn replace(&self, value: T) -> LockResult<T> { + match self.write() { + Ok(mut guard) => Ok(mem::replace(&mut *guard, value)), + Err(_) => Err(PoisonError::new(value)), + } + } } impl<T: ?Sized> RwLock<T> { @@ -244,7 +341,8 @@ impl<T: ?Sized> RwLock<T> { /// This function will return an error if the `RwLock` is poisoned. An /// `RwLock` is poisoned whenever a writer panics while holding an exclusive /// lock. The failure will occur immediately after the lock has been - /// acquired. + /// acquired. The acquired lock guard will be contained in the returned + /// error. /// /// # Panics /// @@ -292,7 +390,8 @@ impl<T: ?Sized> RwLock<T> { /// This function will return the [`Poisoned`] error if the `RwLock` is /// poisoned. An `RwLock` is poisoned whenever a writer panics while holding /// an exclusive lock. `Poisoned` will only be returned if the lock would - /// have otherwise been acquired. + /// have otherwise been acquired. An acquired lock guard will be contained + /// in the returned error. /// /// This function will return the [`WouldBlock`] error if the `RwLock` could /// not be acquired because it was already locked exclusively. @@ -337,7 +436,8 @@ impl<T: ?Sized> RwLock<T> { /// /// This function will return an error if the `RwLock` is poisoned. An /// `RwLock` is poisoned whenever a writer panics while holding an exclusive - /// lock. An error will be returned when the lock is acquired. + /// lock. An error will be returned when the lock is acquired. The acquired + /// lock guard will be contained in the returned error. /// /// # Panics /// @@ -380,7 +480,8 @@ impl<T: ?Sized> RwLock<T> { /// This function will return the [`Poisoned`] error if the `RwLock` is /// poisoned. An `RwLock` is poisoned whenever a writer panics while holding /// an exclusive lock. `Poisoned` will only be returned if the lock would - /// have otherwise been acquired. + /// have otherwise been acquired. An acquired lock guard will be contained + /// in the returned error. /// /// This function will return the [`WouldBlock`] error if the `RwLock` could /// not be acquired because it was already locked exclusively. @@ -481,10 +582,10 @@ impl<T: ?Sized> RwLock<T> { /// /// # Errors /// - /// This function will return an error if the `RwLock` is poisoned. An - /// `RwLock` is poisoned whenever a writer panics while holding an exclusive - /// lock. An error will only be returned if the lock would have otherwise - /// been acquired. + /// This function will return an error containing the underlying data if + /// the `RwLock` is poisoned. An `RwLock` is poisoned whenever a writer + /// panics while holding an exclusive lock. An error will only be returned + /// if the lock would have otherwise been acquired. /// /// # Examples /// @@ -514,10 +615,11 @@ impl<T: ?Sized> RwLock<T> { /// /// # Errors /// - /// This function will return an error if the `RwLock` is poisoned. An - /// `RwLock` is poisoned whenever a writer panics while holding an exclusive - /// lock. An error will only be returned if the lock would have otherwise - /// been acquired. + /// This function will return an error containing a mutable reference to + /// the underlying data if the `RwLock` is poisoned. An `RwLock` is + /// poisoned whenever a writer panics while holding an exclusive lock. + /// An error will only be returned if the lock would have otherwise been + /// acquired. /// /// # Examples /// diff --git a/library/std/src/sync/rwlock/tests.rs b/library/std/src/sync/poison/rwlock/tests.rs index 48d442921f7..057c2f1a5d7 100644 --- a/library/std/src/sync/rwlock/tests.rs +++ b/library/std/src/sync/poison/rwlock/tests.rs @@ -1,16 +1,37 @@ use rand::Rng; +use crate::fmt::Debug; +use crate::ops::FnMut; +use crate::panic::{self, AssertUnwindSafe}; use crate::sync::atomic::{AtomicUsize, Ordering}; use crate::sync::mpsc::channel; use crate::sync::{ Arc, MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard, TryLockError, }; -use crate::thread; +use crate::{hint, mem, thread}; #[derive(Eq, PartialEq, Debug)] struct NonCopy(i32); +#[derive(Eq, PartialEq, Debug)] +struct NonCopyNeedsDrop(i32); + +impl Drop for NonCopyNeedsDrop { + fn drop(&mut self) { + hint::black_box(()); + } +} + +#[test] +fn test_needs_drop() { + assert!(!mem::needs_drop::<NonCopy>()); + assert!(mem::needs_drop::<NonCopyNeedsDrop>()); +} + +#[derive(Clone, Eq, PartialEq, Debug)] +struct Cloneable(i32); + #[test] fn smoke() { let l = RwLock::new(()); @@ -255,6 +276,21 @@ fn test_rwlock_try_write() { drop(mapped_read_guard); } +fn new_poisoned_rwlock<T>(value: T) -> RwLock<T> { + let lock = RwLock::new(value); + + let catch_unwind_result = panic::catch_unwind(AssertUnwindSafe(|| { + let _guard = lock.write().unwrap(); + + panic!("test panic to poison RwLock"); + })); + + assert!(catch_unwind_result.is_err()); + assert!(lock.is_poisoned()); + + lock +} + #[test] fn test_into_inner() { let m = RwLock::new(NonCopy(10)); @@ -281,22 +317,32 @@ fn test_into_inner_drop() { #[test] fn test_into_inner_poison() { - let m = Arc::new(RwLock::new(NonCopy(10))); - let m2 = m.clone(); - let _ = thread::spawn(move || { - let _lock = m2.write().unwrap(); - panic!("test panic in inner thread to poison RwLock"); - }) - .join(); + let m = new_poisoned_rwlock(NonCopy(10)); - assert!(m.is_poisoned()); - match Arc::try_unwrap(m).unwrap().into_inner() { + match m.into_inner() { Err(e) => assert_eq!(e.into_inner(), NonCopy(10)), Ok(x) => panic!("into_inner of poisoned RwLock is Ok: {x:?}"), } } #[test] +fn test_get_cloned() { + let m = RwLock::new(Cloneable(10)); + + assert_eq!(m.get_cloned().unwrap(), Cloneable(10)); +} + +#[test] +fn test_get_cloned_poison() { + let m = new_poisoned_rwlock(Cloneable(10)); + + match m.get_cloned() { + Err(e) => assert_eq!(e.into_inner(), ()), + Ok(x) => panic!("get of poisoned RwLock is Ok: {x:?}"), + } +} + +#[test] fn test_get_mut() { let mut m = RwLock::new(NonCopy(10)); *m.get_mut().unwrap() = NonCopy(20); @@ -305,22 +351,91 @@ fn test_get_mut() { #[test] fn test_get_mut_poison() { - let m = Arc::new(RwLock::new(NonCopy(10))); - let m2 = m.clone(); - let _ = thread::spawn(move || { - let _lock = m2.write().unwrap(); - panic!("test panic in inner thread to poison RwLock"); - }) - .join(); + let mut m = new_poisoned_rwlock(NonCopy(10)); - assert!(m.is_poisoned()); - match Arc::try_unwrap(m).unwrap().get_mut() { + match m.get_mut() { Err(e) => assert_eq!(*e.into_inner(), NonCopy(10)), Ok(x) => panic!("get_mut of poisoned RwLock is Ok: {x:?}"), } } #[test] +fn test_set() { + fn inner<T>(mut init: impl FnMut() -> T, mut value: impl FnMut() -> T) + where + T: Debug + Eq, + { + let m = RwLock::new(init()); + + assert_eq!(*m.read().unwrap(), init()); + m.set(value()).unwrap(); + assert_eq!(*m.read().unwrap(), value()); + } + + inner(|| NonCopy(10), || NonCopy(20)); + inner(|| NonCopyNeedsDrop(10), || NonCopyNeedsDrop(20)); +} + +#[test] +fn test_set_poison() { + fn inner<T>(mut init: impl FnMut() -> T, mut value: impl FnMut() -> T) + where + T: Debug + Eq, + { + let m = new_poisoned_rwlock(init()); + + match m.set(value()) { + Err(e) => { + assert_eq!(e.into_inner(), value()); + assert_eq!(m.into_inner().unwrap_err().into_inner(), init()); + } + Ok(x) => panic!("set of poisoned RwLock is Ok: {x:?}"), + } + } + + inner(|| NonCopy(10), || NonCopy(20)); + inner(|| NonCopyNeedsDrop(10), || NonCopyNeedsDrop(20)); +} + +#[test] +fn test_replace() { + fn inner<T>(mut init: impl FnMut() -> T, mut value: impl FnMut() -> T) + where + T: Debug + Eq, + { + let m = RwLock::new(init()); + + assert_eq!(*m.read().unwrap(), init()); + assert_eq!(m.replace(value()).unwrap(), init()); + assert_eq!(*m.read().unwrap(), value()); + } + + inner(|| NonCopy(10), || NonCopy(20)); + inner(|| NonCopyNeedsDrop(10), || NonCopyNeedsDrop(20)); +} + +#[test] +fn test_replace_poison() { + fn inner<T>(mut init: impl FnMut() -> T, mut value: impl FnMut() -> T) + where + T: Debug + Eq, + { + let m = new_poisoned_rwlock(init()); + + match m.replace(value()) { + Err(e) => { + assert_eq!(e.into_inner(), value()); + assert_eq!(m.into_inner().unwrap_err().into_inner(), init()); + } + Ok(x) => panic!("replace of poisoned RwLock is Ok: {x:?}"), + } + } + + inner(|| NonCopy(10), || NonCopy(20)); + inner(|| NonCopyNeedsDrop(10), || NonCopyNeedsDrop(20)); +} + +#[test] fn test_read_guard_covariance() { fn do_stuff<'a>(_: RwLockReadGuard<'_, &'a i32>, _: &'a i32) {} let j: i32 = 5; @@ -370,7 +485,7 @@ fn test_mapping_mapped_guard() { fn panic_while_mapping_read_unlocked_no_poison() { let lock = RwLock::new(()); - let _ = crate::panic::catch_unwind(|| { + let _ = panic::catch_unwind(|| { let guard = lock.read().unwrap(); let _guard = RwLockReadGuard::map::<(), _>(guard, |_| panic!()); }); @@ -385,7 +500,7 @@ fn panic_while_mapping_read_unlocked_no_poison() { } } - let _ = crate::panic::catch_unwind(|| { + let _ = panic::catch_unwind(|| { let guard = lock.read().unwrap(); let _guard = RwLockReadGuard::try_map::<(), _>(guard, |_| panic!()); }); @@ -400,7 +515,7 @@ fn panic_while_mapping_read_unlocked_no_poison() { } } - let _ = crate::panic::catch_unwind(|| { + let _ = panic::catch_unwind(|| { let guard = lock.read().unwrap(); let guard = RwLockReadGuard::map::<(), _>(guard, |val| val); let _guard = MappedRwLockReadGuard::map::<(), _>(guard, |_| panic!()); @@ -416,7 +531,7 @@ fn panic_while_mapping_read_unlocked_no_poison() { } } - let _ = crate::panic::catch_unwind(|| { + let _ = panic::catch_unwind(|| { let guard = lock.read().unwrap(); let guard = RwLockReadGuard::map::<(), _>(guard, |val| val); let _guard = MappedRwLockReadGuard::try_map::<(), _>(guard, |_| panic!()); @@ -439,7 +554,7 @@ fn panic_while_mapping_read_unlocked_no_poison() { fn panic_while_mapping_write_unlocked_poison() { let lock = RwLock::new(()); - let _ = crate::panic::catch_unwind(|| { + let _ = panic::catch_unwind(|| { let guard = lock.write().unwrap(); let _guard = RwLockWriteGuard::map::<(), _>(guard, |_| panic!()); }); @@ -452,7 +567,7 @@ fn panic_while_mapping_write_unlocked_poison() { Err(TryLockError::Poisoned(_)) => {} } - let _ = crate::panic::catch_unwind(|| { + let _ = panic::catch_unwind(|| { let guard = lock.write().unwrap(); let _guard = RwLockWriteGuard::try_map::<(), _>(guard, |_| panic!()); }); @@ -467,7 +582,7 @@ fn panic_while_mapping_write_unlocked_poison() { Err(TryLockError::Poisoned(_)) => {} } - let _ = crate::panic::catch_unwind(|| { + let _ = panic::catch_unwind(|| { let guard = lock.write().unwrap(); let guard = RwLockWriteGuard::map::<(), _>(guard, |val| val); let _guard = MappedRwLockWriteGuard::map::<(), _>(guard, |_| panic!()); @@ -483,7 +598,7 @@ fn panic_while_mapping_write_unlocked_poison() { Err(TryLockError::Poisoned(_)) => {} } - let _ = crate::panic::catch_unwind(|| { + let _ = panic::catch_unwind(|| { let guard = lock.write().unwrap(); let guard = RwLockWriteGuard::map::<(), _>(guard, |val| val); let _guard = MappedRwLockWriteGuard::try_map::<(), _>(guard, |_| panic!()); diff --git a/library/std/src/sys/pal/hermit/fs.rs b/library/std/src/sys/pal/hermit/fs.rs index 11862a07608..783623552bb 100644 --- a/library/std/src/sys/pal/hermit/fs.rs +++ b/library/std/src/sys/pal/hermit/fs.rs @@ -3,7 +3,7 @@ use super::hermit_abi::{ self, DT_DIR, DT_LNK, DT_REG, DT_UNKNOWN, O_APPEND, O_CREAT, O_DIRECTORY, O_EXCL, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY, S_IFDIR, S_IFLNK, S_IFMT, S_IFREG, dirent64, stat as stat_struct, }; -use crate::ffi::{CStr, OsStr, OsString}; +use crate::ffi::{CStr, OsStr, OsString, c_char}; use crate::io::{self, BorrowedCursor, Error, ErrorKind, IoSlice, IoSliceMut, SeekFrom}; use crate::os::hermit::ffi::OsStringExt; use crate::os::hermit::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}; @@ -135,7 +135,7 @@ impl FileAttr { S_IFREG => DT_REG, _ => DT_UNKNOWN, }; - FileType { mode: mode } + FileType { mode } } } @@ -204,7 +204,7 @@ impl Iterator for ReadDir { // the size of dirent64. The file name is always a C string and terminated by `\0`. // Consequently, we are able to ignore the last byte. let name_bytes = - unsafe { CStr::from_ptr(&dir.d_name as *const _ as *const i8).to_bytes() }; + unsafe { CStr::from_ptr(&dir.d_name as *const _ as *const c_char).to_bytes() }; let entry = DirEntry { root: self.inner.root.clone(), ino: dir.d_ino, @@ -445,7 +445,7 @@ impl DirBuilder { pub fn mkdir(&self, path: &Path) -> io::Result<()> { run_path_with_cstr(path, &|path| { - cvt(unsafe { hermit_abi::mkdir(path.as_ptr(), self.mode.into()) }).map(|_| ()) + cvt(unsafe { hermit_abi::mkdir(path.as_ptr().cast(), self.mode.into()) }).map(|_| ()) }) } diff --git a/library/std/src/sys/pal/hermit/mod.rs b/library/std/src/sys/pal/hermit/mod.rs index f03fca60344..d833c9d632c 100644 --- a/library/std/src/sys/pal/hermit/mod.rs +++ b/library/std/src/sys/pal/hermit/mod.rs @@ -85,7 +85,7 @@ pub unsafe extern "C" fn runtime_entry( } // initialize environment - os::init_environment(env as *const *const i8); + os::init_environment(env); let result = unsafe { main(argc as isize, argv) }; diff --git a/library/std/src/sys/pal/hermit/os.rs b/library/std/src/sys/pal/hermit/os.rs index f8ea80afa43..791cdb1e57e 100644 --- a/library/std/src/sys/pal/hermit/os.rs +++ b/library/std/src/sys/pal/hermit/os.rs @@ -3,7 +3,7 @@ use core::slice::memchr; use super::hermit_abi; use crate::collections::HashMap; use crate::error::Error as StdError; -use crate::ffi::{CStr, OsStr, OsString}; +use crate::ffi::{CStr, OsStr, OsString, c_char}; use crate::marker::PhantomData; use crate::os::hermit::ffi::OsStringExt; use crate::path::{self, PathBuf}; @@ -70,7 +70,7 @@ pub fn current_exe() -> io::Result<PathBuf> { static ENV: Mutex<Option<HashMap<OsString, OsString>>> = Mutex::new(None); -pub fn init_environment(env: *const *const i8) { +pub fn init_environment(env: *const *const c_char) { let mut guard = ENV.lock().unwrap(); let map = guard.insert(HashMap::new()); diff --git a/library/std/src/sys/pal/hermit/thread.rs b/library/std/src/sys/pal/hermit/thread.rs index 2a0b8dcb4ed..4a7afddbec1 100644 --- a/library/std/src/sys/pal/hermit/thread.rs +++ b/library/std/src/sys/pal/hermit/thread.rs @@ -43,7 +43,7 @@ impl Thread { } Err(io::const_error!(io::ErrorKind::Uncategorized, "Unable to create thread!")) } else { - Ok(Thread { tid: tid }) + Ok(Thread { tid }) }; extern "C" fn thread_start(main: usize) { diff --git a/library/std/src/sys/pal/hermit/time.rs b/library/std/src/sys/pal/hermit/time.rs index e0b6eb76b03..f76a5f96c87 100644 --- a/library/std/src/sys/pal/hermit/time.rs +++ b/library/std/src/sys/pal/hermit/time.rs @@ -22,7 +22,7 @@ impl Timespec { const fn new(tv_sec: i64, tv_nsec: i32) -> Timespec { assert!(tv_nsec >= 0 && tv_nsec < NSEC_PER_SEC); // SAFETY: The assert above checks tv_nsec is within the valid range - Timespec { t: timespec { tv_sec: tv_sec, tv_nsec: tv_nsec } } + Timespec { t: timespec { tv_sec, tv_nsec } } } fn sub_timespec(&self, other: &Timespec) -> Result<Duration, Duration> { diff --git a/library/std/src/sys/pal/sgx/fd.rs b/library/std/src/sys/pal/sgx/fd.rs index c41b527cff7..3bb3189a1d1 100644 --- a/library/std/src/sys/pal/sgx/fd.rs +++ b/library/std/src/sys/pal/sgx/fd.rs @@ -12,7 +12,7 @@ pub struct FileDesc { impl FileDesc { pub fn new(fd: Fd) -> FileDesc { - FileDesc { fd: fd } + FileDesc { fd } } pub fn raw(&self) -> Fd { diff --git a/library/std/src/sys/pal/teeos/mod.rs b/library/std/src/sys/pal/teeos/mod.rs index 2bf2e2ceb31..a9900f55b19 100644 --- a/library/std/src/sys/pal/teeos/mod.rs +++ b/library/std/src/sys/pal/teeos/mod.rs @@ -71,7 +71,7 @@ pub fn decode_error_kind(errno: i32) -> ErrorKind { libc::ECONNREFUSED => ConnectionRefused, libc::ECONNRESET => ConnectionReset, libc::EDEADLK => Deadlock, - libc::EDQUOT => FilesystemQuotaExceeded, + libc::EDQUOT => QuotaExceeded, libc::EEXIST => AlreadyExists, libc::EFBIG => FileTooLarge, libc::EHOSTUNREACH => HostUnreachable, diff --git a/library/std/src/sys/pal/unix/fd.rs b/library/std/src/sys/pal/unix/fd.rs index 6a28799ca55..2fc33bdfefb 100644 --- a/library/std/src/sys/pal/unix/fd.rs +++ b/library/std/src/sys/pal/unix/fd.rs @@ -5,7 +5,6 @@ mod tests; #[cfg(not(any( target_os = "linux", - target_os = "emscripten", target_os = "l4re", target_os = "android", target_os = "hurd", @@ -14,7 +13,6 @@ use libc::off_t as off64_t; #[cfg(any( target_os = "android", target_os = "linux", - target_os = "emscripten", target_os = "l4re", target_os = "hurd", ))] diff --git a/library/std/src/sys/pal/unix/fs.rs b/library/std/src/sys/pal/unix/fs.rs index 37029bcd36e..54fa31620ac 100644 --- a/library/std/src/sys/pal/unix/fs.rs +++ b/library/std/src/sys/pal/unix/fs.rs @@ -8,16 +8,11 @@ mod tests; use libc::c_char; #[cfg(any( all(target_os = "linux", not(target_env = "musl")), - target_os = "emscripten", target_os = "android", target_os = "hurd" ))] use libc::dirfd; -#[cfg(any( - all(target_os = "linux", not(target_env = "musl")), - target_os = "emscripten", - target_os = "hurd" -))] +#[cfg(any(all(target_os = "linux", not(target_env = "musl")), target_os = "hurd"))] use libc::fstatat64; #[cfg(any( target_os = "android", @@ -34,7 +29,6 @@ use libc::readdir as readdir64; #[cfg(not(any( target_os = "android", target_os = "linux", - target_os = "emscripten", target_os = "solaris", target_os = "illumos", target_os = "l4re", @@ -48,7 +42,7 @@ use libc::readdir as readdir64; use libc::readdir_r as readdir64_r; #[cfg(any(all(target_os = "linux", not(target_env = "musl")), target_os = "hurd"))] use libc::readdir64; -#[cfg(any(target_os = "emscripten", target_os = "l4re"))] +#[cfg(target_os = "l4re")] use libc::readdir64_r; use libc::{c_int, mode_t}; #[cfg(target_os = "android")] @@ -58,7 +52,6 @@ use libc::{ }; #[cfg(not(any( all(target_os = "linux", not(target_env = "musl")), - target_os = "emscripten", target_os = "l4re", target_os = "android", target_os = "hurd", @@ -69,7 +62,6 @@ use libc::{ }; #[cfg(any( all(target_os = "linux", not(target_env = "musl")), - target_os = "emscripten", target_os = "l4re", target_os = "hurd" ))] @@ -168,7 +160,8 @@ cfg_has_statx! {{ ) -> c_int } - if STATX_SAVED_STATE.load(Ordering::Relaxed) == STATX_STATE::Unavailable as u8 { + let statx_availability = STATX_SAVED_STATE.load(Ordering::Relaxed); + if statx_availability == STATX_STATE::Unavailable as u8 { return None; } @@ -200,6 +193,9 @@ cfg_has_statx! {{ return None; } } + if statx_availability == STATX_STATE::Unknown as u8 { + STATX_SAVED_STATE.store(STATX_STATE::Present as u8, Ordering::Relaxed); + } // We cannot fill `stat64` exhaustively because of private padding fields. let mut stat: stat64 = mem::zeroed(); @@ -895,7 +891,6 @@ impl DirEntry { #[cfg(all( any( all(target_os = "linux", not(target_env = "musl")), - target_os = "emscripten", target_os = "android", target_os = "hurd" ), @@ -924,7 +919,6 @@ impl DirEntry { #[cfg(any( not(any( all(target_os = "linux", not(target_env = "musl")), - target_os = "emscripten", target_os = "android", target_os = "hurd", )), @@ -1944,7 +1938,7 @@ fn open_from(from: &Path) -> io::Result<(crate::fs::File, crate::fs::Metadata)> #[cfg(target_os = "espidf")] fn open_to_and_set_permissions( to: &Path, - _reader_metadata: crate::fs::Metadata, + _reader_metadata: &crate::fs::Metadata, ) -> io::Result<(crate::fs::File, crate::fs::Metadata)> { use crate::fs::OpenOptions; let writer = OpenOptions::new().open(to)?; @@ -1955,7 +1949,7 @@ fn open_to_and_set_permissions( #[cfg(not(target_os = "espidf"))] fn open_to_and_set_permissions( to: &Path, - reader_metadata: crate::fs::Metadata, + reader_metadata: &crate::fs::Metadata, ) -> io::Result<(crate::fs::File, crate::fs::Metadata)> { use crate::fs::OpenOptions; use crate::os::unix::fs::{OpenOptionsExt, PermissionsExt}; @@ -1980,30 +1974,63 @@ fn open_to_and_set_permissions( Ok((writer, writer_metadata)) } -#[cfg(not(any(target_os = "linux", target_os = "android", target_vendor = "apple")))] -pub fn copy(from: &Path, to: &Path) -> io::Result<u64> { - let (mut reader, reader_metadata) = open_from(from)?; - let (mut writer, _) = open_to_and_set_permissions(to, reader_metadata)?; +mod cfm { + use crate::fs::{File, Metadata}; + use crate::io::{BorrowedCursor, IoSlice, IoSliceMut, Read, Result, Write}; - io::copy(&mut reader, &mut writer) -} + #[allow(dead_code)] + pub struct CachedFileMetadata(pub File, pub Metadata); + impl Read for CachedFileMetadata { + fn read(&mut self, buf: &mut [u8]) -> Result<usize> { + self.0.read(buf) + } + fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize> { + self.0.read_vectored(bufs) + } + fn read_buf(&mut self, cursor: BorrowedCursor<'_>) -> Result<()> { + self.0.read_buf(cursor) + } + #[inline] + fn is_read_vectored(&self) -> bool { + self.0.is_read_vectored() + } + fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> { + self.0.read_to_end(buf) + } + fn read_to_string(&mut self, buf: &mut String) -> Result<usize> { + self.0.read_to_string(buf) + } + } + impl Write for CachedFileMetadata { + fn write(&mut self, buf: &[u8]) -> Result<usize> { + self.0.write(buf) + } + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize> { + self.0.write_vectored(bufs) + } + #[inline] + fn is_write_vectored(&self) -> bool { + self.0.is_write_vectored() + } + #[inline] + fn flush(&mut self) -> Result<()> { + self.0.flush() + } + } +} #[cfg(any(target_os = "linux", target_os = "android"))] +pub(crate) use cfm::CachedFileMetadata; + +#[cfg(not(target_vendor = "apple"))] pub fn copy(from: &Path, to: &Path) -> io::Result<u64> { - let (mut reader, reader_metadata) = open_from(from)?; - let max_len = u64::MAX; - let (mut writer, _) = open_to_and_set_permissions(to, reader_metadata)?; - - use super::kernel_copy::{CopyResult, copy_regular_files}; - - match copy_regular_files(reader.as_raw_fd(), writer.as_raw_fd(), max_len) { - CopyResult::Ended(bytes) => Ok(bytes), - CopyResult::Error(e, _) => Err(e), - CopyResult::Fallback(written) => match io::copy::generic_copy(&mut reader, &mut writer) { - Ok(bytes) => Ok(bytes + written), - Err(e) => Err(e), - }, - } + let (reader, reader_metadata) = open_from(from)?; + let (writer, writer_metadata) = open_to_and_set_permissions(to, &reader_metadata)?; + + io::copy( + &mut cfm::CachedFileMetadata(reader, reader_metadata), + &mut cfm::CachedFileMetadata(writer, writer_metadata), + ) } #[cfg(target_vendor = "apple")] @@ -2040,7 +2067,7 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> { } // Fall back to using `fcopyfile` if `fclonefileat` does not succeed. - let (writer, writer_metadata) = open_to_and_set_permissions(to, reader_metadata)?; + let (writer, writer_metadata) = open_to_and_set_permissions(to, &reader_metadata)?; // We ensure that `FreeOnDrop` never contains a null pointer so it is // always safe to call `copyfile_state_free` diff --git a/library/std/src/sys/pal/unix/kernel_copy.rs b/library/std/src/sys/pal/unix/kernel_copy.rs index a671383cb79..36823a503b1 100644 --- a/library/std/src/sys/pal/unix/kernel_copy.rs +++ b/library/std/src/sys/pal/unix/kernel_copy.rs @@ -65,6 +65,7 @@ use crate::process::{ChildStderr, ChildStdin, ChildStdout}; use crate::ptr; use crate::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use crate::sys::cvt; +use crate::sys::fs::CachedFileMetadata; use crate::sys::weak::syscall; #[cfg(test)] @@ -192,7 +193,7 @@ impl<R: CopyRead, W: CopyWrite> SpecCopy for Copier<'_, '_, R, W> { let w_cfg = writer.properties(); // before direct operations on file descriptors ensure that all source and sink buffers are empty - let mut flush = || -> crate::io::Result<u64> { + let mut flush = || -> Result<u64> { let bytes = reader.drain_to(writer, u64::MAX)?; // BufWriter buffered bytes have already been accounted for in earlier write() calls writer.flush()?; @@ -537,6 +538,18 @@ impl<T: ?Sized + CopyWrite> CopyWrite for BufWriter<T> { } } +impl CopyRead for CachedFileMetadata { + fn properties(&self) -> CopyParams { + CopyParams(FdMeta::Metadata(self.1.clone()), Some(self.0.as_raw_fd())) + } +} + +impl CopyWrite for CachedFileMetadata { + fn properties(&self) -> CopyParams { + CopyParams(FdMeta::Metadata(self.1.clone()), Some(self.0.as_raw_fd())) + } +} + fn fd_to_meta<T: AsRawFd>(fd: &T) -> FdMeta { let fd = fd.as_raw_fd(); let file: ManuallyDrop<File> = ManuallyDrop::new(unsafe { File::from_raw_fd(fd) }); diff --git a/library/std/src/sys/pal/unix/mod.rs b/library/std/src/sys/pal/unix/mod.rs index 8eaa50d7f81..3cc1cae8d00 100644 --- a/library/std/src/sys/pal/unix/mod.rs +++ b/library/std/src/sys/pal/unix/mod.rs @@ -254,7 +254,7 @@ pub fn decode_error_kind(errno: i32) -> ErrorKind { libc::ECONNREFUSED => ConnectionRefused, libc::ECONNRESET => ConnectionReset, libc::EDEADLK => Deadlock, - libc::EDQUOT => FilesystemQuotaExceeded, + libc::EDQUOT => QuotaExceeded, libc::EEXIST => AlreadyExists, libc::EFBIG => FileTooLarge, libc::EHOSTUNREACH => HostUnreachable, diff --git a/library/std/src/sys/pal/unix/net.rs b/library/std/src/sys/pal/unix/net.rs index d140607869c..d73b9fd5eb8 100644 --- a/library/std/src/sys/pal/unix/net.rs +++ b/library/std/src/sys/pal/unix/net.rs @@ -81,6 +81,7 @@ impl Socket { target_os = "netbsd", target_os = "openbsd", target_os = "nto", + target_os = "solaris", ))] { // On platforms that support it we pass the SOCK_CLOEXEC // flag to atomically create the socket and set it as diff --git a/library/std/src/sys/pal/unix/thread.rs b/library/std/src/sys/pal/unix/thread.rs index 131a6e81b1e..e360ba0f6d7 100644 --- a/library/std/src/sys/pal/unix/thread.rs +++ b/library/std/src/sys/pal/unix/thread.rs @@ -45,6 +45,7 @@ unsafe impl Sync for Thread {} impl Thread { // unsafe: see thread::Builder::spawn_unchecked for safety requirements + #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub unsafe fn new(stack: usize, p: Box<dyn FnOnce()>) -> io::Result<Thread> { let p = Box::into_raw(Box::new(p)); let mut native: libc::pthread_t = mem::zeroed(); diff --git a/library/std/src/sys/pal/wasi/fs.rs b/library/std/src/sys/pal/wasi/fs.rs index 0667eb90101..7779d2b97d7 100644 --- a/library/std/src/sys/pal/wasi/fs.rs +++ b/library/std/src/sys/pal/wasi/fs.rs @@ -27,10 +27,27 @@ pub struct FileAttr { pub struct ReadDir { inner: Arc<ReadDirInner>, - cookie: Option<wasi::Dircookie>, - buf: Vec<u8>, - offset: usize, - cap: usize, + state: ReadDirState, +} + +enum ReadDirState { + /// Fill `buf` with `buf.len()` bytes starting from `next_read_offset`. + FillBuffer { + next_read_offset: wasi::Dircookie, + buf: Vec<u8>, + }, + ProcessEntry { + buf: Vec<u8>, + next_read_offset: Option<wasi::Dircookie>, + offset: usize, + }, + /// There is no more data to get in [`Self::FillBuffer`]; keep returning + /// entries via ProcessEntry until `buf` is exhausted. + RunUntilExhaustion { + buf: Vec<u8>, + offset: usize, + }, + Done, } struct ReadDirInner { @@ -147,11 +164,8 @@ impl FileType { impl ReadDir { fn new(dir: File, root: PathBuf) -> ReadDir { ReadDir { - cookie: Some(0), - buf: vec![0; 128], - offset: 0, - cap: 0, inner: Arc::new(ReadDirInner { dir, root }), + state: ReadDirState::FillBuffer { next_read_offset: 0, buf: vec![0; 128] }, } } } @@ -162,78 +176,99 @@ impl fmt::Debug for ReadDir { } } +impl core::iter::FusedIterator for ReadDir {} + impl Iterator for ReadDir { type Item = io::Result<DirEntry>; fn next(&mut self) -> Option<io::Result<DirEntry>> { - loop { - // If we've reached the capacity of our buffer then we need to read - // some more from the OS, otherwise we pick up at our old offset. - let offset = if self.offset == self.cap { - let cookie = self.cookie.take()?; - match self.inner.dir.fd.readdir(&mut self.buf, cookie) { - Ok(bytes) => self.cap = bytes, - Err(e) => return Some(Err(e)), - } - self.offset = 0; - self.cookie = Some(cookie); - - // If we didn't actually read anything, this is in theory the - // end of the directory. - if self.cap == 0 { - self.cookie = None; - return None; - } - - 0 - } else { - self.offset - }; - let data = &self.buf[offset..self.cap]; - - // If we're not able to read a directory entry then that means it - // must have been truncated at the end of the buffer, so reset our - // offset so we can go back and reread into the buffer, picking up - // where we last left off. - let dirent_size = mem::size_of::<wasi::Dirent>(); - if data.len() < dirent_size { - assert!(self.cookie.is_some()); - assert!(self.buf.len() >= dirent_size); - self.offset = self.cap; - continue; - } - let (dirent, data) = data.split_at(dirent_size); - let dirent = unsafe { ptr::read_unaligned(dirent.as_ptr() as *const wasi::Dirent) }; - - // If the file name was truncated, then we need to reinvoke - // `readdir` so we truncate our buffer to start over and reread this - // descriptor. Note that if our offset is 0 that means the file name - // is massive and we need a bigger buffer. - if data.len() < dirent.d_namlen as usize { - if offset == 0 { - let amt_to_add = self.buf.capacity(); - self.buf.extend(iter::repeat(0).take(amt_to_add)); + match &mut self.state { + ReadDirState::FillBuffer { next_read_offset, ref mut buf } => { + let result = self.inner.dir.fd.readdir(buf, *next_read_offset); + match result { + Ok(read_bytes) => { + if read_bytes < buf.len() { + buf.truncate(read_bytes); + self.state = + ReadDirState::RunUntilExhaustion { buf: mem::take(buf), offset: 0 }; + } else { + debug_assert_eq!(read_bytes, buf.len()); + self.state = ReadDirState::ProcessEntry { + buf: mem::take(buf), + offset: 0, + next_read_offset: Some(*next_read_offset), + }; + } + self.next() + } + Err(e) => { + self.state = ReadDirState::Done; + return Some(Err(e)); + } } - assert!(self.cookie.is_some()); - self.offset = self.cap; - continue; } - self.cookie = Some(dirent.d_next); - self.offset = offset + dirent_size + dirent.d_namlen as usize; + ReadDirState::ProcessEntry { ref mut buf, next_read_offset, offset } => { + let contents = &buf[*offset..]; + const DIRENT_SIZE: usize = crate::mem::size_of::<wasi::Dirent>(); + if contents.len() >= DIRENT_SIZE { + let (dirent, data) = contents.split_at(DIRENT_SIZE); + let dirent = + unsafe { ptr::read_unaligned(dirent.as_ptr() as *const wasi::Dirent) }; + // If the file name was truncated, then we need to reinvoke + // `readdir` so we truncate our buffer to start over and reread this + // descriptor. + if data.len() < dirent.d_namlen as usize { + if buf.len() < dirent.d_namlen as usize + DIRENT_SIZE { + buf.resize(dirent.d_namlen as usize + DIRENT_SIZE, 0); + } + if let Some(next_read_offset) = *next_read_offset { + self.state = + ReadDirState::FillBuffer { next_read_offset, buf: mem::take(buf) }; + } else { + self.state = ReadDirState::Done; + } + + return self.next(); + } + next_read_offset.as_mut().map(|cookie| { + *cookie = dirent.d_next; + }); + *offset = *offset + DIRENT_SIZE + dirent.d_namlen as usize; - let name = &data[..(dirent.d_namlen as usize)]; + let name = &data[..(dirent.d_namlen as usize)]; + + // These names are skipped on all other platforms, so let's skip + // them here too + if name == b"." || name == b".." { + return self.next(); + } - // These names are skipped on all other platforms, so let's skip - // them here too - if name == b"." || name == b".." { - continue; + return Some(Ok(DirEntry { + meta: dirent, + name: name.to_vec(), + inner: self.inner.clone(), + })); + } else if let Some(next_read_offset) = *next_read_offset { + self.state = ReadDirState::FillBuffer { next_read_offset, buf: mem::take(buf) }; + } else { + self.state = ReadDirState::Done; + } + self.next() } + ReadDirState::RunUntilExhaustion { buf, offset } => { + if *offset >= buf.len() { + self.state = ReadDirState::Done; + } else { + self.state = ReadDirState::ProcessEntry { + buf: mem::take(buf), + offset: *offset, + next_read_offset: None, + }; + } - return Some(Ok(DirEntry { - meta: dirent, - name: name.to_vec(), - inner: self.inner.clone(), - })); + self.next() + } + ReadDirState::Done => None, } } } diff --git a/library/std/src/sys/pal/windows/c/bindings.txt b/library/std/src/sys/pal/windows/c/bindings.txt index 248ce3c9ff6..c06f274685c 100644 --- a/library/std/src/sys/pal/windows/c/bindings.txt +++ b/library/std/src/sys/pal/windows/c/bindings.txt @@ -2295,6 +2295,7 @@ Windows.Win32.Storage.FileSystem.FILE_NAME_OPENED Windows.Win32.Storage.FileSystem.FILE_READ_ATTRIBUTES Windows.Win32.Storage.FileSystem.FILE_READ_DATA Windows.Win32.Storage.FileSystem.FILE_READ_EA +Windows.Win32.Storage.FileSystem.FILE_RENAME_INFO Windows.Win32.Storage.FileSystem.FILE_SHARE_DELETE Windows.Win32.Storage.FileSystem.FILE_SHARE_MODE Windows.Win32.Storage.FileSystem.FILE_SHARE_NONE @@ -2425,6 +2426,7 @@ Windows.Win32.System.Console.ENABLE_VIRTUAL_TERMINAL_PROCESSING Windows.Win32.System.Console.ENABLE_WINDOW_INPUT Windows.Win32.System.Console.ENABLE_WRAP_AT_EOL_OUTPUT Windows.Win32.System.Console.GetConsoleMode +Windows.Win32.System.Console.GetConsoleOutputCP Windows.Win32.System.Console.GetStdHandle Windows.Win32.System.Console.ReadConsoleW Windows.Win32.System.Console.STD_ERROR_HANDLE @@ -2603,5 +2605,7 @@ Windows.Win32.System.Threading.WaitForMultipleObjects Windows.Win32.System.Threading.WaitForSingleObject Windows.Win32.System.Threading.WakeAllConditionVariable Windows.Win32.System.Threading.WakeConditionVariable +Windows.Win32.System.WindowsProgramming.FILE_RENAME_FLAG_POSIX_SEMANTICS +Windows.Win32.System.WindowsProgramming.FILE_RENAME_FLAG_REPLACE_IF_EXISTS Windows.Win32.System.WindowsProgramming.PROGRESS_CONTINUE Windows.Win32.UI.Shell.GetUserProfileDirectoryW diff --git a/library/std/src/sys/pal/windows/c/windows_sys.rs b/library/std/src/sys/pal/windows/c/windows_sys.rs index 19925e59dfe..79513d33a1a 100644 --- a/library/std/src/sys/pal/windows/c/windows_sys.rs +++ b/library/std/src/sys/pal/windows/c/windows_sys.rs @@ -34,6 +34,7 @@ windows_targets::link!("kernel32.dll" "system" fn FreeEnvironmentStringsW(penv : windows_targets::link!("kernel32.dll" "system" fn GetActiveProcessorCount(groupnumber : u16) -> u32); windows_targets::link!("kernel32.dll" "system" fn GetCommandLineW() -> PCWSTR); windows_targets::link!("kernel32.dll" "system" fn GetConsoleMode(hconsolehandle : HANDLE, lpmode : *mut CONSOLE_MODE) -> BOOL); +windows_targets::link!("kernel32.dll" "system" fn GetConsoleOutputCP() -> u32); windows_targets::link!("kernel32.dll" "system" fn GetCurrentDirectoryW(nbufferlength : u32, lpbuffer : PWSTR) -> u32); windows_targets::link!("kernel32.dll" "system" fn GetCurrentProcess() -> HANDLE); windows_targets::link!("kernel32.dll" "system" fn GetCurrentProcessId() -> u32); @@ -2472,6 +2473,22 @@ pub const FILE_RANDOM_ACCESS: NTCREATEFILE_CREATE_OPTIONS = 2048u32; pub const FILE_READ_ATTRIBUTES: FILE_ACCESS_RIGHTS = 128u32; pub const FILE_READ_DATA: FILE_ACCESS_RIGHTS = 1u32; pub const FILE_READ_EA: FILE_ACCESS_RIGHTS = 8u32; +pub const FILE_RENAME_FLAG_POSIX_SEMANTICS: u32 = 2u32; +pub const FILE_RENAME_FLAG_REPLACE_IF_EXISTS: u32 = 1u32; +#[repr(C)] +#[derive(Clone, Copy)] +pub struct FILE_RENAME_INFO { + pub Anonymous: FILE_RENAME_INFO_0, + pub RootDirectory: HANDLE, + pub FileNameLength: u32, + pub FileName: [u16; 1], +} +#[repr(C)] +#[derive(Clone, Copy)] +pub union FILE_RENAME_INFO_0 { + pub ReplaceIfExists: BOOLEAN, + pub Flags: u32, +} pub const FILE_RESERVE_OPFILTER: NTCREATEFILE_CREATE_OPTIONS = 1048576u32; pub const FILE_SEQUENTIAL_ONLY: NTCREATEFILE_CREATE_OPTIONS = 4u32; pub const FILE_SESSION_AWARE: NTCREATEFILE_CREATE_OPTIONS = 262144u32; @@ -3317,6 +3334,7 @@ pub struct XSAVE_FORMAT { pub XmmRegisters: [M128A; 8], pub Reserved4: [u8; 224], } + #[cfg(target_arch = "arm")] #[repr(C)] pub struct WSADATA { diff --git a/library/std/src/sys/pal/windows/fs.rs b/library/std/src/sys/pal/windows/fs.rs index 5bdd5f81b9c..b3659351b8c 100644 --- a/library/std/src/sys/pal/windows/fs.rs +++ b/library/std/src/sys/pal/windows/fs.rs @@ -1,5 +1,6 @@ use super::api::{self, WinError}; use super::{IoResult, to_u16s}; +use crate::alloc::{alloc, handle_alloc_error}; use crate::borrow::Cow; use crate::ffi::{OsStr, OsString, c_void}; use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom}; @@ -315,19 +316,31 @@ impl File { && api::get_last_error() == WinError::ALREADY_EXISTS { unsafe { - // This originally used `FileAllocationInfo` instead of - // `FileEndOfFileInfo` but that wasn't supported by WINE. - // It's arguable which fits the semantics of `OpenOptions` - // better so let's just use the more widely supported method. - let eof = c::FILE_END_OF_FILE_INFO { EndOfFile: 0 }; + // This first tries `FileAllocationInfo` but falls back to + // `FileEndOfFileInfo` in order to support WINE. + // If WINE gains support for FileAllocationInfo, we should + // remove the fallback. + let alloc = c::FILE_ALLOCATION_INFO { AllocationSize: 0 }; let result = c::SetFileInformationByHandle( handle.as_raw_handle(), - c::FileEndOfFileInfo, - (&raw const eof).cast::<c_void>(), - mem::size_of::<c::FILE_END_OF_FILE_INFO>() as u32, + c::FileAllocationInfo, + (&raw const alloc).cast::<c_void>(), + mem::size_of::<c::FILE_ALLOCATION_INFO>() as u32, ); if result == 0 { - return Err(io::Error::last_os_error()); + if api::get_last_error().code != 0 { + panic!("FILE_ALLOCATION_INFO failed!!!"); + } + let eof = c::FILE_END_OF_FILE_INFO { EndOfFile: 0 }; + let result = c::SetFileInformationByHandle( + handle.as_raw_handle(), + c::FileEndOfFileInfo, + (&raw const eof).cast::<c_void>(), + mem::size_of::<c::FILE_END_OF_FILE_INFO>() as u32, + ); + if result == 0 { + return Err(io::Error::last_os_error()); + } } } } @@ -1223,7 +1236,142 @@ pub fn unlink(p: &Path) -> io::Result<()> { pub fn rename(old: &Path, new: &Path) -> io::Result<()> { let old = maybe_verbatim(old)?; let new = maybe_verbatim(new)?; - cvt(unsafe { c::MoveFileExW(old.as_ptr(), new.as_ptr(), c::MOVEFILE_REPLACE_EXISTING) })?; + + let new_len_without_nul_in_bytes = (new.len() - 1).try_into().unwrap(); + + // The last field of FILE_RENAME_INFO, the file name, is unsized, + // and FILE_RENAME_INFO has two padding bytes. + // Therefore we need to make sure to not allocate less than + // size_of::<c::FILE_RENAME_INFO>() bytes, which would be the case with + // 0 or 1 character paths + a null byte. + let struct_size = mem::size_of::<c::FILE_RENAME_INFO>() + .max(mem::offset_of!(c::FILE_RENAME_INFO, FileName) + new.len() * mem::size_of::<u16>()); + + let struct_size: u32 = struct_size.try_into().unwrap(); + + let create_file = |extra_access, extra_flags| { + let handle = unsafe { + HandleOrInvalid::from_raw_handle(c::CreateFileW( + old.as_ptr(), + c::SYNCHRONIZE | c::DELETE | extra_access, + c::FILE_SHARE_READ | c::FILE_SHARE_WRITE | c::FILE_SHARE_DELETE, + ptr::null(), + c::OPEN_EXISTING, + c::FILE_ATTRIBUTE_NORMAL | c::FILE_FLAG_BACKUP_SEMANTICS | extra_flags, + ptr::null_mut(), + )) + }; + + OwnedHandle::try_from(handle).map_err(|_| io::Error::last_os_error()) + }; + + // The following code replicates `MoveFileEx`'s behavior as reverse-engineered from its disassembly. + // If `old` refers to a mount point, we move it instead of the target. + let handle = match create_file(c::FILE_READ_ATTRIBUTES, c::FILE_FLAG_OPEN_REPARSE_POINT) { + Ok(handle) => { + let mut file_attribute_tag_info: MaybeUninit<c::FILE_ATTRIBUTE_TAG_INFO> = + MaybeUninit::uninit(); + + let result = unsafe { + cvt(c::GetFileInformationByHandleEx( + handle.as_raw_handle(), + c::FileAttributeTagInfo, + file_attribute_tag_info.as_mut_ptr().cast(), + mem::size_of::<c::FILE_ATTRIBUTE_TAG_INFO>().try_into().unwrap(), + )) + }; + + if let Err(err) = result { + if err.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as _) + || err.raw_os_error() == Some(c::ERROR_INVALID_FUNCTION as _) + { + // `GetFileInformationByHandleEx` documents that not all underlying drivers support all file information classes. + // Since we know we passed the correct arguments, this means the underlying driver didn't understand our request; + // `MoveFileEx` proceeds by reopening the file without inhibiting reparse point behavior. + None + } else { + Some(Err(err)) + } + } else { + // SAFETY: The struct has been initialized by GetFileInformationByHandleEx + let file_attribute_tag_info = unsafe { file_attribute_tag_info.assume_init() }; + let file_type = FileType::new( + file_attribute_tag_info.FileAttributes, + file_attribute_tag_info.ReparseTag, + ); + + if file_type.is_symlink() { + // The file is a mount point, junction point or symlink so + // don't reopen the file so that the link gets renamed. + Some(Ok(handle)) + } else { + // Otherwise reopen the file without inhibiting reparse point behavior. + None + } + } + } + // The underlying driver may not support `FILE_FLAG_OPEN_REPARSE_POINT`: Retry without it. + Err(err) if err.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as _) => None, + Err(err) => Some(Err(err)), + } + .unwrap_or_else(|| create_file(0, 0))?; + + let layout = core::alloc::Layout::from_size_align( + struct_size as _, + mem::align_of::<c::FILE_RENAME_INFO>(), + ) + .unwrap(); + + let file_rename_info = unsafe { alloc(layout) } as *mut c::FILE_RENAME_INFO; + + if file_rename_info.is_null() { + handle_alloc_error(layout); + } + + // SAFETY: file_rename_info is a non-null pointer pointing to memory allocated by the global allocator. + let mut file_rename_info = unsafe { Box::from_raw(file_rename_info) }; + + // SAFETY: We have allocated enough memory for a full FILE_RENAME_INFO struct and a filename. + unsafe { + (&raw mut (*file_rename_info).Anonymous).write(c::FILE_RENAME_INFO_0 { + Flags: c::FILE_RENAME_FLAG_REPLACE_IF_EXISTS | c::FILE_RENAME_FLAG_POSIX_SEMANTICS, + }); + + (&raw mut (*file_rename_info).RootDirectory).write(ptr::null_mut()); + (&raw mut (*file_rename_info).FileNameLength).write(new_len_without_nul_in_bytes); + + new.as_ptr() + .copy_to_nonoverlapping((&raw mut (*file_rename_info).FileName) as *mut u16, new.len()); + } + + // We don't use `set_file_information_by_handle` here as `FILE_RENAME_INFO` is used for both `FileRenameInfo` and `FileRenameInfoEx`. + let result = unsafe { + cvt(c::SetFileInformationByHandle( + handle.as_raw_handle(), + c::FileRenameInfoEx, + (&raw const *file_rename_info).cast::<c_void>(), + struct_size, + )) + }; + + if let Err(err) = result { + if err.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as _) { + // FileRenameInfoEx and FILE_RENAME_FLAG_POSIX_SEMANTICS were added in Windows 10 1607; retry with FileRenameInfo. + file_rename_info.Anonymous.ReplaceIfExists = 1; + + cvt(unsafe { + c::SetFileInformationByHandle( + handle.as_raw_handle(), + c::FileRenameInfo, + (&raw const *file_rename_info).cast::<c_void>(), + struct_size, + ) + })?; + } else { + return Err(err); + } + } + Ok(()) } diff --git a/library/std/src/sys/pal/windows/mod.rs b/library/std/src/sys/pal/windows/mod.rs index d66ff15e10b..88e6def7a75 100644 --- a/library/std/src/sys/pal/windows/mod.rs +++ b/library/std/src/sys/pal/windows/mod.rs @@ -113,7 +113,7 @@ pub fn decode_error_kind(errno: i32) -> ErrorKind { c::ERROR_WRITE_PROTECT => return ReadOnlyFilesystem, c::ERROR_DISK_FULL | c::ERROR_HANDLE_DISK_FULL => return StorageFull, c::ERROR_SEEK_ON_DEVICE => return NotSeekable, - c::ERROR_DISK_QUOTA_EXCEEDED => return FilesystemQuotaExceeded, + c::ERROR_DISK_QUOTA_EXCEEDED => return QuotaExceeded, c::ERROR_FILE_TOO_LARGE => return FileTooLarge, c::ERROR_BUSY => return ResourceBusy, c::ERROR_POSSIBLE_DEADLOCK => return Deadlock, @@ -138,7 +138,7 @@ pub fn decode_error_kind(errno: i32) -> ErrorKind { c::WSAEHOSTUNREACH => HostUnreachable, c::WSAENETDOWN => NetworkDown, c::WSAENETUNREACH => NetworkUnreachable, - c::WSAEDQUOT => FilesystemQuotaExceeded, + c::WSAEDQUOT => QuotaExceeded, _ => Uncategorized, } diff --git a/library/std/src/sys/pal/windows/process.rs b/library/std/src/sys/pal/windows/process.rs index da0daacd1dd..2ca20a21dfe 100644 --- a/library/std/src/sys/pal/windows/process.rs +++ b/library/std/src/sys/pal/windows/process.rs @@ -10,10 +10,10 @@ use crate::collections::BTreeMap; use crate::env::consts::{EXE_EXTENSION, EXE_SUFFIX}; use crate::ffi::{OsStr, OsString}; use crate::io::{self, Error, ErrorKind}; -use crate::mem::MaybeUninit; use crate::num::NonZero; use crate::os::windows::ffi::{OsStrExt, OsStringExt}; use crate::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, IntoRawHandle}; +use crate::os::windows::process::ProcThreadAttributeList; use crate::path::{Path, PathBuf}; use crate::sync::Mutex; use crate::sys::args::{self, Arg}; @@ -162,7 +162,6 @@ pub struct Command { stdout: Option<Stdio>, stderr: Option<Stdio>, force_quotes_enabled: bool, - proc_thread_attributes: BTreeMap<usize, ProcThreadAttributeValue>, } pub enum Stdio { @@ -194,7 +193,6 @@ impl Command { stdout: None, stderr: None, force_quotes_enabled: false, - proc_thread_attributes: Default::default(), } } @@ -248,21 +246,19 @@ impl Command { self.cwd.as_ref().map(Path::new) } - pub unsafe fn raw_attribute<T: Copy + Send + Sync + 'static>( + pub fn spawn( &mut self, - attribute: usize, - value: T, - ) { - self.proc_thread_attributes.insert(attribute, ProcThreadAttributeValue { - size: mem::size_of::<T>(), - data: Box::new(value), - }); + default: Stdio, + needs_stdin: bool, + ) -> io::Result<(Process, StdioPipes)> { + self.spawn_with_attributes(default, needs_stdin, None) } - pub fn spawn( + pub fn spawn_with_attributes( &mut self, default: Stdio, needs_stdin: bool, + proc_thread_attribute_list: Option<&ProcThreadAttributeList<'_>>, ) -> io::Result<(Process, StdioPipes)> { let maybe_env = self.env.capture_if_changed(); @@ -355,18 +351,18 @@ impl Command { let si_ptr: *mut c::STARTUPINFOW; - let mut proc_thread_attribute_list; let mut si_ex; - if !self.proc_thread_attributes.is_empty() { + if let Some(proc_thread_attribute_list) = proc_thread_attribute_list { si.cb = mem::size_of::<c::STARTUPINFOEXW>() as u32; flags |= c::EXTENDED_STARTUPINFO_PRESENT; - proc_thread_attribute_list = - make_proc_thread_attribute_list(&self.proc_thread_attributes)?; si_ex = c::STARTUPINFOEXW { StartupInfo: si, - lpAttributeList: proc_thread_attribute_list.0.as_mut_ptr() as _, + // SAFETY: Casting this `*const` pointer to a `*mut` pointer is "safe" + // here because windows does not internally mutate the attribute list. + // Ideally this should be reflected in the interface of the `windows-sys` crate. + lpAttributeList: proc_thread_attribute_list.as_ptr().cast::<c_void>().cast_mut(), }; si_ptr = (&raw mut si_ex) as _; } else { @@ -896,79 +892,6 @@ fn make_dirp(d: Option<&OsString>) -> io::Result<(*const u16, Vec<u16>)> { } } -struct ProcThreadAttributeList(Box<[MaybeUninit<u8>]>); - -impl Drop for ProcThreadAttributeList { - fn drop(&mut self) { - let lp_attribute_list = self.0.as_mut_ptr() as _; - unsafe { c::DeleteProcThreadAttributeList(lp_attribute_list) } - } -} - -/// Wrapper around the value data to be used as a Process Thread Attribute. -struct ProcThreadAttributeValue { - data: Box<dyn Send + Sync>, - size: usize, -} - -fn make_proc_thread_attribute_list( - attributes: &BTreeMap<usize, ProcThreadAttributeValue>, -) -> io::Result<ProcThreadAttributeList> { - // To initialize our ProcThreadAttributeList, we need to determine - // how many bytes to allocate for it. The Windows API simplifies this process - // by allowing us to call `InitializeProcThreadAttributeList` with - // a null pointer to retrieve the required size. - let mut required_size = 0; - let Ok(attribute_count) = attributes.len().try_into() else { - return Err(io::const_error!( - ErrorKind::InvalidInput, - "maximum number of ProcThreadAttributes exceeded", - )); - }; - unsafe { - c::InitializeProcThreadAttributeList( - ptr::null_mut(), - attribute_count, - 0, - &mut required_size, - ) - }; - - let mut proc_thread_attribute_list = - ProcThreadAttributeList(vec![MaybeUninit::uninit(); required_size].into_boxed_slice()); - - // Once we've allocated the necessary memory, it's safe to invoke - // `InitializeProcThreadAttributeList` to properly initialize the list. - cvt(unsafe { - c::InitializeProcThreadAttributeList( - proc_thread_attribute_list.0.as_mut_ptr() as *mut _, - attribute_count, - 0, - &mut required_size, - ) - })?; - - // # Add our attributes to the buffer. - // It's theoretically possible for the attribute count to exceed a u32 value. - // Therefore, we ensure that we don't add more attributes than the buffer was initialized for. - for (&attribute, value) in attributes.iter().take(attribute_count as usize) { - let value_ptr = (&raw const *value.data) as _; - cvt(unsafe { - c::UpdateProcThreadAttribute( - proc_thread_attribute_list.0.as_mut_ptr() as _, - 0, - attribute, - value_ptr, - value.size, - ptr::null_mut(), - ptr::null_mut(), - ) - })?; - } - - Ok(proc_thread_attribute_list) -} - pub struct CommandArgs<'a> { iter: crate::slice::Iter<'a, Arg>, } diff --git a/library/std/src/sys/pal/windows/stdio.rs b/library/std/src/sys/pal/windows/stdio.rs index 642c8bc4df7..1b735e7f0cb 100644 --- a/library/std/src/sys/pal/windows/stdio.rs +++ b/library/std/src/sys/pal/windows/stdio.rs @@ -84,21 +84,43 @@ fn is_console(handle: c::HANDLE) -> bool { unsafe { c::GetConsoleMode(handle, &mut mode) != 0 } } +/// Returns true if the attached console's code page is currently UTF-8. +#[cfg(not(target_vendor = "win7"))] +fn is_utf8_console() -> bool { + unsafe { c::GetConsoleOutputCP() == c::CP_UTF8 } +} + +#[cfg(target_vendor = "win7")] +fn is_utf8_console() -> bool { + // Windows 7 has a fun "feature" where WriteFile on a console handle will return + // the number of UTF-16 code units written and not the number of bytes from the input string. + // So we always claim the console isn't UTF-8 to trigger the WriteConsole fallback code. + false +} + fn write(handle_id: u32, data: &[u8], incomplete_utf8: &mut IncompleteUtf8) -> io::Result<usize> { if data.is_empty() { return Ok(0); } let handle = get_handle(handle_id)?; - if !is_console(handle) { + if !is_console(handle) || is_utf8_console() { unsafe { let handle = Handle::from_raw_handle(handle); let ret = handle.write(data); let _ = handle.into_raw_handle(); // Don't close the handle return ret; } + } else { + write_console_utf16(data, incomplete_utf8, handle) } +} +fn write_console_utf16( + data: &[u8], + incomplete_utf8: &mut IncompleteUtf8, + handle: c::HANDLE, +) -> io::Result<usize> { if incomplete_utf8.len > 0 { assert!( incomplete_utf8.len < 4, diff --git a/library/std/src/sys/pal/windows/thread.rs b/library/std/src/sys/pal/windows/thread.rs index 2c8ce42f414..45e52cf4d04 100644 --- a/library/std/src/sys/pal/windows/thread.rs +++ b/library/std/src/sys/pal/windows/thread.rs @@ -19,6 +19,7 @@ pub struct Thread { impl Thread { // unsafe: see thread::Builder::spawn_unchecked for safety requirements + #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub unsafe fn new(stack: usize, p: Box<dyn FnOnce()>) -> io::Result<Thread> { let p = Box::into_raw(Box::new(p)); diff --git a/library/std/src/sys/personality/mod.rs b/library/std/src/sys/personality/mod.rs index 9754e840d15..2e1d2e53a29 100644 --- a/library/std/src/sys/personality/mod.rs +++ b/library/std/src/sys/personality/mod.rs @@ -31,7 +31,7 @@ cfg_if::cfg_if! { target_os = "psp", target_os = "xous", target_os = "solid_asp3", - all(target_family = "unix", not(target_os = "espidf"), not(target_os = "l4re"), not(target_os = "rtems"), not(target_os = "nuttx")), + all(target_family = "unix", not(target_os = "espidf"), not(target_os = "l4re"), not(target_os = "nuttx")), all(target_vendor = "fortanix", target_env = "sgx"), ))] { mod gcc; diff --git a/library/std/src/sys/sync/once/futex.rs b/library/std/src/sys/sync/once/futex.rs index 10bfa81a6d7..539f0fe89ea 100644 --- a/library/std/src/sys/sync/once/futex.rs +++ b/library/std/src/sys/sync/once/futex.rs @@ -1,7 +1,7 @@ use crate::cell::Cell; use crate::sync as public; use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release}; -use crate::sync::once::ExclusiveState; +use crate::sync::poison::once::ExclusiveState; use crate::sys::futex::{Futex, Primitive, futex_wait, futex_wake_all}; // On some platforms, the OS is very nice and handles the waiter queue for us. diff --git a/library/std/src/sys/sync/once/no_threads.rs b/library/std/src/sys/sync/once/no_threads.rs index 88a1d50361e..2568059cfe3 100644 --- a/library/std/src/sys/sync/once/no_threads.rs +++ b/library/std/src/sys/sync/once/no_threads.rs @@ -1,6 +1,6 @@ use crate::cell::Cell; use crate::sync as public; -use crate::sync::once::ExclusiveState; +use crate::sync::poison::once::ExclusiveState; pub struct Once { state: Cell<State>, diff --git a/library/std/src/sys/sync/once/queue.rs b/library/std/src/sys/sync/once/queue.rs index 5beff4ce683..fde1e0ca510 100644 --- a/library/std/src/sys/sync/once/queue.rs +++ b/library/std/src/sys/sync/once/queue.rs @@ -58,7 +58,7 @@ use crate::cell::Cell; use crate::sync::atomic::Ordering::{AcqRel, Acquire, Release}; use crate::sync::atomic::{AtomicBool, AtomicPtr}; -use crate::sync::once::ExclusiveState; +use crate::sync::poison::once::ExclusiveState; use crate::thread::{self, Thread}; use crate::{fmt, ptr, sync as public}; diff --git a/library/std/src/sys/thread_local/key/unix.rs b/library/std/src/sys/thread_local/key/unix.rs index 28e48a750b9..b4b58b34706 100644 --- a/library/std/src/sys/thread_local/key/unix.rs +++ b/library/std/src/sys/thread_local/key/unix.rs @@ -1,5 +1,25 @@ use crate::mem; +// For WASI add a few symbols not in upstream `libc` just yet. +#[cfg(all(target_os = "wasi", target_env = "p1", target_feature = "atomics"))] +mod libc { + use crate::ffi; + + #[allow(non_camel_case_types)] + pub type pthread_key_t = ffi::c_uint; + + extern "C" { + pub fn pthread_key_create( + key: *mut pthread_key_t, + destructor: unsafe extern "C" fn(*mut ffi::c_void), + ) -> ffi::c_int; + #[allow(dead_code)] + pub fn pthread_getspecific(key: pthread_key_t) -> *mut ffi::c_void; + pub fn pthread_setspecific(key: pthread_key_t, value: *const ffi::c_void) -> ffi::c_int; + pub fn pthread_key_delete(key: pthread_key_t) -> ffi::c_int; + } +} + pub type Key = libc::pthread_key_t; #[inline] diff --git a/library/std/src/sys/thread_local/mod.rs b/library/std/src/sys/thread_local/mod.rs index 31d3b439060..f0a13323ec9 100644 --- a/library/std/src/sys/thread_local/mod.rs +++ b/library/std/src/sys/thread_local/mod.rs @@ -86,7 +86,9 @@ pub(crate) mod guard { mod windows; pub(crate) use windows::enable; } else if #[cfg(any( - target_family = "wasm", + all(target_family = "wasm", not( + all(target_os = "wasi", target_env = "p1", target_feature = "atomics") + )), target_os = "uefi", target_os = "zkvm", ))] { @@ -135,6 +137,7 @@ pub(crate) mod key { target_family = "unix", ), target_os = "teeos", + all(target_os = "wasi", target_env = "p1", target_feature = "atomics"), ))] { mod racy; mod unix; diff --git a/library/std/src/sys_common/process.rs b/library/std/src/sys_common/process.rs index 5333ee146f7..9f61d69d858 100644 --- a/library/std/src/sys_common/process.rs +++ b/library/std/src/sys_common/process.rs @@ -8,19 +8,13 @@ use crate::sys::process::{EnvKey, ExitStatus, Process, StdioPipes}; use crate::{env, fmt, io}; // Stores a set of changes to an environment -#[derive(Clone)] +#[derive(Clone, Default)] pub struct CommandEnv { clear: bool, saw_path: bool, vars: BTreeMap<EnvKey, Option<OsString>>, } -impl Default for CommandEnv { - fn default() -> Self { - CommandEnv { clear: false, saw_path: false, vars: Default::default() } - } -} - impl fmt::Debug for CommandEnv { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut debug_command_env = f.debug_struct("CommandEnv"); diff --git a/library/std/src/thread/current.rs b/library/std/src/thread/current.rs index 1048ef97356..3d2c288b360 100644 --- a/library/std/src/thread/current.rs +++ b/library/std/src/thread/current.rs @@ -136,7 +136,7 @@ pub(crate) fn set_current(thread: Thread) -> Result<(), Thread> { /// one thread and is guaranteed not to call the global allocator. #[inline] pub(crate) fn current_id() -> ThreadId { - // If accessing the persistant thread ID takes multiple TLS accesses, try + // If accessing the persistent thread ID takes multiple TLS accesses, try // to retrieve it from the current thread handle, which will only take one // TLS access. if !id::CHEAP { diff --git a/library/std/src/thread/local.rs b/library/std/src/thread/local.rs index 56cf438bd9b..2313f4b5beb 100644 --- a/library/std/src/thread/local.rs +++ b/library/std/src/thread/local.rs @@ -12,7 +12,7 @@ use crate::cell::{Cell, RefCell}; use crate::error::Error; use crate::fmt; -/// A thread local storage key which owns its contents. +/// A thread local storage (TLS) key which owns its contents. /// /// This key uses the fastest possible implementation available to it for the /// target platform. It is instantiated with the [`thread_local!`] macro and the diff --git a/library/std/src/thread/mod.rs b/library/std/src/thread/mod.rs index cfbf6548a38..85ee369ca2b 100644 --- a/library/std/src/thread/mod.rs +++ b/library/std/src/thread/mod.rs @@ -391,6 +391,7 @@ impl Builder { /// handler.join().unwrap(); /// ``` #[stable(feature = "rust1", since = "1.0.0")] + #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub fn spawn<F, T>(self, f: F) -> io::Result<JoinHandle<T>> where F: FnOnce() -> T, @@ -458,6 +459,7 @@ impl Builder { /// /// [`io::Result`]: crate::io::Result #[stable(feature = "thread_spawn_unchecked", since = "1.82.0")] + #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub unsafe fn spawn_unchecked<F, T>(self, f: F) -> io::Result<JoinHandle<T>> where F: FnOnce() -> T, @@ -467,6 +469,7 @@ impl Builder { Ok(JoinHandle(unsafe { self.spawn_unchecked_(f, None) }?)) } + #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces unsafe fn spawn_unchecked_<'scope, F, T>( self, f: F, @@ -721,6 +724,7 @@ impl Builder { /// [`join`]: JoinHandle::join /// [`Err`]: crate::result::Result::Err #[stable(feature = "rust1", since = "1.0.0")] +#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub fn spawn<F, T>(f: F) -> JoinHandle<T> where F: FnOnce() -> T, |
