Import fs-err 2.9.0 upstream upstream/2.9.0
authorRoy7Kim <myoungwoon.kim@samsung.com>
Mon, 13 Mar 2023 09:09:26 +0000 (18:09 +0900)
committerRoy7Kim <myoungwoon.kim@samsung.com>
Mon, 13 Mar 2023 09:09:26 +0000 (18:09 +0900)
21 files changed:
.cargo_vcs_info.json [new file with mode: 0644]
CHANGELOG.md [new file with mode: 0644]
Cargo.toml [new file with mode: 0644]
Cargo.toml.orig [new file with mode: 0644]
LICENSE-APACHE [new file with mode: 0644]
LICENSE-MIT [new file with mode: 0644]
README.md [new file with mode: 0644]
src/dir.rs [new file with mode: 0644]
src/errors.rs [new file with mode: 0644]
src/file.rs [new file with mode: 0644]
src/lib.rs [new file with mode: 0644]
src/open_options.rs [new file with mode: 0644]
src/os.rs [new file with mode: 0644]
src/os/unix.rs [new file with mode: 0644]
src/os/windows.rs [new file with mode: 0644]
src/path.rs [new file with mode: 0644]
src/tokio/dir_builder.rs [new file with mode: 0644]
src/tokio/file.rs [new file with mode: 0644]
src/tokio/mod.rs [new file with mode: 0644]
src/tokio/open_options.rs [new file with mode: 0644]
src/tokio/read_dir.rs [new file with mode: 0644]

diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644 (file)
index 0000000..1114130
--- /dev/null
@@ -0,0 +1,6 @@
+{
+  "git": {
+    "sha1": "3f59ce2d8a8fc1bbd267eac6848fb9d683542769"
+  },
+  "path_in_vcs": ""
+}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644 (file)
index 0000000..aa4079e
--- /dev/null
@@ -0,0 +1,72 @@
+# fs-err Changelog\r
+\r
+## 2.9.0\r
+\r
+* Add wrappers for [`tokio::fs`](https://docs.rs/tokio/latest/tokio/fs/index.html) ([#40](https://github.com/andrewhickman/fs-err/pull/40)).\r
+\r
+## 2.8.1\r
+\r
+* Fixed docs.rs build\r
+\r
+## 2.8.0\r
+\r
+* Implement I/O safety traits (`AsFd`/`AsHandle`, `Into<OwnedFd>`/`Into<OwnedHandle>`) for file. This feature requires Rust 1.63 or later and is gated behind the `io_safety` feature flag. ([#39](https://github.com/andrewhickman/fs-err/pull/39))\r
+\r
+## 2.7.0\r
+\r
+* Implement `From<fs_err::File> for std::fs::File` ([#38](https://github.com/andrewhickman/fs-err/pull/38))\r
+\r
+## 2.6.0\r
+\r
+* Added [`File::into_parts`](https://docs.rs/fs-err/2.6.0/fs_err/struct.File.html#method.into_parts) and [`File::file_mut`](https://docs.rs/fs-err/2.6.0/fs_err/struct.File.html#method.file_mut) to provide more access to the underlying `std::fs::File`.\r
+* Fixed some typos in documention ([#33](https://github.com/andrewhickman/fs-err/pull/33))\r
+\r
+## 2.5.0\r
+* Added `symlink` for unix platforms\r
+* Added `symlink_file` and `symlink_dir` for windows\r
+* Implemented os-specific extension traits for `File`\r
+  - `std::os::unix::io::{AsRawFd, IntoRawFd}`\r
+  - `std::os::windows::io::{AsRawHandle, IntoRawHandle}`\r
+  - Added trait wrappers for `std::os::{unix, windows}::fs::FileExt` and implemented them for `fs_err::File`\r
+* Implemented os-specific extension traits for `OpenOptions`\r
+  - Added trait wrappers for `std::os::{unix, windows}::fs::OpenOptionsExt` and implemented them for `fs_err::OpenOptions`\r
+* Improved compile times by converting arguments early and forwarding only a small number of types internally. There will be a slight performance hit only in the error case.\r
+* Reduced trait bounds on generics from `AsRef<Path> + Into<PathBuf>` to either `AsRef<Path>` or `Into<PathBuf>`, making the functions more general.\r
+\r
+## 2.4.0\r
+* Added `canonicalize`, `hard link`, `read_link`, `rename`, `symlink_metadata` and `soft_link`. ([#25](https://github.com/andrewhickman/fs-err/pull/25))\r
+* Added aliases to `std::path::Path` via extension trait ([#26](https://github.com/andrewhickman/fs-err/pull/26))\r
+* Added `OpenOptions` ([#27](https://github.com/andrewhickman/fs-err/pull/27))\r
+* Added `set_permissions` ([#28](https://github.com/andrewhickman/fs-err/pull/28))\r
+\r
+## 2.3.0\r
+* Added `create_dir` and `create_dir_all`. ([#19](https://github.com/andrewhickman/fs-err/pull/19))\r
+* Added `remove_file`, `remove_dir`, and `remove_dir_all`. ([#16](https://github.com/andrewhickman/fs-err/pull/16))\r
+\r
+## 2.2.0\r
+* Added `metadata`. ([#15](https://github.com/andrewhickman/fs-err/pull/15))\r
+\r
+## 2.1.0\r
+* Updated crate-level documentation. ([#8](https://github.com/andrewhickman/fs-err/pull/8))\r
+* Added `read_dir`, `ReadDir`, and `DirEntry`. ([#9](https://github.com/andrewhickman/fs-err/pull/9))\r
+\r
+## 2.0.1 (2020-02-22)\r
+* Added `copy`. ([#7](https://github.com/andrewhickman/fs-err/pull/7))\r
+\r
+## 2.0.0 (2020-02-19)\r
+* Removed custom error type in favor of `std::io::Error`. ([#2](https://github.com/andrewhickman/fs-err/pull/2))\r
+\r
+## 1.0.1 (2020-02-15)\r
+* Fixed bad documentation link in `Cargo.toml`.\r
+\r
+## 1.0.0 (2020-02-15)\r
+* No changes from 0.1.2.\r
+\r
+## 0.1.2 (2020-02-10)\r
+* Added `Error::cause` implementation for `fs_err::Error`.\r
+\r
+## 0.1.1 (2020-02-05)\r
+* Added wrappers for `std::fs::*` functions.\r
+\r
+## 0.1.0 (2020-02-02)\r
+* Initial release, containing a wrapper around `std::fs::File`.\r
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644 (file)
index 0000000..f7a7194
--- /dev/null
@@ -0,0 +1,59 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2018"
+name = "fs-err"
+version = "2.9.0"
+authors = ["Andrew Hickman <andrew.hickman1@sky.com>"]
+exclude = [
+    ".github",
+    ".gitignore",
+    "README.tpl",
+]
+description = "A drop-in replacement for std::fs with more helpful error messages."
+documentation = "https://docs.rs/fs-err"
+readme = "README.md"
+categories = [
+    "command-line-interface",
+    "filesystem",
+]
+license = "MIT/Apache-2.0"
+repository = "https://github.com/andrewhickman/fs-err"
+
+[package.metadata.release]
+tag-name = "{{version}}"
+sign-tag = true
+
+[[package.metadata.release.pre-release-replacements]]
+file = "src/lib.rs"
+search = 'html_root_url = "https://docs\.rs/fs-err/.*?"'
+replace = "html_root_url = \"https://docs.rs/fs-err/{{version}}\""
+exactly = 1
+
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = [
+    "--cfg",
+    "docsrs",
+]
+
+[dependencies.tokio]
+version = "1.21"
+features = ["fs"]
+optional = true
+default_features = false
+
+[dev-dependencies.serde_json]
+version = "1.0.64"
+
+[features]
+io_safety = []
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644 (file)
index 0000000..b0627e4
--- /dev/null
@@ -0,0 +1,36 @@
+[package]
+name = "fs-err"
+description = "A drop-in replacement for std::fs with more helpful error messages."
+version = "2.9.0"
+authors = ["Andrew Hickman <andrew.hickman1@sky.com>"]
+edition = "2018"
+repository = "https://github.com/andrewhickman/fs-err"
+documentation = "https://docs.rs/fs-err"
+categories = ["command-line-interface", "filesystem"]
+license = "MIT/Apache-2.0"
+readme = "README.md"
+exclude = [".github", ".gitignore", "README.tpl"]
+
+[dependencies]
+tokio = { version = "1.21", optional = true, default_features = false, features = ["fs"] }
+
+[dev-dependencies]
+serde_json = "1.0.64"
+
+[features]
+# Adds I/O safety traits introduced in Rust 1.63
+io_safety = []
+
+[package.metadata.release]
+tag-name = "{{version}}"
+sign-tag = true
+
+[[package.metadata.release.pre-release-replacements]]
+file = "src/lib.rs"
+search = "html_root_url = \"https://docs\\.rs/fs-err/.*?\""
+replace = "html_root_url = \"https://docs.rs/fs-err/{{version}}\""
+exactly = 1
+
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = ["--cfg", "docsrs"]
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
new file mode 100644 (file)
index 0000000..f47c941
--- /dev/null
@@ -0,0 +1,201 @@
+                              Apache License\r
+                        Version 2.0, January 2004\r
+                     http://www.apache.org/licenses/\r
+\r
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\r
+\r
+1. Definitions.\r
+\r
+   "License" shall mean the terms and conditions for use, reproduction,\r
+   and distribution as defined by Sections 1 through 9 of this document.\r
+\r
+   "Licensor" shall mean the copyright owner or entity authorized by\r
+   the copyright owner that is granting the License.\r
+\r
+   "Legal Entity" shall mean the union of the acting entity and all\r
+   other entities that control, are controlled by, or are under common\r
+   control with that entity. For the purposes of this definition,\r
+   "control" means (i) the power, direct or indirect, to cause the\r
+   direction or management of such entity, whether by contract or\r
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the\r
+   outstanding shares, or (iii) beneficial ownership of such entity.\r
+\r
+   "You" (or "Your") shall mean an individual or Legal Entity\r
+   exercising permissions granted by this License.\r
+\r
+   "Source" form shall mean the preferred form for making modifications,\r
+   including but not limited to software source code, documentation\r
+   source, and configuration files.\r
+\r
+   "Object" form shall mean any form resulting from mechanical\r
+   transformation or translation of a Source form, including but\r
+   not limited to compiled object code, generated documentation,\r
+   and conversions to other media types.\r
+\r
+   "Work" shall mean the work of authorship, whether in Source or\r
+   Object form, made available under the License, as indicated by a\r
+   copyright notice that is included in or attached to the work\r
+   (an example is provided in the Appendix below).\r
+\r
+   "Derivative Works" shall mean any work, whether in Source or Object\r
+   form, that is based on (or derived from) the Work and for which the\r
+   editorial revisions, annotations, elaborations, or other modifications\r
+   represent, as a whole, an original work of authorship. For the purposes\r
+   of this License, Derivative Works shall not include works that remain\r
+   separable from, or merely link (or bind by name) to the interfaces of,\r
+   the Work and Derivative Works thereof.\r
+\r
+   "Contribution" shall mean any work of authorship, including\r
+   the original version of the Work and any modifications or additions\r
+   to that Work or Derivative Works thereof, that is intentionally\r
+   submitted to Licensor for inclusion in the Work by the copyright owner\r
+   or by an individual or Legal Entity authorized to submit on behalf of\r
+   the copyright owner. For the purposes of this definition, "submitted"\r
+   means any form of electronic, verbal, or written communication sent\r
+   to the Licensor or its representatives, including but not limited to\r
+   communication on electronic mailing lists, source code control systems,\r
+   and issue tracking systems that are managed by, or on behalf of, the\r
+   Licensor for the purpose of discussing and improving the Work, but\r
+   excluding communication that is conspicuously marked or otherwise\r
+   designated in writing by the copyright owner as "Not a Contribution."\r
+\r
+   "Contributor" shall mean Licensor and any individual or Legal Entity\r
+   on behalf of whom a Contribution has been received by Licensor and\r
+   subsequently incorporated within the Work.\r
+\r
+2. Grant of Copyright License. Subject to the terms and conditions of\r
+   this License, each Contributor hereby grants to You a perpetual,\r
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\r
+   copyright license to reproduce, prepare Derivative Works of,\r
+   publicly display, publicly perform, sublicense, and distribute the\r
+   Work and such Derivative Works in Source or Object form.\r
+\r
+3. Grant of Patent License. Subject to the terms and conditions of\r
+   this License, each Contributor hereby grants to You a perpetual,\r
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\r
+   (except as stated in this section) patent license to make, have made,\r
+   use, offer to sell, sell, import, and otherwise transfer the Work,\r
+   where such license applies only to those patent claims licensable\r
+   by such Contributor that are necessarily infringed by their\r
+   Contribution(s) alone or by combination of their Contribution(s)\r
+   with the Work to which such Contribution(s) was submitted. If You\r
+   institute patent litigation against any entity (including a\r
+   cross-claim or counterclaim in a lawsuit) alleging that the Work\r
+   or a Contribution incorporated within the Work constitutes direct\r
+   or contributory patent infringement, then any patent licenses\r
+   granted to You under this License for that Work shall terminate\r
+   as of the date such litigation is filed.\r
+\r
+4. Redistribution. You may reproduce and distribute copies of the\r
+   Work or Derivative Works thereof in any medium, with or without\r
+   modifications, and in Source or Object form, provided that You\r
+   meet the following conditions:\r
+\r
+   (a) You must give any other recipients of the Work or\r
+       Derivative Works a copy of this License; and\r
+\r
+   (b) You must cause any modified files to carry prominent notices\r
+       stating that You changed the files; and\r
+\r
+   (c) You must retain, in the Source form of any Derivative Works\r
+       that You distribute, all copyright, patent, trademark, and\r
+       attribution notices from the Source form of the Work,\r
+       excluding those notices that do not pertain to any part of\r
+       the Derivative Works; and\r
+\r
+   (d) If the Work includes a "NOTICE" text file as part of its\r
+       distribution, then any Derivative Works that You distribute must\r
+       include a readable copy of the attribution notices contained\r
+       within such NOTICE file, excluding those notices that do not\r
+       pertain to any part of the Derivative Works, in at least one\r
+       of the following places: within a NOTICE text file distributed\r
+       as part of the Derivative Works; within the Source form or\r
+       documentation, if provided along with the Derivative Works; or,\r
+       within a display generated by the Derivative Works, if and\r
+       wherever such third-party notices normally appear. The contents\r
+       of the NOTICE file are for informational purposes only and\r
+       do not modify the License. You may add Your own attribution\r
+       notices within Derivative Works that You distribute, alongside\r
+       or as an addendum to the NOTICE text from the Work, provided\r
+       that such additional attribution notices cannot be construed\r
+       as modifying the License.\r
+\r
+   You may add Your own copyright statement to Your modifications and\r
+   may provide additional or different license terms and conditions\r
+   for use, reproduction, or distribution of Your modifications, or\r
+   for any such Derivative Works as a whole, provided Your use,\r
+   reproduction, and distribution of the Work otherwise complies with\r
+   the conditions stated in this License.\r
+\r
+5. Submission of Contributions. Unless You explicitly state otherwise,\r
+   any Contribution intentionally submitted for inclusion in the Work\r
+   by You to the Licensor shall be under the terms and conditions of\r
+   this License, without any additional terms or conditions.\r
+   Notwithstanding the above, nothing herein shall supersede or modify\r
+   the terms of any separate license agreement you may have executed\r
+   with Licensor regarding such Contributions.\r
+\r
+6. Trademarks. This License does not grant permission to use the trade\r
+   names, trademarks, service marks, or product names of the Licensor,\r
+   except as required for reasonable and customary use in describing the\r
+   origin of the Work and reproducing the content of the NOTICE file.\r
+\r
+7. Disclaimer of Warranty. Unless required by applicable law or\r
+   agreed to in writing, Licensor provides the Work (and each\r
+   Contributor provides its Contributions) on an "AS IS" BASIS,\r
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\r
+   implied, including, without limitation, any warranties or conditions\r
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\r
+   PARTICULAR PURPOSE. You are solely responsible for determining the\r
+   appropriateness of using or redistributing the Work and assume any\r
+   risks associated with Your exercise of permissions under this License.\r
+\r
+8. Limitation of Liability. In no event and under no legal theory,\r
+   whether in tort (including negligence), contract, or otherwise,\r
+   unless required by applicable law (such as deliberate and grossly\r
+   negligent acts) or agreed to in writing, shall any Contributor be\r
+   liable to You for damages, including any direct, indirect, special,\r
+   incidental, or consequential damages of any character arising as a\r
+   result of this License or out of the use or inability to use the\r
+   Work (including but not limited to damages for loss of goodwill,\r
+   work stoppage, computer failure or malfunction, or any and all\r
+   other commercial damages or losses), even if such Contributor\r
+   has been advised of the possibility of such damages.\r
+\r
+9. Accepting Warranty or Additional Liability. While redistributing\r
+   the Work or Derivative Works thereof, You may choose to offer,\r
+   and charge a fee for, acceptance of support, warranty, indemnity,\r
+   or other liability obligations and/or rights consistent with this\r
+   License. However, in accepting such obligations, You may act only\r
+   on Your own behalf and on Your sole responsibility, not on behalf\r
+   of any other Contributor, and only if You agree to indemnify,\r
+   defend, and hold each Contributor harmless for any liability\r
+   incurred by, or claims asserted against, such Contributor by reason\r
+   of your accepting any such warranty or additional liability.\r
+\r
+END OF TERMS AND CONDITIONS\r
+\r
+APPENDIX: How to apply the Apache License to your work.\r
+\r
+   To apply the Apache License to your work, attach the following\r
+   boilerplate notice, with the fields enclosed by brackets "[]"\r
+   replaced with your own identifying information. (Don't include\r
+   the brackets!)  The text should be enclosed in the appropriate\r
+   comment syntax for the file format. We also recommend that a\r
+   file or class name and description of purpose be included on the\r
+   same "printed page" as the copyright notice for easier\r
+   identification within third-party archives.\r
+\r
+Copyright [yyyy] [name of copyright owner]\r
+\r
+Licensed under the Apache License, Version 2.0 (the "License");\r
+you may not use this file except in compliance with the License.\r
+You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+Unless required by applicable law or agreed to in writing, software\r
+distributed under the License is distributed on an "AS IS" BASIS,\r
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+See the License for the specific language governing permissions and\r
+limitations under the License.\r
diff --git a/LICENSE-MIT b/LICENSE-MIT
new file mode 100644 (file)
index 0000000..458723b
--- /dev/null
@@ -0,0 +1,23 @@
+Permission is hereby granted, free of charge, to any\r
+person obtaining a copy of this software and associated\r
+documentation files (the "Software"), to deal in the\r
+Software without restriction, including without\r
+limitation the rights to use, copy, modify, merge,\r
+publish, distribute, sublicense, and/or sell copies of\r
+the Software, and to permit persons to whom the Software\r
+is furnished to do so, subject to the following\r
+conditions:\r
+\r
+The above copyright notice and this permission notice\r
+shall be included in all copies or substantial portions\r
+of the Software.\r
+\r
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF\r
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\r
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\r
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\r
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\r
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\r
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r
+DEALINGS IN THE SOFTWARE.\r
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..0dc5fc1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,88 @@
+<!--\r
+       This readme is created with https://github.com/livioribeiro/cargo-readme\r
+\r
+       Edit `src/lib.rs` and use `cargo readme > README.md` to update it.\r
+-->\r
+\r
+# fs-err\r
+\r
+[![Crates.io](https://img.shields.io/crates/v/fs-err.svg)](https://crates.io/crates/fs-err)\r
+[![GitHub Actions](https://github.com/andrewhickman/fs-err/workflows/CI/badge.svg)](https://github.com/andrewhickman/fs-err/actions?query=workflow%3ACI)\r
+\r
+fs-err is a drop-in replacement for [`std::fs`][std::fs] that provides more\r
+helpful messages on errors. Extra information includes which operations was\r
+attempted and any involved paths.\r
+\r
+## Error Messages\r
+\r
+Using [`std::fs`][std::fs], if this code fails:\r
+\r
+```rust\r
+let file = File::open("does not exist.txt")?;\r
+```\r
+\r
+The error message that Rust gives you isn't very useful:\r
+\r
+```txt\r
+The system cannot find the file specified. (os error 2)\r
+```\r
+\r
+...but if we use fs-err instead, our error contains more actionable information:\r
+\r
+```txt\r
+failed to open file `does not exist.txt`\r
+    caused by: The system cannot find the file specified. (os error 2)\r
+```\r
+\r
+## Usage\r
+\r
+fs-err's API is the same as [`std::fs`][std::fs], so migrating code to use it is easy.\r
+\r
+```rust\r
+// use std::fs;\r
+use fs_err as fs;\r
+\r
+let contents = fs::read_to_string("foo.txt")?;\r
+\r
+println!("Read foo.txt: {}", contents);\r
+\r
+```\r
+\r
+fs-err uses [`std::io::Error`][std::io::Error] for all errors. This helps fs-err\r
+compose well with traits from the standard library like\r
+[`std::io::Read`][std::io::Read] and crates that use them like\r
+[`serde_json`][serde_json]:\r
+\r
+```rust\r
+use fs_err::File;\r
+\r
+let file = File::open("my-config.json")?;\r
+\r
+// If an I/O error occurs inside serde_json, the error will include a file path\r
+// as well as what operation was being performed.\r
+let decoded: Vec<String> = serde_json::from_reader(file)?;\r
+\r
+println!("Program config: {:?}", decoded);\r
+\r
+```\r
+\r
+[std::fs]: https://doc.rust-lang.org/stable/std/fs/\r
+[std::io::Error]: https://doc.rust-lang.org/stable/std/io/struct.Error.html\r
+[std::io::Read]: https://doc.rust-lang.org/stable/std/io/trait.Read.html\r
+[serde_json]: https://crates.io/crates/serde_json\r
+\r
+## License\r
+\r
+Licensed under either of\r
+\r
+* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0)\r
+* MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT)\r
+\r
+at your option.\r
+\r
+### Contribution\r
+\r
+Unless you explicitly state otherwise, any contribution intentionally\r
+submitted for inclusion in the work by you, as defined in the Apache-2.0\r
+license, shall be dual licensed as above, without any additional terms or\r
+conditions.\r
diff --git a/src/dir.rs b/src/dir.rs
new file mode 100644 (file)
index 0000000..6efa58d
--- /dev/null
@@ -0,0 +1,90 @@
+use std::ffi::OsString;\r
+use std::fs;\r
+use std::io;\r
+use std::path::PathBuf;\r
+\r
+use crate::errors::{Error, ErrorKind};\r
+\r
+/// Wrapper for [`fs::read_dir`](https://doc.rust-lang.org/stable/std/fs/fn.read_dir.html).\r
+pub fn read_dir<P: Into<PathBuf>>(path: P) -> io::Result<ReadDir> {\r
+    let path = path.into();\r
+\r
+    match fs::read_dir(&path) {\r
+        Ok(inner) => Ok(ReadDir { inner, path }),\r
+        Err(source) => Err(Error::build(source, ErrorKind::ReadDir, path)),\r
+    }\r
+}\r
+\r
+/// Wrapper around [`std::fs::ReadDir`][std::fs::ReadDir] which adds more\r
+/// helpful information to all errors.\r
+///\r
+/// This struct is created via [`fs_err::read_dir`][fs_err::read_dir].\r
+///\r
+/// [std::fs::ReadDir]: https://doc.rust-lang.org/stable/std/fs/struct.ReadDir.html\r
+/// [fs_err::read_dir]: fn.read_dir.html\r
+#[derive(Debug)]\r
+pub struct ReadDir {\r
+    inner: fs::ReadDir,\r
+    path: PathBuf,\r
+}\r
+\r
+impl Iterator for ReadDir {\r
+    type Item = io::Result<DirEntry>;\r
+\r
+    fn next(&mut self) -> Option<Self::Item> {\r
+        Some(\r
+            self.inner\r
+                .next()?\r
+                .map_err(|source| Error::build(source, ErrorKind::ReadDir, &self.path))\r
+                .map(|inner| DirEntry { inner }),\r
+        )\r
+    }\r
+}\r
+\r
+/// Wrapper around [`std::fs::DirEntry`][std::fs::DirEntry] which adds more\r
+/// helpful information to all errors.\r
+///\r
+/// [std::fs::DirEntry]: https://doc.rust-lang.org/stable/std/fs/struct.DirEntry.html\r
+#[derive(Debug)]\r
+pub struct DirEntry {\r
+    inner: fs::DirEntry,\r
+}\r
+\r
+impl DirEntry {\r
+    /// Wrapper for [`DirEntry::path`](https://doc.rust-lang.org/stable/std/fs/struct.DirEntry.html#method.path).\r
+    pub fn path(&self) -> PathBuf {\r
+        self.inner.path()\r
+    }\r
+\r
+    /// Wrapper for [`DirEntry::metadata`](https://doc.rust-lang.org/stable/std/fs/struct.DirEntry.html#method.metadata).\r
+    pub fn metadata(&self) -> io::Result<fs::Metadata> {\r
+        self.inner\r
+            .metadata()\r
+            .map_err(|source| Error::build(source, ErrorKind::Metadata, self.path()))\r
+    }\r
+\r
+    /// Wrapper for [`DirEntry::file_type`](https://doc.rust-lang.org/stable/std/fs/struct.DirEntry.html#method.file_type).\r
+    pub fn file_type(&self) -> io::Result<fs::FileType> {\r
+        self.inner\r
+            .file_type()\r
+            .map_err(|source| Error::build(source, ErrorKind::Metadata, self.path()))\r
+    }\r
+\r
+    /// Wrapper for [`DirEntry::file_name`](https://doc.rust-lang.org/stable/std/fs/struct.DirEntry.html#method.file_name).\r
+    pub fn file_name(&self) -> OsString {\r
+        self.inner.file_name()\r
+    }\r
+}\r
+\r
+#[cfg(unix)]\r
+mod unix {\r
+    use std::os::unix::fs::DirEntryExt;\r
+\r
+    use super::*;\r
+\r
+    impl DirEntryExt for DirEntry {\r
+        fn ino(&self) -> u64 {\r
+            self.inner.ino()\r
+        }\r
+    }\r
+}\r
diff --git a/src/errors.rs b/src/errors.rs
new file mode 100644 (file)
index 0000000..43bc4ba
--- /dev/null
@@ -0,0 +1,198 @@
+use std::error::Error as StdError;\r
+use std::fmt;\r
+use std::io;\r
+use std::path::PathBuf;\r
+\r
+#[derive(Debug, Clone, Copy)]\r
+pub(crate) enum ErrorKind {\r
+    OpenFile,\r
+    CreateFile,\r
+    CreateDir,\r
+    SyncFile,\r
+    SetLen,\r
+    Metadata,\r
+    Clone,\r
+    SetPermissions,\r
+    Read,\r
+    Seek,\r
+    Write,\r
+    Flush,\r
+    ReadDir,\r
+    RemoveFile,\r
+    RemoveDir,\r
+    Canonicalize,\r
+    ReadLink,\r
+    SymlinkMetadata,\r
+\r
+    #[cfg(windows)]\r
+    SeekRead,\r
+    #[cfg(windows)]\r
+    SeekWrite,\r
+\r
+    #[cfg(unix)]\r
+    ReadAt,\r
+    #[cfg(unix)]\r
+    WriteAt,\r
+}\r
+\r
+/// Contains an IO error that has a file path attached.\r
+///\r
+/// This type is never returned directly, but is instead wrapped inside yet\r
+/// another IO error.\r
+#[derive(Debug)]\r
+pub(crate) struct Error {\r
+    kind: ErrorKind,\r
+    source: io::Error,\r
+    path: PathBuf,\r
+}\r
+\r
+impl Error {\r
+    pub fn build(source: io::Error, kind: ErrorKind, path: impl Into<PathBuf>) -> io::Error {\r
+        io::Error::new(\r
+            source.kind(),\r
+            Self {\r
+                kind,\r
+                source,\r
+                path: path.into(),\r
+            },\r
+        )\r
+    }\r
+}\r
+\r
+impl fmt::Display for Error {\r
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\r
+        use ErrorKind::*;\r
+\r
+        let path = self.path.display();\r
+\r
+        match self.kind {\r
+            OpenFile => write!(formatter, "failed to open file `{}`", path),\r
+            CreateFile => write!(formatter, "failed to create file `{}`", path),\r
+            CreateDir => write!(formatter, "failed to create directory `{}`", path),\r
+            SyncFile => write!(formatter, "failed to sync file `{}`", path),\r
+            SetLen => write!(formatter, "failed to set length of file `{}`", path),\r
+            Metadata => write!(formatter, "failed to query metadata of file `{}`", path),\r
+            Clone => write!(formatter, "failed to clone handle for file `{}`", path),\r
+            SetPermissions => write!(formatter, "failed to set permissions for file `{}`", path),\r
+            Read => write!(formatter, "failed to read from file `{}`", path),\r
+            Seek => write!(formatter, "failed to seek in file `{}`", path),\r
+            Write => write!(formatter, "failed to write to file `{}`", path),\r
+            Flush => write!(formatter, "failed to flush file `{}`", path),\r
+            ReadDir => write!(formatter, "failed to read directory `{}`", path),\r
+            RemoveFile => write!(formatter, "failed to remove file `{}`", path),\r
+            RemoveDir => write!(formatter, "failed to remove directory `{}`", path),\r
+            Canonicalize => write!(formatter, "failed to canonicalize path `{}`", path),\r
+            ReadLink => write!(formatter, "failed to read symbolic link `{}`", path),\r
+            SymlinkMetadata => write!(formatter, "failed to query metadata of symlink `{}`", path),\r
+\r
+            #[cfg(windows)]\r
+            SeekRead => write!(formatter, "failed to seek and read from `{}`", path),\r
+            #[cfg(windows)]\r
+            SeekWrite => write!(formatter, "failed to seek and write to `{}`", path),\r
+\r
+            #[cfg(unix)]\r
+            ReadAt => write!(formatter, "failed to read with offset from `{}`", path),\r
+            #[cfg(unix)]\r
+            WriteAt => write!(formatter, "failed to write with offset to `{}`", path),\r
+        }\r
+    }\r
+}\r
+\r
+impl StdError for Error {\r
+    fn cause(&self) -> Option<&dyn StdError> {\r
+        self.source()\r
+    }\r
+\r
+    fn source(&self) -> Option<&(dyn StdError + 'static)> {\r
+        Some(&self.source)\r
+    }\r
+}\r
+\r
+#[derive(Debug, Clone, Copy)]\r
+pub(crate) enum SourceDestErrorKind {\r
+    Copy,\r
+    HardLink,\r
+    Rename,\r
+    SoftLink,\r
+\r
+    #[cfg(unix)]\r
+    Symlink,\r
+\r
+    #[cfg(windows)]\r
+    SymlinkDir,\r
+    #[cfg(windows)]\r
+    SymlinkFile,\r
+}\r
+\r
+/// Error type used by functions like `fs::copy` that holds two paths.\r
+#[derive(Debug)]\r
+pub(crate) struct SourceDestError {\r
+    kind: SourceDestErrorKind,\r
+    source: io::Error,\r
+    from_path: PathBuf,\r
+    to_path: PathBuf,\r
+}\r
+\r
+impl SourceDestError {\r
+    pub fn build(\r
+        source: io::Error,\r
+        kind: SourceDestErrorKind,\r
+        from_path: impl Into<PathBuf>,\r
+        to_path: impl Into<PathBuf>,\r
+    ) -> io::Error {\r
+        io::Error::new(\r
+            source.kind(),\r
+            Self {\r
+                kind,\r
+                source,\r
+                from_path: from_path.into(),\r
+                to_path: to_path.into(),\r
+            },\r
+        )\r
+    }\r
+}\r
+\r
+impl fmt::Display for SourceDestError {\r
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {\r
+        let from = self.from_path.display();\r
+        let to = self.to_path.display();\r
+        match self.kind {\r
+            SourceDestErrorKind::Copy => {\r
+                write!(formatter, "failed to copy file from {} to {}", from, to)\r
+            }\r
+            SourceDestErrorKind::HardLink => {\r
+                write!(formatter, "failed to hardlink file from {} to {}", from, to)\r
+            }\r
+            SourceDestErrorKind::Rename => {\r
+                write!(formatter, "failed to rename file from {} to {}", from, to)\r
+            }\r
+            SourceDestErrorKind::SoftLink => {\r
+                write!(formatter, "failed to softlink file from {} to {}", from, to)\r
+            }\r
+\r
+            #[cfg(unix)]\r
+            SourceDestErrorKind::Symlink => {\r
+                write!(formatter, "failed to symlink file from {} to {}", from, to)\r
+            }\r
+\r
+            #[cfg(windows)]\r
+            SourceDestErrorKind::SymlinkFile => {\r
+                write!(formatter, "failed to symlink file from {} to {}", from, to)\r
+            }\r
+            #[cfg(windows)]\r
+            SourceDestErrorKind::SymlinkDir => {\r
+                write!(formatter, "failed to symlink dir from {} to {}", from, to)\r
+            }\r
+        }\r
+    }\r
+}\r
+\r
+impl StdError for SourceDestError {\r
+    fn cause(&self) -> Option<&dyn StdError> {\r
+        self.source()\r
+    }\r
+\r
+    fn source(&self) -> Option<&(dyn StdError + 'static)> {\r
+        Some(&self.source)\r
+    }\r
+}\r
diff --git a/src/file.rs b/src/file.rs
new file mode 100644 (file)
index 0000000..fcf54fe
--- /dev/null
@@ -0,0 +1,366 @@
+use std::fs;\r
+use std::io::{self, Read, Seek, Write};\r
+use std::path::{Path, PathBuf};\r
+\r
+use crate::errors::{Error, ErrorKind};\r
+\r
+/// Wrapper around [`std::fs::File`][std::fs::File] which adds more helpful\r
+/// information to all errors.\r
+///\r
+/// [std::fs::File]: https://doc.rust-lang.org/stable/std/fs/struct.File.html\r
+#[derive(Debug)]\r
+pub struct File {\r
+    file: fs::File,\r
+    path: PathBuf,\r
+}\r
+\r
+// Opens a std File and returns it or an error generator which only needs the path to produce the error.\r
+// Exists for the `crate::read*` functions so they don't unconditionally build a PathBuf.\r
+pub(crate) fn open(path: &Path) -> Result<std::fs::File, impl FnOnce(PathBuf) -> io::Error> {\r
+    fs::File::open(&path).map_err(|err| |path| Error::build(err, ErrorKind::OpenFile, path))\r
+}\r
+\r
+// like `open()` but for `crate::write`\r
+pub(crate) fn create(path: &Path) -> Result<std::fs::File, impl FnOnce(PathBuf) -> io::Error> {\r
+    fs::File::create(&path).map_err(|err| |path| Error::build(err, ErrorKind::CreateFile, path))\r
+}\r
+\r
+/// Wrappers for methods from [`std::fs::File`][std::fs::File].\r
+///\r
+/// [std::fs::File]: https://doc.rust-lang.org/stable/std/fs/struct.File.html\r
+impl File {\r
+    /// Wrapper for [`File::open`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.open).\r
+    pub fn open<P>(path: P) -> Result<Self, io::Error>\r
+    where\r
+        P: Into<PathBuf>,\r
+    {\r
+        let path = path.into();\r
+        match open(&path) {\r
+            Ok(file) => Ok(File::from_parts(file, path)),\r
+            Err(err_gen) => Err(err_gen(path)),\r
+        }\r
+    }\r
+\r
+    /// Wrapper for [`File::create`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.create).\r
+    pub fn create<P>(path: P) -> Result<Self, io::Error>\r
+    where\r
+        P: Into<PathBuf>,\r
+    {\r
+        let path = path.into();\r
+        match create(&path) {\r
+            Ok(file) => Ok(File::from_parts(file, path)),\r
+            Err(err_gen) => Err(err_gen(path)),\r
+        }\r
+    }\r
+\r
+    /// Wrapper for [`OpenOptions::open`](https://doc.rust-lang.org/stable/std/fs/struct.OpenOptions.html#method.open).\r
+    ///\r
+    /// This takes [`&std::fs::OpenOptions`](https://doc.rust-lang.org/stable/std/fs/struct.OpenOptions.html),\r
+    /// not [`crate::OpenOptions`].\r
+    #[deprecated = "use fs_err::OpenOptions::open instead"]\r
+    pub fn from_options<P>(path: P, options: &fs::OpenOptions) -> Result<Self, io::Error>\r
+    where\r
+        P: Into<PathBuf>,\r
+    {\r
+        let path = path.into();\r
+        match options.open(&path) {\r
+            Ok(file) => Ok(File::from_parts(file, path)),\r
+            Err(source) => Err(Error::build(source, ErrorKind::OpenFile, path)),\r
+        }\r
+    }\r
+\r
+    /// Wrapper for [`File::sync_all`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.sync_all).\r
+    pub fn sync_all(&self) -> Result<(), io::Error> {\r
+        self.file\r
+            .sync_all()\r
+            .map_err(|source| self.error(source, ErrorKind::SyncFile))\r
+    }\r
+\r
+    /// Wrapper for [`File::sync_data`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.sync_data).\r
+    pub fn sync_data(&self) -> Result<(), io::Error> {\r
+        self.file\r
+            .sync_data()\r
+            .map_err(|source| self.error(source, ErrorKind::SyncFile))\r
+    }\r
+\r
+    /// Wrapper for [`File::set_len`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.set_len).\r
+    pub fn set_len(&self, size: u64) -> Result<(), io::Error> {\r
+        self.file\r
+            .set_len(size)\r
+            .map_err(|source| self.error(source, ErrorKind::SetLen))\r
+    }\r
+\r
+    /// Wrapper for [`File::metadata`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.metadata).\r
+    pub fn metadata(&self) -> Result<fs::Metadata, io::Error> {\r
+        self.file\r
+            .metadata()\r
+            .map_err(|source| self.error(source, ErrorKind::Metadata))\r
+    }\r
+\r
+    /// Wrapper for [`File::try_clone`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.try_clone).\r
+    pub fn try_clone(&self) -> Result<Self, io::Error> {\r
+        self.file\r
+            .try_clone()\r
+            .map(|file| File {\r
+                file,\r
+                path: self.path.clone(),\r
+            })\r
+            .map_err(|source| self.error(source, ErrorKind::Clone))\r
+    }\r
+\r
+    /// Wrapper for [`File::set_permissions`](https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.set_permissions).\r
+    pub fn set_permissions(&self, perm: fs::Permissions) -> Result<(), io::Error> {\r
+        self.file\r
+            .set_permissions(perm)\r
+            .map_err(|source| self.error(source, ErrorKind::SetPermissions))\r
+    }\r
+}\r
+\r
+/// Methods added by fs-err that are not available on\r
+/// [`std::fs::File`][std::fs::File].\r
+///\r
+/// [std::fs::File]: https://doc.rust-lang.org/stable/std/fs/struct.File.html\r
+impl File {\r
+    /// Creates a [`File`](struct.File.html) from a raw file and its path.\r
+    pub fn from_parts<P>(file: fs::File, path: P) -> Self\r
+    where\r
+        P: Into<PathBuf>,\r
+    {\r
+        File {\r
+            file,\r
+            path: path.into(),\r
+        }\r
+    }\r
+\r
+    /// Extract the raw file and its path from this [`File`](struct.File.html)\r
+    pub fn into_parts(self) -> (fs::File, PathBuf) {\r
+        (self.file, self.path)\r
+    }\r
+\r
+    /// Returns a reference to the underlying [`std::fs::File`][std::fs::File].\r
+    ///\r
+    /// [std::fs::File]: https://doc.rust-lang.org/stable/std/fs/struct.File.html\r
+    pub fn file(&self) -> &fs::File {\r
+        &self.file\r
+    }\r
+\r
+    /// Returns a mutable reference to the underlying [`std::fs::File`][std::fs::File].\r
+    ///\r
+    /// [std::fs::File]: https://doc.rust-lang.org/stable/std/fs/struct.File.html\r
+    pub fn file_mut(&mut self) -> &mut fs::File {\r
+        &mut self.file\r
+    }\r
+\r
+    /// Returns a reference to the path that this file was created with.\r
+    pub fn path(&self) -> &Path {\r
+        &self.path\r
+    }\r
+\r
+    /// Wrap the error in information specific to this `File` object.\r
+    fn error(&self, source: io::Error, kind: ErrorKind) -> io::Error {\r
+        Error::build(source, kind, &self.path)\r
+    }\r
+}\r
+\r
+impl Read for File {\r
+    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {\r
+        self.file\r
+            .read(buf)\r
+            .map_err(|source| self.error(source, ErrorKind::Read))\r
+    }\r
+\r
+    fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result<usize> {\r
+        self.file\r
+            .read_vectored(bufs)\r
+            .map_err(|source| self.error(source, ErrorKind::Read))\r
+    }\r
+}\r
+\r
+impl<'a> Read for &'a File {\r
+    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {\r
+        (&(**self).file)\r
+            .read(buf)\r
+            .map_err(|source| self.error(source, ErrorKind::Read))\r
+    }\r
+\r
+    fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result<usize> {\r
+        (&(**self).file)\r
+            .read_vectored(bufs)\r
+            .map_err(|source| self.error(source, ErrorKind::Read))\r
+    }\r
+}\r
+\r
+impl From<File> for fs::File {\r
+    fn from(file: File) -> Self {\r
+        file.into_parts().0\r
+    }\r
+}\r
+\r
+impl Seek for File {\r
+    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {\r
+        self.file\r
+            .seek(pos)\r
+            .map_err(|source| self.error(source, ErrorKind::Seek))\r
+    }\r
+}\r
+\r
+impl<'a> Seek for &'a File {\r
+    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {\r
+        (&(**self).file)\r
+            .seek(pos)\r
+            .map_err(|source| self.error(source, ErrorKind::Seek))\r
+    }\r
+}\r
+\r
+impl Write for File {\r
+    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {\r
+        self.file\r
+            .write(buf)\r
+            .map_err(|source| self.error(source, ErrorKind::Write))\r
+    }\r
+\r
+    fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {\r
+        self.file\r
+            .write_vectored(bufs)\r
+            .map_err(|source| self.error(source, ErrorKind::Write))\r
+    }\r
+\r
+    fn flush(&mut self) -> std::io::Result<()> {\r
+        self.file\r
+            .flush()\r
+            .map_err(|source| self.error(source, ErrorKind::Flush))\r
+    }\r
+}\r
+\r
+impl<'a> Write for &'a File {\r
+    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {\r
+        (&(**self).file)\r
+            .write(buf)\r
+            .map_err(|source| self.error(source, ErrorKind::Write))\r
+    }\r
+\r
+    fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {\r
+        (&(**self).file)\r
+            .write_vectored(bufs)\r
+            .map_err(|source| self.error(source, ErrorKind::Write))\r
+    }\r
+\r
+    fn flush(&mut self) -> std::io::Result<()> {\r
+        (&(**self).file)\r
+            .flush()\r
+            .map_err(|source| self.error(source, ErrorKind::Flush))\r
+    }\r
+}\r
+\r
+#[cfg(unix)]\r
+mod unix {\r
+    use crate::os::unix::fs::FileExt;\r
+    use crate::ErrorKind;\r
+    use std::io;\r
+    use std::os::unix::fs::FileExt as _;\r
+    use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};\r
+\r
+    impl AsRawFd for crate::File {\r
+        fn as_raw_fd(&self) -> RawFd {\r
+            self.file().as_raw_fd()\r
+        }\r
+    }\r
+\r
+    impl IntoRawFd for crate::File {\r
+        fn into_raw_fd(self) -> RawFd {\r
+            self.file.into_raw_fd()\r
+        }\r
+    }\r
+\r
+    impl FileExt for crate::File {\r
+        fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {\r
+            self.file()\r
+                .read_at(buf, offset)\r
+                .map_err(|err| self.error(err, ErrorKind::ReadAt))\r
+        }\r
+        fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {\r
+            self.file()\r
+                .write_at(buf, offset)\r
+                .map_err(|err| self.error(err, ErrorKind::WriteAt))\r
+        }\r
+    }\r
+\r
+    #[cfg(feature = "io_safety")]\r
+    mod io_safety {\r
+        use std::os::unix::io::{AsFd, BorrowedFd, OwnedFd};\r
+\r
+        #[cfg_attr(docsrs, doc(cfg(feature = "io_safety")))]\r
+        impl AsFd for crate::File {\r
+            fn as_fd(&self) -> BorrowedFd<'_> {\r
+                self.file().as_fd()\r
+            }\r
+        }\r
+\r
+        #[cfg_attr(docsrs, doc(cfg(feature = "io_safety")))]\r
+        impl From<crate::File> for OwnedFd {\r
+            fn from(file: crate::File) -> Self {\r
+                file.into_parts().0.into()\r
+            }\r
+        }\r
+    }\r
+}\r
+\r
+#[cfg(windows)]\r
+mod windows {\r
+    use crate::os::windows::fs::FileExt;\r
+    use crate::ErrorKind;\r
+    use std::io;\r
+    use std::os::windows::{\r
+        fs::FileExt as _,\r
+        io::{AsRawHandle, IntoRawHandle, RawHandle},\r
+    };\r
+\r
+    impl FileExt for crate::File {\r
+        fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {\r
+            self.file()\r
+                .seek_read(buf, offset)\r
+                .map_err(|err| self.error(err, ErrorKind::SeekRead))\r
+        }\r
+\r
+        fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result<usize> {\r
+            self.file()\r
+                .seek_write(buf, offset)\r
+                .map_err(|err| self.error(err, ErrorKind::SeekWrite))\r
+        }\r
+    }\r
+\r
+    impl AsRawHandle for crate::File {\r
+        fn as_raw_handle(&self) -> RawHandle {\r
+            self.file().as_raw_handle()\r
+        }\r
+    }\r
+\r
+    // can't be implemented, because the trait doesn't give us a Path\r
+    // impl std::os::windows::io::FromRawHandle for crate::File {\r
+    // }\r
+\r
+    impl IntoRawHandle for crate::File {\r
+        fn into_raw_handle(self) -> RawHandle {\r
+            self.file.into_raw_handle()\r
+        }\r
+    }\r
+\r
+    #[cfg(feature = "io_safety")]\r
+    mod io_safety {\r
+        use std::os::windows::io::{AsHandle, BorrowedHandle, OwnedHandle};\r
+\r
+        #[cfg_attr(docsrs, doc(cfg(feature = "io_safety")))]\r
+        impl AsHandle for crate::File {\r
+            fn as_handle(&self) -> BorrowedHandle<'_> {\r
+                self.file().as_handle()\r
+            }\r
+        }\r
+\r
+        #[cfg_attr(docsrs, doc(cfg(feature = "io_safety")))]\r
+        impl From<crate::File> for OwnedHandle {\r
+            fn from(file: crate::File) -> Self {\r
+                file.into_parts().0.into()\r
+            }\r
+        }\r
+    }\r
+}\r
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644 (file)
index 0000000..b1ceae5
--- /dev/null
@@ -0,0 +1,250 @@
+/*!\r
+fs-err is a drop-in replacement for [`std::fs`][std::fs] that provides more\r
+helpful messages on errors. Extra information includes which operations was\r
+attempted and any involved paths.\r
+\r
+# Error Messages\r
+\r
+Using [`std::fs`][std::fs], if this code fails:\r
+\r
+```no_run\r
+# use std::fs::File;\r
+let file = File::open("does not exist.txt")?;\r
+# Ok::<(), std::io::Error>(())\r
+```\r
+\r
+The error message that Rust gives you isn't very useful:\r
+\r
+```txt\r
+The system cannot find the file specified. (os error 2)\r
+```\r
+\r
+...but if we use fs-err instead, our error contains more actionable information:\r
+\r
+```txt\r
+failed to open file `does not exist.txt`\r
+    caused by: The system cannot find the file specified. (os error 2)\r
+```\r
+\r
+# Usage\r
+\r
+fs-err's API is the same as [`std::fs`][std::fs], so migrating code to use it is easy.\r
+\r
+```no_run\r
+// use std::fs;\r
+use fs_err as fs;\r
+\r
+let contents = fs::read_to_string("foo.txt")?;\r
+\r
+println!("Read foo.txt: {}", contents);\r
+\r
+# Ok::<(), std::io::Error>(())\r
+```\r
+\r
+fs-err uses [`std::io::Error`][std::io::Error] for all errors. This helps fs-err\r
+compose well with traits from the standard library like\r
+[`std::io::Read`][std::io::Read] and crates that use them like\r
+[`serde_json`][serde_json]:\r
+\r
+```no_run\r
+use fs_err::File;\r
+\r
+let file = File::open("my-config.json")?;\r
+\r
+// If an I/O error occurs inside serde_json, the error will include a file path\r
+// as well as what operation was being performed.\r
+let decoded: Vec<String> = serde_json::from_reader(file)?;\r
+\r
+println!("Program config: {:?}", decoded);\r
+\r
+# Ok::<(), Box<dyn std::error::Error>>(())\r
+```\r
+\r
+[std::fs]: https://doc.rust-lang.org/stable/std/fs/\r
+[std::io::Error]: https://doc.rust-lang.org/stable/std/io/struct.Error.html\r
+[std::io::Read]: https://doc.rust-lang.org/stable/std/io/trait.Read.html\r
+[serde_json]: https://crates.io/crates/serde_json\r
+*/\r
+\r
+#![doc(html_root_url = "https://docs.rs/fs-err/2.9.0")]\r
+#![deny(missing_debug_implementations, missing_docs)]\r
+#![cfg_attr(docsrs, feature(doc_cfg))]\r
+\r
+mod dir;\r
+mod errors;\r
+mod file;\r
+mod open_options;\r
+pub mod os;\r
+mod path;\r
+#[cfg(feature = "tokio")]\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub mod tokio;\r
+\r
+use std::fs;\r
+use std::io::{self, Read, Write};\r
+use std::path::{Path, PathBuf};\r
+\r
+use errors::{Error, ErrorKind, SourceDestError, SourceDestErrorKind};\r
+\r
+pub use dir::*;\r
+pub use file::*;\r
+pub use open_options::OpenOptions;\r
+pub use path::PathExt;\r
+\r
+/// Wrapper for [`fs::read`](https://doc.rust-lang.org/stable/std/fs/fn.read.html).\r
+pub fn read<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {\r
+    let path = path.as_ref();\r
+    let mut file = file::open(path).map_err(|err_gen| err_gen(path.to_path_buf()))?;\r
+    let mut bytes = Vec::with_capacity(initial_buffer_size(&file));\r
+    file.read_to_end(&mut bytes)\r
+        .map_err(|err| Error::build(err, ErrorKind::Read, path))?;\r
+    Ok(bytes)\r
+}\r
+\r
+/// Wrapper for [`fs::read_to_string`](https://doc.rust-lang.org/stable/std/fs/fn.read_to_string.html).\r
+pub fn read_to_string<P: AsRef<Path>>(path: P) -> io::Result<String> {\r
+    let path = path.as_ref();\r
+    let mut file = file::open(path).map_err(|err_gen| err_gen(path.to_path_buf()))?;\r
+    let mut string = String::with_capacity(initial_buffer_size(&file));\r
+    file.read_to_string(&mut string)\r
+        .map_err(|err| Error::build(err, ErrorKind::Read, path))?;\r
+    Ok(string)\r
+}\r
+\r
+/// Wrapper for [`fs::write`](https://doc.rust-lang.org/stable/std/fs/fn.write.html).\r
+pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()> {\r
+    let path = path.as_ref();\r
+    file::create(path)\r
+        .map_err(|err_gen| err_gen(path.to_path_buf()))?\r
+        .write_all(contents.as_ref())\r
+        .map_err(|err| Error::build(err, ErrorKind::Write, path))\r
+}\r
+\r
+/// Wrapper for [`fs::copy`](https://doc.rust-lang.org/stable/std/fs/fn.copy.html).\r
+pub fn copy<P, Q>(from: P, to: Q) -> io::Result<u64>\r
+where\r
+    P: AsRef<Path>,\r
+    Q: AsRef<Path>,\r
+{\r
+    let from = from.as_ref();\r
+    let to = to.as_ref();\r
+    fs::copy(from, to)\r
+        .map_err(|source| SourceDestError::build(source, SourceDestErrorKind::Copy, from, to))\r
+}\r
+\r
+/// Wrapper for [`fs::create_dir`](https://doc.rust-lang.org/stable/std/fs/fn.create_dir.html).\r
+pub fn create_dir<P>(path: P) -> io::Result<()>\r
+where\r
+    P: AsRef<Path>,\r
+{\r
+    let path = path.as_ref();\r
+    fs::create_dir(path).map_err(|source| Error::build(source, ErrorKind::CreateDir, path))\r
+}\r
+\r
+/// Wrapper for [`fs::create_dir_all`](https://doc.rust-lang.org/stable/std/fs/fn.create_dir_all.html).\r
+pub fn create_dir_all<P>(path: P) -> io::Result<()>\r
+where\r
+    P: AsRef<Path>,\r
+{\r
+    let path = path.as_ref();\r
+    fs::create_dir_all(path).map_err(|source| Error::build(source, ErrorKind::CreateDir, path))\r
+}\r
+\r
+/// Wrapper for [`fs::remove_dir`](https://doc.rust-lang.org/stable/std/fs/fn.remove_dir.html).\r
+pub fn remove_dir<P>(path: P) -> io::Result<()>\r
+where\r
+    P: AsRef<Path>,\r
+{\r
+    let path = path.as_ref();\r
+    fs::remove_dir(path).map_err(|source| Error::build(source, ErrorKind::RemoveDir, path))\r
+}\r
+\r
+/// Wrapper for [`fs::remove_dir_all`](https://doc.rust-lang.org/stable/std/fs/fn.remove_dir_all.html).\r
+pub fn remove_dir_all<P>(path: P) -> io::Result<()>\r
+where\r
+    P: AsRef<Path>,\r
+{\r
+    let path = path.as_ref();\r
+    fs::remove_dir_all(path).map_err(|source| Error::build(source, ErrorKind::RemoveDir, path))\r
+}\r
+\r
+/// Wrapper for [`fs::remove_file`](https://doc.rust-lang.org/stable/std/fs/fn.remove_file.html).\r
+pub fn remove_file<P>(path: P) -> io::Result<()>\r
+where\r
+    P: AsRef<Path>,\r
+{\r
+    let path = path.as_ref();\r
+    fs::remove_file(path).map_err(|source| Error::build(source, ErrorKind::RemoveFile, path))\r
+}\r
+\r
+/// Wrapper for [`fs::metadata`](https://doc.rust-lang.org/stable/std/fs/fn.metadata.html).\r
+pub fn metadata<P: AsRef<Path>>(path: P) -> io::Result<fs::Metadata> {\r
+    let path = path.as_ref();\r
+    fs::metadata(path).map_err(|source| Error::build(source, ErrorKind::Metadata, path))\r
+}\r
+\r
+/// Wrapper for [`fs::canonicalize`](https://doc.rust-lang.org/stable/std/fs/fn.canonicalize.html).\r
+pub fn canonicalize<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {\r
+    let path = path.as_ref();\r
+    fs::canonicalize(path).map_err(|source| Error::build(source, ErrorKind::Canonicalize, path))\r
+}\r
+\r
+/// Wrapper for [`fs::hard_link`](https://doc.rust-lang.org/stable/std/fs/fn.hard_link.html).\r
+pub fn hard_link<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Result<()> {\r
+    let src = src.as_ref();\r
+    let dst = dst.as_ref();\r
+    fs::hard_link(src, dst)\r
+        .map_err(|source| SourceDestError::build(source, SourceDestErrorKind::HardLink, src, dst))\r
+}\r
+\r
+/// Wrapper for [`fs::read_link`](https://doc.rust-lang.org/stable/std/fs/fn.read_link.html).\r
+pub fn read_link<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {\r
+    let path = path.as_ref();\r
+    fs::read_link(path).map_err(|source| Error::build(source, ErrorKind::ReadLink, path))\r
+}\r
+\r
+/// Wrapper for [`fs::rename`](https://doc.rust-lang.org/stable/std/fs/fn.rename.html).\r
+pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> {\r
+    let from = from.as_ref();\r
+    let to = to.as_ref();\r
+    fs::rename(from, to)\r
+        .map_err(|source| SourceDestError::build(source, SourceDestErrorKind::Rename, from, to))\r
+}\r
+\r
+/// Wrapper for [`fs::soft_link`](https://doc.rust-lang.org/stable/std/fs/fn.soft_link.html).\r
+#[deprecated = "replaced with std::os::unix::fs::symlink and \\r
+std::os::windows::fs::{symlink_file, symlink_dir}"]\r
+pub fn soft_link<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Result<()> {\r
+    let src = src.as_ref();\r
+    let dst = dst.as_ref();\r
+    #[allow(deprecated)]\r
+    fs::soft_link(src, dst)\r
+        .map_err(|source| SourceDestError::build(source, SourceDestErrorKind::SoftLink, src, dst))\r
+}\r
+\r
+/// Wrapper for [`fs::symlink_metadata`](https://doc.rust-lang.org/stable/std/fs/fn.symlink_metadata.html).\r
+pub fn symlink_metadata<P: AsRef<Path>>(path: P) -> io::Result<fs::Metadata> {\r
+    let path = path.as_ref();\r
+    fs::symlink_metadata(path)\r
+        .map_err(|source| Error::build(source, ErrorKind::SymlinkMetadata, path))\r
+}\r
+\r
+/// Wrapper for [`fs::set_permissions`](https://doc.rust-lang.org/stable/std/fs/fn.set_permissions.html).\r
+pub fn set_permissions<P: AsRef<Path>>(path: P, perm: fs::Permissions) -> io::Result<()> {\r
+    let path = path.as_ref();\r
+    fs::set_permissions(path, perm)\r
+        .map_err(|source| Error::build(source, ErrorKind::SetPermissions, path))\r
+}\r
+\r
+fn initial_buffer_size(file: &std::fs::File) -> usize {\r
+    file.metadata().map(|m| m.len() as usize + 1).unwrap_or(0)\r
+}\r
+\r
+pub(crate) use private::Sealed;\r
+mod private {\r
+    pub trait Sealed {}\r
+\r
+    impl Sealed for crate::File {}\r
+    impl Sealed for std::path::Path {}\r
+    impl Sealed for crate::OpenOptions {}\r
+}\r
diff --git a/src/open_options.rs b/src/open_options.rs
new file mode 100644 (file)
index 0000000..557fa7a
--- /dev/null
@@ -0,0 +1,134 @@
+use std::{fs, io, path::PathBuf};\r
+#[derive(Clone, Debug)]\r
+/// Wrapper around [`std::fs::OptionOptions`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html)\r
+pub struct OpenOptions(fs::OpenOptions);\r
+\r
+impl OpenOptions {\r
+    /// Wrapper for [`std::fs::OpenOptions::new`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.new)\r
+    #[allow(clippy::new_without_default)]\r
+    pub fn new() -> Self {\r
+        OpenOptions(fs::OpenOptions::new())\r
+    }\r
+\r
+    /// Wrapper for [`std::fs::OpenOptions::read`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.read)\r
+    pub fn read(&mut self, read: bool) -> &mut Self {\r
+        self.0.read(read);\r
+        self\r
+    }\r
+\r
+    /// Wrapper for [`std::fs::OpenOptions::write`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.write)\r
+    pub fn write(&mut self, write: bool) -> &mut Self {\r
+        self.0.write(write);\r
+        self\r
+    }\r
+\r
+    /// Wrapper for [`std::fs::OpenOptions::append`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.append)\r
+    pub fn append(&mut self, append: bool) -> &mut Self {\r
+        self.0.append(append);\r
+        self\r
+    }\r
+\r
+    /// Wrapper for [`std::fs::OpenOptions::truncate`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.truncate)\r
+    pub fn truncate(&mut self, truncate: bool) -> &mut Self {\r
+        self.0.truncate(truncate);\r
+        self\r
+    }\r
+\r
+    /// Wrapper for [`std::fs::OpenOptions::create`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.create)\r
+    pub fn create(&mut self, create: bool) -> &mut Self {\r
+        self.0.create(create);\r
+        self\r
+    }\r
+\r
+    /// Wrapper for [`std::fs::OpenOptions::create_new`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.create_new)\r
+    pub fn create_new(&mut self, create_new: bool) -> &mut Self {\r
+        self.0.create_new(create_new);\r
+        self\r
+    }\r
+\r
+    /// Wrapper for [`std::fs::OpenOptions::open`](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.open)\r
+    pub fn open<P>(&self, path: P) -> io::Result<crate::File>\r
+    where\r
+        P: Into<PathBuf>,\r
+    {\r
+        // We have to either duplicate the logic or call the deprecated method here.\r
+        // We can't let the deprecated function call this method, because we can't construct\r
+        // `&fs_err::OpenOptions` from `&fs::OpenOptions` without cloning\r
+        // (although cloning would probably be cheap).\r
+        #[allow(deprecated)]\r
+        crate::File::from_options(path.into(), self.options())\r
+    }\r
+}\r
+\r
+/// Methods added by fs-err that are not available on\r
+/// [`std::fs::OpenOptions`](https://doc.rust-lang.org/stable/std/fs/struct.OpenOptions.html).\r
+impl OpenOptions {\r
+    /// Constructs `Self` from [`std::fs::OpenOptions`](https://doc.rust-lang.org/stable/std/fs/struct.OpenOptions.html)\r
+    pub fn from_options(options: fs::OpenOptions) -> Self {\r
+        Self(options)\r
+    }\r
+\r
+    /// Returns a reference to the underlying [`std::fs::OpenOptions`](https://doc.rust-lang.org/stable/std/fs/struct.OpenOptions.html).\r
+    ///\r
+    /// Note that calling `open()` on this reference will NOT give you the improved errors from fs-err.\r
+    pub fn options(&self) -> &fs::OpenOptions {\r
+        &self.0\r
+    }\r
+\r
+    /// Returns a mutable reference to the underlying [`std::fs::OpenOptions`](https://doc.rust-lang.org/stable/std/fs/struct.OpenOptions.html).\r
+    ///\r
+    /// This allows you to change settings that don't yet have wrappers in fs-err.\r
+    /// Note that calling `open()` on this reference will NOT give you the improved errors from fs-err.\r
+    pub fn options_mut(&mut self) -> &mut fs::OpenOptions {\r
+        &mut self.0\r
+    }\r
+}\r
+\r
+#[cfg(unix)]\r
+mod unix {\r
+    use crate::os::unix::fs::OpenOptionsExt;\r
+    use std::os::unix::fs::OpenOptionsExt as _;\r
+    impl OpenOptionsExt for crate::OpenOptions {\r
+        fn mode(&mut self, mode: u32) -> &mut Self {\r
+            self.options_mut().mode(mode);\r
+            self\r
+        }\r
+\r
+        fn custom_flags(&mut self, flags: i32) -> &mut Self {\r
+            self.options_mut().custom_flags(flags);\r
+            self\r
+        }\r
+    }\r
+}\r
+\r
+#[cfg(windows)]\r
+mod windows {\r
+    use crate::os::windows::fs::OpenOptionsExt;\r
+    use std::os::windows::fs::OpenOptionsExt as _;\r
+\r
+    impl OpenOptionsExt for crate::OpenOptions {\r
+        fn access_mode(&mut self, access: u32) -> &mut Self {\r
+            self.options_mut().access_mode(access);\r
+            self\r
+        }\r
+\r
+        fn share_mode(&mut self, val: u32) -> &mut Self {\r
+            self.options_mut().share_mode(val);\r
+            self\r
+        }\r
+        fn custom_flags(&mut self, flags: u32) -> &mut Self {\r
+            self.options_mut().custom_flags(flags);\r
+            self\r
+        }\r
+\r
+        fn attributes(&mut self, val: u32) -> &mut Self {\r
+            self.options_mut().attributes(val);\r
+            self\r
+        }\r
+\r
+        fn security_qos_flags(&mut self, flags: u32) -> &mut Self {\r
+            self.options_mut().security_qos_flags(flags);\r
+            self\r
+        }\r
+    }\r
+}\r
diff --git a/src/os.rs b/src/os.rs
new file mode 100644 (file)
index 0000000..b801e60
--- /dev/null
+++ b/src/os.rs
@@ -0,0 +1,11 @@
+//! OS-specific functionality.\r
+\r
+// The std-library has a couple more platforms than just `unix` for which these apis\r
+// are defined, but we're using just `unix` here. We can always expand later.\r
+#[cfg(unix)]\r
+/// Platform-specific extensions for Unix platforms.\r
+pub mod unix;\r
+\r
+#[cfg(windows)]\r
+/// Platform-specific extensions for Windows.\r
+pub mod windows;\r
diff --git a/src/os/unix.rs b/src/os/unix.rs
new file mode 100644 (file)
index 0000000..ad7c488
--- /dev/null
@@ -0,0 +1,38 @@
+/// Unix-specific extensions to wrappers in `fs_err` for `std::fs` types.\r
+pub mod fs {\r
+    use std::io;\r
+    use std::path::Path;\r
+\r
+    use crate::SourceDestError;\r
+    use crate::SourceDestErrorKind;\r
+\r
+    /// Wrapper for [`std::os::unix::fs::symlink`](https://doc.rust-lang.org/std/os/unix/fs/fn.symlink.html)\r
+    pub fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Result<()> {\r
+        let src = src.as_ref();\r
+        let dst = dst.as_ref();\r
+        std::os::unix::fs::symlink(src, dst)\r
+            .map_err(|err| SourceDestError::build(err, SourceDestErrorKind::Symlink, src, dst))\r
+    }\r
+\r
+    /// Wrapper for [`std::os::unix::fs::FileExt`](https://doc.rust-lang.org/std/os/unix/fs/trait.FileExt.html).\r
+    ///\r
+    /// The std traits might be extended in the future (See issue [#49961](https://github.com/rust-lang/rust/issues/49961#issuecomment-382751777)).\r
+    /// This trait is sealed and can not be implemented by other crates.\r
+    pub trait FileExt: crate::Sealed {\r
+        /// Wrapper for [`FileExt::read_at`](https://doc.rust-lang.org/std/os/unix/fs/trait.FileExt.html#tymethod.read_at)\r
+        fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize>;\r
+        /// Wrapper for [`FileExt::write_at`](https://doc.rust-lang.org/std/os/unix/fs/trait.FileExt.html#tymethod.write_at)\r
+        fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize>;\r
+    }\r
+\r
+    /// Wrapper for [`std::os::unix::fs::OpenOptionsExt`](https://doc.rust-lang.org/std/os/unix/fs/trait.OpenOptionsExt.html)\r
+    ///\r
+    /// The std traits might be extended in the future (See issue [#49961](https://github.com/rust-lang/rust/issues/49961#issuecomment-382751777)).\r
+    /// This trait is sealed and can not be implemented by other crates.\r
+    pub trait OpenOptionsExt: crate::Sealed {\r
+        /// Wrapper for [`OpenOptionsExt::mode`](https://doc.rust-lang.org/std/os/unix/fs/trait.OpenOptionsExt.html#tymethod.mode)\r
+        fn mode(&mut self, mode: u32) -> &mut Self;\r
+        /// Wrapper for [`OpenOptionsExt::custom_flags`](https://doc.rust-lang.org/std/os/unix/fs/trait.OpenOptionsExt.html#tymethod.custom_flags)\r
+        fn custom_flags(&mut self, flags: i32) -> &mut Self;\r
+    }\r
+}\r
diff --git a/src/os/windows.rs b/src/os/windows.rs
new file mode 100644 (file)
index 0000000..f559856
--- /dev/null
@@ -0,0 +1,49 @@
+/// Windows-specific extensions to wrappers in `fs_err` for `std::fs` types.\r
+pub mod fs {\r
+    use crate::{SourceDestError, SourceDestErrorKind};\r
+    use std::io;\r
+    use std::path::Path;\r
+    /// Wrapper for [std::os::windows::fs::symlink_dir](https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_dir.html)\r
+    pub fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Result<()> {\r
+        let src = src.as_ref();\r
+        let dst = dst.as_ref();\r
+        std::os::windows::fs::symlink_dir(src, dst)\r
+            .map_err(|err| SourceDestError::build(err, SourceDestErrorKind::SymlinkDir, src, dst))\r
+    }\r
+\r
+    /// Wrapper for [std::os::windows::fs::symlink_file](https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_file.html)\r
+    pub fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> io::Result<()> {\r
+        let src = src.as_ref();\r
+        let dst = dst.as_ref();\r
+        std::os::windows::fs::symlink_file(src, dst)\r
+            .map_err(|err| SourceDestError::build(err, SourceDestErrorKind::SymlinkFile, src, dst))\r
+    }\r
+\r
+    /// Wrapper for [`std::os::windows::fs::FileExt`](https://doc.rust-lang.org/std/os/windows/fs/trait.FileExt.html).\r
+    ///\r
+    /// The std traits might be extended in the future (See issue [#49961](https://github.com/rust-lang/rust/issues/49961#issuecomment-382751777)).\r
+    /// This trait is sealed and can not be implemented by other crates.\r
+    pub trait FileExt: crate::Sealed {\r
+        /// Wrapper for [`FileExt::seek_read`](https://doc.rust-lang.org/std/os/windows/fs/trait.FileExt.html#tymethod.seek_read)\r
+        fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result<usize>;\r
+        /// Wrapper for [`FileExt::seek_wriite`](https://doc.rust-lang.org/std/os/windows/fs/trait.FileExt.html#tymethod.seek_write)\r
+        fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result<usize>;\r
+    }\r
+\r
+    /// Wrapper for [`std::os::windows::fs::OpenOptionsExt`](https://doc.rust-lang.org/std/os/windows/fs/trait.OpenOptionsExt.html)\r
+    ///\r
+    /// The std traits might be extended in the future (See issue [#49961](https://github.com/rust-lang/rust/issues/49961#issuecomment-382751777)).\r
+    /// This trait is sealed and can not be implemented by other crates.\r
+    pub trait OpenOptionsExt: crate::Sealed {\r
+        /// Wrapper for [`OpenOptionsExt::access_mode`](https://doc.rust-lang.org/std/os/windows/fs/trait.OpenOptionsExt.html#tymethod.access_mode)\r
+        fn access_mode(&mut self, access: u32) -> &mut Self;\r
+        /// Wrapper for [`OpenOptionsExt::share_mode`](https://doc.rust-lang.org/std/os/windows/fs/trait.OpenOptionsExt.html#tymethod.share_mode)\r
+        fn share_mode(&mut self, val: u32) -> &mut Self;\r
+        /// Wrapper for [`OpenOptionsExt::custom_flags`](https://doc.rust-lang.org/std/os/windows/fs/trait.OpenOptionsExt.html#tymethod.custom_flags)\r
+        fn custom_flags(&mut self, flags: u32) -> &mut Self;\r
+        /// Wrapper for [`OpenOptionsExt::attributes`](https://doc.rust-lang.org/std/os/windows/fs/trait.OpenOptionsExt.html#tymethod.attributes)\r
+        fn attributes(&mut self, val: u32) -> &mut Self;\r
+        /// Wrapper for [`OpenOptionsExt::security_qos_flags`](https://doc.rust-lang.org/std/os/windows/fs/trait.OpenOptionsExt.html#tymethod.security_qos_flags)\r
+        fn security_qos_flags(&mut self, flags: u32) -> &mut Self;\r
+    }\r
+}\r
diff --git a/src/path.rs b/src/path.rs
new file mode 100644 (file)
index 0000000..28549a3
--- /dev/null
@@ -0,0 +1,43 @@
+use std::fs;\r
+use std::io;\r
+use std::path::{Path, PathBuf};\r
+\r
+/// Defines aliases on [`Path`](https://doc.rust-lang.org/std/path/struct.Path.html) for `fs_err` functions.\r
+///\r
+/// This trait is sealed and can not be implemented by other crates.\r
+//\r
+// Because no one else can implement it, we can add methods backwards-compatibly.\r
+pub trait PathExt: crate::Sealed {\r
+    /// Wrapper for [`crate::metadata`].\r
+    fn fs_err_metadata(&self) -> io::Result<fs::Metadata>;\r
+    /// Wrapper for [`crate::symlink_metadata`].\r
+    fn fs_err_symlink_metadata(&self) -> io::Result<fs::Metadata>;\r
+    /// Wrapper for [`crate::canonicalize`].\r
+    fn fs_err_canonicalize(&self) -> io::Result<PathBuf>;\r
+    /// Wrapper for [`crate::read_link`].\r
+    fn fs_err_read_link(&self) -> io::Result<PathBuf>;\r
+    /// Wrapper for [`crate::read_dir`].\r
+    fn fs_err_read_dir(&self) -> io::Result<crate::ReadDir>;\r
+}\r
+\r
+impl PathExt for Path {\r
+    fn fs_err_metadata(&self) -> io::Result<fs::Metadata> {\r
+        crate::metadata(self)\r
+    }\r
+\r
+    fn fs_err_symlink_metadata(&self) -> io::Result<fs::Metadata> {\r
+        crate::symlink_metadata(self)\r
+    }\r
+\r
+    fn fs_err_canonicalize(&self) -> io::Result<PathBuf> {\r
+        crate::canonicalize(self)\r
+    }\r
+\r
+    fn fs_err_read_link(&self) -> io::Result<PathBuf> {\r
+        crate::read_link(self)\r
+    }\r
+\r
+    fn fs_err_read_dir(&self) -> io::Result<crate::ReadDir> {\r
+        crate::read_dir(self)\r
+    }\r
+}\r
diff --git a/src/tokio/dir_builder.rs b/src/tokio/dir_builder.rs
new file mode 100644 (file)
index 0000000..0615999
--- /dev/null
@@ -0,0 +1,54 @@
+use crate::errors::{Error, ErrorKind};\r
+use std::io;\r
+use std::path::Path;\r
+\r
+/// A builder for creating directories in various manners.\r
+///\r
+/// This is a wrapper around [`tokio::fs::DirBuilder`].\r
+#[derive(Debug, Default)]\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub struct DirBuilder {\r
+    inner: tokio::fs::DirBuilder,\r
+}\r
+\r
+impl DirBuilder {\r
+    /// Creates a new set of options with default mode/security settings for all\r
+    /// platforms and also non-recursive.\r
+    ///\r
+    /// This is a wrapper version of [`tokio::fs::DirBuilder::new`]\r
+    ///\r
+    /// # Examples\r
+    ///\r
+    /// ```no_run\r
+    /// use fs_err::tokio::DirBuilder;\r
+    ///\r
+    /// let builder = DirBuilder::new();\r
+    /// ```\r
+    pub fn new() -> Self {\r
+        Default::default()\r
+    }\r
+\r
+    /// Wrapper around [`tokio::fs::DirBuilder::recursive`].\r
+    pub fn recursive(&mut self, recursive: bool) -> &mut Self {\r
+        self.inner.recursive(recursive);\r
+        self\r
+    }\r
+\r
+    /// Wrapper around [`tokio::fs::DirBuilder::create`].\r
+    pub async fn create(&self, path: impl AsRef<Path>) -> io::Result<()> {\r
+        let path = path.as_ref();\r
+        self.inner\r
+            .create(path)\r
+            .await\r
+            .map_err(|err| Error::build(err, ErrorKind::CreateDir, path))\r
+    }\r
+}\r
+\r
+#[cfg(unix)]\r
+impl DirBuilder {\r
+    /// Wrapper around [`tokio::fs::DirBuilder::mode`].\r
+    pub fn mode(&mut self, mode: u32) -> &mut Self {\r
+        self.inner.mode(mode);\r
+        self\r
+    }\r
+}\r
diff --git a/src/tokio/file.rs b/src/tokio/file.rs
new file mode 100644 (file)
index 0000000..c0dac4c
--- /dev/null
@@ -0,0 +1,243 @@
+use crate::errors::{Error, ErrorKind};\r
+use std::fs::{Metadata, Permissions};\r
+use std::io;\r
+use std::io::{IoSlice, SeekFrom};\r
+use std::path::{Path, PathBuf};\r
+use std::pin::Pin;\r
+use std::task::{ready, Context, Poll};\r
+use tokio::fs;\r
+use tokio::fs::File as TokioFile;\r
+use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};\r
+\r
+/// Wrapper around [`tokio::fs::File`] which adds more helpful\r
+/// information to all errors.\r
+#[derive(Debug)]\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub struct File {\r
+    tokio: fs::File,\r
+    path: PathBuf,\r
+}\r
+\r
+impl File {\r
+    /// Wrapper for [`tokio::fs::File::open`].\r
+    pub async fn open(path: impl Into<PathBuf>) -> io::Result<File> {\r
+        let path = path.into();\r
+        let f = TokioFile::open(&path)\r
+            .await\r
+            .map_err(|err| Error::build(err, ErrorKind::OpenFile, &path))?;\r
+        Ok(File::from_parts(f, path))\r
+    }\r
+\r
+    /// Wrapper for [`tokio::fs::File::create`].\r
+    pub async fn create(path: impl Into<PathBuf>) -> io::Result<File> {\r
+        let path = path.into();\r
+        match TokioFile::create(&path).await {\r
+            Ok(f) => Ok(File::from_parts(f, path)),\r
+            Err(err) => Err(Error::build(err, ErrorKind::CreateFile, &path)),\r
+        }\r
+    }\r
+\r
+    /// Wrapper for [`tokio::fs::File::from_std`].\r
+    pub fn from_std(std: crate::File) -> File {\r
+        let (std, path) = std.into_parts();\r
+        File::from_parts(TokioFile::from_std(std), path)\r
+    }\r
+\r
+    /// Wrapper for [`tokio::fs::File::sync_all`].\r
+    pub async fn sync_all(&self) -> io::Result<()> {\r
+        self.tokio\r
+            .sync_all()\r
+            .await\r
+            .map_err(|err| self.error(err, ErrorKind::SyncFile))\r
+    }\r
+\r
+    /// Wrapper for [`tokio::fs::File::sync_data`].\r
+    pub async fn sync_data(&self) -> io::Result<()> {\r
+        self.tokio\r
+            .sync_data()\r
+            .await\r
+            .map_err(|err| self.error(err, ErrorKind::SyncFile))\r
+    }\r
+\r
+    /// Wrapper for [`tokio::fs::File::set_len`].\r
+    pub async fn set_len(&self, size: u64) -> io::Result<()> {\r
+        self.tokio\r
+            .set_len(size)\r
+            .await\r
+            .map_err(|err| self.error(err, ErrorKind::SetLen))\r
+    }\r
+\r
+    /// Wrapper for [`tokio::fs::File::metadata`].\r
+    pub async fn metadata(&self) -> io::Result<Metadata> {\r
+        self.tokio\r
+            .metadata()\r
+            .await\r
+            .map_err(|err| self.error(err, ErrorKind::Metadata))\r
+    }\r
+\r
+    /// Wrapper for [`tokio::fs::File::try_clone`].\r
+    pub async fn try_clone(&self) -> io::Result<File> {\r
+        match self.tokio.try_clone().await {\r
+            Ok(file) => Ok(File::from_parts(file, self.path.clone())),\r
+            Err(err) => Err(self.error(err, ErrorKind::Clone)),\r
+        }\r
+    }\r
+\r
+    /// Wrapper for [`tokio::fs::File::into_std`].\r
+    pub async fn into_std(self) -> crate::File {\r
+        crate::File::from_parts(self.tokio.into_std().await, self.path)\r
+    }\r
+\r
+    /// Wrapper for [`tokio::fs::File::try_into_std`].\r
+    pub fn try_into_std(self) -> Result<crate::File, File> {\r
+        match self.tokio.try_into_std() {\r
+            Ok(f) => Ok(crate::File::from_parts(f, self.path)),\r
+            Err(f) => Err(File::from_parts(f, self.path)),\r
+        }\r
+    }\r
+\r
+    /// Wrapper for [`tokio::fs::File::set_permissions`].\r
+    pub async fn set_permissions(&self, perm: Permissions) -> io::Result<()> {\r
+        self.tokio\r
+            .set_permissions(perm)\r
+            .await\r
+            .map_err(|err| self.error(err, ErrorKind::SetPermissions))\r
+    }\r
+}\r
+\r
+/// Methods added by fs-err that are not available on\r
+/// [`tokio::fs::File`].\r
+impl File {\r
+    /// Creates a [`File`](struct.File.html) from a raw file and its path.\r
+    pub fn from_parts<P>(file: TokioFile, path: P) -> Self\r
+    where\r
+        P: Into<PathBuf>,\r
+    {\r
+        File {\r
+            tokio: file,\r
+            path: path.into(),\r
+        }\r
+    }\r
+\r
+    /// Extract the raw file and its path from this [`File`](struct.File.html).\r
+    pub fn into_parts(self) -> (TokioFile, PathBuf) {\r
+        (self.tokio, self.path)\r
+    }\r
+\r
+    /// Returns a reference to the underlying [`tokio::fs::File`].\r
+    pub fn file(&self) -> &TokioFile {\r
+        &self.tokio\r
+    }\r
+\r
+    /// Returns a mutable reference to the underlying [`tokio::fs::File`].\r
+    pub fn file_mut(&mut self) -> &mut TokioFile {\r
+        &mut self.tokio\r
+    }\r
+\r
+    /// Returns a reference to the path that this file was created with.\r
+    pub fn path(&self) -> &Path {\r
+        &self.path\r
+    }\r
+\r
+    /// Wrap the error in information specific to this `File` object.\r
+    fn error(&self, source: io::Error, kind: ErrorKind) -> io::Error {\r
+        Error::build(source, kind, &self.path)\r
+    }\r
+}\r
+\r
+impl From<crate::File> for File {\r
+    fn from(f: crate::File) -> Self {\r
+        let (f, path) = f.into_parts();\r
+        File::from_parts(f.into(), path)\r
+    }\r
+}\r
+\r
+impl From<File> for TokioFile {\r
+    fn from(f: File) -> Self {\r
+        f.into_parts().0\r
+    }\r
+}\r
+\r
+#[cfg(unix)]\r
+impl std::os::unix::io::AsRawFd for File {\r
+    fn as_raw_fd(&self) -> std::os::unix::io::RawFd {\r
+        self.tokio.as_raw_fd()\r
+    }\r
+}\r
+\r
+#[cfg(windows)]\r
+impl std::os::windows::io::AsRawHandle for File {\r
+    fn as_raw_handle(&self) -> std::os::windows::io::RawHandle {\r
+        self.tokio.as_raw_handle()\r
+    }\r
+}\r
+\r
+impl AsyncRead for File {\r
+    fn poll_read(\r
+        mut self: Pin<&mut Self>,\r
+        cx: &mut Context<'_>,\r
+        buf: &mut ReadBuf<'_>,\r
+    ) -> Poll<io::Result<()>> {\r
+        Poll::Ready(\r
+            ready!(Pin::new(&mut self.tokio).poll_read(cx, buf))\r
+                .map_err(|err| self.error(err, ErrorKind::Read)),\r
+        )\r
+    }\r
+}\r
+\r
+impl AsyncSeek for File {\r
+    fn start_seek(mut self: Pin<&mut Self>, position: SeekFrom) -> io::Result<()> {\r
+        Pin::new(&mut self.tokio)\r
+            .start_seek(position)\r
+            .map_err(|err| self.error(err, ErrorKind::Seek))\r
+    }\r
+\r
+    fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {\r
+        Poll::Ready(\r
+            ready!(Pin::new(&mut self.tokio).poll_complete(cx))\r
+                .map_err(|err| self.error(err, ErrorKind::Seek)),\r
+        )\r
+    }\r
+}\r
+\r
+impl AsyncWrite for File {\r
+    fn poll_write(\r
+        mut self: Pin<&mut Self>,\r
+        cx: &mut Context<'_>,\r
+        buf: &[u8],\r
+    ) -> Poll<io::Result<usize>> {\r
+        Poll::Ready(\r
+            ready!(Pin::new(&mut self.tokio).poll_write(cx, buf))\r
+                .map_err(|err| self.error(err, ErrorKind::Write)),\r
+        )\r
+    }\r
+\r
+    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {\r
+        Poll::Ready(\r
+            ready!(Pin::new(&mut self.tokio).poll_flush(cx))\r
+                .map_err(|err| self.error(err, ErrorKind::Flush)),\r
+        )\r
+    }\r
+\r
+    fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {\r
+        Poll::Ready(\r
+            ready!(Pin::new(&mut self.tokio).poll_shutdown(cx))\r
+                .map_err(|err| self.error(err, ErrorKind::Flush)),\r
+        )\r
+    }\r
+\r
+    fn poll_write_vectored(\r
+        mut self: Pin<&mut Self>,\r
+        cx: &mut Context<'_>,\r
+        bufs: &[IoSlice<'_>],\r
+    ) -> Poll<io::Result<usize>> {\r
+        Poll::Ready(\r
+            ready!(Pin::new(&mut self.tokio).poll_write_vectored(cx, bufs))\r
+                .map_err(|err| self.error(err, ErrorKind::Write)),\r
+        )\r
+    }\r
+\r
+    fn is_write_vectored(&self) -> bool {\r
+        self.tokio.is_write_vectored()\r
+    }\r
+}\r
diff --git a/src/tokio/mod.rs b/src/tokio/mod.rs
new file mode 100644 (file)
index 0000000..bf4ac94
--- /dev/null
@@ -0,0 +1,189 @@
+//! Tokio-specific wrappers that use `fs_err` error messages.\r
+\r
+use crate::errors::{Error, ErrorKind, SourceDestError, SourceDestErrorKind};\r
+use std::fs::{Metadata, Permissions};\r
+use std::path::{Path, PathBuf};\r
+use tokio::io;\r
+mod dir_builder;\r
+mod file;\r
+mod open_options;\r
+mod read_dir;\r
+\r
+pub use self::open_options::OpenOptions;\r
+pub use self::read_dir::{read_dir, DirEntry, ReadDir};\r
+pub use dir_builder::DirBuilder;\r
+pub use file::File;\r
+\r
+/// Wrapper for [`tokio::fs::canonicalize`].\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub async fn canonicalize(path: impl AsRef<Path>) -> io::Result<PathBuf> {\r
+    let path = path.as_ref();\r
+    tokio::fs::canonicalize(path)\r
+        .await\r
+        .map_err(|err| Error::build(err, ErrorKind::Canonicalize, path))\r
+}\r
+\r
+/// Wrapper for [`tokio::fs::copy`].\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub async fn copy(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<u64, io::Error> {\r
+    let (from, to) = (from.as_ref(), to.as_ref());\r
+    tokio::fs::copy(from, to)\r
+        .await\r
+        .map_err(|err| SourceDestError::build(err, SourceDestErrorKind::Copy, from, to))\r
+}\r
+\r
+/// Wrapper for [`tokio::fs::create_dir`].\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub async fn create_dir(path: impl AsRef<Path>) -> io::Result<()> {\r
+    let path = path.as_ref();\r
+    tokio::fs::create_dir(path)\r
+        .await\r
+        .map_err(|err| Error::build(err, ErrorKind::CreateDir, path))\r
+}\r
+\r
+/// Wrapper for [`tokio::fs::create_dir_all`].\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub async fn create_dir_all(path: impl AsRef<Path>) -> io::Result<()> {\r
+    let path = path.as_ref();\r
+    tokio::fs::create_dir_all(path)\r
+        .await\r
+        .map_err(|err| Error::build(err, ErrorKind::CreateDir, path))\r
+}\r
+\r
+/// Wrapper for [`tokio::fs::hard_link`].\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub async fn hard_link(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {\r
+    let (src, dst) = (src.as_ref(), dst.as_ref());\r
+    tokio::fs::hard_link(src, dst)\r
+        .await\r
+        .map_err(|err| SourceDestError::build(err, SourceDestErrorKind::HardLink, src, dst))\r
+}\r
+\r
+/// Wrapper for [`tokio::fs::metadata`].\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub async fn metadata(path: impl AsRef<Path>) -> io::Result<Metadata> {\r
+    let path = path.as_ref();\r
+    tokio::fs::metadata(path)\r
+        .await\r
+        .map_err(|err| Error::build(err, ErrorKind::Metadata, path))\r
+}\r
+\r
+/// Wrapper for [`tokio::fs::read`].\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub async fn read(path: impl AsRef<Path>) -> io::Result<Vec<u8>> {\r
+    let path = path.as_ref();\r
+    tokio::fs::read(path)\r
+        .await\r
+        .map_err(|err| Error::build(err, ErrorKind::Read, path))\r
+}\r
+\r
+/// Wrapper for [`tokio::fs::read_link`].\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub async fn read_link(path: impl AsRef<Path>) -> io::Result<PathBuf> {\r
+    let path = path.as_ref();\r
+    tokio::fs::read_link(path)\r
+        .await\r
+        .map_err(|err| Error::build(err, ErrorKind::ReadLink, path))\r
+}\r
+\r
+/// Wrapper for [`tokio::fs::read_to_string`].\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub async fn read_to_string(path: impl AsRef<Path>) -> io::Result<String> {\r
+    let path = path.as_ref();\r
+    tokio::fs::read_to_string(path)\r
+        .await\r
+        .map_err(|err| Error::build(err, ErrorKind::Read, path))\r
+}\r
+\r
+/// Wrapper for [`tokio::fs::remove_dir`].\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub async fn remove_dir(path: impl AsRef<Path>) -> io::Result<()> {\r
+    let path = path.as_ref();\r
+    tokio::fs::remove_dir(path)\r
+        .await\r
+        .map_err(|err| Error::build(err, ErrorKind::RemoveDir, path))\r
+}\r
+\r
+/// Wrapper for [`tokio::fs::remove_dir_all`].\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub async fn remove_dir_all(path: impl AsRef<Path>) -> io::Result<()> {\r
+    let path = path.as_ref();\r
+    tokio::fs::remove_dir_all(path)\r
+        .await\r
+        .map_err(|err| Error::build(err, ErrorKind::RemoveDir, path))\r
+}\r
+\r
+/// Wrapper for [`tokio::fs::remove_file`].\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub async fn remove_file(path: impl AsRef<Path>) -> io::Result<()> {\r
+    let path = path.as_ref();\r
+    tokio::fs::remove_file(path)\r
+        .await\r
+        .map_err(|err| Error::build(err, ErrorKind::RemoveFile, path))\r
+}\r
+\r
+/// Wrapper for [`tokio::fs::rename`].\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub async fn rename(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {\r
+    let (from, to) = (from.as_ref(), to.as_ref());\r
+    tokio::fs::rename(from, to)\r
+        .await\r
+        .map_err(|err| SourceDestError::build(err, SourceDestErrorKind::Rename, from, to))\r
+}\r
+\r
+/// Wrapper for [`tokio::fs::set_permissions`].\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub async fn set_permissions(path: impl AsRef<Path>, perm: Permissions) -> io::Result<()> {\r
+    let path = path.as_ref();\r
+    tokio::fs::set_permissions(path, perm)\r
+        .await\r
+        .map_err(|err| Error::build(err, ErrorKind::SetPermissions, path))\r
+}\r
+\r
+/// Wrapper for [`tokio::fs::symlink_metadata`].\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub async fn symlink_metadata(path: impl AsRef<Path>) -> io::Result<Metadata> {\r
+    let path = path.as_ref();\r
+    tokio::fs::symlink_metadata(path)\r
+        .await\r
+        .map_err(|err| Error::build(err, ErrorKind::SymlinkMetadata, path))\r
+}\r
+\r
+/// Wrapper for [`tokio::fs::symlink`].\r
+#[cfg(unix)]\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub async fn symlink(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {\r
+    let (src, dst) = (src.as_ref(), dst.as_ref());\r
+    tokio::fs::symlink(src, dst)\r
+        .await\r
+        .map_err(|err| SourceDestError::build(err, SourceDestErrorKind::Symlink, src, dst))\r
+}\r
+\r
+/// Wrapper for [`tokio::fs::symlink_dir`].\r
+#[cfg(windows)]\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub async fn symlink(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {\r
+    let (src, dst) = (src.as_ref(), dst.as_ref());\r
+    tokio::fs::symlink_dir(src, dst)\r
+        .await\r
+        .map_err(|err| SourceDestError::build(err, SourceDestErrorKind::SymlinkDir, src, dst))\r
+}\r
+\r
+/// Wrapper for [`tokio::fs::symlink_file`].\r
+#[cfg(windows)]\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub async fn symlink_file(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {\r
+    let (src, dst) = (src.as_ref(), dst.as_ref());\r
+    tokio::fs::symlink_file(src, dst)\r
+        .await\r
+        .map_err(|err| SourceDestError::build(err, SourceDestErrorKind::SymlinkFile, src, dst))\r
+}\r
+\r
+/// Wrapper for [`tokio::fs::write`].\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub async fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> io::Result<()> {\r
+    let (path, contents) = (path.as_ref(), contents.as_ref());\r
+    tokio::fs::write(path, contents)\r
+        .await\r
+        .map_err(|err| Error::build(err, ErrorKind::Write, path))\r
+}\r
diff --git a/src/tokio/open_options.rs b/src/tokio/open_options.rs
new file mode 100644 (file)
index 0000000..6a2b5bf
--- /dev/null
@@ -0,0 +1,109 @@
+use crate::errors::{Error, ErrorKind};\r
+use crate::tokio::File;\r
+use std::io;\r
+use std::path::Path;\r
+use tokio::fs::OpenOptions as TokioOpenOptions;\r
+\r
+/// Options and flags which can be used to configure how a file is opened.\r
+///\r
+/// This is a wrapper around [`tokio::fs::OpenOptions`].\r
+#[derive(Clone, Debug, Default)]\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub struct OpenOptions {\r
+    tokio: TokioOpenOptions,\r
+}\r
+\r
+impl OpenOptions {\r
+    /// Creates a blank new set of options ready for configuration.\r
+    ///\r
+    /// All options are initially set to `false`.\r
+    ///\r
+    /// This is a wrapped version of [`tokio::fs::OpenOptions::new`]\r
+    ///\r
+    /// # Examples\r
+    ///\r
+    /// ```no_run\r
+    /// use fs_err::tokio::OpenOptions;\r
+    ///\r
+    /// let mut options = OpenOptions::new();\r
+    /// let future = options.read(true).open("foo.txt");\r
+    /// ```\r
+    pub fn new() -> OpenOptions {\r
+        OpenOptions {\r
+            tokio: TokioOpenOptions::new(),\r
+        }\r
+    }\r
+\r
+    /// Wrapper for [`tokio::fs::OpenOptions::read`].\r
+    pub fn read(&mut self, read: bool) -> &mut OpenOptions {\r
+        self.tokio.read(read);\r
+        self\r
+    }\r
+\r
+    /// Wrapper for [`tokio::fs::OpenOptions::write`].\r
+    pub fn write(&mut self, write: bool) -> &mut OpenOptions {\r
+        self.tokio.write(write);\r
+        self\r
+    }\r
+\r
+    /// Wrapper for [`tokio::fs::OpenOptions::append`].\r
+    pub fn append(&mut self, append: bool) -> &mut OpenOptions {\r
+        self.tokio.append(append);\r
+        self\r
+    }\r
+\r
+    /// Wrapper for [`tokio::fs::OpenOptions::truncate`].\r
+    pub fn truncate(&mut self, truncate: bool) -> &mut OpenOptions {\r
+        self.tokio.truncate(truncate);\r
+        self\r
+    }\r
+\r
+    /// Wrapper for [`tokio::fs::OpenOptions::create`].\r
+    pub fn create(&mut self, create: bool) -> &mut OpenOptions {\r
+        self.tokio.create(create);\r
+        self\r
+    }\r
+\r
+    /// Wrapper for [`tokio::fs::OpenOptions::create_new`].\r
+    pub fn create_new(&mut self, create_new: bool) -> &mut OpenOptions {\r
+        self.tokio.create_new(create_new);\r
+        self\r
+    }\r
+\r
+    /// Wrapper for [`tokio::fs::OpenOptions::open`].\r
+    pub async fn open(&self, path: impl AsRef<Path>) -> io::Result<File> {\r
+        let path = path.as_ref();\r
+        self.tokio\r
+            .open(path)\r
+            .await\r
+            .map(|f| File::from_parts(f, path))\r
+            .map_err(|err| Error::build(err, ErrorKind::OpenFile, path))\r
+    }\r
+}\r
+\r
+#[cfg(unix)]\r
+impl OpenOptions {\r
+    /// Wrapper for [`tokio::fs::OpenOptions::mode`].\r
+    pub fn mode(&mut self, mode: u32) -> &mut OpenOptions {\r
+        self.tokio.mode(mode);\r
+        self\r
+    }\r
+\r
+    /// Wrapper for [`tokio::fs::OpenOptions::custom_flags`].\r
+    pub fn custom_flags(&mut self, flags: i32) -> &mut OpenOptions {\r
+        self.tokio.custom_flags(flags);\r
+        self\r
+    }\r
+}\r
+\r
+impl From<std::fs::OpenOptions> for OpenOptions {\r
+    fn from(std: std::fs::OpenOptions) -> Self {\r
+        OpenOptions { tokio: std.into() }\r
+    }\r
+}\r
+\r
+impl From<TokioOpenOptions> for OpenOptions {\r
+    fn from(tokio: TokioOpenOptions) -> Self {\r
+        OpenOptions { tokio }\r
+    }\r
+}\r
diff --git a/src/tokio/read_dir.rs b/src/tokio/read_dir.rs
new file mode 100644 (file)
index 0000000..2594edf
--- /dev/null
@@ -0,0 +1,94 @@
+use crate::errors::{Error, ErrorKind};\r
+use std::ffi::OsString;\r
+use std::fs::{FileType, Metadata};\r
+use std::io;\r
+use std::path::{Path, PathBuf};\r
+use std::task::{ready, Context, Poll};\r
+use tokio::fs;\r
+\r
+/// Wrapper for [`tokio::fs::read_dir`].\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub async fn read_dir(path: impl AsRef<Path>) -> io::Result<ReadDir> {\r
+    let path = path.as_ref();\r
+    let tokio = fs::read_dir(path)\r
+        .await\r
+        .map_err(|err| Error::build(err, ErrorKind::ReadDir, path))?;\r
+    Ok(ReadDir {\r
+        tokio,\r
+        path: path.to_owned(),\r
+    })\r
+}\r
+\r
+/// Reads the entries in a directory.\r
+///\r
+/// This is a wrapper around [`tokio::fs::ReadDir`].\r
+#[derive(Debug)]\r
+#[must_use = "streams do nothing unless polled"]\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub struct ReadDir {\r
+    tokio: fs::ReadDir,\r
+    path: PathBuf,\r
+}\r
+\r
+impl ReadDir {\r
+    /// Wrapper around [`tokio::fs::ReadDir::next_entry`].\r
+    pub async fn next_entry(&mut self) -> io::Result<Option<DirEntry>> {\r
+        match self.tokio.next_entry().await {\r
+            Ok(entry) => Ok(entry.map(|e| DirEntry { tokio: e })),\r
+            Err(err) => Err(Error::build(err, ErrorKind::ReadDir, &self.path)),\r
+        }\r
+    }\r
+\r
+    /// Wrapper around [`tokio::fs::ReadDir::poll_next_entry`].\r
+    pub fn poll_next_entry(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<Option<DirEntry>>> {\r
+        Poll::Ready(match ready!(self.tokio.poll_next_entry(cx)) {\r
+            Ok(entry) => Ok(entry.map(|e| DirEntry { tokio: e })),\r
+            Err(err) => Err(Error::build(err, ErrorKind::ReadDir, &self.path)),\r
+        })\r
+    }\r
+}\r
+\r
+/// Entries returned by the [`ReadDir`] stream.\r
+///\r
+/// This is a wrapper around [`tokio::fs::DirEntry`].\r
+#[derive(Debug)]\r
+#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]\r
+pub struct DirEntry {\r
+    tokio: fs::DirEntry,\r
+}\r
+\r
+impl DirEntry {\r
+    /// Wrapper around [`tokio::fs::DirEntry::path`].\r
+    pub fn path(&self) -> PathBuf {\r
+        self.tokio.path()\r
+    }\r
+\r
+    /// Wrapper around [`tokio::fs::DirEntry::file_name`].\r
+    pub fn file_name(&self) -> OsString {\r
+        self.tokio.file_name()\r
+    }\r
+\r
+    /// Wrapper around [`tokio::fs::DirEntry::metadata`].\r
+    pub async fn metadata(&self) -> io::Result<Metadata> {\r
+        self.tokio\r
+            .metadata()\r
+            .await\r
+            .map_err(|err| Error::build(err, ErrorKind::Metadata, self.path()))\r
+    }\r
+\r
+    /// Wrapper around [`tokio::fs::DirEntry::file_type`].\r
+    pub async fn file_type(&self) -> io::Result<FileType> {\r
+        self.tokio\r
+            .file_type()\r
+            .await\r
+            .map_err(|err| Error::build(err, ErrorKind::Metadata, self.path()))\r
+    }\r
+}\r
+\r
+#[cfg(unix)]\r
+impl DirEntry {\r
+    /// Wrapper around [`tokio::fs::DirEntry::ino`].\r
+    pub fn ino(&self) -> u64 {\r
+        self.tokio.ino()\r
+    }\r
+}\r