Merge "Update fs-err to 3.1.0" into main
diff --git a/crates/fs-err/.android-checksum.json b/crates/fs-err/.android-checksum.json
index 4fa6903..0efaf8c 100644
--- a/crates/fs-err/.android-checksum.json
+++ b/crates/fs-err/.android-checksum.json
@@ -1 +1 @@
-{"package":null,"files":{".cargo-checksum.json":"c54f34e3f4b1d64848e6da90d966b117bdef12576cf6b066dc6b5c624b56d8be","Android.bp":"0f2feb7fd5a76caac208f8b777aba0d7a12ffff3d6e5ca95a0513befb431240c","CHANGELOG.md":"ba225e2e4a163959c7f47c7d44dcc576d3d7ebf4dada88b2cbec8fc84353bbe4","Cargo.toml":"5dabbf8ed505b7dc761147e8332894e89777896d7c0b6d8217c73e0da7001f8d","LICENSE":"72683dc236dc9b1a88efcc4b5cf18c78f2149c21c9729fba58750a233b93fe47","LICENSE-APACHE":"72683dc236dc9b1a88efcc4b5cf18c78f2149c21c9729fba58750a233b93fe47","LICENSE-MIT":"233fd6bd2767911ab144db38ed781bbf1251e15eaad26b0b53db7b3dc324d255","METADATA":"11d09f7593494c0c059ded191c42fb35ef2a7c3244a2e9fde88d1c9a3134a600","MODULE_LICENSE_APACHE2":"0d6f8afa3940b7f06bebee651376d43bc8b0d5b437337be2696d30377451e93a","README.md":"7065f8d5aa3f62ca4d065acde95377882bc5351093a08e63a77fd88caafd98ae","build.rs":"3f306eb75e4fe2dcf1f10192acdb35127dea3d91e70c032bb8b15f420911a82a","cargo_embargo.json":"651827b0be315dc6c3973d2a32e15065ce98eb06fe43e132de89c8885c120ddd","src/dir.rs":"ecd7c0bee824830a8cf2cc8737d7f03a093315e2c14454ea41b739fb99c9a48d","src/errors.rs":"4220b09455f8a7bd539003975df2841ae54a3f8907ff34691fe67c222a510d98","src/file.rs":"8c9ab40a18e0aee1ced955c510ea1ee941bdcf87abd911b06b1a13a16c177ce5","src/lib.rs":"a2d3626cd71652228fa83b9b5312ca429343774dec44975876b296deb0da29d8","src/open_options.rs":"86e4290023672f9eafbb6f9c13b9b9f7e385b0d70d8bff41ef3e7eaa2f162e05","src/os.rs":"ffb7e398c586997868776af2325ac798525753ae171ac88e87a36d994f519f38","src/os/unix.rs":"f793f46d29e6354be2e7726e475cce3862ed1d02e30f5219890b76d522f22e6a","src/os/windows.rs":"bb74c318777b1d429c07e80f739d689260da47564dac991b72fde447a8ab1d9b","src/path.rs":"a1f22cc73fe7a2c370566a2d830c8e38847b763c600ceb3d6d8345c687f1964c","src/tokio/dir_builder.rs":"5cbed6d1f1cc9eab8f2d41e8f924279675c9eae8c18380c3cc3f591e194747d9","src/tokio/file.rs":"848eca9921df0fc40ab4d300d1682fdabd000d7f6207ae87cf5f1c51714bb6be","src/tokio/mod.rs":"1cbd8f3990bd7b10fc7a46ace89cb73c63525c13ea48c95f794f060e0cbaddb3","src/tokio/open_options.rs":"b47e8bd98bce103e5d41fdf0ff289b211f29a1ac64fa170602e965dad3784bd6","src/tokio/read_dir.rs":"e6e94f8b22a3ff02a982c6394ab41701cccd8cb8099090905545970eaf7c8c7e"}}
\ No newline at end of file
+{"package":null,"files":{".cargo-checksum.json":"6cbfbcdc6fec6b8899f08bde09fd7ce428168c370e39a3a66f4e2c3a5ce8e3c5","Android.bp":"0119a6e10d1dca33b1da5c538080be59b918096412e70a9130d80577f42207ba","CHANGELOG.md":"330a5c58566439bcd7f18a9c6042420f7ea5020f2260a70990a7f3bc7a40f7a8","Cargo.toml":"60aacbb01e2de5c4b6f5d33056475d7a797194ee3cd0dc6d5fbc5c61c28916f9","LICENSE":"72683dc236dc9b1a88efcc4b5cf18c78f2149c21c9729fba58750a233b93fe47","LICENSE-APACHE":"72683dc236dc9b1a88efcc4b5cf18c78f2149c21c9729fba58750a233b93fe47","LICENSE-MIT":"233fd6bd2767911ab144db38ed781bbf1251e15eaad26b0b53db7b3dc324d255","METADATA":"69757f385995432041d415239977f9a20bd2a9b95d432e87b68758cd2f31e6ef","MODULE_LICENSE_APACHE2":"0d6f8afa3940b7f06bebee651376d43bc8b0d5b437337be2696d30377451e93a","README.md":"6146d75312f08a8d8e710f9c3019d49b8f593c0adfaefbfb31e40609007a820b","build.rs":"063693fbd5587847ab073aab7e087cecdf944aa514b3088c27dc55136ac427b3","cargo_embargo.json":"651827b0be315dc6c3973d2a32e15065ce98eb06fe43e132de89c8885c120ddd","src/dir.rs":"ecd7c0bee824830a8cf2cc8737d7f03a093315e2c14454ea41b739fb99c9a48d","src/errors.rs":"61936d4341c9afa77158b0233837ed50bc2425d0026ec8daba1c83543a84bfd2","src/file.rs":"c1ecbd1bde7a380a43b38e0083271b18edb9aa989e1235699486e9f019a66d8e","src/lib.rs":"da6f098deec068b8450b1585a36580d1f4cb671b0d50b4d57ddddfe564f1f3fb","src/open_options.rs":"c456cc8308706a0778a857159899bb89ee712380e77e1f56076b2f5f3c1851e9","src/os.rs":"ffb7e398c586997868776af2325ac798525753ae171ac88e87a36d994f519f38","src/os/unix.rs":"f793f46d29e6354be2e7726e475cce3862ed1d02e30f5219890b76d522f22e6a","src/os/windows.rs":"bb74c318777b1d429c07e80f739d689260da47564dac991b72fde447a8ab1d9b","src/path.rs":"a1f22cc73fe7a2c370566a2d830c8e38847b763c600ceb3d6d8345c687f1964c","src/tokio/dir_builder.rs":"5cbed6d1f1cc9eab8f2d41e8f924279675c9eae8c18380c3cc3f591e194747d9","src/tokio/file.rs":"07203622146c198a06a47dbd51558d5b884576a59766436c4743f43e8301f15f","src/tokio/mod.rs":"12b6bc3d0ead2a075cb098086affe7a08dd2b877793c4d7cc93d470ba4767861","src/tokio/open_options.rs":"b47e8bd98bce103e5d41fdf0ff289b211f29a1ac64fa170602e965dad3784bd6","src/tokio/read_dir.rs":"e6e94f8b22a3ff02a982c6394ab41701cccd8cb8099090905545970eaf7c8c7e"}}
\ No newline at end of file
diff --git a/crates/fs-err/.cargo-checksum.json b/crates/fs-err/.cargo-checksum.json
index 8d549a9..ca49dd3 100644
--- a/crates/fs-err/.cargo-checksum.json
+++ b/crates/fs-err/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"CHANGELOG.md":"10773f5a450b13fcafece2790a2f061479ecaae87ef48c9b10052c5bdeeb5f70","Cargo.toml":"e5c911a423edc6acbe06979b3a1c91a81a33aca3ba19be638c5a121729552725","LICENSE-APACHE":"7cfd738c53d61c79f07e348f622bf7707c9084237054d37fbe07788a75f5881c","LICENSE-MIT":"36516aefdc84c5d5a1e7485425913a22dbda69eb1930c5e84d6ae4972b5194b9","README.md":"80a833a373a9ba25fbf8ff6e6f93806fce48c39b38c453ada025429a592d490d","build.rs":"f1d5a299d68f91e26fbe9dd642dfcd00e122ec9cb999d4a4b38c6d7200fb9763","src/dir.rs":"07d484f2f9efbfcc67846eb05d687813903f9007bd55aef2d2c7d3b4e7cadd96","src/errors.rs":"80184ce74d67b1d197530eaa56d071bc3df1856bf12bbaa04dee270a6388b0d0","src/file.rs":"75bb885b440f31a067870acdf5fc6080158bf82e31a8cd79536fbc0b2cf1c49f","src/lib.rs":"491160d092e9525d754ac4c7849294cbefe14ef6949363fa644f364e3d6f1c03","src/open_options.rs":"2d93a5431294880b4eab925a752e3f3999e14bdc4a79a26d5516e43c82c42675","src/os.rs":"54fe6cb71a24592de1cb4e1fcebdeaba5e58b26925dbf2dc868e8dd0b0a7bef7","src/os/unix.rs":"e94dd4043babe183f5d77146ca6fef9fe11ecc1e4b572e7eac9814d87c86ba26","src/os/windows.rs":"5f9da192f8f4442fe9656432bf994d7996d4ee1e757ced446bbdc5d057332456","src/path.rs":"13538c226e689b3e5a50e5c39009df665685fafa3a693cc426fd207efb79bf29","src/tokio/dir_builder.rs":"855460e182ebc6a35ff0b381bac4fc5ac1270bab27daa41244211248c9bc316f","src/tokio/file.rs":"4988a9bae3246364c7a8fbb5bbf87ddade75346728d3e8255b5d7d19f51a1068","src/tokio/mod.rs":"bacbe3ceaaefc3a68838d1067ef04981c31be42e34ffcb13d8d2f1f43b5c85d8","src/tokio/open_options.rs":"ec65d13e9b1cf87dc600912749fb3d646b162144ee37b530b96d7705130fef84","src/tokio/read_dir.rs":"be4a567caf7ae253fd4f4925964d65e4b3afa119204156166879afc26e6c548e"},"package":"88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41"}
\ No newline at end of file
+{"files":{"CHANGELOG.md":"5072579891e1c6cd05d4909819519be07217e124c29f18eb2fd4f71184800c93","Cargo.toml":"75b3f41f5452a60699e12645f919bc6232d03bd8c1a1a4a8e22cfaab6ce0904c","LICENSE-APACHE":"7cfd738c53d61c79f07e348f622bf7707c9084237054d37fbe07788a75f5881c","LICENSE-MIT":"36516aefdc84c5d5a1e7485425913a22dbda69eb1930c5e84d6ae4972b5194b9","README.md":"2336f74decead21e0d3f6a3122082d2a2ee28a27ed1f05e6cd9f74396dd5e315","build.rs":"11095b891b5601e6a6992df9c65d6385247103d59ceef22c919d351a4ce09f2d","src/dir.rs":"07d484f2f9efbfcc67846eb05d687813903f9007bd55aef2d2c7d3b4e7cadd96","src/errors.rs":"bad82faaf17d505b1faf44f9e5680d97ad3b23b413c2a7987fe715f30e78347e","src/file.rs":"1b4a7bd82a3e06e9ca2286cc8018fb1ed8b65a74b08a4a17c3eca702ecc0acdf","src/lib.rs":"2e9c9871194d19b6b24e03ba119317a93b16bfa5ec29e65cdd896d93c10314ab","src/open_options.rs":"64213c8fe66a42409d2175c56c10fb380d196edac3fe30e33507c87e13c2a703","src/os.rs":"54fe6cb71a24592de1cb4e1fcebdeaba5e58b26925dbf2dc868e8dd0b0a7bef7","src/os/unix.rs":"e94dd4043babe183f5d77146ca6fef9fe11ecc1e4b572e7eac9814d87c86ba26","src/os/windows.rs":"5f9da192f8f4442fe9656432bf994d7996d4ee1e757ced446bbdc5d057332456","src/path.rs":"13538c226e689b3e5a50e5c39009df665685fafa3a693cc426fd207efb79bf29","src/tokio/dir_builder.rs":"855460e182ebc6a35ff0b381bac4fc5ac1270bab27daa41244211248c9bc316f","src/tokio/file.rs":"e24ee167b34cb2ee21f3ab600ef111727cd7f749303bd8fbf5f2fd0a01b871fb","src/tokio/mod.rs":"77075f7d90b14e1b2df32549de698e8d986dbd1e2c26716a7c4e4bdb2940e3e1","src/tokio/open_options.rs":"ec65d13e9b1cf87dc600912749fb3d646b162144ee37b530b96d7705130fef84","src/tokio/read_dir.rs":"be4a567caf7ae253fd4f4925964d65e4b3afa119204156166879afc26e6c548e"},"package":"1f89bda4c2a21204059a977ed3bfe746677dfd137b83c339e702b0ac91d482aa"}
\ No newline at end of file
diff --git a/crates/fs-err/Android.bp b/crates/fs-err/Android.bp
index 046d511..d636f9e 100644
--- a/crates/fs-err/Android.bp
+++ b/crates/fs-err/Android.bp
@@ -18,7 +18,7 @@
     host_supported: true,
     crate_name: "fs_err",
     cargo_env_compat: true,
-    cargo_pkg_version: "2.11.0",
+    cargo_pkg_version: "3.1.0",
     crate_root: "src/lib.rs",
     edition: "2018",
     cfgs: ["rustc_1_63"],
diff --git a/crates/fs-err/CHANGELOG.md b/crates/fs-err/CHANGELOG.md
index c2a5d0e..0311ac4 100644
--- a/crates/fs-err/CHANGELOG.md
+++ b/crates/fs-err/CHANGELOG.md
@@ -1,5 +1,29 @@
 # fs-err Changelog

 

+## 3.1.0

+

+* Added new wrappers for `create_new` and `options` functions on `File` ([#69](https://github.com/andrewhickman/fs-err/pull/69))

+

+## 3.0.0

+

+* Error messages now include the original message from `std::io::Error` by default ([#60](https://github.com/andrewhickman/fs-err/pull/60)). Previously this was exposed through the [`Error::source()`](https://doc.rust-lang.org/stable/std/error/trait.Error.html#method.source) method. For example, previously a message would look like:

+

+  ```

+  failed to open file `file.txt`

+  ```

+

+  and you would have to remember to print the source, or use a library like `anyhow` to print the full chain of source errors. The new error message includes the cause by default

+

+  ```

+  failed to open file `file.txt`: The system cannot find the file specified. (os error 2)

+  ```

+

+  Note that the original error is no longer exposed though [`Error::source()`](https://doc.rust-lang.org/stable/std/error/trait.Error.html#method.source) by default. If you need access to it, you can restore the previous behaviour with the `expose_original_error` feature flag.

+

+* The `io_safety` feature flag has been removed, and this functionality is now always enabled on Rust versions which support it (1.63.0 and greater).

+

+* Removed deprecated APIs: `File::from_options`, `tokio::symlink`

+

 ## 2.11.0

 

 * Added the first line of the standard library documentation to each function's rustdocs, to make them more useful in IDEs ([#50](https://github.com/andrewhickman/fs-err/issues/45))

diff --git a/crates/fs-err/Cargo.toml b/crates/fs-err/Cargo.toml
index f2ea497..b2282b7 100644
--- a/crates/fs-err/Cargo.toml
+++ b/crates/fs-err/Cargo.toml
@@ -12,13 +12,19 @@
 [package]
 edition = "2018"
 name = "fs-err"
-version = "2.11.0"
+version = "3.1.0"
 authors = ["Andrew Hickman <[email protected]>"]
+build = "build.rs"
 exclude = [
     ".github",
     ".gitignore",
     "README.tpl",
 ]
+autolib = false
+autobins = false
+autoexamples = false
+autotests = false
+autobenches = false
 description = "A drop-in replacement for std::fs with more helpful error messages."
 documentation = "https://docs.rs/fs-err"
 readme = "README.md"
@@ -43,14 +49,18 @@
 [[package.metadata.release.pre-release-replacements]]
 exactly = 1
 file = "src/lib.rs"
-replace = "html_root_url = \"https://docs.rs/fs-err/{{version}}\""
+replace = 'html_root_url = "https://docs.rs/fs-err/{{version}}"'
 search = 'html_root_url = "https://docs\.rs/fs-err/.*?"'
 
+[lib]
+name = "fs_err"
+path = "src/lib.rs"
+
 [dependencies.tokio]
 version = "1.21"
 features = ["fs"]
 optional = true
-default_features = false
+default-features = false
 
 [dev-dependencies.serde_json]
 version = "1.0.64"
@@ -59,4 +69,4 @@
 version = "1"
 
 [features]
-io_safety = []
+expose_original_error = []
diff --git a/crates/fs-err/METADATA b/crates/fs-err/METADATA
index e238e59..88210b1 100644
--- a/crates/fs-err/METADATA
+++ b/crates/fs-err/METADATA
@@ -1,17 +1,17 @@
 name: "fs-err"
 description: "A drop-in replacement for std::fs with more helpful error messages."
 third_party {
-  version: "2.11.0"
+  version: "3.1.0"
   license_type: NOTICE
   last_upgrade_date {
-    year: 2024
-    month: 3
-    day: 21
+    year: 2025
+    month: 1
+    day: 22
   }
   homepage: "https://crates.io/crates/fs-err"
   identifier {
     type: "Archive"
-    value: "https://static.crates.io/crates/fs-err/fs-err-2.11.0.crate"
-    version: "2.11.0"
+    value: "https://static.crates.io/crates/fs-err/fs-err-3.1.0.crate"
+    version: "3.1.0"
   }
 }
diff --git a/crates/fs-err/README.md b/crates/fs-err/README.md
index a0f63ea..d36dc35 100644
--- a/crates/fs-err/README.md
+++ b/crates/fs-err/README.md
@@ -9,76 +9,81 @@
 [![Crates.io](https://img.shields.io/crates/v/fs-err.svg)](https://crates.io/crates/fs-err)

 [![GitHub Actions](https://github.com/andrewhickman/fs-err/workflows/CI/badge.svg)](https://github.com/andrewhickman/fs-err/actions?query=workflow%3ACI)

 

-fs-err is a drop-in replacement for [`std::fs`][std::fs] that provides more

-helpful messages on errors. Extra information includes which operations was

-attempted and any involved paths.

-

-## Error Messages

-

-Using [`std::fs`][std::fs], if this code fails:

-

-```rust

-let file = File::open("does not exist.txt")?;

-```

-

-The error message that Rust gives you isn't very useful:

-

-```txt

-The system cannot find the file specified. (os error 2)

-```

-

-...but if we use fs-err instead, our error contains more actionable information:

-

-```txt

-failed to open file `does not exist.txt`

-    caused by: The system cannot find the file specified. (os error 2)

-```

-

-## Usage

-

-fs-err's API is the same as [`std::fs`][std::fs], so migrating code to use it is easy.

-

-```rust

-// use std::fs;

-use fs_err as fs;

-

-let contents = fs::read_to_string("foo.txt")?;

-

-println!("Read foo.txt: {}", contents);

-

-```

-

-fs-err uses [`std::io::Error`][std::io::Error] for all errors. This helps fs-err

-compose well with traits from the standard library like

-[`std::io::Read`][std::io::Read] and crates that use them like

-[`serde_json`][serde_json]:

-

-```rust

-use fs_err::File;

-

-let file = File::open("my-config.json")?;

-

-// If an I/O error occurs inside serde_json, the error will include a file path

-// as well as what operation was being performed.

-let decoded: Vec<String> = serde_json::from_reader(file)?;

-

-println!("Program config: {:?}", decoded);

-

-```

-

-[std::fs]: https://doc.rust-lang.org/stable/std/fs/

-[std::io::Error]: https://doc.rust-lang.org/stable/std/io/struct.Error.html

-[std::io::Read]: https://doc.rust-lang.org/stable/std/io/trait.Read.html

+fs-err is a drop-in replacement for [`std::fs`][std::fs] that provides more
+helpful messages on errors. Extra information includes which operations was
+attempted and any involved paths.
+
+## Error Messages
+
+Using [`std::fs`][std::fs], if this code fails:
+
+```rust
+let file = File::open("does not exist.txt")?;
+```
+
+The error message that Rust gives you isn't very useful:
+
+```txt
+The system cannot find the file specified. (os error 2)
+```
+
+...but if we use fs-err instead, our error contains more actionable information:
+
+```txt
+failed to open file `does not exist.txt`: The system cannot find the file specified. (os error 2)
+```
+
+## Usage
+
+fs-err's API is the same as [`std::fs`][std::fs], so migrating code to use it is easy.
+
+```rust
+// use std::fs;
+use fs_err as fs;
+
+let contents = fs::read_to_string("foo.txt")?;
+
+println!("Read foo.txt: {}", contents);
+
+```
+
+fs-err uses [`std::io::Error`][std::io::Error] for all errors. This helps fs-err
+compose well with traits from the standard library like
+[`std::io::Read`][std::io::Read] and crates that use them like
+[`serde_json`][serde_json]:
+
+```rust
+use fs_err::File;
+
+let file = File::open("my-config.json")?;
+
+// If an I/O error occurs inside serde_json, the error will include a file path
+// as well as what operation was being performed.
+let decoded: Vec<String> = serde_json::from_reader(file)?;
+
+println!("Program config: {:?}", decoded);
+
+```
+
+## Feature flags
+
+* `expose_original_error`: when enabled, the [`Error::source()`](https://doc.rust-lang.org/stable/std/error/trait.Error.html#method.source) method of errors returned by this crate return the original `io::Error`. To avoid duplication in error messages,
+  this also suppresses printing its message in their `Display` implementation, so make sure that you are printing the full error chain.
+
+
+## Minimum Supported Rust Version
+
+The oldest rust version this crate is tested on is **1.40**.
+
+This crate will generally be conservative with rust version updates. It uses the [`autocfg`](https://crates.io/crates/autocfg) crate to allow wrapping new APIs without incrementing the MSRV.
+
+If the `tokio` feature is enabled, this crate will inherit the MSRV of the selected [`tokio`](https://crates.io/crates/tokio) version.
+
+[std::fs]: https://doc.rust-lang.org/stable/std/fs/
+[std::io::Error]: https://doc.rust-lang.org/stable/std/io/struct.Error.html
+[std::io::Read]: https://doc.rust-lang.org/stable/std/io/trait.Read.html
 [serde_json]: https://crates.io/crates/serde_json

 

-## Minimum Supported Rust Version

-

-The oldest rust version this crate is tested on is **1.40**.

-

-This crate will generally be conservative with rust version updates. It uses the [`autocfg`](https://crates.io/crates/autocfg) crate to allow wrapping new APIs without incrementing the MSRV.

-

-If the `tokio` feature is enabled, this crate will inherit the MSRV of the selected [`tokio`](https://crates.io/crates/tokio) version.

-

 ## License

 

 Licensed under either of

@@ -93,4 +98,4 @@
 Unless you explicitly state otherwise, any contribution intentionally

 submitted for inclusion in the work by you, as defined in the Apache-2.0

 license, shall be dual licensed as above, without any additional terms or

-conditions.

+conditions.
diff --git a/crates/fs-err/build.rs b/crates/fs-err/build.rs
index 954dd27..0879439 100644
--- a/crates/fs-err/build.rs
+++ b/crates/fs-err/build.rs
@@ -4,4 +4,7 @@
     let ac = autocfg::new();

     // Allows `#[cfg(rustc_1_63)]` to be used in code

     ac.emit_rustc_version(1, 63);

+

+    // Re-run if this file changes

+    autocfg::rerun_path("build.rs");

 }

diff --git a/crates/fs-err/src/errors.rs b/crates/fs-err/src/errors.rs
index d732c0e..906be14 100644
--- a/crates/fs-err/src/errors.rs
+++ b/crates/fs-err/src/errors.rs
@@ -63,41 +63,49 @@
 

 impl fmt::Display for Error {

     fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {

-        use ErrorKind::*;

+        use ErrorKind as E;

 

         let path = self.path.display();

 

         match self.kind {

-            OpenFile => write!(formatter, "failed to open file `{}`", path),

-            CreateFile => write!(formatter, "failed to create file `{}`", path),

-            CreateDir => write!(formatter, "failed to create directory `{}`", path),

-            SyncFile => write!(formatter, "failed to sync file `{}`", path),

-            SetLen => write!(formatter, "failed to set length of file `{}`", path),

-            Metadata => write!(formatter, "failed to query metadata of file `{}`", path),

-            Clone => write!(formatter, "failed to clone handle for file `{}`", path),

-            SetPermissions => write!(formatter, "failed to set permissions for file `{}`", path),

-            Read => write!(formatter, "failed to read from file `{}`", path),

-            Seek => write!(formatter, "failed to seek in file `{}`", path),

-            Write => write!(formatter, "failed to write to file `{}`", path),

-            Flush => write!(formatter, "failed to flush file `{}`", path),

-            ReadDir => write!(formatter, "failed to read directory `{}`", path),

-            RemoveFile => write!(formatter, "failed to remove file `{}`", path),

-            RemoveDir => write!(formatter, "failed to remove directory `{}`", path),

-            Canonicalize => write!(formatter, "failed to canonicalize path `{}`", path),

-            ReadLink => write!(formatter, "failed to read symbolic link `{}`", path),

-            SymlinkMetadata => write!(formatter, "failed to query metadata of symlink `{}`", path),

-            FileExists => write!(formatter, "failed to check file existance `{}`", path),

+            E::OpenFile => write!(formatter, "failed to open file `{}`", path),

+            E::CreateFile => write!(formatter, "failed to create file `{}`", path),

+            E::CreateDir => write!(formatter, "failed to create directory `{}`", path),

+            E::SyncFile => write!(formatter, "failed to sync file `{}`", path),

+            E::SetLen => write!(formatter, "failed to set length of file `{}`", path),

+            E::Metadata => write!(formatter, "failed to query metadata of file `{}`", path),

+            E::Clone => write!(formatter, "failed to clone handle for file `{}`", path),

+            E::SetPermissions => write!(formatter, "failed to set permissions for file `{}`", path),

+            E::Read => write!(formatter, "failed to read from file `{}`", path),

+            E::Seek => write!(formatter, "failed to seek in file `{}`", path),

+            E::Write => write!(formatter, "failed to write to file `{}`", path),

+            E::Flush => write!(formatter, "failed to flush file `{}`", path),

+            E::ReadDir => write!(formatter, "failed to read directory `{}`", path),

+            E::RemoveFile => write!(formatter, "failed to remove file `{}`", path),

+            E::RemoveDir => write!(formatter, "failed to remove directory `{}`", path),

+            E::Canonicalize => write!(formatter, "failed to canonicalize path `{}`", path),

+            E::ReadLink => write!(formatter, "failed to read symbolic link `{}`", path),

+            E::SymlinkMetadata => {

+                write!(formatter, "failed to query metadata of symlink `{}`", path)

+            }

+            E::FileExists => write!(formatter, "failed to check file existence `{}`", path),

 

             #[cfg(windows)]

-            SeekRead => write!(formatter, "failed to seek and read from `{}`", path),

+            E::SeekRead => write!(formatter, "failed to seek and read from `{}`", path),

             #[cfg(windows)]

-            SeekWrite => write!(formatter, "failed to seek and write to `{}`", path),

+            E::SeekWrite => write!(formatter, "failed to seek and write to `{}`", path),

 

             #[cfg(unix)]

-            ReadAt => write!(formatter, "failed to read with offset from `{}`", path),

+            E::ReadAt => write!(formatter, "failed to read with offset from `{}`", path),

             #[cfg(unix)]

-            WriteAt => write!(formatter, "failed to write with offset to `{}`", path),

-        }

+            E::WriteAt => write!(formatter, "failed to write with offset to `{}`", path),

+        }?;

+

+        // The `expose_original_error` feature indicates the caller should display the original error

+        #[cfg(not(feature = "expose_original_error"))]

+        write!(formatter, ": {}", self.source)?;

+

+        Ok(())

     }

 }

 

@@ -106,6 +114,12 @@
         self.source()

     }

 

+    #[cfg(not(feature = "expose_original_error"))]

+    fn source(&self) -> Option<&(dyn StdError + 'static)> {

+        None

+    }

+

+    #[cfg(feature = "expose_original_error")]

     fn source(&self) -> Option<&(dyn StdError + 'static)> {

         Some(&self.source)

     }

@@ -186,7 +200,13 @@
             SourceDestErrorKind::SymlinkDir => {

                 write!(formatter, "failed to symlink dir from {} to {}", from, to)

             }

-        }

+        }?;

+

+        // The `expose_original_error` feature indicates the caller should display the original error

+        #[cfg(not(feature = "expose_original_error"))]

+        write!(formatter, ": {}", self.source)?;

+

+        Ok(())

     }

 }

 

@@ -195,6 +215,12 @@
         self.source()

     }

 

+    #[cfg(not(feature = "expose_original_error"))]

+    fn source(&self) -> Option<&(dyn StdError + 'static)> {

+        None

+    }

+

+    #[cfg(feature = "expose_original_error")]

     fn source(&self) -> Option<&(dyn StdError + 'static)> {

         Some(&self.source)

     }

diff --git a/crates/fs-err/src/file.rs b/crates/fs-err/src/file.rs
index acdb3c2..f8c5ad1 100644
--- a/crates/fs-err/src/file.rs
+++ b/crates/fs-err/src/file.rs
@@ -3,6 +3,7 @@
 use std::path::{Path, PathBuf};

 

 use crate::errors::{Error, ErrorKind};

+use crate::OpenOptions;

 

 /// Wrapper around [`std::fs::File`][std::fs::File] which adds more helpful

 /// information to all errors.

@@ -57,22 +58,33 @@
         }

     }

 

-    /// Wrapper for [`OpenOptions::open`](https://doc.rust-lang.org/stable/std/fs/struct.OpenOptions.html#method.open).

+    /// Opens a file in read-write mode.

     ///

-    /// This takes [`&std::fs::OpenOptions`](https://doc.rust-lang.org/stable/std/fs/struct.OpenOptions.html),

-    /// not [`crate::OpenOptions`].

-    #[deprecated = "use fs_err::OpenOptions::open instead"]

-    pub fn from_options<P>(path: P, options: &fs::OpenOptions) -> Result<Self, io::Error>

+    /// Wrapper for [`File::create_new`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.create_new).

+    pub fn create_new<P>(path: P) -> Result<Self, io::Error>

     where

         P: Into<PathBuf>,

     {

         let path = path.into();

-        match options.open(&path) {

+        // TODO: Use fs::File::create_new once MSRV is at least 1.77

+        match fs::OpenOptions::new()

+            .read(true)

+            .write(true)

+            .create_new(true)

+            .open(&path)

+        {

             Ok(file) => Ok(File::from_parts(file, path)),

-            Err(source) => Err(Error::build(source, ErrorKind::OpenFile, path)),

+            Err(err) => Err(Error::build(err, ErrorKind::CreateFile, path)),

         }

     }

 

+    /// Returns a new `OpenOptions` object.

+    ///

+    /// Wrapper for [`File::options`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.options).

+    pub fn options() -> OpenOptions {

+        OpenOptions::new()

+    }

+

     /// Attempts to sync all OS-internal metadata to disk.

     ///

     /// Wrapper for [`File::sync_all`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.sync_all).

@@ -194,7 +206,7 @@
     }

 }

 

-impl<'a> Read for &'a File {

+impl Read for &File {

     fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {

         (&self.file)

             .read(buf)

@@ -222,7 +234,7 @@
     }

 }

 

-impl<'a> Seek for &'a File {

+impl Seek for &File {

     fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {

         (&self.file)

             .seek(pos)

@@ -250,7 +262,7 @@
     }

 }

 

-impl<'a> Write for &'a File {

+impl Write for &File {

     fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {

         (&self.file)

             .write(buf)

@@ -303,18 +315,16 @@
         }

     }

 

-    #[cfg(feature = "io_safety")]

+    #[cfg(rustc_1_63)]

     mod io_safety {

         use std::os::unix::io::{AsFd, BorrowedFd, OwnedFd};

 

-        #[cfg_attr(docsrs, doc(cfg(feature = "io_safety")))]

         impl AsFd for crate::File {

             fn as_fd(&self) -> BorrowedFd<'_> {

                 self.file().as_fd()

             }

         }

 

-        #[cfg_attr(docsrs, doc(cfg(feature = "io_safety")))]

         impl From<crate::File> for OwnedFd {

             fn from(file: crate::File) -> Self {

                 file.into_parts().0.into()

@@ -363,18 +373,16 @@
         }

     }

 

-    #[cfg(feature = "io_safety")]

+    #[cfg(rustc_1_63)]

     mod io_safety {

         use std::os::windows::io::{AsHandle, BorrowedHandle, OwnedHandle};

 

-        #[cfg_attr(docsrs, doc(cfg(feature = "io_safety")))]

         impl AsHandle for crate::File {

             fn as_handle(&self) -> BorrowedHandle<'_> {

                 self.file().as_handle()

             }

         }

 

-        #[cfg_attr(docsrs, doc(cfg(feature = "io_safety")))]

         impl From<crate::File> for OwnedHandle {

             fn from(file: crate::File) -> Self {

                 file.into_parts().0.into()

diff --git a/crates/fs-err/src/lib.rs b/crates/fs-err/src/lib.rs
index b1ef0d5..51b64fa 100644
--- a/crates/fs-err/src/lib.rs
+++ b/crates/fs-err/src/lib.rs
@@ -22,8 +22,7 @@
 ...but if we use fs-err instead, our error contains more actionable information:

 

 ```txt

-failed to open file `does not exist.txt`

-    caused by: The system cannot find the file specified. (os error 2)

+failed to open file `does not exist.txt`: The system cannot find the file specified. (os error 2)

 ```

 

 # Usage

@@ -60,13 +59,27 @@
 # Ok::<(), Box<dyn std::error::Error>>(())

 ```

 

+# Feature flags

+

+* `expose_original_error`: when enabled, the [`Error::source()`](https://doc.rust-lang.org/stable/std/error/trait.Error.html#method.source) method of errors returned by this crate return the original `io::Error`. To avoid duplication in error messages,

+  this also suppresses printing its message in their `Display` implementation, so make sure that you are printing the full error chain.

+

+

+# Minimum Supported Rust Version

+

+The oldest rust version this crate is tested on is **1.40**.

+

+This crate will generally be conservative with rust version updates. It uses the [`autocfg`](https://crates.io/crates/autocfg) crate to allow wrapping new APIs without incrementing the MSRV.

+

+If the `tokio` feature is enabled, this crate will inherit the MSRV of the selected [`tokio`](https://crates.io/crates/tokio) version.

+

 [std::fs]: https://doc.rust-lang.org/stable/std/fs/

 [std::io::Error]: https://doc.rust-lang.org/stable/std/io/struct.Error.html

 [std::io::Read]: https://doc.rust-lang.org/stable/std/io/trait.Read.html

 [serde_json]: https://crates.io/crates/serde_json

 */

 

-#![doc(html_root_url = "https://docs.rs/fs-err/2.11.0")]

+#![doc(html_root_url = "https://docs.rs/fs-err/3.1.0")]

 #![deny(missing_debug_implementations, missing_docs)]

 #![cfg_attr(docsrs, feature(doc_cfg))]

 

diff --git a/crates/fs-err/src/open_options.rs b/crates/fs-err/src/open_options.rs
index 08c3562..66f006c 100644
--- a/crates/fs-err/src/open_options.rs
+++ b/crates/fs-err/src/open_options.rs
@@ -1,4 +1,7 @@
 use std::{fs, io, path::PathBuf};

+

+use crate::errors::{Error, ErrorKind};

+

 #[derive(Clone, Debug)]

 /// Wrapper around [`std::fs::OpenOptions`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html)

 pub struct OpenOptions(fs::OpenOptions);

@@ -67,12 +70,11 @@
     where

         P: Into<PathBuf>,

     {

-        // We have to either duplicate the logic or call the deprecated method here.

-        // We can't let the deprecated function call this method, because we can't construct

-        // `&fs_err::OpenOptions` from `&fs::OpenOptions` without cloning

-        // (although cloning would probably be cheap).

-        #[allow(deprecated)]

-        crate::File::from_options(path.into(), self.options())

+        let path = path.into();

+        match self.0.open(&path) {

+            Ok(file) => Ok(crate::File::from_parts(file, path)),

+            Err(source) => Err(Error::build(source, ErrorKind::OpenFile, path)),

+        }

     }

 }

 

diff --git a/crates/fs-err/src/tokio/file.rs b/crates/fs-err/src/tokio/file.rs
index 2a005ba..7e73ba7 100644
--- a/crates/fs-err/src/tokio/file.rs
+++ b/crates/fs-err/src/tokio/file.rs
@@ -9,6 +9,8 @@
 use tokio::fs::File as TokioFile;

 use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};

 

+use super::OpenOptions;

+

 /// Wrapper around [`tokio::fs::File`] which adds more helpful

 /// information to all errors.

 #[derive(Debug)]

@@ -41,6 +43,24 @@
         }

     }

 

+    /// Opens a file in read-write mode.

+    ///

+    /// Wrapper for [`tokio::fs::File::create_new`].

+    pub async fn create_new(path: impl Into<PathBuf>) -> Result<Self, io::Error> {

+        let path = path.into();

+        match fs::File::create_new(&path).await {

+            Ok(file) => Ok(File::from_parts(file, path)),

+            Err(err) => Err(Error::build(err, ErrorKind::CreateFile, path)),

+        }

+    }

+

+    /// Returns a new `OpenOptions` object.

+    ///

+    /// Wrapper for [`tokio::fs::File::options`].

+    pub fn options() -> OpenOptions {

+        OpenOptions::new()

+    }

+

     /// Converts a [`crate::File`] to a [`tokio::fs::File`].

     ///

     /// Wrapper for [`tokio::fs::File::from_std`].

diff --git a/crates/fs-err/src/tokio/mod.rs b/crates/fs-err/src/tokio/mod.rs
index 1a56532..e34f514 100644
--- a/crates/fs-err/src/tokio/mod.rs
+++ b/crates/fs-err/src/tokio/mod.rs
@@ -203,16 +203,6 @@
 /// Wrapper for [`tokio::fs::symlink_dir`].

 #[cfg(windows)]

 #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]

-#[deprecated = "use fs_err::tokio::symlink_dir instead"]

-pub async fn symlink(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {

-    symlink_dir(src, dst).await

-}

-

-/// Creates a new directory symlink on the filesystem.

-///

-/// Wrapper for [`tokio::fs::symlink_dir`].

-#[cfg(windows)]

-#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]

 pub async fn symlink_dir(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {

     let (src, dst) = (src.as_ref(), dst.as_ref());

     tokio::fs::symlink_dir(src, dst)

diff --git a/pseudo_crate/Cargo.lock b/pseudo_crate/Cargo.lock
index 6e7ff72..b75a369 100644
--- a/pseudo_crate/Cargo.lock
+++ b/pseudo_crate/Cargo.lock
@@ -2122,9 +2122,9 @@
 
 [[package]]
 name = "fs-err"
-version = "2.11.0"
+version = "3.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41"
+checksum = "1f89bda4c2a21204059a977ed3bfe746677dfd137b83c339e702b0ac91d482aa"
 dependencies = [
  "autocfg",
 ]
diff --git a/pseudo_crate/Cargo.toml b/pseudo_crate/Cargo.toml
index 0b5ce4f..8523bc5 100644
--- a/pseudo_crate/Cargo.toml
+++ b/pseudo_crate/Cargo.toml
@@ -114,7 +114,7 @@
 foreign-types-shared = "=0.1.1"
 form_urlencoded = "=1.2.1"
 fragile = "=2.0.0"
-fs-err = "=2.11.0"
+fs-err = "=3.1.0"
 futures = "=0.3.31"
 futures-channel = "=0.3.31"
 futures-core = "=0.3.31"