cap-primitives-3.4.1/.cargo_vcs_info.json0000644000000001540000000000100137750ustar { "git": { "sha1": "c461c0799f831df5d8ca2e121b7f541d9e8143af" }, "path_in_vcs": "cap-primitives" }cap-primitives-3.4.1/COPYRIGHT000064400000000000000000000015651046102023000140670ustar 00000000000000Short version for non-lawyers: `cap-primitives` is triple-licensed under Apache 2.0 with the LLVM Exception, Apache 2.0, and MIT terms. Longer version: Copyrights in the `cap-primitives` project are retained by their contributors. No copyright assignment is required to contribute to the `cap-primitives` project. Some files include code derived from Rust's `libstd`; see the comments in the code for details. Except as otherwise noted (below and/or in individual files), `cap-primitives` is licensed under: - the Apache License, Version 2.0, with the LLVM Exception or - the Apache License, Version 2.0 or , - or the MIT license or , at your option. cap-primitives-3.4.1/Cargo.toml0000644000000037260000000000100120030ustar # 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 = "2021" name = "cap-primitives" version = "3.4.1" authors = [ "Dan Gohman ", "Jakub Konka ", ] build = "build.rs" autobins = false autoexamples = false autotests = false autobenches = false description = "Capability-based primitives" readme = "README.md" keywords = [ "api", "network", "file", ] categories = [ "filesystem", "network-programming", ] license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" repository = "https://github.com/bytecodealliance/cap-std" [lib] name = "cap_primitives" path = "src/lib.rs" [dependencies.ambient-authority] version = "0.0.2" [dependencies.arbitrary] version = "1.0.0" features = ["derive"] optional = true [dependencies.fs-set-times] version = "0.20.0" [dependencies.io-extras] version = "0.18.3" [dependencies.io-lifetimes] version = "2.0.0" default-features = false [dependencies.ipnet] version = "2.5.0" [dependencies.maybe-owned] version = "0.3.4" [dev-dependencies] [target."cfg(not(windows))".dependencies.rustix] version = "0.38.38" features = [ "fs", "process", "procfs", "termios", "time", ] [target."cfg(windows)".dependencies.windows-sys] version = "0.52.0" features = [ "Win32_Foundation", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Kernel", "Win32_System_WindowsProgramming", "Win32_System_IO", "Wdk_Storage_FileSystem", "Wdk_Foundation", ] [target."cfg(windows)".dependencies.winx] version = "0.36.0" cap-primitives-3.4.1/Cargo.toml.orig000064400000000000000000000023351046102023000154570ustar 00000000000000[package] name = "cap-primitives" version = "3.4.1" description = "Capability-based primitives" authors = [ "Dan Gohman ", "Jakub Konka ", ] license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" keywords = ["api", "network", "file"] categories = ["filesystem", "network-programming"] repository = "https://github.com/bytecodealliance/cap-std" edition = "2021" [dependencies] ambient-authority = "0.0.2" arbitrary = { version = "1.0.0", optional = true, features = ["derive"] } ipnet = "2.5.0" maybe-owned = "0.3.4" fs-set-times = "0.20.0" io-extras = "0.18.3" io-lifetimes = { version = "2.0.0", default-features = false } [dev-dependencies] cap-tempfile = { path = "../cap-tempfile" } [target.'cfg(not(windows))'.dependencies] rustix = { version = "0.38.38", features = ["fs", "process", "procfs", "termios", "time"] } [target.'cfg(windows)'.dependencies] winx = "0.36.0" [target.'cfg(windows)'.dependencies.windows-sys] version = "0.52.0" features = [ "Win32_Foundation", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Kernel", "Win32_System_WindowsProgramming", "Win32_System_IO", "Wdk_Storage_FileSystem", "Wdk_Foundation", ] cap-primitives-3.4.1/LICENSE-APACHE000064400000000000000000000251371046102023000145210ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. cap-primitives-3.4.1/LICENSE-Apache-2.0_WITH_LLVM-exception000064400000000000000000000277231046102023000207420ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --- LLVM Exceptions to the Apache 2.0 License ---- As an exception, if, as a result of your compiling your source code, portions of this Software are embedded into an Object form of such source code, you may redistribute such embedded portions in such Object form without complying with the conditions of Sections 4(a), 4(b) and 4(d) of the License. In addition, if you combine or link compiled forms of this Software with software that is licensed under the GPLv2 ("Combined Software") and if a court of competent jurisdiction determines that the patent provision (Section 3), the indemnity provision (Section 9) or other Section of the License conflicts with the conditions of the GPLv2, you may retroactively and prospectively choose to deem waived or otherwise exclude such Section(s) of the License, but only in their entirety and only with respect to the Combined Software. cap-primitives-3.4.1/LICENSE-MIT000064400000000000000000000017771046102023000142350ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cap-primitives-3.4.1/README.md000064400000000000000000000026411046102023000140470ustar 00000000000000

cap-primitives

Capability-based primitives

Github Actions CI Status crates.io page docs.rs docs

The `cap-primitives` crate provides primitive sandboxing operations that [`cap-std`] and [`cap-async-std`] are built on. The filesystem module [`cap_primitives::fs`], the networking module [`cap_primitives::net`], and time module [`cap_primitives::time`] currently support Linux, macOS, FreeBSD, and Windows. WASI support is in development, though not yet usable. [`cap-std`]: https://github.com/bytecodealliance/cap-std/blob/main/cap-std/README.md [`cap-async-std`]: https://github.com/bytecodealliance/cap-std/blob/main/cap-async-std/README.md [`cap_primitives::fs`]: https://docs.rs/cap-primitives/latest/cap_primitives/fs/index.html [`cap_primitives::net`]: https://docs.rs/cap-primitives/latest/cap_primitives/net/index.html [`cap_primitives::time`]: https://docs.rs/cap-primitives/latest/cap_primitives/time/index.html cap-primitives-3.4.1/build.rs000064400000000000000000000061731046102023000142410ustar 00000000000000use std::env::var; use std::io::Write; fn main() { use_feature_or_nothing("unix_file_vectored_at"); use_feature_or_nothing("windows_by_handle"); // https://github.com/rust-lang/rust/issues/63010 // https://doc.rust-lang.org/unstable-book/library-features/windows-file-type-ext.html use_feature_or_nothing("windows_file_type_ext"); use_feature_or_nothing("io_error_more"); // https://github.com/rust-lang/rust/issues/86442 use_feature_or_nothing("io_error_uncategorized"); // Cfgs that users may set. println!("cargo:rustc-check-cfg=cfg(racy_asserts)"); println!("cargo:rustc-check-cfg=cfg(emulate_second_only_system)"); println!("cargo:rustc-check-cfg=cfg(io_lifetimes_use_std)"); // Don't rerun this on changes other than build.rs, as we only depend on // the rustc version. println!("cargo:rerun-if-changed=build.rs"); } fn use_feature_or_nothing(feature: &str) { if has_feature(feature) { use_feature(feature); } println!("cargo:rustc-check-cfg=cfg({})", feature); } fn use_feature(feature: &str) { println!("cargo:rustc-cfg={}", feature); } /// Test whether the rustc at `var("RUSTC")` supports the given feature. fn has_feature(feature: &str) -> bool { can_compile(&format!( "#![allow(stable_features)]\n#![feature({})]", feature )) } /// Test whether the rustc at `var("RUSTC")` can compile the given code. fn can_compile>(test: T) -> bool { use std::process::Stdio; let out_dir = var("OUT_DIR").unwrap(); let rustc = var("RUSTC").unwrap(); let target = var("TARGET").unwrap(); // Use `RUSTC_WRAPPER` if it's set, unless it's set to an empty string, // as documented [here]. // [here]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-reads let wrapper = var("RUSTC_WRAPPER") .ok() .and_then(|w| if w.is_empty() { None } else { Some(w) }); let mut cmd = if let Some(wrapper) = wrapper { let mut cmd = std::process::Command::new(wrapper); // The wrapper's first argument is supposed to be the path to rustc. cmd.arg(rustc); cmd } else { std::process::Command::new(rustc) }; cmd.arg("--crate-type=rlib") // Don't require `main`. .arg("--emit=metadata") // Do as little as possible but still parse. .arg("--target") .arg(target) .arg("--out-dir") .arg(out_dir); // Put the output somewhere inconsequential. // If Cargo wants to set RUSTFLAGS, use that. if let Ok(rustflags) = var("CARGO_ENCODED_RUSTFLAGS") { if !rustflags.is_empty() { for arg in rustflags.split('\x1f') { cmd.arg(arg); } } } let mut child = cmd .arg("-") // Read from stdin. .stdin(Stdio::piped()) // Stdin is a pipe. .stderr(Stdio::null()) // Errors from feature detection aren't interesting and can be confusing. .spawn() .unwrap(); writeln!(child.stdin.take().unwrap(), "{}", test.as_ref()).unwrap(); child.wait().unwrap().success() } cap-primitives-3.4.1/src/fs/access.rs000064400000000000000000000044671046102023000156060ustar 00000000000000//! Access test functions. use crate::fs::{access_impl, FollowSymlinks}; #[cfg(racy_asserts)] use crate::fs::{access_unchecked, file_path}; use std::path::Path; use std::{fs, io}; /// Access modes for use with [`DirExt::access`]. #[derive(Clone, Copy, Debug)] pub struct AccessModes { /// Is the object readable? pub readable: bool, /// Is the object writable? pub writable: bool, /// Is the object executable? pub executable: bool, } /// Access modes for use with [`DirExt::access`]. #[derive(Clone, Copy, Debug)] pub enum AccessType { /// Test whether the named object is accessible in the given modes. Access(AccessModes), /// Test whether the named object exists. Exists, } /// Canonicalize the given path, ensuring that the resolution of the path never /// escapes the directory tree rooted at `start`. #[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] pub fn access( start: &fs::File, path: &Path, type_: AccessType, follow: FollowSymlinks, ) -> io::Result<()> { // Call the underlying implementation. let result = access_impl(start, path, type_, follow); #[cfg(racy_asserts)] let unchecked = access_unchecked(start, path, type_, follow); #[cfg(racy_asserts)] check_access(start, path, type_, follow, &result, &unchecked); result } #[cfg(racy_asserts)] #[allow(clippy::enum_glob_use)] fn check_access( start: &fs::File, path: &Path, _type_: AccessType, _follow: FollowSymlinks, result: &io::Result<()>, unchecked: &io::Result<()>, ) { use io::ErrorKind::*; match (map_result(result), map_result(stat)) { (Ok(()), Ok(())) => {} (Err((PermissionDenied, message)), _) => { // TODO: Check that access in the no-follow case got the right // error. } (Err((kind, message)), Err((unchecked_kind, unchecked_message))) => { assert_eq!(kind, unchecked_kind); assert_eq!( message, unchecked_message, "start='{:?}', path='{:?}'", start, path.display() ); } other => panic!( "unexpected result from access start='{:?}', path='{}':\n{:#?}", start, path.display(), other, ), } } cap-primitives-3.4.1/src/fs/assert_same_file.rs000064400000000000000000000070001046102023000176340ustar 00000000000000/// Assert that two arguments refer to the same underlying file. macro_rules! assert_same_file { ($left:expr, $right:expr) => ({ let (left, right) = (&($left), &($right)); if crate::rustix::fs::is_different_file(left, right).unwrap() { panic!("assertion failed: left is a different file from right\n left: `{:?}`,\n right: `{:?}`", left, right); } }); ($left:expr, $right:expr, ) => ({ assert_le!($left, $right); }); ($left:expr, $right:expr, $($msg_args:tt)+) => ({ let (left, right) = (&($left), &($right)); if crate::rustix::fs::is_different_file(left, right).unwrap() { panic!("assertion failed: left is a different file from right\n left: `{:?}`,\n right: `{:?}`: {}", left, right, format_args!($($msg_args)+)); } }) } /// Assert that two arguments are metadata for the same underlying file. macro_rules! assert_same_file_metadata { ($left:expr, $right:expr) => ({ let (left, right) = (&($left), &($right)); if crate::rustix::fs::is_different_file_metadata(left, right).unwrap() { panic!("assertion failed: left is a different file from right\n left: `{:?}`,\n right: `{:?}`", left, right); } }); ($left:expr, $right:expr, ) => ({ assert_le!($left, $right); }); ($left:expr, $right:expr, $($msg_args:tt)+) => ({ let (left, right) = (&($left), &($right)); if crate::rustix::fs::is_different_file_metadata(left, right).unwrap() { panic!("assertion failed: left is a different file from right\n left: `{:?}`,\n right: `{:?}`: {}", left, right, format_args!($($msg_args)+)); } }) } /// Assert that two arguments refer to different underlying files. #[allow(unused_macros)] macro_rules! assert_different_file { ($left:expr, $right:expr) => ({ let (left, right) = (&($left), &($right)); if !crate::rustix::fs::is_different_file(left, right).unwrap() { panic!("assertion failed: left is the same file as right\n left: `{:?}`,\n right: `{:?}`", left, right); } }); ($left:expr, $right:expr, ) => ({ assert_le!($left, $right); }); ($left:expr, $right:expr, $($msg_args:tt)+) => ({ let (left, right) = (&($left), &($right)); if !crate::rustix::fs::is_different_file(left, right).unwrap() { panic!("assertion failed: left is the same file as right\n left: `{:?}`,\n right: `{:?}`: {}", left, right, format_args!($($msg_args)+)); } }) } /// Assert that two arguments are metadata for the same underlying file. #[allow(unused_macros)] macro_rules! assert_different_metadata { ($left:expr, $right:expr) => ({ let (left, right) = (&($left), &($right)); if !crate::rustix::fs::is_different_file_metadata(left, right).unwrap() { panic!("assertion failed: left is the same file as right\n left: `{:?}`,\n right: `{:?}`", left, right); } }); ($left:expr, $right:expr, ) => ({ assert_le!($left, $right); }); ($left:expr, $right:expr, $($msg_args:tt)+) => ({ let (left, right) = (&($left), &($right)); if !crate::rustix::fs::is_different_file_metadata(left, right).unwrap() { panic!("assertion failed: left is the same file as right\n left: `{:?}`,\n right: `{:?}`: {}", left, right, format_args!($($msg_args)+)); } }) } cap-primitives-3.4.1/src/fs/canonicalize.rs000064400000000000000000000057511046102023000170010ustar 00000000000000//! Sandboxed path canonicalization. use crate::fs::canonicalize_impl; #[cfg(racy_asserts)] use crate::fs::{file_path, open, OpenOptions}; use std::path::{Path, PathBuf}; use std::{fs, io}; /// Canonicalize the given path, ensuring that the resolution of the path never /// escapes the directory tree rooted at `start`. #[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] #[inline] pub fn canonicalize(start: &fs::File, path: &Path) -> io::Result { // Call the underlying implementation. let result = canonicalize_impl(start, path); #[cfg(racy_asserts)] check_canonicalize(start, path, &result); result } #[cfg(racy_asserts)] fn check_canonicalize(start: &fs::File, path: &Path, result: &io::Result) { if let Ok(canonical_path) = result { let path_result = open(start, path, OpenOptions::new().read(true)); let canonical_result = open(start, canonical_path, OpenOptions::new().read(true)); match (path_result, canonical_result) { (Ok(path_file), Ok(canonical_file)) => assert_same_file!( &path_file, &canonical_file, "we should be able to stat paths that we just canonicalized" ), (Err(path_err), Err(canonical_err)) => { assert_eq!(path_err.to_string(), canonical_err.to_string()) } other => { // TODO: Checking in the case it does end with ".". if !path.to_string_lossy().ends_with("/.") { panic!("inconsistent canonicalize checks: {:?}", other); } } } // On operating systems which can tell us the path of a file descriptor, // assert that the path we computed canonicalizes to the same thing as // the input canonicalizes too. if let Some(start_abspath) = file_path(start) { let check_abspath = start_abspath.join(path); let result_abspath = start_abspath.join(canonical_path); if let Ok(check_abspath) = fs::canonicalize(check_abspath) { let result_abspath = fs::canonicalize(result_abspath).expect("we already canonicalized this"); assert_eq!( check_abspath, result_abspath, "incorrect canonicalization: start='{:?}' path='{}' result='{}'", start, path.display(), canonical_path.display() ); // TODO: When porting to Windows, check whether `start_abspath` not being // a canonicalized path leads to `\\?\` extended path prefix differences. assert!( result_abspath.starts_with(start_abspath), "sandbox escape: start='{:?}' path='{}' result='{}'", start, path.display(), canonical_path.display() ); } } } } cap-primitives-3.4.1/src/fs/copy.rs000064400000000000000000000007531046102023000153110ustar 00000000000000use crate::fs::copy_impl; use std::path::Path; use std::{fs, io}; /// Copies the contents of one file to another. #[inline] pub fn copy( from_start: &fs::File, from_path: &Path, to_start: &fs::File, to_path: &Path, ) -> io::Result { // In theory we could do extra sanity checks here, but `copy_impl` // implementations use other sandboxed routines to open the files, // so it'd be mostly redundant. copy_impl(from_start, from_path, to_start, to_path) } cap-primitives-3.4.1/src/fs/create_dir.rs000064400000000000000000000070431046102023000164370ustar 00000000000000//! This defines `create_dir`, the primary entrypoint to sandboxed directory //! creation. #[cfg(racy_asserts)] use crate::fs::{ canonicalize, create_dir_unchecked, map_result, stat_unchecked, FollowSymlinks, Metadata, }; use crate::fs::{create_dir_impl, DirOptions}; use std::path::Path; use std::{fs, io}; /// Perform a `mkdirat`-like operation, ensuring that the resolution of the /// path never escapes the directory tree rooted at `start`. #[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] #[inline] pub fn create_dir(start: &fs::File, path: &Path, options: &DirOptions) -> io::Result<()> { #[cfg(racy_asserts)] let stat_before = stat_unchecked(start, path, FollowSymlinks::No); // Call the underlying implementation. let result = create_dir_impl(start, path, options); #[cfg(racy_asserts)] let stat_after = stat_unchecked(start, path, FollowSymlinks::No); #[cfg(racy_asserts)] check_create_dir(start, path, options, &stat_before, &result, &stat_after); result } #[cfg(racy_asserts)] #[allow(clippy::enum_glob_use)] fn check_create_dir( start: &fs::File, path: &Path, options: &DirOptions, stat_before: &io::Result, result: &io::Result<()>, stat_after: &io::Result, ) { use io::ErrorKind::*; match ( map_result(stat_before), map_result(result), map_result(stat_after), ) { (Err((NotFound, _)), Ok(()), Ok(metadata)) => { assert!(metadata.is_dir()); assert_same_file_metadata!( &stat_unchecked( start, &canonicalize(start, path).unwrap(), FollowSymlinks::No ) .unwrap(), &metadata ); } (Ok(metadata_before), Err((AlreadyExists, _)), Ok(metadata_after)) => { assert_same_file_metadata!(&metadata_before, &metadata_after); } (_, Err((kind, message)), _) => { // TODO: Checking in the case it does end with ".". if !path.to_string_lossy().ends_with(".") { match map_result(&canonicalize(start, path)) { Ok(canon) => match map_result(&create_dir_unchecked(start, &canon, options)) { Err((unchecked_kind, unchecked_message)) => { assert_eq!( kind, unchecked_kind, "unexpected error kind from create_dir start='{:?}', \ path='{}':\nstat_before={:#?}\nresult={:#?}\nstat_after={:#?}", start, path.display(), stat_before, result, stat_after ); assert_eq!(message, unchecked_message); } _ => panic!("unsandboxed create_dir success"), }, Err((_canon_kind, _canon_message)) => { /* TODO: Check error messages assert_eq!(kind, canon_kind); assert_eq!(message, canon_message); */ } } } } other => panic!( "inconsistent create_dir checks: start='{:?}' path='{}':\n{:#?}", start, path.display(), other, ), } } cap-primitives-3.4.1/src/fs/dir_builder.rs000064400000000000000000000045011046102023000166160ustar 00000000000000use crate::fs::DirOptions; use std::fmt; /// A builder used to create directories in various manners. /// /// This corresponds to [`std::fs::DirBuilder`]. /// /// Unlike `std::fs::DirBuilder`, this API has no `DirBuilder::create`, because /// creating directories requires a capability. Use [`Dir::create_dir_with`] /// instead. /// /// [`Dir::create_dir_with`]: https://docs.rs/cap-std/latest/cap_std/fs/struct.Dir.html#method.create_dir_with /// ///
/// We need to define our own version because the libstd `DirBuilder` doesn't /// have public accessors that we can use. ///
pub struct DirBuilder { pub(crate) recursive: bool, pub(crate) options: DirOptions, } impl DirBuilder { /// Creates a new set of options with default mode/security settings for /// all platforms and also non-recursive. /// /// This corresponds to [`std::fs::DirBuilder::new`]. #[allow(clippy::new_without_default)] #[inline] pub const fn new() -> Self { Self { recursive: false, options: DirOptions::new(), } } /// Indicates that directories should be created recursively, creating all /// parent directories. /// /// This corresponds to [`std::fs::DirBuilder::recursive`]. #[inline] pub fn recursive(&mut self, recursive: bool) -> &mut Self { self.recursive = recursive; self } /// Return the `DirOptions` contained in this `DirBuilder`. #[inline] pub const fn options(&self) -> &DirOptions { &self.options } /// Return the value of the `recursive` flag. #[inline] pub const fn is_recursive(&self) -> bool { self.recursive } } /// Unix-specific extensions to [`fs::DirBuilder`]. #[cfg(unix)] pub trait DirBuilderExt { /// Sets the mode to create new directories with. This option defaults to /// 0o777. fn mode(&mut self, mode: u32) -> &mut Self; } #[cfg(unix)] impl DirBuilderExt for DirBuilder { #[inline] fn mode(&mut self, mode: u32) -> &mut Self { self.options.mode(mode); self } } impl fmt::Debug for DirBuilder { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut b = f.debug_struct("DirBuilder"); b.field("recursive", &self.recursive); b.field("options", &self.options); b.finish() } } cap-primitives-3.4.1/src/fs/dir_entry.rs000064400000000000000000000105011046102023000163260ustar 00000000000000use crate::fs::{ dir_options, DirEntryInner, FileType, FollowSymlinks, Metadata, OpenOptions, ReadDir, }; #[cfg(not(windows))] use rustix::fs::DirEntryExt; use std::ffi::OsString; use std::{fmt, fs, io}; /// Entries returned by the `ReadDir` iterator. /// /// This corresponds to [`std::fs::DirEntry`]. /// /// Unlike `std::fs::DirEntry`, this API has no `DirEntry::path`, because /// absolute paths don't interoperate well with the capability model. /// /// There is a `file_name` function, however there are also `open`, /// `open_with`, `open_dir`, `remove_file`, and `remove_dir` functions for /// opening or removing the entry directly, which can be more efficient and /// convenient. /// /// There is no `from_std` method, as `std::fs::DirEntry` doesn't provide a way /// to construct a `DirEntry` without opening directories by ambient paths. pub struct DirEntry { pub(crate) inner: DirEntryInner, } impl DirEntry { /// Open the file for reading. #[inline] pub fn open(&self) -> io::Result { self.open_with(OpenOptions::new().read(true)) } /// Open the file with the given options. #[inline] pub fn open_with(&self, options: &OpenOptions) -> io::Result { self.inner.open(options) } /// Open the entry as a directory. #[inline] pub fn open_dir(&self) -> io::Result { self.open_with(&dir_options()) } /// Removes the file from its filesystem. #[inline] pub fn remove_file(&self) -> io::Result<()> { self.inner.remove_file() } /// Removes the directory from its filesystem. #[inline] pub fn remove_dir(&self) -> io::Result<()> { self.inner.remove_dir() } /// Returns an iterator over the entries within the subdirectory. #[inline] pub fn read_dir(&self) -> io::Result { self.inner.read_dir(FollowSymlinks::Yes) } /// Returns the metadata for the file that this entry points at. /// /// This corresponds to [`std::fs::DirEntry::metadata`]. /// /// # Platform-specific behavior /// /// On Windows, this produces a `Metadata` object which does not contain /// the optional values returned by [`MetadataExt`]. Use /// [`cap_fs_ext::DirEntryExt::full_metadata`] to obtain a `Metadata` with /// the values filled in. /// /// [`MetadataExt`]: https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html /// [`cap_fs_ext::DirEntryExt::full_metadata`]: https://docs.rs/cap-fs-ext/latest/cap_fs_ext/trait.DirEntryExt.html#tymethod.full_metadata #[inline] pub fn metadata(&self) -> io::Result { self.inner.metadata() } /// Returns the file type for the file that this entry points at. /// /// This corresponds to [`std::fs::DirEntry::file_type`]. /// /// # Platform-specific behavior /// /// On Windows and most Unix platforms this function is free (no extra system calls needed), but /// some Unix platforms may require the equivalent call to `metadata` to learn about the target /// file type. #[inline] pub fn file_type(&self) -> io::Result { self.inner.file_type() } /// Returns the bare file name of this directory entry without any other /// leading path component. /// /// This corresponds to [`std::fs::DirEntry::file_name`]. #[inline] pub fn file_name(&self) -> OsString { self.inner.file_name() } #[cfg(any(not(windows), windows_by_handle))] #[cfg_attr(windows, allow(dead_code))] #[inline] pub(crate) fn is_same_file(&self, metadata: &Metadata) -> io::Result { self.inner.is_same_file(metadata) } } #[cfg(not(windows))] impl DirEntryExt for DirEntry { #[inline] fn ino(&self) -> u64 { self.inner.ino() } } impl fmt::Debug for DirEntry { // Like libstd's version, but doesn't print the path. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.inner.fmt(f) } } /// Extension trait to allow `full_metadata` etc. to be exposed by /// the `cap-fs-ext` crate. /// /// This is hidden from the main API since this functionality isn't present in /// `std`. Use `cap_fs_ext::DirEntryExt` instead of calling this directly. #[cfg(windows)] #[doc(hidden)] pub trait _WindowsDirEntryExt { fn full_metadata(&self) -> io::Result; } cap-primitives-3.4.1/src/fs/dir_options.rs000064400000000000000000000031251046102023000166640ustar 00000000000000#[cfg(not(target_os = "wasi"))] use crate::fs::DirOptionsExt; /// Options and flags which can be used to configure how a directory is /// created. /// /// This is to `create_dir` what to `OpenOptions` is to `open`. #[derive(Debug, Clone)] pub struct DirOptions { #[cfg(not(target_os = "wasi"))] #[allow(dead_code)] pub(crate) ext: DirOptionsExt, } impl DirOptions { /// Creates a blank new set of options ready for configuration. #[allow(clippy::new_without_default)] #[inline] pub const fn new() -> Self { Self { #[cfg(not(target_os = "wasi"))] ext: DirOptionsExt::new(), } } } #[cfg(unix)] impl crate::fs::DirBuilderExt for DirOptions { #[inline] fn mode(&mut self, mode: u32) -> &mut Self { self.ext.mode(mode); self } } #[cfg(target_os = "vxworks")] impl crate::fs::DirBuilderExt for DirOptions { #[inline] fn mode(&mut self, mode: u32) -> &mut Self { self.ext.mode(mode); self } } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for DirOptions { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { #[cfg(any(unix, target_os = "vxworks"))] use crate::fs::DirBuilderExt; #[allow(unused_mut)] let mut dir_options = Self::new(); #[cfg(any(unix, target_os = "vxworks"))] dir_options.mode(u.int_in_range(0..=0o777)?); // Unix is currently the only platform with a `DirBuilderExt`. #[cfg(not(any(unix, target_os = "vxworks")))] let _ = u; Ok(dir_options) } } cap-primitives-3.4.1/src/fs/errors.rs000064400000000000000000000004721046102023000156510ustar 00000000000000use std::io; #[cfg(not(windows))] pub(crate) use crate::rustix::fs::errors::*; #[cfg(windows)] pub(crate) use crate::windows::fs::errors::*; #[cold] pub(crate) fn escape_attempt() -> io::Error { io::Error::new( io::ErrorKind::PermissionDenied, "a path led outside of the filesystem", ) } cap-primitives-3.4.1/src/fs/file.rs000064400000000000000000000157141046102023000152610ustar 00000000000000use std::io; /// Unix-specific extensions to [`fs::File`]. #[cfg(unix)] pub trait FileExt { /// Reads a number of bytes starting from a given offset. fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result; /// Like `read_at`, except that it reads into a slice of buffers. #[cfg(unix_file_vectored_at)] fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut<'_>], offset: u64) -> io::Result { default_read_vectored(|b| self.read_at(b, offset), bufs) } /// Reads the exact number of bytes required to fill `buf` from the given offset. fn read_exact_at(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> { while !buf.is_empty() { match self.read_at(buf, offset) { Ok(0) => break, Ok(n) => { let tmp = buf; buf = &mut tmp[n..]; offset += n as u64; } Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} Err(e) => return Err(e), } } if !buf.is_empty() { Err(io::Error::new( io::ErrorKind::UnexpectedEof, "failed to fill whole buffer", )) } else { Ok(()) } } /// Writes a number of bytes starting from a given offset. fn write_at(&self, buf: &[u8], offset: u64) -> io::Result; /// Like `write_at`, except that it writes from a slice of buffers. #[cfg(unix_file_vectored_at)] fn write_vectored_at(&self, bufs: &[io::IoSlice<'_>], offset: u64) -> io::Result { default_write_vectored(|b| self.write_at(b, offset), bufs) } /// Attempts to write an entire buffer starting from a given offset. fn write_all_at(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> { while !buf.is_empty() { match self.write_at(buf, offset) { Ok(0) => { return Err(io::Error::new( io::ErrorKind::WriteZero, "failed to write whole buffer", )); } Ok(n) => { buf = &buf[n..]; offset += n as u64 } Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} Err(e) => return Err(e), } } Ok(()) } } #[cfg(unix_file_vectored_at)] fn default_read_vectored(read: F, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result where F: FnOnce(&mut [u8]) -> io::Result, { let buf = bufs .iter_mut() .find(|b| !b.is_empty()) .map_or(&mut [][..], |b| &mut **b); read(buf) } #[cfg(unix_file_vectored_at)] fn default_write_vectored(write: F, bufs: &[io::IoSlice<'_>]) -> io::Result where F: FnOnce(&[u8]) -> io::Result, { let buf = bufs .iter() .find(|b| !b.is_empty()) .map_or(&[][..], |b| &**b); write(buf) } /// WASI-specific extensions to [`fs::File`]. #[cfg(target_os = "wasi")] pub trait FileExt { /// Reads a number of bytes starting from a given offset. fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result { let bufs = &mut [io::IoSliceMut::new(buf)]; self.read_vectored_at(bufs, offset) } /// Reads a number of bytes starting from a given offset. fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut<'_>], offset: u64) -> io::Result; /// Reads the exact number of byte required to fill `buf` from the given offset. fn read_exact_at(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> { while !buf.is_empty() { match self.read_at(buf, offset) { Ok(0) => break, Ok(n) => { let tmp = buf; buf = &mut tmp[n..]; offset += n as u64; } Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} Err(e) => return Err(e), } } if !buf.is_empty() { Err(io::Error::new( io::ErrorKind::UnexpectedEof, "failed to fill whole buffer", )) } else { Ok(()) } } /// Writes a number of bytes starting from a given offset. fn write_at(&self, buf: &[u8], offset: u64) -> io::Result { let bufs = &[io::IoSlice::new(buf)]; self.write_vectored_at(bufs, offset) } /// Writes a number of bytes starting from a given offset. fn write_vectored_at(&self, bufs: &[io::IoSlice<'_>], offset: u64) -> io::Result; /// Attempts to write an entire buffer starting from a given offset. fn write_all_at(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> { while !buf.is_empty() { match self.write_at(buf, offset) { Ok(0) => { return Err(io::Error::new( io::ErrorKind::WriteZero, "failed to write whole buffer", )); } Ok(n) => { buf = &buf[n..]; offset += n as u64 } Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} Err(e) => return Err(e), } } Ok(()) } /// Returns the current position within the file. fn tell(&self) -> io::Result; /// Adjust the flags associated with this file. fn fdstat_set_flags(&self, flags: u16) -> io::Result<()>; /// Adjust the rights associated with this file. fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()>; /// Provide file advisory information on a file descriptor. fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()>; /// Force the allocation of space in a file. fn allocate(&self, offset: u64, len: u64) -> io::Result<()>; /// Create a directory. fn create_directory>(&self, dir: P) -> io::Result<()>; /// Read the contents of a symbolic link. fn read_link>(&self, path: P) -> io::Result; /// Return the attributes of a file or directory. fn metadata_at>( &self, lookup_flags: u32, path: P, ) -> io::Result; /// Unlink a file. fn remove_file>(&self, path: P) -> io::Result<()>; /// Remove a directory. fn remove_directory>(&self, path: P) -> io::Result<()>; } /// Windows-specific extensions to [`fs::File`]. #[cfg(windows)] pub trait FileExt { /// Seeks to a given position and reads a number of bytes. fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result; /// Seeks to a given position and writes a number of bytes. fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result; } cap-primitives-3.4.1/src/fs/file_path_by_searching.rs000064400000000000000000000036731046102023000210130ustar 00000000000000use crate::fs::{ is_root_dir, open_dir_unchecked, read_dir_unchecked, FollowSymlinks, MaybeOwnedFile, Metadata, }; use std::fs; use std::path::{Component, PathBuf}; /// Implementation of `file_path` for directories by opening `..` and searching /// for a directory among `..`'s children to find its name. pub(crate) fn file_path_by_searching(file: &fs::File) -> Option { // Use the `_noassert` functions because the asserts depend on `file_path`, // which is what we're implementing here. let mut base = MaybeOwnedFile::borrowed_noassert(file); let mut components = Vec::new(); // Iterate with `..` until we reach the root directory. 'next_component: loop { // Open `..`. let mut iter = read_dir_unchecked(&base, Component::ParentDir.as_ref(), FollowSymlinks::No).ok()?; let metadata = Metadata::from_file(&*base).ok()?; // Search the children until we find one with matching metadata, and // then record its name. while let Some(child) = iter.next() { let child = child.ok()?; if child.is_same_file(&metadata).ok()? { // Found a match. Record the name and continue to the next component. components.push(child.file_name()); base = MaybeOwnedFile::owned_noassert( open_dir_unchecked(&base, Component::ParentDir.as_ref()).ok()?, ); continue 'next_component; } } // We didn't find the directory among its parent's children. If we're at // the root directory, we're done. if is_root_dir(&base, &iter).ok()? { break; } // Otherwise, something went wrong and we can't determine the path. return None; } let mut path = PathBuf::new(); path.push(Component::RootDir); for component in components.iter().rev() { path.push(component); } Some(path) } cap-primitives-3.4.1/src/fs/file_type.rs000064400000000000000000000104471046102023000163200ustar 00000000000000//! The `FileType` struct. use crate::fs::ImplFileTypeExt; /// `FileType`'s inner state. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] enum Inner { /// A directory. Dir, /// A file. File, /// An unknown entity. Unknown, /// A `FileTypeExt` type. Ext(ImplFileTypeExt), } /// A structure representing a type of file with accessors for each file type. /// /// This corresponds to [`std::fs::FileType`]. /// ///
/// We need to define our own version because the libstd `FileType` doesn't /// have a public constructor that we can use. ///
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[repr(transparent)] pub struct FileType(Inner); impl FileType { /// Creates a `FileType` for which `is_dir()` returns `true`. #[inline] pub const fn dir() -> Self { Self(Inner::Dir) } /// Creates a `FileType` for which `is_file()` returns `true`. #[inline] pub const fn file() -> Self { Self(Inner::File) } /// Creates a `FileType` for which `is_unknown()` returns `true`. #[inline] pub const fn unknown() -> Self { Self(Inner::Unknown) } /// Creates a `FileType` from extension type. #[inline] pub(crate) const fn ext(ext: ImplFileTypeExt) -> Self { Self(Inner::Ext(ext)) } /// Tests whether this file type represents a directory. /// /// This corresponds to [`std::fs::FileType::is_dir`]. #[inline] pub fn is_dir(&self) -> bool { self.0 == Inner::Dir } /// Tests whether this file type represents a regular file. /// /// This corresponds to [`std::fs::FileType::is_file`]. #[inline] pub fn is_file(&self) -> bool { self.0 == Inner::File } /// Tests whether this file type represents a symbolic link. /// /// This corresponds to [`std::fs::FileType::is_symlink`]. #[inline] pub fn is_symlink(&self) -> bool { if let Inner::Ext(ext) = self.0 { ext.is_symlink() } else { false } } } /// Unix-specific extensions for [`FileType`]. /// /// This corresponds to [`std::os::unix::fs::FileTypeExt`]. #[cfg(any(unix, target_os = "vxworks"))] pub trait FileTypeExt { /// Returns `true` if this file type is a block device. fn is_block_device(&self) -> bool; /// Returns `true` if this file type is a character device. fn is_char_device(&self) -> bool; /// Returns `true` if this file type is a fifo. fn is_fifo(&self) -> bool; /// Returns `true` if this file type is a socket. fn is_socket(&self) -> bool; } #[cfg(any(unix, target_os = "vxworks"))] impl FileTypeExt for FileType { #[inline] fn is_block_device(&self) -> bool { self.0 == Inner::Ext(ImplFileTypeExt::block_device()) } #[inline] fn is_char_device(&self) -> bool { self.0 == Inner::Ext(ImplFileTypeExt::char_device()) } #[inline] fn is_fifo(&self) -> bool { self.0 == Inner::Ext(ImplFileTypeExt::fifo()) } #[inline] fn is_socket(&self) -> bool { self.0 == Inner::Ext(ImplFileTypeExt::socket()) } } /// Windows-specific extensions for [`FileType`]. /// /// This corresponds to [`std::os::windows::fs::FileTypeExt`]. #[cfg(all(windows, windows_file_type_ext))] pub trait FileTypeExt { /// Returns `true` if this file type is a symbolic link that is also a /// directory. fn is_symlink_dir(&self) -> bool; /// Returns `true` if this file type is a symbolic link that is also a /// file. fn is_symlink_file(&self) -> bool; } #[cfg(all(windows, windows_file_type_ext))] impl FileTypeExt for FileType { #[inline] fn is_symlink_dir(&self) -> bool { self.0 == Inner::Ext(ImplFileTypeExt::symlink_dir()) } #[inline] fn is_symlink_file(&self) -> bool { self.0 == Inner::Ext(ImplFileTypeExt::symlink_file()) } } /// Extension trait to allow `is_block_device` etc. to be exposed by /// the `cap-fs-ext` crate. /// /// This is hidden from the main API since this functionality isn't present in /// `std`. Use `cap_fs_ext::FileTypeExt` instead of calling this directly. #[cfg(windows)] #[doc(hidden)] pub trait _WindowsFileTypeExt { fn is_block_device(&self) -> bool; fn is_char_device(&self) -> bool; fn is_fifo(&self) -> bool; fn is_socket(&self) -> bool; } cap-primitives-3.4.1/src/fs/follow_symlinks.rs000064400000000000000000000017301046102023000175660ustar 00000000000000/// Should symlinks be followed in the last component of a path? /// /// This doesn't affect path components other than the last. So for example in /// "foo/bar/baz", if "foo" or "bar" are symlinks, they will always be /// followed. This enum value only determines whether "baz" is followed. /// /// Instead of passing bare `bool`s as parameters, pass a distinct enum so that /// the intent is clear. #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum FollowSymlinks { /// Yes, do follow symlinks in the last component of a path. Yes, /// No, do not follow symlinks in the last component of a path. No, } impl FollowSymlinks { /// Convert a bool where true means "follow" and false means "don't follow" /// to a `FollowSymlinks`. #[inline] pub const fn follow(follow: bool) -> Self { if follow { Self::Yes } else { Self::No } } } cap-primitives-3.4.1/src/fs/hard_link.rs000064400000000000000000000101351046102023000162650ustar 00000000000000//! This defines `hard_link`, the primary entrypoint to sandboxed hard-link //! creation. use crate::fs::hard_link_impl; #[cfg(racy_asserts)] use crate::fs::{ canonicalize, hard_link_unchecked, map_result, stat_unchecked, FollowSymlinks, Metadata, }; use std::path::Path; use std::{fs, io}; /// Perform a `linkat`-like operation, ensuring that the resolution of the path /// never escapes the directory tree rooted at `start`. #[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] #[inline] pub fn hard_link( old_start: &fs::File, old_path: &Path, new_start: &fs::File, new_path: &Path, ) -> io::Result<()> { #[cfg(racy_asserts)] let (old_metadata_before, new_metadata_before) = ( stat_unchecked(old_start, old_path, FollowSymlinks::No), stat_unchecked(new_start, new_path, FollowSymlinks::No), ); // Call the underlying implementation. let result = hard_link_impl(old_start, old_path, new_start, new_path); #[cfg(racy_asserts)] let (old_metadata_after, new_metadata_after) = ( stat_unchecked(old_start, old_path, FollowSymlinks::No), stat_unchecked(new_start, new_path, FollowSymlinks::No), ); #[cfg(racy_asserts)] check_hard_link( old_start, old_path, new_start, new_path, &old_metadata_before, &new_metadata_before, &result, &old_metadata_after, &new_metadata_after, ); result } #[cfg(racy_asserts)] #[allow(clippy::too_many_arguments)] #[allow(clippy::enum_glob_use)] fn check_hard_link( old_start: &fs::File, old_path: &Path, new_start: &fs::File, new_path: &Path, old_metadata_before: &io::Result, new_metadata_before: &io::Result, result: &io::Result<()>, old_metadata_after: &io::Result, new_metadata_after: &io::Result, ) { use io::ErrorKind::*; match ( map_result(old_metadata_before), map_result(new_metadata_before), map_result(result), map_result(old_metadata_after), map_result(new_metadata_after), ) { ( Ok(old_metadata_before), Err((NotFound, _)), Ok(()), Ok(old_metadata_after), Ok(new_metadata_after), ) => { assert_same_file_metadata!(old_metadata_before, old_metadata_after); assert_same_file_metadata!(old_metadata_before, new_metadata_after); } (_, Ok(new_metadata_before), Err((AlreadyExists, _)), _, Ok(new_metadata_after)) => { assert_same_file_metadata!(&new_metadata_before, &new_metadata_after); } (_, _, Err((_kind, _message)), _, _) => match ( map_result(&canonicalize(old_start, old_path)), map_result(&canonicalize(new_start, new_path)), ) { (Ok(old_canon), Ok(new_canon)) => match map_result(&hard_link_unchecked( old_start, &old_canon, new_start, &new_canon, )) { Err((_unchecked_kind, _unchecked_message)) => { /* TODO: Check error messages. assert_eq!(kind, unchecked_kind); assert_eq!(message, unchecked_message); */ } _ => panic!("unsandboxed link success"), }, (Err((_old_canon_kind, _old_canon_message)), _) => { /* TODO: Check error messages. assert_eq!(kind, old_canon_kind); assert_eq!(message, old_canon_message); */ } (_, Err((_new_canon_kind, _new_canon_message))) => { /* TODO: Check error messages. assert_eq!(kind, new_canon_kind); assert_eq!(message, new_canon_message); */ } }, other => panic!( "inconsistent link checks: old_start='{:?}', old_path='{}', new_start='{:?}', \ new_path='{}':\n{:#?}", old_start, old_path.display(), new_start, new_path.display(), other ), } } cap-primitives-3.4.1/src/fs/is_file_read_write.rs000064400000000000000000000004501046102023000201500ustar 00000000000000use crate::fs::is_file_read_write_impl; use std::{fs, io}; /// Return a pair of booleans indicating whether the given file is opened /// for reading and writing, respectively. #[inline] pub fn is_file_read_write(file: &fs::File) -> io::Result<(bool, bool)> { is_file_read_write_impl(file) } cap-primitives-3.4.1/src/fs/manually/canonical_path.rs000064400000000000000000000040171046102023000211210ustar 00000000000000use std::ffi::OsStr; use std::path::{Component, PathBuf}; /// Utility for collecting the canonical path components. pub(super) struct CanonicalPath<'path_buf> { /// If the user requested a canonical path, a reference to the `PathBuf` to /// write it to. path: Option<&'path_buf mut PathBuf>, /// Our own private copy of the canonical path, for assertion checking. #[cfg(racy_asserts)] pub(super) debug: PathBuf, } impl<'path_buf> CanonicalPath<'path_buf> { pub(super) fn new(path: Option<&'path_buf mut PathBuf>) -> Self { Self { #[cfg(racy_asserts)] debug: PathBuf::new(), path, } } pub(super) fn push(&mut self, one: &OsStr) { #[cfg(racy_asserts)] self.debug.push(one); if let Some(path) = &mut self.path { path.push(one) } } pub(super) fn pop(&mut self) -> bool { #[cfg(racy_asserts)] self.debug.pop(); if let Some(path) = &mut self.path { path.pop() } else { true } } /// The complete canonical path has been scanned. Set `path` to `None` /// so that it isn't cleared when `self` is dropped. pub(super) fn complete(&mut self) { // Replace "" with ".", since "" as a relative path is interpreted as // an error. if let Some(path) = &mut self.path { if path.as_os_str().is_empty() { path.push(Component::CurDir); } self.path = None; } } } impl<'path_buf> Drop for CanonicalPath<'path_buf> { fn drop(&mut self) { // If `self.path` is still `Some` here, it means that we haven't called // `complete()` yet, meaning the `CanonicalPath` is being dropped // before the complete path has been processed. In that case, clear // `path` to indicate that we weren't able to obtain a complete path. if let Some(path) = &mut self.path { path.clear(); self.path = None; } } } cap-primitives-3.4.1/src/fs/manually/canonicalize.rs000064400000000000000000000041371046102023000206200ustar 00000000000000//! Manual path canonicalization, one component at a time, with manual symlink //! resolution, in order to enforce sandboxing. use super::internal_open; use crate::fs::{canonicalize_options, FollowSymlinks, MaybeOwnedFile}; use std::path::{Path, PathBuf}; use std::{fs, io}; /// Implement `canonicalize` by breaking up the path into components and /// resolving each component individually, and resolving symbolic links /// manually. pub(crate) fn canonicalize(start: &fs::File, path: &Path) -> io::Result { canonicalize_with(start, path, FollowSymlinks::Yes) } /// The main body of `canonicalize`, which takes an extra `follow` flag /// allowing the caller to disable following symlinks in the last component. pub(crate) fn canonicalize_with( start: &fs::File, path: &Path, follow: FollowSymlinks, ) -> io::Result { let mut symlink_count = 0; let mut canonical_path = PathBuf::new(); let start = MaybeOwnedFile::borrowed(start); match internal_open( start, path, canonicalize_options().follow(follow), &mut symlink_count, Some(&mut canonical_path), ) { // If the open succeeded, we got our path. Ok(_) => (), // If it failed due to an invalid argument or filename, report it. Err(err) if err.kind() == io::ErrorKind::InvalidInput => { return Err(err); } #[cfg(io_error_more)] Err(err) if err.kind() == io::ErrorKind::InvalidFilename => { return Err(err); } #[cfg(windows)] Err(err) if err.raw_os_error() == Some(windows_sys::Win32::Foundation::ERROR_INVALID_NAME as _) || err.raw_os_error() == Some(windows_sys::Win32::Foundation::ERROR_DIRECTORY as _) => { return Err(err); } // For any other error, like permission denied, it's ok as long as // we got our path. Err(err) => { if canonical_path.as_os_str().is_empty() { return Err(err); } } } Ok(canonical_path) } cap-primitives-3.4.1/src/fs/manually/cow_component.rs000064400000000000000000000030211046102023000210220ustar 00000000000000use std::borrow::Cow; use std::ffi::OsStr; use std::path::Component; /// Like `std::path::Component` except we combine `Prefix` and `RootDir` since /// we don't support absolute paths, and `Normal` has a `Cow` instead of a /// plain `OsStr` reference, so it can optionally own its own string. pub(super) enum CowComponent<'borrow> { PrefixOrRootDir, CurDir, ParentDir, Normal(Cow<'borrow, OsStr>), } impl<'borrow> CowComponent<'borrow> { /// Convert a `Component` into a `CowComponent` which borrows strings. pub(super) fn borrowed(component: Component<'borrow>) -> Self { match component { Component::Prefix(_) | Component::RootDir => Self::PrefixOrRootDir, Component::CurDir => Self::CurDir, Component::ParentDir => Self::ParentDir, Component::Normal(os_str) => Self::Normal(os_str.into()), } } /// Convert a `Component` into a `CowComponent` which owns strings. pub(super) fn owned(component: Component) -> Self { match component { Component::Prefix(_) | Component::RootDir => Self::PrefixOrRootDir, Component::CurDir => Self::CurDir, Component::ParentDir => Self::ParentDir, Component::Normal(os_str) => Self::Normal(os_str.to_os_string().into()), } } /// Test whether `self` is `Component::Normal`. #[cfg(windows)] pub(super) fn is_normal(&self) -> bool { match self { CowComponent::Normal(_) => true, _ => false, } } } cap-primitives-3.4.1/src/fs/manually/mod.rs000064400000000000000000000011631046102023000167340ustar 00000000000000//! Functions that perform path lookup manually, one component //! at a time, with manual symlink resolution. mod canonical_path; mod canonicalize; mod cow_component; mod open; #[cfg(not(any(windows, target_os = "freebsd")))] mod open_entry; mod read_link_one; use canonical_path::CanonicalPath; use cow_component::CowComponent; use open::internal_open; use read_link_one::read_link_one; #[cfg(racy_asserts)] pub(super) use canonicalize::canonicalize_with; pub(crate) use canonicalize::canonicalize; pub(crate) use open::{open, stat}; #[cfg(not(any(windows, target_os = "freebsd")))] pub(crate) use open_entry::open_entry; cap-primitives-3.4.1/src/fs/manually/open.rs000064400000000000000000000542161046102023000171250ustar 00000000000000//! Manual path resolution, one component at a time, with manual symlink //! resolution, in order to enforce sandboxing. use super::{read_link_one, CanonicalPath, CowComponent}; use crate::fs::{ dir_options, errors, open_unchecked, path_has_trailing_dot, path_has_trailing_slash, stat_unchecked, FollowSymlinks, MaybeOwnedFile, Metadata, OpenOptions, OpenUncheckedError, }; #[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))] use rustix::fs::OFlags; use std::borrow::Cow; use std::ffi::OsStr; use std::path::{Component, Path, PathBuf}; use std::{fs, io, mem}; #[cfg(windows)] use { crate::fs::{open_dir_unchecked, path_really_has_trailing_dot, SymlinkKind}, windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_DIRECTORY, }; /// Implement `open` by breaking up the path into components, resolving each /// component individually, and resolving symbolic links manually. pub(crate) fn open(start: &fs::File, path: &Path, options: &OpenOptions) -> io::Result { let mut symlink_count = 0; let start = MaybeOwnedFile::borrowed(start); let maybe_owned = internal_open(start, path, options, &mut symlink_count, None)?; maybe_owned.into_file(options) } /// Context for performing manual component-at-a-time path resolution. struct Context<'start> { /// The current base directory handle for path lookups. base: MaybeOwnedFile<'start>, /// The stack of directory handles below the base. dirs: Vec>, /// The current worklist stack of path components to process. components: Vec>, /// If requested, the canonical path is constructed here. canonical_path: CanonicalPath<'start>, /// Does the path end in `/` or similar, so it requires a directory? dir_required: bool, /// Are we requesting write permissions, so we can't open a directory? dir_precluded: bool, /// Where there a trailing slash on the path? trailing_slash: bool, /// If a path ends in `.`, `..`, or `/`, including after expanding /// symlinks, we need to follow path resolution by opening `.` so that we /// obtain a full `dir_options` file descriptor and confirm that we have /// search rights in the last component. follow_with_dot: bool, /// A `PathBuf` that we reuse for calling `read_link_one` to minimize /// allocations. reuse: PathBuf, #[cfg(racy_asserts)] start_clone: MaybeOwnedFile<'start>, } impl<'start> Context<'start> { /// Construct a new instance of `Self`. fn new( start: MaybeOwnedFile<'start>, path: &'start Path, _options: &OpenOptions, canonical_path: Option<&'start mut PathBuf>, ) -> Self { let trailing_slash = path_has_trailing_slash(path); let trailing_dot = path_has_trailing_dot(path); let trailing_dotdot = path.ends_with(Component::ParentDir); let mut components: Vec = Vec::new(); #[cfg(windows)] { // Windows resolves `..` before doing filesystem lookups. for component in path.components().map(CowComponent::borrowed) { match component { CowComponent::ParentDir if !components.is_empty() && components.last().unwrap().is_normal() => { let _ = components.pop(); } _ => components.push(component), } } components.reverse(); } #[cfg(not(windows))] { // Add the path components to the worklist. Rust's `Path` // normalizes away `.` components, however a trailing `.` affects // path lookup, so special-case it here. if trailing_dot { components.push(CowComponent::CurDir); } components.extend(path.components().rev().map(CowComponent::borrowed)); } #[cfg(racy_asserts)] let start_clone = MaybeOwnedFile::owned(start.try_clone().unwrap()); Self { base: start, dirs: Vec::with_capacity(components.len()), components, canonical_path: CanonicalPath::new(canonical_path), dir_required: trailing_slash, #[cfg(not(windows))] dir_precluded: _options.write || _options.append, #[cfg(windows)] dir_precluded: false, trailing_slash, follow_with_dot: trailing_dot | trailing_dotdot, reuse: PathBuf::new(), #[cfg(racy_asserts)] start_clone, } } fn check_dot_access(&self) -> io::Result<()> { // Manually check that we have permissions to search `self.base` to // search for `.` in it, since we otherwise resolve `.` and `..` // ourselves by just manipulating the `dirs` stack. #[cfg(not(windows))] { // Use `faccess` with `AT_EACCESS`. `AT_EACCESS` is not often the // right tool for the job; in POSIX, it's better to ask for errno // than to ask for permission. But we use `check_dot_access` to // check access for opening `.` and `..` in situations where we // already have open handles to them, and now we're accessing them // through different paths, and we need to check whether these // paths allow us access. // // Android and Emscripten lack `AT_EACCESS`. // #[cfg(any(target_os = "emscripten", target_os = "android"))] let at_flags = rustix::fs::AtFlags::empty(); #[cfg(not(any(target_os = "emscripten", target_os = "android")))] let at_flags = rustix::fs::AtFlags::EACCESS; // Always use `CurDir`, even though this code is used to check // permissions for both `.` and `..`, because in both cases we // already know we can access the referenced directory, and we // just need to check for the ability to search for `.` or `..` // within `self.base`, which should always be the same. And // using `.` means we avoid asking the OS to access a `..` path // for us. Ok(rustix::fs::accessat( &*self.base, Component::CurDir.as_os_str(), rustix::fs::Access::EXEC_OK, at_flags, )?) } #[cfg(windows)] open_dir_unchecked(&self.base, Component::CurDir.as_ref()).map(|_| ()) } /// Handle a "." path component. fn cur_dir(&mut self) -> io::Result<()> { // This is a no-op. If this occurs at the end of the path, it does // imply that we need search access to the directory, and it requires // we open a directory, however we'll handle that in the // `follow_with_dot` check. Ok(()) } /// Handle a ".." path component. fn parent_dir(&mut self) -> io::Result<()> { #[cfg(racy_asserts)] if !self.dirs.is_empty() { assert_different_file!(&self.start_clone, &self.base); } // We hold onto all the parent directory descriptors so that we // don't have to re-open anything when we encounter a `..`. This // way, even if the directory is concurrently moved, we don't have // to worry about `..` leaving the sandbox. match self.dirs.pop() { Some(dir) => { // Check that we have permission to look up `..`. self.check_dot_access()?; // Looks good. self.base = dir; } None => return Err(errors::escape_attempt()), } assert!(self.canonical_path.pop()); Ok(()) } /// Handle a "normal" path component. fn normal( &mut self, one: &OsStr, options: &OpenOptions, symlink_count: &mut u8, ) -> io::Result<()> { // If there are more named components left, this will be a base // directory from which to open subsequent components, so use "path" // options (`O_PATH` on Linux). let use_options = if self.components.is_empty() { options.clone() } else { dir_options() }; // If the last path component ended in a slash, re-add the slash, // as Rust's `Path` will have removed it, and we need it to get the // same behavior from the OS. let use_path: Cow = if self.components.is_empty() && self.trailing_slash { let mut p = one.to_os_string(); p.push("/"); Cow::Owned(p) } else { Cow::Borrowed(one) }; let dir_required = self.dir_required || use_options.dir_required; #[allow(clippy::redundant_clone)] match open_unchecked( &self.base, use_path.as_ref(), use_options .clone() .follow(FollowSymlinks::No) .dir_required(dir_required), ) { Ok(file) => { // Emulate `O_PATH` + `FollowSymlinks::Yes` on Linux. If `file` // is a symlink, follow it. #[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))] if should_emulate_o_path(&use_options) { match read_link_one( &file, Default::default(), symlink_count, mem::take(&mut self.reuse), ) { Ok(destination) => { return self.push_symlink_destination(destination); } // If it isn't a symlink, handle it as normal. // `readlinkat` returns `ENOENT` if the file isn't a // symlink in this situation. Err(err) if err.kind() == io::ErrorKind::NotFound => (), // If `readlinkat` fails any other way, pass it on. Err(err) => return Err(err), } } // Normal case let prev_base = self.base.descend_to(MaybeOwnedFile::owned(file)); self.dirs.push(prev_base); self.canonical_path.push(one); Ok(()) } #[cfg(not(windows))] Err(OpenUncheckedError::Symlink(err, ())) => { self.maybe_last_component_symlink(one, symlink_count, options.follow, err) } #[cfg(windows)] Err(OpenUncheckedError::Symlink(err, SymlinkKind::Dir)) => { // If this is a Windows directory symlink, require a directory. self.dir_required |= self.components.is_empty(); self.maybe_last_component_symlink(one, symlink_count, options.follow, err) } #[cfg(windows)] Err(OpenUncheckedError::Symlink(err, SymlinkKind::File)) => { // If this is a Windows file symlink, preclude a directory. self.dir_precluded = true; self.maybe_last_component_symlink(one, symlink_count, options.follow, err) } Err(OpenUncheckedError::NotFound(err)) => Err(err), Err(OpenUncheckedError::Other(err)) => { // An error occurred. If this was the last component, and the // error wasn't due to invalid inputs (eg. the path has an // embedded NUL), record it as the last component of the // canonical path, even if we couldn't open it. if self.components.is_empty() && err.kind() != io::ErrorKind::InvalidInput { self.canonical_path.push(one); self.canonical_path.complete(); } Err(err) } } } /// Dereference one symlink level. fn symlink(&mut self, one: &OsStr, symlink_count: &mut u8) -> io::Result<()> { let destination = read_link_one(&self.base, one, symlink_count, mem::take(&mut self.reuse))?; self.push_symlink_destination(destination) } /// Push the components of `destination` onto the worklist stack. fn push_symlink_destination(&mut self, destination: PathBuf) -> io::Result<()> { let at_end = self.components.is_empty(); let trailing_slash = path_has_trailing_slash(&destination); let trailing_dot = path_has_trailing_dot(&destination); let trailing_dotdot = destination.ends_with(Component::ParentDir); #[cfg(windows)] { // `path_has_trailing_dot` returns false so that we don't open `.` // at the end of path resolution. But for determining the Windows // symlink restrictions, we need to know whether the path really // ends in a `.`. let trailing_dot_really = path_really_has_trailing_dot(&destination); // Windows appears to disallow symlinks to paths with trailing // slashes, slashdots, or slashdotdots. if trailing_slash || (trailing_dot_really && destination.as_os_str() != Component::CurDir.as_os_str()) || (trailing_dotdot && destination.as_os_str() != Component::ParentDir.as_os_str()) { return Err(io::Error::from_raw_os_error(123)); } // Windows resolves `..` before doing filesystem lookups. let mut components: Vec = Vec::new(); for component in destination.components().map(CowComponent::owned) { match component { CowComponent::ParentDir if !components.is_empty() && components.last().unwrap().is_normal() => { let _ = components.pop(); } _ => components.push(component), } } self.components.extend(components.into_iter().rev()); } #[cfg(not(windows))] { // Rust's `Path` hides a trailing dot, so handle it manually. if trailing_dot { self.components.push(CowComponent::CurDir); } self.components .extend(destination.components().rev().map(CowComponent::owned)); } // Record whether the new components ended with a path that implies // an open of `.` at the end of path resolution. if at_end { self.follow_with_dot |= trailing_dot | trailing_dotdot; self.trailing_slash |= trailing_slash; self.dir_required |= trailing_slash; } // As an optimization, hold onto the `PathBuf` buffer for later reuse. self.reuse = destination; Ok(()) } /// Check whether this is the last component and we don't need /// to dereference; otherwise call `Self::symlink`. fn maybe_last_component_symlink( &mut self, one: &OsStr, symlink_count: &mut u8, follow: FollowSymlinks, err: io::Error, ) -> io::Result<()> { if follow == FollowSymlinks::No && !self.trailing_slash && self.components.is_empty() { self.canonical_path.push(one); self.canonical_path.complete(); return Err(err); } self.symlink(one, symlink_count) } } /// Internal implementation of manual `open`, exposing some additional /// parameters. /// /// Callers can request the canonical path by passing `Some` to /// `canonical_path`. If the complete canonical path is processed, it will be /// stored in the provided `&mut PathBuf`, even if the actual open fails. If /// a failure occurs before the complete canonical path is processed, the /// provided `&mut PathBuf` is cleared to empty. /// /// A note on lifetimes: `path` and `canonical_path` here don't strictly /// need `'start`, but using them makes it easier to store them in the /// `Context` struct. pub(super) fn internal_open<'start>( start: MaybeOwnedFile<'start>, path: &'start Path, options: &OpenOptions, symlink_count: &mut u8, canonical_path: Option<&'start mut PathBuf>, ) -> io::Result> { // POSIX returns `ENOENT` on an empty path. TODO: On Windows, we should // be compatible with what Windows does instead. if path.as_os_str().is_empty() { return Err(errors::no_such_file_or_directory()); } let mut ctx = Context::new(start, path, options, canonical_path); while let Some(c) = ctx.components.pop() { match c { CowComponent::PrefixOrRootDir => return Err(errors::escape_attempt()), CowComponent::CurDir => ctx.cur_dir()?, CowComponent::ParentDir => ctx.parent_dir()?, CowComponent::Normal(one) => ctx.normal(&one, options, symlink_count)?, } } // We've now finished all the path components other than any trailing `.`s, // so we have the complete canonical path. ctx.canonical_path.complete(); // If the path ended in `.` (explicit or implied) or `..`, we may have // opened the directory with eg. `O_PATH` on Linux, or we may have skipped // checking for search access to `.`, so re-open it. if ctx.follow_with_dot { if ctx.dir_precluded { return Err(errors::is_directory()); } ctx.base = MaybeOwnedFile::owned(open_unchecked( &ctx.base, Component::CurDir.as_ref(), options, )?); } #[cfg(racy_asserts)] check_internal_open(&ctx, path, options); Ok(ctx.base) } /// Implement manual `stat` in a similar manner as manual `open`. pub(crate) fn stat(start: &fs::File, path: &Path, follow: FollowSymlinks) -> io::Result { // POSIX returns `ENOENT` on an empty path. TODO: On Windows, we should // be compatible with what Windows does instead. if path.as_os_str().is_empty() { return Err(errors::no_such_file_or_directory()); } let mut options = OpenOptions::new(); options.follow(follow); let mut symlink_count = 0; let mut ctx = Context::new(MaybeOwnedFile::borrowed(start), path, &options, None); assert!(!ctx.dir_precluded); while let Some(c) = ctx.components.pop() { match c { CowComponent::PrefixOrRootDir => return Err(errors::escape_attempt()), CowComponent::CurDir => ctx.cur_dir()?, CowComponent::ParentDir => ctx.parent_dir()?, CowComponent::Normal(one) => { if ctx.components.is_empty() { // If this is the last component, do a non-following // `stat_unchecked` on it. let stat = stat_unchecked(&ctx.base, one.as_ref(), FollowSymlinks::No)?; // If we weren't asked to follow symlinks, or it wasn't a // symlink, we're done. if options.follow == FollowSymlinks::No || !stat.file_type().is_symlink() { if stat.is_dir() { if ctx.dir_precluded { return Err(errors::is_directory()); } } else if ctx.dir_required { return Err(errors::is_not_directory()); } return Ok(stat); } // On Windows, symlinks know whether they are a file or // directory. #[cfg(windows)] if stat.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 { ctx.dir_required = true; } else { ctx.dir_precluded = true; } // If it was a symlink and we're asked to follow symlinks, // dereference it. ctx.symlink(&one, &mut symlink_count)? } else { // Otherwise open the path component normally. ctx.normal(&one, &options, &mut symlink_count)? } } } } // If the path ended in `.` (explicit or implied) or `..`, we may have // opened the directory with eg. `O_PATH` on Linux, or we may have skipped // checking for search access to `.`, so re-check it. if ctx.follow_with_dot { if ctx.dir_precluded { return Err(errors::is_directory()); } ctx.check_dot_access()?; } // If the path ended in `.` or `..`, we already have it open, so just do // `.metadata()` on it. Metadata::from_file(&ctx.base) } /// Test whether the given options imply that we should treat an open file as /// potentially being a symlink we need to follow, due to use of `O_PATH`. #[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))] fn should_emulate_o_path(use_options: &OpenOptions) -> bool { (use_options.ext.custom_flags & (OFlags::PATH.bits() as i32)) == (OFlags::PATH.bits() as i32) && use_options.follow == FollowSymlinks::Yes } #[cfg(racy_asserts)] fn check_internal_open(ctx: &Context, path: &Path, options: &OpenOptions) { match open_unchecked( &ctx.start_clone, ctx.canonical_path.debug.as_ref(), options .clone() .create(false) .create_new(false) .truncate(false), ) { Ok(unchecked_file) => { assert_same_file!( &ctx.base, &unchecked_file, "path resolution inconsistency: start='{:?}', path='{}'; canonical_path='{}'", ctx.start_clone, path.display(), ctx.canonical_path.debug.display(), ); } Err(_unchecked_error) => { /* TODO: Check error messages. panic!( "unexpected success opening result={:?} start='{:?}', path='{}'; canonical_path='{}'; \ expected {:?}", ctx.base, ctx.start_clone, path.display(), ctx.canonical_path.debug.display(), unchecked_error, */ } } } cap-primitives-3.4.1/src/fs/manually/open_entry.rs000064400000000000000000000020611046102023000203350ustar 00000000000000use super::{internal_open, read_link_one}; use crate::fs::{open_unchecked, FollowSymlinks, MaybeOwnedFile, OpenOptions, OpenUncheckedError}; use std::ffi::OsStr; use std::path::PathBuf; use std::{fs, io}; pub(crate) fn open_entry( start: &fs::File, path: &OsStr, options: &OpenOptions, ) -> io::Result { match open_unchecked( start, path.as_ref(), options.clone().follow(FollowSymlinks::No), ) { Ok(file) => Ok(file), Err(OpenUncheckedError::Symlink(_, _)) if options.follow == FollowSymlinks::Yes => { let mut symlink_count = 0; let destination = read_link_one(start, path, &mut symlink_count, PathBuf::new())?; let maybe = MaybeOwnedFile::borrowed(start); internal_open(maybe, &destination, options, &mut symlink_count, None) .map(MaybeOwnedFile::unwrap_owned) } Err(OpenUncheckedError::NotFound(err)) | Err(OpenUncheckedError::Other(err)) | Err(OpenUncheckedError::Symlink(err, _)) => Err(err), } } cap-primitives-3.4.1/src/fs/manually/read_link_one.rs000064400000000000000000000021121046102023000207410ustar 00000000000000use crate::fs::{errors, read_link_unchecked, MAX_SYMLINK_EXPANSIONS}; use std::ffi::OsStr; use std::path::{Path, PathBuf}; use std::{fs, io}; /// This is a wrapper around `read_link_unchecked` which performs a single /// symlink expansion on a single path component, and which enforces the /// recursion limit. pub(super) fn read_link_one( base: &fs::File, name: &OsStr, symlink_count: &mut u8, reuse: PathBuf, ) -> io::Result { let name: &Path = name.as_ref(); assert!( name.as_os_str().is_empty() || name.file_name().is_some(), "read_link_one expects a single normal path component, got '{}'", name.display() ); assert!( name.as_os_str().is_empty() || name.parent().unwrap().as_os_str().is_empty(), "read_link_one expects a single normal path component, got '{}'", name.display() ); if *symlink_count == MAX_SYMLINK_EXPANSIONS { return Err(errors::too_many_symlinks()); } let destination = read_link_unchecked(base, name, reuse)?; *symlink_count += 1; Ok(destination) } cap-primitives-3.4.1/src/fs/maybe_owned_file.rs000064400000000000000000000107341046102023000176270ustar 00000000000000use crate::fs::{open_unchecked, OpenOptions}; use maybe_owned::MaybeOwned; use std::ops::Deref; use std::path::Component; use std::{fmt, fs, io, mem}; #[cfg(racy_asserts)] use {crate::fs::file_path, std::path::PathBuf}; /// Several places in the code need to be able to handle either owned or /// borrowed [`std::fs::File]`s. Cloning a `File` to let them always have an /// owned `File` is expensive and fallible, so use this `struct` to hold either /// one, and implement [`Deref`] to allow them to be handled in a uniform way. /// /// This is similar to [`Cow`], except without the copy-on-write part ;-). /// `Cow` requires a `Clone` implementation, which `File` doesn't have, and /// most users of this type don't need copy-on-write behavior. /// /// And, this type has the special `descend_to`, which just does an assignment, /// but also some useful assertion checks. /// /// [`Deref`]: std::ops::Deref /// [`Cow`]: std::borrow::Cow pub(super) struct MaybeOwnedFile<'borrow> { inner: MaybeOwned<'borrow, fs::File>, #[cfg(racy_asserts)] path: Option, } impl<'borrow> MaybeOwnedFile<'borrow> { /// Constructs a new `MaybeOwnedFile` which is not owned. pub(super) fn borrowed(file: &'borrow fs::File) -> Self { #[cfg(racy_asserts)] let path = file_path(file); Self { inner: MaybeOwned::Borrowed(file), #[cfg(racy_asserts)] path, } } /// Constructs a new `MaybeOwnedFile` which is owned. pub(super) fn owned(file: fs::File) -> Self { #[cfg(racy_asserts)] let path = file_path(&file); Self { inner: MaybeOwned::Owned(file), #[cfg(racy_asserts)] path, } } /// Like `borrowed` but does not do path checks. #[allow(dead_code)] pub(super) const fn borrowed_noassert(file: &'borrow fs::File) -> Self { Self { inner: MaybeOwned::Borrowed(file), #[cfg(racy_asserts)] path: None, } } /// Like `owned` but does not do path checks. #[allow(dead_code)] pub(super) const fn owned_noassert(file: fs::File) -> Self { Self { inner: MaybeOwned::Owned(file), #[cfg(racy_asserts)] path: None, } } /// Set this `MaybeOwnedFile` to a new owned file which is from a subtree /// of the current file. Return a `MaybeOwnedFile` representing the /// previous state. pub(super) fn descend_to(&mut self, to: MaybeOwnedFile<'borrow>) -> Self { #[cfg(racy_asserts)] let path = self.path.clone(); #[cfg(racy_asserts)] if let Some(to_path) = file_path(&to) { if let Some(current_path) = &self.path { assert!( to_path.starts_with(current_path), "attempted to descend from {:?} to {:?}", to_path.display(), current_path.display() ); } self.path = Some(to_path); } Self { inner: mem::replace(&mut self.inner, to.inner), #[cfg(racy_asserts)] path, } } /// Produce an owned `File`. This uses `open` on "." if needed to convert a /// borrowed `File` to an owned one. #[cfg_attr(windows, allow(dead_code))] pub(super) fn into_file(self, options: &OpenOptions) -> io::Result { match self.inner { MaybeOwned::Owned(file) => Ok(file), MaybeOwned::Borrowed(file) => { // The only situation in which we'd be asked to produce an owned // `File` is when there's a need to open "." within a directory // to obtain a new handle. open_unchecked(file, Component::CurDir.as_ref(), options).map_err(Into::into) } } } /// Assuming `self` holds an owned `File`, return it. #[cfg_attr(any(windows, target_os = "freebsd"), allow(dead_code))] pub(super) fn unwrap_owned(self) -> fs::File { match self.inner { MaybeOwned::Owned(file) => file, MaybeOwned::Borrowed(_) => panic!("expected owned file"), } } } impl<'borrow> Deref for MaybeOwnedFile<'borrow> { type Target = fs::File; #[inline] fn deref(&self) -> &Self::Target { self.inner.as_ref() } } impl<'borrow> fmt::Debug for MaybeOwnedFile<'borrow> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.deref().fmt(f) } } cap-primitives-3.4.1/src/fs/metadata.rs000064400000000000000000000356511046102023000161240ustar 00000000000000use crate::fs::{FileType, ImplFileTypeExt, ImplMetadataExt, Permissions}; use crate::time::SystemTime; use std::{fs, io}; /// Metadata information about a file. /// /// This corresponds to [`std::fs::Metadata`]. /// ///
/// We need to define our own version because the libstd `Metadata` doesn't /// have a public constructor that we can use. ///
#[derive(Debug, Clone)] pub struct Metadata { pub(crate) file_type: FileType, pub(crate) len: u64, pub(crate) permissions: Permissions, pub(crate) modified: Option, pub(crate) accessed: Option, pub(crate) created: Option, pub(crate) ext: ImplMetadataExt, } #[allow(clippy::len_without_is_empty)] impl Metadata { /// Constructs a new instance of `Self` from the given [`std::fs::File`]. #[inline] pub fn from_file(file: &fs::File) -> io::Result { let std = file.metadata()?; let ext = ImplMetadataExt::from(file, &std)?; let file_type = ImplFileTypeExt::from(file, &std)?; Ok(Self::from_parts(std, ext, file_type)) } /// Constructs a new instance of `Self` from the given /// [`std::fs::Metadata`]. /// /// As with the comments in [`std::fs::Metadata::volume_serial_number`] and /// nearby functions, some fields of the resulting metadata will be `None`. /// /// [`std::fs::Metadata::volume_serial_number`]: https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html#tymethod.volume_serial_number #[inline] pub fn from_just_metadata(std: fs::Metadata) -> Self { let ext = ImplMetadataExt::from_just_metadata(&std); let file_type = ImplFileTypeExt::from_just_metadata(&std); Self::from_parts(std, ext, file_type) } #[inline] fn from_parts(std: fs::Metadata, ext: ImplMetadataExt, file_type: FileType) -> Self { Self { file_type, len: std.len(), permissions: Permissions::from_std(std.permissions()), modified: std.modified().ok().map(SystemTime::from_std), accessed: std.accessed().ok().map(SystemTime::from_std), created: std.created().ok().map(SystemTime::from_std), ext, } } /// Returns the file type for this metadata. /// /// This corresponds to [`std::fs::Metadata::file_type`]. #[inline] pub const fn file_type(&self) -> FileType { self.file_type } /// Returns `true` if this metadata is for a directory. /// /// This corresponds to [`std::fs::Metadata::is_dir`]. #[inline] pub fn is_dir(&self) -> bool { self.file_type.is_dir() } /// Returns `true` if this metadata is for a regular file. /// /// This corresponds to [`std::fs::Metadata::is_file`]. #[inline] pub fn is_file(&self) -> bool { self.file_type.is_file() } /// Returns `true` if this metadata is for a symbolic link. /// /// This corresponds to [`std::fs::Metadata::is_symlink`]. #[inline] pub fn is_symlink(&self) -> bool { self.file_type.is_symlink() } /// Returns the size of the file, in bytes, this metadata is for. /// /// This corresponds to [`std::fs::Metadata::len`]. #[inline] pub const fn len(&self) -> u64 { self.len } /// Returns the permissions of the file this metadata is for. /// /// This corresponds to [`std::fs::Metadata::permissions`]. #[inline] pub fn permissions(&self) -> Permissions { self.permissions.clone() } /// Returns the last modification time listed in this metadata. /// /// This corresponds to [`std::fs::Metadata::modified`]. #[inline] pub fn modified(&self) -> io::Result { #[cfg(io_error_uncategorized)] { self.modified.ok_or_else(|| { io::Error::new( io::ErrorKind::Unsupported, "modified time metadata not available on this platform", ) }) } #[cfg(not(io_error_uncategorized))] { self.modified.ok_or_else(|| { io::Error::new( io::ErrorKind::Other, "modified time metadata not available on this platform", ) }) } } /// Returns the last access time of this metadata. /// /// This corresponds to [`std::fs::Metadata::accessed`]. #[inline] pub fn accessed(&self) -> io::Result { #[cfg(io_error_uncategorized)] { self.accessed.ok_or_else(|| { io::Error::new( io::ErrorKind::Unsupported, "accessed time metadata not available on this platform", ) }) } #[cfg(not(io_error_uncategorized))] { self.accessed.ok_or_else(|| { io::Error::new( io::ErrorKind::Other, "accessed time metadata not available on this platform", ) }) } } /// Returns the creation time listed in this metadata. /// /// This corresponds to [`std::fs::Metadata::created`]. #[inline] pub fn created(&self) -> io::Result { #[cfg(io_error_uncategorized)] { self.created.ok_or_else(|| { io::Error::new( io::ErrorKind::Unsupported, "created time metadata not available on this platform", ) }) } #[cfg(not(io_error_uncategorized))] { self.created.ok_or_else(|| { io::Error::new( io::ErrorKind::Other, "created time metadata not available on this platform", ) }) } } /// Determine if `self` and `other` refer to the same inode on the same /// device. #[cfg(any(not(windows), windows_by_handle))] pub(crate) fn is_same_file(&self, other: &Self) -> bool { self.ext.is_same_file(&other.ext) } /// `MetadataExt` requires nightly to be implemented, but we sometimes /// just need the file attributes. #[cfg(windows)] #[inline] pub(crate) fn file_attributes(&self) -> u32 { self.ext.file_attributes() } } /// Unix-specific extensions for [`MetadataExt`]. /// /// This corresponds to [`std::os::unix::fs::MetadataExt`]. #[cfg(any(unix, target_os = "vxworks"))] pub trait MetadataExt { /// Returns the ID of the device containing the file. fn dev(&self) -> u64; /// Returns the inode number. fn ino(&self) -> u64; /// Returns the rights applied to this file. fn mode(&self) -> u32; /// Returns the number of hard links pointing to this file. fn nlink(&self) -> u64; /// Returns the user ID of the owner of this file. fn uid(&self) -> u32; /// Returns the group ID of the owner of this file. fn gid(&self) -> u32; /// Returns the device ID of this file (if it is a special one). fn rdev(&self) -> u64; /// Returns the total size of this file in bytes. fn size(&self) -> u64; /// Returns the last access time of the file, in seconds since Unix Epoch. fn atime(&self) -> i64; /// Returns the last access time of the file, in nanoseconds since [`atime`]. fn atime_nsec(&self) -> i64; /// Returns the last modification time of the file, in seconds since Unix Epoch. fn mtime(&self) -> i64; /// Returns the last modification time of the file, in nanoseconds since [`mtime`]. fn mtime_nsec(&self) -> i64; /// Returns the last status change time of the file, in seconds since Unix Epoch. fn ctime(&self) -> i64; /// Returns the last status change time of the file, in nanoseconds since [`ctime`]. fn ctime_nsec(&self) -> i64; /// Returns the block size for filesystem I/O. fn blksize(&self) -> u64; /// Returns the number of blocks allocated to the file, in 512-byte units. fn blocks(&self) -> u64; #[cfg(target_os = "vxworks")] fn attrib(&self) -> u8; } /// WASI-specific extensions for [`MetadataExt`]. /// /// This corresponds to [`std::os::wasi::fs::MetadataExt`]. #[cfg(target_os = "wasi")] pub trait MetadataExt { /// Returns the ID of the device containing the file. fn dev(&self) -> u64; /// Returns the inode number. fn ino(&self) -> u64; /// Returns the number of hard links pointing to this file. fn nlink(&self) -> u64; /// Returns the total size of this file in bytes. fn size(&self) -> u64; /// Returns the last access time of the file, in seconds since Unix Epoch. fn atim(&self) -> u64; /// Returns the last modification time of the file, in seconds since Unix Epoch. fn mtim(&self) -> u64; /// Returns the last status change time of the file, in seconds since Unix Epoch. fn ctim(&self) -> u64; } /// Windows-specific extensions to [`Metadata`]. /// /// This corresponds to [`std::os::windows::fs::MetadataExt`]. #[cfg(windows)] pub trait MetadataExt { /// Returns the value of the `dwFileAttributes` field of this metadata. fn file_attributes(&self) -> u32; /// Returns the value of the `ftCreationTime` field of this metadata. fn creation_time(&self) -> u64; /// Returns the value of the `ftLastAccessTime` field of this metadata. fn last_access_time(&self) -> u64; /// Returns the value of the `ftLastWriteTime` field of this metadata. fn last_write_time(&self) -> u64; /// Returns the value of the `nFileSize{High,Low}` fields of this metadata. fn file_size(&self) -> u64; /// Returns the value of the `dwVolumeSerialNumber` field of this metadata. #[cfg(windows_by_handle)] fn volume_serial_number(&self) -> Option; /// Returns the value of the `nNumberOfLinks` field of this metadata. #[cfg(windows_by_handle)] fn number_of_links(&self) -> Option; /// Returns the value of the `nFileIndex{Low,High}` fields of this metadata. #[cfg(windows_by_handle)] fn file_index(&self) -> Option; } #[cfg(unix)] impl MetadataExt for Metadata { #[inline] fn dev(&self) -> u64 { crate::fs::MetadataExt::dev(&self.ext) } #[inline] fn ino(&self) -> u64 { crate::fs::MetadataExt::ino(&self.ext) } #[inline] fn mode(&self) -> u32 { crate::fs::MetadataExt::mode(&self.ext) } #[inline] fn nlink(&self) -> u64 { crate::fs::MetadataExt::nlink(&self.ext) } #[inline] fn uid(&self) -> u32 { crate::fs::MetadataExt::uid(&self.ext) } #[inline] fn gid(&self) -> u32 { crate::fs::MetadataExt::gid(&self.ext) } #[inline] fn rdev(&self) -> u64 { crate::fs::MetadataExt::rdev(&self.ext) } #[inline] fn size(&self) -> u64 { crate::fs::MetadataExt::size(&self.ext) } #[inline] fn atime(&self) -> i64 { crate::fs::MetadataExt::atime(&self.ext) } #[inline] fn atime_nsec(&self) -> i64 { crate::fs::MetadataExt::atime_nsec(&self.ext) } #[inline] fn mtime(&self) -> i64 { crate::fs::MetadataExt::mtime(&self.ext) } #[inline] fn mtime_nsec(&self) -> i64 { crate::fs::MetadataExt::mtime_nsec(&self.ext) } #[inline] fn ctime(&self) -> i64 { crate::fs::MetadataExt::ctime(&self.ext) } #[inline] fn ctime_nsec(&self) -> i64 { crate::fs::MetadataExt::ctime_nsec(&self.ext) } #[inline] fn blksize(&self) -> u64 { crate::fs::MetadataExt::blksize(&self.ext) } #[inline] fn blocks(&self) -> u64 { crate::fs::MetadataExt::blocks(&self.ext) } } #[cfg(target_os = "wasi")] impl MetadataExt for Metadata { #[inline] fn dev(&self) -> u64 { crate::fs::MetadataExt::dev(&self.ext) } #[inline] fn ino(&self) -> u64 { crate::fs::MetadataExt::ino(&self.ext) } #[inline] fn nlink(&self) -> u64 { crate::fs::MetadataExt::nlink(&self.ext) } #[inline] fn size(&self) -> u64 { crate::fs::MetadataExt::size(&self.ext) } #[inline] fn atim(&self) -> u64 { crate::fs::MetadataExt::atim(&self.ext) } #[inline] fn mtim(&self) -> u64 { crate::fs::MetadataExt::mtim(&self.ext) } #[inline] fn ctim(&self) -> u64 { crate::fs::MetadataExt::ctim(&self.ext) } } #[cfg(target_os = "vxworks")] impl MetadataExt for Metadata { #[inline] fn dev(&self) -> u64 { self.ext.dev() } #[inline] fn ino(&self) -> u64 { self.ext.ino() } #[inline] fn mode(&self) -> u32 { self.ext.mode() } #[inline] fn nlink(&self) -> u64 { self.ext.nlink() } #[inline] fn uid(&self) -> u32 { self.ext.uid() } #[inline] fn gid(&self) -> u32 { self.ext.gid() } #[inline] fn rdev(&self) -> u64 { self.ext.rdev() } #[inline] fn size(&self) -> u64 { self.ext.size() } #[inline] fn atime(&self) -> i64 { self.ext.atime() } #[inline] fn atime_nsec(&self) -> i64 { self.ext.atime_nsec() } #[inline] fn mtime(&self) -> i64 { self.ext.mtime() } #[inline] fn mtime_nsec(&self) -> i64 { self.ext.mtime_nsec() } #[inline] fn ctime(&self) -> i64 { self.ext.ctime() } #[inline] fn ctime_nsec(&self) -> i64 { self.ext.ctime_nsec() } #[inline] fn blksize(&self) -> u64 { self.ext.blksize() } #[inline] fn blocks(&self) -> u64 { self.ext.blocks() } } #[cfg(windows)] impl MetadataExt for Metadata { #[inline] fn file_attributes(&self) -> u32 { self.ext.file_attributes() } #[inline] fn creation_time(&self) -> u64 { self.ext.creation_time() } #[inline] fn last_access_time(&self) -> u64 { self.ext.last_access_time() } #[inline] fn last_write_time(&self) -> u64 { self.ext.last_write_time() } #[inline] fn file_size(&self) -> u64 { self.ext.file_size() } #[inline] #[cfg(windows_by_handle)] fn volume_serial_number(&self) -> Option { self.ext.volume_serial_number() } #[inline] #[cfg(windows_by_handle)] fn number_of_links(&self) -> Option { self.ext.number_of_links() } #[inline] #[cfg(windows_by_handle)] fn file_index(&self) -> Option { self.ext.file_index() } } /// Extension trait to allow `volume_serial_number` etc. to be exposed by /// the `cap-fs-ext` crate. /// /// This is hidden from the main API since this functionality isn't present in /// `std`. Use `cap_fs_ext::MetadataExt` instead of calling this directly. #[cfg(windows)] #[doc(hidden)] pub trait _WindowsByHandle { fn file_attributes(&self) -> u32; fn volume_serial_number(&self) -> Option; fn number_of_links(&self) -> Option; fn file_index(&self) -> Option; } cap-primitives-3.4.1/src/fs/mod.rs000064400000000000000000000067511046102023000151220ustar 00000000000000//! Filesystem utilities. #[cfg(racy_asserts)] #[macro_use] pub(crate) mod assert_same_file; mod access; mod canonicalize; mod copy; mod create_dir; mod dir_builder; mod dir_entry; mod dir_options; mod file; #[cfg(not(any(target_os = "android", target_os = "linux", windows)))] mod file_path_by_searching; mod file_type; mod follow_symlinks; mod hard_link; mod is_file_read_write; mod maybe_owned_file; mod metadata; mod open; mod open_ambient; mod open_dir; mod open_options; mod open_unchecked_error; mod permissions; mod read_dir; mod read_link; mod remove_dir; mod remove_dir_all; mod remove_file; mod remove_open_dir; mod rename; mod reopen; #[cfg(not(target_os = "wasi"))] mod set_permissions; mod set_times; mod stat; mod symlink; mod system_time_spec; pub(crate) mod errors; pub(crate) mod manually; pub(crate) mod via_parent; use maybe_owned_file::MaybeOwnedFile; #[cfg(not(any(target_os = "android", target_os = "linux", windows)))] pub(crate) use file_path_by_searching::file_path_by_searching; pub(crate) use open_unchecked_error::*; #[cfg(not(windows))] pub(crate) use super::rustix::fs::*; #[cfg(windows)] pub(crate) use super::windows::fs::*; #[cfg(not(windows))] pub(crate) use read_dir::{read_dir_nofollow, read_dir_unchecked}; pub use access::{access, AccessModes, AccessType}; pub use canonicalize::canonicalize; pub use copy::copy; pub use create_dir::create_dir; pub use dir_builder::*; pub use dir_entry::DirEntry; #[cfg(windows)] pub use dir_entry::_WindowsDirEntryExt; pub use dir_options::DirOptions; pub use file::FileExt; pub use file_type::FileType; #[cfg(any(unix, target_os = "vxworks", all(windows, windows_file_type_ext)))] pub use file_type::FileTypeExt; #[cfg(windows)] pub use file_type::_WindowsFileTypeExt; pub use follow_symlinks::FollowSymlinks; pub use hard_link::hard_link; pub use is_file_read_write::is_file_read_write; #[cfg(windows)] pub use metadata::_WindowsByHandle; pub use metadata::{Metadata, MetadataExt}; pub use open::open; pub use open_ambient::open_ambient; pub use open_dir::*; pub use open_options::*; pub use permissions::Permissions; #[cfg(unix)] pub use permissions::PermissionsExt; pub use read_dir::{read_base_dir, read_dir, ReadDir}; pub use read_link::{read_link, read_link_contents}; pub use remove_dir::remove_dir; pub use remove_dir_all::remove_dir_all; pub use remove_file::remove_file; pub use remove_open_dir::{remove_open_dir, remove_open_dir_all}; pub use rename::rename; pub use reopen::reopen; #[cfg(not(target_os = "wasi"))] pub use set_permissions::{set_permissions, set_symlink_permissions}; pub use set_times::{set_times, set_times_nofollow}; pub use stat::stat; #[cfg(not(windows))] pub use symlink::{symlink, symlink_contents}; #[cfg(windows)] pub use symlink::{symlink_dir, symlink_file}; pub use system_time_spec::SystemTimeSpec; #[cfg(racy_asserts)] fn map_result(result: &std::io::Result) -> Result { match result { Ok(t) => Ok(t.clone()), Err(e) => Err((e.kind(), e.to_string())), } } /// Test that `file_path` works on a few miscellaneous directory paths. #[test] fn dir_paths() { use crate::ambient_authority; for path in [std::env::current_dir().unwrap(), std::env::temp_dir()] { let dir = open_ambient_dir(&path, ambient_authority()).unwrap(); assert_eq!( file_path(&dir) .as_ref() .map(std::fs::canonicalize) .map(Result::unwrap), Some(std::fs::canonicalize(path).unwrap()) ); } } cap-primitives-3.4.1/src/fs/open.rs000064400000000000000000000072351046102023000153020ustar 00000000000000//! This defines `open`, the primary entrypoint to sandboxed file and directory //! opening. #[cfg(racy_asserts)] use crate::fs::{file_path, open_unchecked, stat_unchecked, Metadata}; use crate::fs::{open_impl, OpenOptions}; use std::path::Path; use std::{fs, io}; /// Perform an `openat`-like operation, ensuring that the resolution of the /// path never escapes the directory tree rooted at `start`. #[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] #[inline] pub fn open(start: &fs::File, path: &Path, options: &OpenOptions) -> io::Result { #[cfg(racy_asserts)] let stat_before = stat_unchecked(start, path, options.follow); // Call the underlying implementation. let result = open_impl(start, path, options); #[cfg(racy_asserts)] let stat_after = stat_unchecked(start, path, options.follow); #[cfg(racy_asserts)] check_open(start, path, options, &stat_before, &result, &stat_after); result } #[cfg(racy_asserts)] fn check_open( start: &fs::File, path: &Path, options: &OpenOptions, _stat_before: &io::Result, result: &io::Result, _stat_after: &io::Result, ) { let unchecked_result = open_unchecked( start, path, options .clone() .create(false) .create_new(false) .truncate(false), ); match (&result, &unchecked_result) { (Ok(result_file), Ok(unchecked_file)) => { assert_same_file!( &result_file, &unchecked_file, "path resolution inconsistency: start='{:?}', path='{}'", start, path.display(), ); } (Ok(result_file), Err(unchecked_error)) => { if unchecked_error.kind() == io::ErrorKind::PermissionDenied { assert!(options.create || options.create_new); } else { panic!( "unexpected success opening start='{:?}', path='{}'; expected {:?}; got {:?}", start, path.display(), unchecked_error, result_file ); } } (Err(result_error), Ok(_unchecked_file)) => match result_error.kind() { io::ErrorKind::PermissionDenied | io::ErrorKind::InvalidInput => (), io::ErrorKind::AlreadyExists if options.create_new => (), _ => panic!( "unexpected error opening start='{:?}', path='{}': {:?}", start, path.display(), result_error ), }, (Err(result_error), Err(_unchecked_error)) => match result_error.kind() { io::ErrorKind::PermissionDenied | io::ErrorKind::InvalidInput => (), _ => { /* TODO: Check error messages. let unchecked_error = unchecked_error.into(); assert_eq!(result_error.to_string(), unchecked_error.to_string()); assert_eq!(result_error.kind(), unchecked_error.kind()); */ } }, } // On operating systems which can tell us the path of a file descriptor, // assert that the start path is a parent of the result path. if let Ok(result_file) = &result { if let Some(result_path) = file_path(result_file) { if let Some(start_path) = file_path(start) { assert!( result_path.starts_with(start_path), "sandbox escape: start='{:?}' result='{}'", start, result_path.display() ); } } } } cap-primitives-3.4.1/src/fs/open_ambient.rs000064400000000000000000000011511046102023000167700ustar 00000000000000//! This defines `open_ambient`, for unsandboxed file opening. use crate::fs::{open_ambient_impl, OpenOptions}; use crate::AmbientAuthority; use std::path::Path; use std::{fs, io}; /// Open a file named by a bare path, using the host process' ambient /// authority. /// /// # Ambient Authority /// /// This function is not sandboxed and may trivially access any path that the /// host process has access to. #[inline] pub fn open_ambient( path: &Path, options: &OpenOptions, ambient_authority: AmbientAuthority, ) -> io::Result { Ok(open_ambient_impl(path, options, ambient_authority)?) } cap-primitives-3.4.1/src/fs/open_dir.rs000064400000000000000000000051071046102023000161340ustar 00000000000000//! This defines `open_dir`, a wrapper around `open` which can be used to open //! path as a directory. #[allow(unused_imports)] use crate::fs::open_unchecked; use crate::fs::{dir_options, open, open_ambient_dir_impl, readdir_options, FollowSymlinks}; use ambient_authority::AmbientAuthority; use std::path::{Component, Path}; use std::{fs, io}; /// Open a directory by performing an `openat`-like operation, /// ensuring that the resolution of the path never escapes /// the directory tree rooted at `start`. #[inline] pub fn open_dir(start: &fs::File, path: &Path) -> io::Result { open(start, path, &dir_options()) } /// Like `open_dir`, but additionally request the ability to read the directory /// entries. #[cfg(not(windows))] #[inline] pub(crate) fn open_dir_for_reading( start: &fs::File, path: &Path, follow: FollowSymlinks, ) -> io::Result { open(start, path, readdir_options().follow(follow)) } /// Similar to `open_dir`, but fails if the path names a symlink. #[inline] pub fn open_dir_nofollow(start: &fs::File, path: &Path) -> io::Result { open(start, path, dir_options().follow(FollowSymlinks::No)) } /// Open a directory by performing an unsandboxed `openat`-like operation. #[inline] #[allow(dead_code)] pub(crate) fn open_dir_unchecked(start: &fs::File, path: &Path) -> io::Result { open_unchecked(start, path, &dir_options()).map_err(Into::into) } /// Like `open_dir_unchecked`, but additionally request the ability to read the /// directory entries. #[inline] #[allow(dead_code)] pub(crate) fn open_dir_for_reading_unchecked( start: &fs::File, path: &Path, follow: FollowSymlinks, ) -> io::Result { open_unchecked(start, path, readdir_options().follow(follow)).map_err(Into::into) } /// Open a directory named by a bare path, using the host process' ambient /// authority. /// /// # Ambient Authority /// /// This function is not sandboxed and may trivially access any path that the /// host process has access to. #[inline] pub fn open_ambient_dir(path: &Path, ambient_authority: AmbientAuthority) -> io::Result { open_ambient_dir_impl(path, ambient_authority) } /// Open the parent directory of a given open directory, using the host /// process' ambient authority. /// /// # Ambient Authority /// /// This function accesses a path outside of the `start` directory subtree. #[inline] pub fn open_parent_dir( start: &fs::File, ambient_authority: AmbientAuthority, ) -> io::Result { let _ = ambient_authority; open_dir_unchecked(start, Component::ParentDir.as_ref()) } cap-primitives-3.4.1/src/fs/open_options.rs000064400000000000000000000363161046102023000170570ustar 00000000000000use crate::fs::{FollowSymlinks, ImplOpenOptionsExt}; /// Options and flags which can be used to configure how a file is opened. /// /// This corresponds to [`std::fs::OpenOptions`]. /// /// This `OpenOptions` has no `open` method. To open a file with an /// `OptionOptions`, first obtain a [`Dir`] containing the path, and then call /// [`Dir::open_with`]. /// /// [`Dir`]: https://docs.rs/cap-std/latest/cap_std/fs/struct.Dir.html /// [`Dir::open_with`]: https://docs.rs/cap-std/latest/cap_std/fs/struct.Dir.html#method.open_with /// ///
/// We need to define our own version because the libstd `OpenOptions` doesn't /// have public accessors that we can use. ///
#[derive(Debug, Clone)] pub struct OpenOptions { pub(crate) read: bool, pub(crate) write: bool, pub(crate) append: bool, pub(crate) truncate: bool, pub(crate) create: bool, pub(crate) create_new: bool, pub(crate) dir_required: bool, pub(crate) maybe_dir: bool, pub(crate) sync: bool, pub(crate) dsync: bool, pub(crate) rsync: bool, pub(crate) nonblock: bool, pub(crate) readdir_required: bool, pub(crate) follow: FollowSymlinks, #[cfg(any(unix, windows, target_os = "vxworks"))] pub(crate) ext: ImplOpenOptionsExt, } impl OpenOptions { /// Creates a blank new set of options ready for configuration. /// /// This corresponds to [`std::fs::OpenOptions::new`]. #[allow(clippy::new_without_default)] #[inline] pub const fn new() -> Self { Self { read: false, write: false, append: false, truncate: false, create: false, create_new: false, dir_required: false, maybe_dir: false, sync: false, dsync: false, rsync: false, nonblock: false, readdir_required: false, follow: FollowSymlinks::Yes, #[cfg(any(unix, windows, target_os = "vxworks"))] ext: ImplOpenOptionsExt::new(), } } /// Sets the option for read access. /// /// This corresponds to [`std::fs::OpenOptions::read`]. #[inline] pub fn read(&mut self, read: bool) -> &mut Self { self.read = read; self } /// Sets the option for write access. /// /// This corresponds to [`std::fs::OpenOptions::write`]. #[inline] pub fn write(&mut self, write: bool) -> &mut Self { self.write = write; self } /// Sets the option for the append mode. /// /// This corresponds to [`std::fs::OpenOptions::append`]. #[inline] pub fn append(&mut self, append: bool) -> &mut Self { self.append = append; self } /// Sets the option for truncating a previous file. /// /// This corresponds to [`std::fs::OpenOptions::truncate`]. #[inline] pub fn truncate(&mut self, truncate: bool) -> &mut Self { self.truncate = truncate; self } /// Sets the option to create a new file. /// /// This corresponds to [`std::fs::OpenOptions::create`]. #[inline] pub fn create(&mut self, create: bool) -> &mut Self { self.create = create; self } /// Sets the option to always create a new file. /// /// This corresponds to [`std::fs::OpenOptions::create_new`]. #[inline] pub fn create_new(&mut self, create_new: bool) -> &mut Self { self.create_new = create_new; self } /// Sets the option to enable or suppress following of symlinks. #[inline] pub(crate) fn follow(&mut self, follow: FollowSymlinks) -> &mut Self { self.follow = follow; self } /// Sets the option to enable an error if the opened object is not a /// directory. #[inline] pub(crate) fn dir_required(&mut self, dir_required: bool) -> &mut Self { self.dir_required = dir_required; self } /// Sets the option to disable an error if the opened object is a /// directory. #[inline] pub(crate) fn maybe_dir(&mut self, maybe_dir: bool) -> &mut Self { self.maybe_dir = maybe_dir; self } /// Requests write operations complete as defined by synchronized I/O file /// integrity completion. #[inline] pub(crate) fn sync(&mut self, enable: bool) -> &mut Self { self.sync = enable; self } /// Requests write operations complete as defined by synchronized I/O data /// integrity completion. #[inline] pub(crate) fn dsync(&mut self, enable: bool) -> &mut Self { self.dsync = enable; self } /// Requests read operations complete as defined by the level of integrity /// specified by `sync` and `dsync`. #[inline] pub(crate) fn rsync(&mut self, enable: bool) -> &mut Self { self.rsync = enable; self } /// Requests that I/O operations fail with `std::io::ErrorKind::WouldBlock` /// if they would otherwise block. /// /// This option is commonly not implemented for regular files, so blocking /// may still occur. #[inline] pub(crate) fn nonblock(&mut self, enable: bool) -> &mut Self { self.nonblock = enable; self } /// Sets the option to request the ability to read directory entries. #[inline] pub(crate) fn readdir_required(&mut self, readdir_required: bool) -> &mut Self { self.readdir_required = readdir_required; self } /// Wrapper to allow `follow` to be exposed by the `cap-fs-ext` crate. /// /// This is hidden from the main API since this functionality isn't present /// in `std`. Use `cap_fs_ext::OpenOptionsFollowExt` instead of calling /// this directly. #[doc(hidden)] #[inline] pub fn _cap_fs_ext_follow(&mut self, follow: FollowSymlinks) -> &mut Self { self.follow(follow) } /// Wrapper to allow `maybe_dir` to be exposed by the `cap-fs-ext` crate. /// /// This is hidden from the main API since this functionality isn't present /// in `std`. Use `cap_fs_ext::OpenOptionsMaybeDirExt` instead of /// calling this directly. #[doc(hidden)] #[inline] pub fn _cap_fs_ext_maybe_dir(&mut self, maybe_dir: bool) -> &mut Self { self.maybe_dir(maybe_dir) } /// Wrapper to allow `sync` to be exposed by the `cap-fs-ext` crate. /// /// This is hidden from the main API since this functionality isn't present /// in `std`. Use `cap_fs_ext::OpenOptionsSyncExt` instead of /// calling this directly. #[doc(hidden)] #[inline] pub fn _cap_fs_ext_sync(&mut self, enable: bool) -> &mut Self { self.sync(enable) } /// Wrapper to allow `dsync` to be exposed by the `cap-fs-ext` crate. /// /// This is hidden from the main API since this functionality isn't present /// in `std`. Use `cap_fs_ext::OpenOptionsSyncExt` instead of /// calling this directly. #[doc(hidden)] #[inline] pub fn _cap_fs_ext_dsync(&mut self, enable: bool) -> &mut Self { self.dsync(enable) } /// Wrapper to allow `rsync` to be exposed by the `cap-fs-ext` crate. /// /// This is hidden from the main API since this functionality isn't present /// in `std`. Use `cap_fs_ext::OpenOptionsSyncExt` instead of /// calling this directly. #[doc(hidden)] #[inline] pub fn _cap_fs_ext_rsync(&mut self, enable: bool) -> &mut Self { self.rsync(enable) } /// Wrapper to allow `nonblock` to be exposed by the `cap-fs-ext` crate. /// /// This is hidden from the main API since this functionality isn't present /// in `std`. Use `cap_fs_ext::OpenOptionsSyncExt` instead of /// calling this directly. #[doc(hidden)] #[inline] pub fn _cap_fs_ext_nonblock(&mut self, enable: bool) -> &mut Self { self.nonblock(enable) } } /// Unix-specific extensions to [`fs::OpenOptions`]. #[cfg(unix)] pub trait OpenOptionsExt { /// Sets the mode bits that a new file will be created with. fn mode(&mut self, mode: u32) -> &mut Self; /// Pass custom flags to the `flags` argument of `open`. fn custom_flags(&mut self, flags: i32) -> &mut Self; } /// WASI-specific extensions to [`fs::OpenOptions`]. #[cfg(target_os = "wasi")] pub trait OpenOptionsExt { /// Pass custom `dirflags` argument to `path_open`. fn lookup_flags(&mut self, flags: u32) -> &mut Self; /// Indicates whether `OpenOptions` must open a directory or not. fn directory(&mut self, dir: bool) -> &mut Self; /// Indicates whether `__WASI_FDFLAG_DSYNC` is passed in the `fs_flags` /// field of `path_open`. fn dsync(&mut self, dsync: bool) -> &mut Self; /// Indicates whether `__WASI_FDFLAG_NONBLOCK` is passed in the `fs_flags` /// field of `path_open`. fn nonblock(&mut self, nonblock: bool) -> &mut Self; /// Indicates whether `__WASI_FDFLAG_RSYNC` is passed in the `fs_flags` /// field of `path_open`. fn rsync(&mut self, rsync: bool) -> &mut Self; /// Indicates whether `__WASI_FDFLAG_SYNC` is passed in the `fs_flags` /// field of `path_open`. fn sync(&mut self, sync: bool) -> &mut Self; /// Indicates the value that should be passed in for the `fs_rights_base` /// parameter of `path_open`. fn fs_rights_base(&mut self, rights: u64) -> &mut Self; /// Indicates the value that should be passed in for the /// `fs_rights_inheriting` parameter of `path_open`. fn fs_rights_inheriting(&mut self, rights: u64) -> &mut Self; /// Open a file or directory. fn open_at>( &self, file: &std::fs::File, path: P, ) -> std::io::Result; } /// Windows-specific extensions to [`fs::OpenOptions`]. #[cfg(windows)] pub trait OpenOptionsExt { /// Overrides the `dwDesiredAccess` argument to the call to [`CreateFile`] /// with the specified value. fn access_mode(&mut self, access: u32) -> &mut Self; /// Overrides the `dwShareMode` argument to the call to [`CreateFile`] with /// the specified value. fn share_mode(&mut self, val: u32) -> &mut Self; /// Sets extra flags for the `dwFileFlags` argument to the call to /// [`CreateFile2`] to the specified value (or combines it with /// `attributes` and `security_qos_flags` to set the `dwFlagsAndAttributes` /// for [`CreateFile`]). /// /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea /// [`CreateFile2`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2 fn custom_flags(&mut self, flags: u32) -> &mut Self; /// Sets the `dwFileAttributes` argument to the call to [`CreateFile2`] to /// the specified value (or combines it with `custom_flags` and /// `security_qos_flags` to set the `dwFlagsAndAttributes` for /// [`CreateFile`]). /// /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea /// [`CreateFile2`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2 fn attributes(&mut self, val: u32) -> &mut Self; /// Sets the `dwSecurityQosFlags` argument to the call to [`CreateFile2`] to /// the specified value (or combines it with `custom_flags` and `attributes` /// to set the `dwFlagsAndAttributes` for [`CreateFile`]). /// /// [`CreateFile`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea /// [`CreateFile2`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfile2 /// [Impersonation Levels]: /// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-security_impersonation_level fn security_qos_flags(&mut self, flags: u32) -> &mut Self; } #[cfg(unix)] impl OpenOptionsExt for OpenOptions { #[inline] fn mode(&mut self, mode: u32) -> &mut Self { self.ext.mode(mode); self } #[inline] fn custom_flags(&mut self, flags: i32) -> &mut Self { self.ext.custom_flags(flags); self } } #[cfg(target_os = "wasi")] impl OpenOptionsExt for OpenOptions { fn lookup_flags(&mut self, _: u32) -> &mut Self { todo!() } fn directory(&mut self, dir_required: bool) -> &mut Self { self.dir_required = dir_required; self } fn dsync(&mut self, _: bool) -> &mut Self { todo!() } fn nonblock(&mut self, _: bool) -> &mut Self { todo!() } fn rsync(&mut self, _: bool) -> &mut Self { todo!() } fn sync(&mut self, _: bool) -> &mut Self { todo!() } fn fs_rights_base(&mut self, _: u64) -> &mut Self { todo!() } fn fs_rights_inheriting(&mut self, _: u64) -> &mut Self { todo!() } fn open_at

(&self, dirfd: &std::fs::File, path: P) -> Result where P: AsRef, { crate::fs::open(dirfd, path.as_ref(), self) } } #[cfg(target_os = "vxworks")] impl OpenOptionsExt for OpenOptions { #[inline] fn mode(&mut self, mode: u32) -> &mut Self { self.ext.mode(mode); self } #[inline] fn custom_flags(&mut self, flags: i32) -> &mut Self { self.ext.custom_flags(flags); self } } #[cfg(windows)] impl OpenOptionsExt for OpenOptions { #[inline] fn access_mode(&mut self, access: u32) -> &mut Self { self.ext.access_mode(access); self } /// To prevent race conditions on Windows, handles for directories must be /// opened without `FILE_SHARE_DELETE`. #[inline] fn share_mode(&mut self, val: u32) -> &mut Self { self.ext.share_mode(val); self } #[inline] fn custom_flags(&mut self, flags: u32) -> &mut Self { self.ext.custom_flags(flags); self } #[inline] fn attributes(&mut self, val: u32) -> &mut Self { self.ext.attributes(val); self } #[inline] fn security_qos_flags(&mut self, flags: u32) -> &mut Self { self.ext.security_qos_flags(flags); self } } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for OpenOptions { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { use arbitrary::Arbitrary; let (read, write) = match u.int_in_range(0..=2)? { 0 => (true, false), 1 => (false, true), 2 => (true, true), _ => panic!(), }; // TODO: `OpenOptionsExt` options. Ok(Self::new() .read(read) .write(write) .create(::arbitrary(u)?) .append(::arbitrary(u)?) .truncate(::arbitrary(u)?) .create(::arbitrary(u)?) .create_new(::arbitrary(u)?) .dir_required(::arbitrary(u)?) .maybe_dir(::arbitrary(u)?) .sync(::arbitrary(u)?) .dsync(::arbitrary(u)?) .rsync(::arbitrary(u)?) .nonblock(::arbitrary(u)?) .readdir_required(::arbitrary(u)?) .follow(::arbitrary(u)?) .clone()) } } cap-primitives-3.4.1/src/fs/open_unchecked_error.rs000064400000000000000000000014511046102023000205160ustar 00000000000000use std::io; #[derive(Debug)] pub(crate) enum OpenUncheckedError { Other(io::Error), Symlink(io::Error, SymlinkKind), NotFound(io::Error), } #[cfg(not(windows))] pub(crate) type SymlinkKind = (); #[cfg(windows)] #[derive(Debug)] pub(crate) enum SymlinkKind { File, Dir, } impl OpenUncheckedError { #[allow(dead_code)] pub(crate) fn kind(&self) -> io::ErrorKind { match self { Self::Other(err) | Self::Symlink(err, _) | Self::NotFound(err) => err.kind(), } } } impl From for io::Error { fn from(error: OpenUncheckedError) -> Self { match error { OpenUncheckedError::Other(err) | OpenUncheckedError::Symlink(err, _) | OpenUncheckedError::NotFound(err) => err, } } } cap-primitives-3.4.1/src/fs/permissions.rs000064400000000000000000000100601046102023000167020ustar 00000000000000#[cfg(not(windows))] use crate::fs::ImplPermissionsExt; #[cfg(unix)] use rustix::fs::RawMode; use std::{fs, io}; /// Representation of the various permissions on a file. /// /// This corresponds to [`std::fs::Permissions`]. /// ///

/// We need to define our own version because the libstd `Permissions` doesn't /// have a public constructor that we can use. ///
#[derive(Debug, Clone, Eq, PartialEq)] pub struct Permissions { pub(crate) readonly: bool, #[cfg(any(unix, target_os = "vxworks"))] pub(crate) ext: ImplPermissionsExt, } impl Permissions { /// Constructs a new instance of `Self` from the given /// `std::fs::Permissions`. #[inline] pub fn from_std(std: fs::Permissions) -> Self { Self { readonly: std.readonly(), #[cfg(any(unix, target_os = "vxworks"))] ext: ImplPermissionsExt::from_std(std), } } /// Consumes `self` and produces a new instance of `std::fs::Permissions`. /// ///
/// The `file` parameter works around the fact that we can't construct a /// `Permissions` object ourselves on Windows. ///
#[inline] pub fn into_std(self, file: &fs::File) -> io::Result { self._into_std(file) } #[cfg(unix)] #[inline] #[allow(clippy::unnecessary_wraps)] fn _into_std(self, _file: &fs::File) -> io::Result { use std::os::unix::fs::PermissionsExt; Ok(fs::Permissions::from_mode(crate::fs::PermissionsExt::mode( &self.ext, ))) } #[cfg(target_os = "wasi")] #[inline] #[allow(clippy::unnecessary_wraps)] fn _into_std(self, file: &fs::File) -> io::Result { let mut permissions = file.metadata()?.permissions(); permissions.set_readonly(self.readonly()); Ok(permissions) } #[cfg(windows)] #[inline] fn _into_std(self, file: &fs::File) -> io::Result { let mut permissions = file.metadata()?.permissions(); permissions.set_readonly(self.readonly()); Ok(permissions) } /// Returns `true` if these permissions describe a readonly (unwritable) /// file. /// /// This corresponds to [`std::fs::Permissions::readonly`]. #[inline] pub const fn readonly(&self) -> bool { self.readonly } /// Modifies the readonly flag for this set of permissions. /// /// This corresponds to [`std::fs::Permissions::set_readonly`]. #[inline] pub fn set_readonly(&mut self, readonly: bool) { self.readonly = readonly; #[cfg(any(unix, target_os = "vxworks"))] self.ext.set_readonly(readonly); } } /// Unix-specific extensions to [`Permissions`]. #[cfg(unix)] pub trait PermissionsExt { /// Returns the underlying raw `st_mode` bits that contain the standard /// Unix permissions for this file. fn mode(&self) -> u32; /// Sets the underlying raw bits for this set of permissions. fn set_mode(&mut self, mode: u32); /// Creates a new instance of `Permissions` from the given set of Unix /// permission bits. fn from_mode(mode: u32) -> Self; } #[cfg(unix)] impl PermissionsExt for Permissions { #[inline] fn mode(&self) -> u32 { crate::fs::PermissionsExt::mode(&self.ext) } #[inline] fn set_mode(&mut self, mode: u32) { crate::fs::PermissionsExt::set_mode(&mut self.ext, mode) } #[inline] fn from_mode(mode: u32) -> Self { Self { readonly: ImplPermissionsExt::readonly(mode as RawMode), ext: crate::fs::PermissionsExt::from_mode(mode), } } } #[cfg(target_os = "vxworks")] impl PermissionsExt for Permissions { #[inline] fn mode(&self) -> u32 { self.ext.mode() } #[inline] fn set_mode(&mut self, mode: u32) { self.ext.set_mode(mode) } #[inline] fn from_mode(mode: u32) -> Self { Self { readonly: ImplPermissionsExt::readonly(mode), ext: ImplPermissionsExt::from(mode), } } } cap-primitives-3.4.1/src/fs/read_dir.rs000064400000000000000000000036561046102023000161150ustar 00000000000000use crate::fs::{DirEntry, FollowSymlinks, ReadDirInner}; use std::path::Path; use std::{fmt, fs, io}; /// Construct a `ReadDir` to iterate over the contents of a directory, /// ensuring that the resolution of the path never escapes the directory /// tree rooted at `start`. #[inline] pub fn read_dir(start: &fs::File, path: &Path) -> io::Result { Ok(ReadDir { inner: ReadDirInner::new(start, path, FollowSymlinks::Yes)?, }) } /// Like `read_dir`, but fails if `path` names a symlink. #[inline] #[cfg(not(windows))] pub(crate) fn read_dir_nofollow(start: &fs::File, path: &Path) -> io::Result { Ok(ReadDir { inner: ReadDirInner::new(start, path, FollowSymlinks::No)?, }) } /// Like `read_dir` but operates on the base directory itself, rather than /// on a path based on it. #[inline] pub fn read_base_dir(start: &fs::File) -> io::Result { Ok(ReadDir { inner: ReadDirInner::read_base_dir(start)?, }) } /// Like `read_dir`, but doesn't perform sandboxing. #[inline] #[cfg(not(windows))] pub(crate) fn read_dir_unchecked( start: &fs::File, path: &Path, follow: FollowSymlinks, ) -> io::Result { Ok(ReadDir { inner: ReadDirInner::new_unchecked(start, path, follow)?, }) } /// Iterator over the entries in a directory. /// /// This corresponds to [`std::fs::ReadDir`]. /// /// There is no `from_std` method, as `std::fs::ReadDir` doesn't provide a way /// to construct a `ReadDir` without opening directories by ambient paths. pub struct ReadDir { pub(crate) inner: ReadDirInner, } impl Iterator for ReadDir { type Item = io::Result; #[inline] fn next(&mut self) -> Option { self.inner .next() .map(|inner| inner.map(|inner| DirEntry { inner })) } } impl fmt::Debug for ReadDir { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.inner.fmt(f) } } cap-primitives-3.4.1/src/fs/read_link.rs000064400000000000000000000071131046102023000162640ustar 00000000000000//! This defines `read_link`, the primary entrypoint to sandboxed symbolic link //! dereferencing. use crate::fs::{errors, read_link_impl}; #[cfg(racy_asserts)] use crate::fs::{map_result, read_link_unchecked, stat, FollowSymlinks}; use std::path::{Path, PathBuf}; use std::{fs, io}; /// Perform a `readlinkat`-like operation, ensuring that the resolution of the /// link path never escapes the directory tree rooted at `start`. #[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] #[inline] pub fn read_link_contents(start: &fs::File, path: &Path) -> io::Result { // Call the underlying implementation. let result = read_link_impl(start, path); #[cfg(racy_asserts)] let unchecked = read_link_unchecked(start, path, PathBuf::new()); #[cfg(racy_asserts)] check_read_link(start, path, &result, &unchecked); result } /// Perform a `readlinkat`-like operation, ensuring that the resolution of the /// path never escapes the directory tree rooted at `start`, and also verifies /// that the link target is not absolute. #[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] #[inline] pub fn read_link(start: &fs::File, path: &Path) -> io::Result { // Call the underlying implementation. let result = read_link_contents(start, path); // Don't allow reading symlinks to absolute paths. This isn't strictly // necessary to preserve the sandbox, since `open` will refuse to follow // absolute paths in any case. However, it is useful to enforce this // restriction to avoid leaking information about the host filesystem // outside the sandbox. if let Ok(path) = &result { if path.has_root() { return Err(errors::escape_attempt()); } } result } #[cfg(racy_asserts)] #[allow(clippy::enum_glob_use)] fn check_read_link( start: &fs::File, path: &Path, result: &io::Result, unchecked: &io::Result, ) { use io::ErrorKind::*; match (map_result(result), map_result(unchecked)) { (Ok(target), Ok(unchecked_target)) => { assert_eq!(target, unchecked_target); } (Err((PermissionDenied, message)), _) => { match map_result(&stat(start, path, FollowSymlinks::No)) { Err((PermissionDenied, canon_message)) => { assert_eq!(message, canon_message); } _ => panic!("read_link failed where canonicalize succeeded"), } } (Err((_kind, _message)), Err((_unchecked_kind, _unchecked_message))) => { /* TODO: Check error messages. assert_eq!(kind, unchecked_kind); assert_eq!(message, unchecked_message); */ } other => panic!( "unexpected result from read_link start='{:?}', path='{}':\n{:#?}", start, path.display(), other, ), } } #[cfg(not(windows))] #[test] fn test_read_link_contents() { use io_lifetimes::AsFilelike; let td = cap_tempfile::tempdir(cap_tempfile::ambient_authority()).unwrap(); let td_view = &td.as_filelike_view::(); let valid = [ "relative/path", "/some/absolute/path", "/", "../", "basepath", ]; for case in valid { let linkname = Path::new("linkname"); crate::fs::symlink_contents(case, td_view, linkname).unwrap(); let contents = crate::fs::read_link_contents(td_view, linkname).unwrap(); assert_eq!(contents.to_str().unwrap(), case); crate::fs::remove_file(td_view, linkname).unwrap(); } } cap-primitives-3.4.1/src/fs/remove_dir.rs000064400000000000000000000122551046102023000164720ustar 00000000000000//! This defines `remove_dir`, the primary entrypoint to sandboxed file //! removal. use crate::fs::remove_dir_impl; #[cfg(racy_asserts)] use crate::fs::{ manually, map_result, remove_dir_unchecked, stat_unchecked, FollowSymlinks, Metadata, }; use std::path::Path; use std::{fs, io}; /// Perform a `rmdirat`-like operation, ensuring that the resolution of the /// path never escapes the directory tree rooted at `start`. #[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] #[inline] pub fn remove_dir(start: &fs::File, path: &Path) -> io::Result<()> { #[cfg(racy_asserts)] let stat_before = stat_unchecked(start, path, FollowSymlinks::No); // Call the underlying implementation. let result = remove_dir_impl(start, path); #[cfg(racy_asserts)] let stat_after = stat_unchecked(start, path, FollowSymlinks::No); #[cfg(racy_asserts)] check_remove_dir(start, path, &stat_before, &result, &stat_after); result } #[cfg(racy_asserts)] #[allow(clippy::enum_glob_use)] fn check_remove_dir( start: &fs::File, path: &Path, stat_before: &io::Result, result: &io::Result<()>, stat_after: &io::Result, ) { use io::ErrorKind::*; match ( map_result(stat_before), map_result(result), map_result(stat_after), ) { (Ok(metadata), Ok(()), Err((NotFound, _))) => { // TODO: Check that the path was inside the sandbox. assert!(metadata.is_dir()); } (Err((Other, _)), Ok(()), Err((NotFound, _))) => { // TODO: Check that the path was inside the sandbox. } (_, Err((InvalidInput, _)), _) => { // `remove_dir(".")` apparently returns `EINVAL` } (_, Err((kind, message)), _) => { match map_result(&manually::canonicalize_with( start, path, FollowSymlinks::No, )) { Ok(canon) => match map_result(&remove_dir_unchecked(start, &canon)) { Err((_unchecked_kind, _unchecked_message)) => { /* TODO: Check error messages. assert_eq!( kind, unchecked_kind, "unexpected error kind from remove_dir start='{:?}', \ path='{}':\nstat_before={:#?}\nresult={:#?}\nstat_after={:#?}", start, path.display(), stat_before, result, stat_after ); assert_eq!(message, unchecked_message); */ } _ => { // TODO: Checking in the case it does end with ".". if !path.to_string_lossy().ends_with(".") { panic!( "unsandboxed remove_dir success on start={:?} path={:?}; expected \ {:?}: {}", start, path, kind, message ); } } }, Err((_canon_kind, _canon_message)) => { /* TODO: Check error messages. assert_eq!(kind, canon_kind, "'{}' vs '{}'", message, canon_message); assert_eq!(message, canon_message); */ } } } other => panic!( "inconsistent remove_dir checks: start='{:?}' path='{}':\n{:#?}", start, path.display(), other, ), } match stat_after { Ok(unchecked_metadata) => match &result { Ok(()) => panic!( "file still exists after remove_dir start='{:?}', path='{}'", start, path.display() ), Err(e) => match e.kind() { #[cfg(io_error_more)] io::ErrorKind::NotADirectory => assert!(!unchecked_metadata.is_dir()), #[cfg(io_error_more)] io::ErrorKind::DirectoryNotEmpty => (), io::ErrorKind::PermissionDenied | io::ErrorKind::InvalidInput // `remove_dir(".")` apparently returns `EINVAL` | io::ErrorKind::Other => (), // directory not empty, among other things _ => panic!( "unexpected error remove_dir'ing start='{:?}', path='{}': {:?}", start, path.display(), e ), }, }, Err(_unchecked_error) => match &result { Ok(()) => (), Err(result_error) => match result_error.kind() { io::ErrorKind::PermissionDenied => (), _ => { /* TODO: Check error messages. assert_eq!(result_error.to_string(), unchecked_error.to_string()); assert_eq!(result_error.kind(), unchecked_error.kind()); */ } }, }, } } cap-primitives-3.4.1/src/fs/remove_dir_all.rs000064400000000000000000000003701046102023000173150ustar 00000000000000use crate::fs::remove_dir_all_impl; use std::path::Path; use std::{fs, io}; /// Removes a directory and all of its contents. #[inline] pub fn remove_dir_all(start: &fs::File, path: &Path) -> io::Result<()> { remove_dir_all_impl(start, path) } cap-primitives-3.4.1/src/fs/remove_file.rs000064400000000000000000000106151046102023000166310ustar 00000000000000//! This defines `remove_file`, the primary entrypoint to sandboxed file //! removal. use crate::fs::remove_file_impl; #[cfg(racy_asserts)] use crate::fs::{ manually, map_result, remove_file_unchecked, stat_unchecked, FollowSymlinks, Metadata, }; use std::path::Path; use std::{fs, io}; /// Perform a `remove_fileat`-like operation, ensuring that the resolution of /// the path never escapes the directory tree rooted at `start`. #[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] #[inline] pub fn remove_file(start: &fs::File, path: &Path) -> io::Result<()> { #[cfg(racy_asserts)] let stat_before = stat_unchecked(start, path, FollowSymlinks::No); // Call the underlying implementation. let result = remove_file_impl(start, path); #[cfg(racy_asserts)] let stat_after = stat_unchecked(start, path, FollowSymlinks::No); #[cfg(racy_asserts)] check_remove_file(start, path, &stat_before, &result, &stat_after); result } #[cfg(racy_asserts)] #[allow(clippy::enum_glob_use)] fn check_remove_file( start: &fs::File, path: &Path, stat_before: &io::Result, result: &io::Result<()>, stat_after: &io::Result, ) { use io::ErrorKind::*; match ( map_result(stat_before), map_result(result), map_result(stat_after), ) { (Ok(metadata), Ok(()), Err((NotFound, _))) => { // TODO: Check that the path was inside the sandbox. assert!(!metadata.is_dir()); } (Err((Other, _)), Ok(()), Err((NotFound, _))) => { // TODO: Check that the path was inside the sandbox. } (_, Err((_kind, _message)), _) => { match map_result(&manually::canonicalize_with( start, path, FollowSymlinks::No, )) { Ok(canon) => match map_result(&remove_file_unchecked(start, &canon)) { Err((_unchecked_kind, _unchecked_message)) => { /* TODO: Check error messages. assert_eq!( kind, unchecked_kind, "unexpected error kind from remove_file start='{:?}', \ path='{}':\nstat_before={:#?}\nresult={:#?}\nstat_after={:#?}", start, path.display(), stat_before, result, stat_after ); assert_eq!(message, unchecked_message); */ } _ => panic!("unsandboxed remove_file success"), }, Err((_canon_kind, _canon_message)) => { /* TODO: Check error messages. assert_eq!(kind, canon_kind, "'{}' vs '{}'", message, canon_message); assert_eq!(message, canon_message); */ } } } other => panic!( "inconsistent remove_file checks: start='{:?}' path='{}':\n{:#?}", start, path.display(), other, ), } match (result, stat_after) { (Ok(()), Ok(_unchecked_metadata)) => panic!( "file still exists after remove_file start='{:?}', path='{}'", start, path.display() ), (Err(e), Ok(unchecked_metadata)) => match e.kind() { #[cfg(io_error_more)] io::ErrorKind::IsADirectory => assert!(unchecked_metadata.is_dir()), io::ErrorKind::PermissionDenied => (), #[cfg(not(io_error_more))] io::ErrorKind::Other if unchecked_metadata.is_dir() => (), _ => panic!( "unexpected error removing file start='{:?}', path='{}': {:?}", start, path.display(), e ), }, (Ok(()), Err(_unchecked_error)) => (), (Err(result_error), Err(_unchecked_error)) => match result_error.kind() { io::ErrorKind::PermissionDenied => (), _ => { /* TODO: Check error messages. assert_eq!(result_error.to_string(), unchecked_error.to_string()); assert_eq!(result_error.kind(), unchecked_error.kind()); */ } }, } } cap-primitives-3.4.1/src/fs/remove_open_dir.rs000064400000000000000000000007761046102023000175200ustar 00000000000000use crate::fs::{remove_open_dir_all_impl, remove_open_dir_impl}; use std::{fs, io}; /// Given an open directory handle, delete the directory. #[inline] pub fn remove_open_dir(dir: fs::File) -> io::Result<()> { remove_open_dir_impl(dir) } /// Given an open directory handle, recursively delete the contents of the /// directory plus the directory itself. #[allow(clippy::module_name_repetitions)] #[inline] pub fn remove_open_dir_all(dir: fs::File) -> io::Result<()> { remove_open_dir_all_impl(dir) } cap-primitives-3.4.1/src/fs/rename.rs000064400000000000000000000116201046102023000156010ustar 00000000000000//! This defines `rename`, the primary entrypoint to sandboxed renaming. #[cfg(all(racy_asserts, not(windows)))] use crate::fs::append_dir_suffix; use crate::fs::rename_impl; use std::path::Path; use std::{fs, io}; #[cfg(racy_asserts)] use { crate::fs::{ manually, map_result, path_requires_dir, rename_unchecked, stat_unchecked, FollowSymlinks, Metadata, }, std::path::PathBuf, }; /// Perform a `renameat`-like operation, ensuring that the resolution of both /// the old and new paths never escape the directory tree rooted at their /// respective starts. #[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] #[inline] pub fn rename( old_start: &fs::File, old_path: &Path, new_start: &fs::File, new_path: &Path, ) -> io::Result<()> { #[cfg(racy_asserts)] let (old_metadata_before, new_metadata_before) = ( stat_unchecked(old_start, old_path, FollowSymlinks::No), stat_unchecked(new_start, new_path, FollowSymlinks::No), ); // Call the underlying implementation. let result = rename_impl(old_start, old_path, new_start, new_path); #[cfg(racy_asserts)] let (old_metadata_after, new_metadata_after) = ( stat_unchecked(old_start, old_path, FollowSymlinks::No), stat_unchecked(new_start, new_path, FollowSymlinks::No), ); #[cfg(racy_asserts)] check_rename( old_start, old_path, new_start, new_path, &old_metadata_before, &new_metadata_before, &result, &old_metadata_after, &new_metadata_after, ); result } #[cfg(racy_asserts)] #[allow(clippy::too_many_arguments)] #[allow(clippy::enum_glob_use)] fn check_rename( old_start: &fs::File, old_path: &Path, new_start: &fs::File, new_path: &Path, old_metadata_before: &io::Result, new_metadata_before: &io::Result, result: &io::Result<()>, old_metadata_after: &io::Result, new_metadata_after: &io::Result, ) { use io::ErrorKind::*; match ( map_result(old_metadata_before), map_result(new_metadata_before), map_result(result), map_result(old_metadata_after), map_result(new_metadata_after), ) { ( Ok(old_metadata_before), Err((NotFound, _)), Ok(()), Err((NotFound, _)), Ok(new_metadata_after), ) => { assert_same_file_metadata!(&old_metadata_before, &new_metadata_after); } (_, Ok(new_metadata_before), Err((AlreadyExists, _)), _, Ok(new_metadata_after)) => { assert_same_file_metadata!(&new_metadata_before, &new_metadata_after); } (_, _, Err((kind, message)), _, _) => match ( map_result(&canonicalize_for_rename(old_start, old_path)), map_result(&canonicalize_for_rename(new_start, new_path)), ) { (Ok(old_canon), Ok(new_canon)) => match map_result(&rename_unchecked( old_start, &old_canon, new_start, &new_canon, )) { Err((_unchecked_kind, _unchecked_message)) => { /* TODO: Check error messages. assert_eq!(kind, unchecked_kind); assert_eq!(message, unchecked_message); */ } other => panic!( "unsandboxed rename success:\n{:#?}\n{:?} {:?}", other, kind, message ), }, (Err((_old_canon_kind, _old_canon_message)), _) => { /* TODO: Check error messages. assert_eq!(kind, old_canon_kind); assert_eq!(message, old_canon_message); */ } (_, Err((_new_canon_kind, _new_canon_message))) => { /* TODO: Check error messages. assert_eq!(kind, new_canon_kind); assert_eq!(message, new_canon_message); */ } }, _other => { /* TODO: Check error messages. panic!( "inconsistent rename checks: old_start='{:?}', old_path='{}', new_start='{:?}', \ new_path='{}':\n{:#?}", old_start, old_path.display(), new_start, new_path.display(), other ) */ } } } #[cfg(racy_asserts)] fn canonicalize_for_rename(start: &fs::File, path: &Path) -> io::Result { let mut canon = manually::canonicalize_with(start, path, FollowSymlinks::No)?; // Rename on paths ending in `.` or `/.` fails due to the directory already // being open. Ensure that this happens on the canonical paths too. #[cfg(not(windows))] if path_requires_dir(path) { canon = append_dir_suffix(path.to_path_buf()); assert!(path_requires_dir(&canon)); } Ok(canon) } cap-primitives-3.4.1/src/fs/reopen.rs000064400000000000000000000022341046102023000156230ustar 00000000000000//! Re-open a `fs::File` to produce an independent handle. use crate::fs::{is_file_read_write, is_same_file, reopen_impl, OpenOptions}; use std::{fs, io}; /// Re-open an `fs::File` to produce an independent handle. /// /// This operation isn't supported by all operating systems in all /// circumstances, or in some operating systems in any circumstances, /// so it may return an `io::ErrorKind::Other` error if the file /// cannot be reopened. #[inline] pub fn reopen(file: &fs::File, options: &OpenOptions) -> io::Result { let (read, write) = is_file_read_write(file)?; // Don't grant more rights than the original file had. And don't allow // it to create a file. if options.create || options.create_new || (!read && options.read) || (!write && (options.write || options.append || options.truncate)) { return Err(io::Error::new( io::ErrorKind::PermissionDenied, "Couldn't reopen file", )); } let new = reopen_impl(file, options)?; if !is_same_file(file, &new)? { return Err(io::Error::new(io::ErrorKind::Other, "Couldn't reopen file")); } Ok(new) } cap-primitives-3.4.1/src/fs/set_permissions.rs000064400000000000000000000053721046102023000175670ustar 00000000000000//! This defines `set_permissions`, the primary entrypoint to sandboxed //! filesystem permissions modification. #[cfg(racy_asserts)] use crate::fs::{map_result, stat, stat_unchecked, FollowSymlinks, Metadata}; use crate::fs::{set_permissions_impl, set_symlink_permissions_impl, Permissions}; use std::path::Path; use std::{fs, io}; /// Perform a `chmodat`-like operation, ensuring that the resolution of the /// path never escapes the directory tree rooted at `start`. #[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] #[inline] pub fn set_permissions(start: &fs::File, path: &Path, perm: Permissions) -> io::Result<()> { #[cfg(racy_asserts)] let perm_clone = perm.clone(); #[cfg(racy_asserts)] let stat_before = stat(start, path, FollowSymlinks::Yes); // Call the underlying implementation. let result = set_permissions_impl(start, path, perm); #[cfg(racy_asserts)] let stat_after = stat_unchecked(start, path, FollowSymlinks::Yes); #[cfg(racy_asserts)] check_set_permissions(start, path, perm_clone, &stat_before, &result, &stat_after); result } /// Perform a `chmodat`-like operation, ensuring that the resolution of the /// path never escapes the directory tree rooted at `start`, without following /// symlinks. #[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] #[inline] pub fn set_symlink_permissions(start: &fs::File, path: &Path, perm: Permissions) -> io::Result<()> { #[cfg(racy_asserts)] let perm_clone = perm.clone(); #[cfg(racy_asserts)] let stat_before = stat(start, path, FollowSymlinks::No); // Call the underlying implementation. let result = set_symlink_permissions_impl(start, path, perm); #[cfg(racy_asserts)] let stat_after = stat_unchecked(start, path, FollowSymlinks::No); #[cfg(racy_asserts)] check_set_permissions(start, path, perm_clone, &stat_before, &result, &stat_after); result } #[cfg(racy_asserts)] fn check_set_permissions( start: &fs::File, path: &Path, perm: Permissions, stat_before: &io::Result, result: &io::Result<()>, stat_after: &io::Result, ) { match ( map_result(stat_before), map_result(result), map_result(stat_after), ) { (Ok(_), Ok(()), Ok(metadata)) => { assert_eq!(perm, metadata.permissions()); } (Ok(metadata_before), Err(_), Ok(metadata_after)) => { assert_eq!(metadata_before.permissions(), metadata_after.permissions()); } // TODO: Check error messages (Err(_), Err(_), Err(_)) => (), other => panic!( "inconsistent set_permissions checks: start='{:?}' path='{}':\n{:#?}", start, path.display(), other, ), } } cap-primitives-3.4.1/src/fs/set_times.rs000064400000000000000000000016201046102023000163250ustar 00000000000000//! This defines `set_times`, the primary entrypoint to sandboxed //! filesystem times modification. //! //! TODO: `check_set_times` etc. use crate::fs::{set_times_impl, set_times_nofollow_impl, SystemTimeSpec}; use std::path::Path; use std::{fs, io}; /// Perform a `utimensat`-like operation, ensuring that the resolution of the /// path never escapes the directory tree rooted at `start`. This function /// follows symlinks. #[inline] pub fn set_times( start: &fs::File, path: &Path, atime: Option, mtime: Option, ) -> io::Result<()> { set_times_impl(start, path, atime, mtime) } /// Like `set_times`, but never follows symlinks. #[inline] pub fn set_times_nofollow( start: &fs::File, path: &Path, atime: Option, mtime: Option, ) -> io::Result<()> { set_times_nofollow_impl(start, path, atime, mtime) } cap-primitives-3.4.1/src/fs/stat.rs000064400000000000000000000047471046102023000153210ustar 00000000000000//! This defines `stat`, the primary entrypoint to sandboxed metadata querying. #[cfg(racy_asserts)] use crate::fs::{canonicalize, map_result, stat_unchecked}; use crate::fs::{stat_impl, FollowSymlinks, Metadata}; use std::path::Path; use std::{fs, io}; /// Perform an `fstatat`-like operation, ensuring that the resolution of the /// path never escapes the directory tree rooted at `start`. #[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] #[inline] pub fn stat(start: &fs::File, path: &Path, follow: FollowSymlinks) -> io::Result { // Call the underlying implementation. let result = stat_impl(start, path, follow); #[cfg(racy_asserts)] let stat = stat_unchecked(start, path, follow); #[cfg(racy_asserts)] check_stat(start, path, follow, &result, &stat); result } #[cfg(racy_asserts)] #[allow(clippy::enum_glob_use)] fn check_stat( start: &fs::File, path: &Path, follow: FollowSymlinks, result: &io::Result, stat: &io::Result, ) { use io::ErrorKind::*; match (map_result(result), map_result(stat)) { (Ok(metadata), Ok(unchecked_metadata)) => { assert_same_file_metadata!( metadata, unchecked_metadata, "path resolution inconsistency: start='{:?}', path='{}'", start, path.display(), ); } (Err((PermissionDenied, message)), _) => { if let FollowSymlinks::Yes = follow { match map_result(&canonicalize(start, path)) { Ok(_) => (), Err((PermissionDenied, canon_message)) => { assert_eq!(message, canon_message); } err => panic!("stat failed where canonicalize succeeded: {:?}", err), } } else { // TODO: Check that stat in the no-follow case got the right // error. } } (Err((kind, message)), Err((unchecked_kind, unchecked_message))) => { assert_eq!(kind, unchecked_kind); assert_eq!( message, unchecked_message, "start='{:?}', path='{:?}'", start, path.display() ); } other => panic!( "unexpected result from stat start='{:?}', path='{}':\n{:#?}", start, path.display(), other, ), } } cap-primitives-3.4.1/src/fs/symlink.rs000064400000000000000000000271021046102023000160220ustar 00000000000000//! This defines `symlink`, the primary entrypoint to sandboxed symlink //! creation. use crate::fs::errors; #[cfg(all(racy_asserts, not(windows)))] use crate::fs::symlink_unchecked; #[cfg(racy_asserts)] use crate::fs::{canonicalize, manually, map_result, stat_unchecked, FollowSymlinks, Metadata}; #[cfg(all(racy_asserts, windows))] use crate::fs::{symlink_dir_unchecked, symlink_file_unchecked}; use std::path::Path; use std::{fs, io}; /// Perform a `symlinkat`-like operation, ensuring that the resolution of the /// path never escapes the directory tree rooted at `start`. An error is /// returned if the target path is absolute. #[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] #[cfg(not(windows))] #[inline] pub fn symlink(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { // Don't allow creating symlinks to absolute paths. This isn't strictly // necessary to preserve the sandbox, since `open` will refuse to follow // absolute symlinks in any case. However, it is useful to enforce this // restriction so that a WASI program can't trick some other non-WASI // program into following an absolute path. if old_path.has_root() { return Err(errors::escape_attempt()); } write_symlink_impl(old_path, new_start, new_path) } #[cfg(not(windows))] fn write_symlink_impl(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { use crate::fs::symlink_impl; #[cfg(racy_asserts)] let stat_before = stat_unchecked(new_start, new_path, FollowSymlinks::No); // Call the underlying implementation. let result = symlink_impl(old_path, new_start, new_path); #[cfg(racy_asserts)] let stat_after = stat_unchecked(new_start, new_path, FollowSymlinks::No); #[cfg(racy_asserts)] check_symlink( old_path, new_start, new_path, &stat_before, &result, &stat_after, ); result } /// Perform a `symlinkat`-like operation, ensuring that the resolution of the /// link path never escapes the directory tree rooted at `start`. #[cfg(not(windows))] pub fn symlink_contents, Q: AsRef>( old_path: P, new_start: &fs::File, new_path: Q, ) -> io::Result<()> { write_symlink_impl(old_path.as_ref(), new_start, new_path.as_ref()) } /// Perform a `symlink_file`-like operation, ensuring that the resolution of /// the path never escapes the directory tree rooted at `start`. #[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] #[cfg(windows)] #[inline] pub fn symlink_file(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { use crate::fs::symlink_file_impl; // As above, don't allow creating symlinks to absolute paths. if old_path.has_root() { return Err(errors::escape_attempt()); } #[cfg(racy_asserts)] let stat_before = stat_unchecked(new_start, new_path, FollowSymlinks::No); // Call the underlying implementation. let result = symlink_file_impl(old_path, new_start, new_path); #[cfg(racy_asserts)] let stat_after = stat_unchecked(new_start, new_path, FollowSymlinks::No); #[cfg(racy_asserts)] check_symlink_file( old_path, new_start, new_path, &stat_before, &result, &stat_after, ); result } /// Perform a `symlink_dir`-like operation, ensuring that the resolution of the /// path never escapes the directory tree rooted at `start`. #[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))] #[cfg(windows)] #[inline] pub fn symlink_dir(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { use crate::fs::symlink_dir_impl; // As above, don't allow creating symlinks to absolute paths. if old_path.has_root() { return Err(errors::escape_attempt()); } #[cfg(racy_asserts)] let stat_before = stat_unchecked(new_start, new_path, FollowSymlinks::No); // Call the underlying implementation. let result = symlink_dir_impl(old_path, new_start, new_path); #[cfg(racy_asserts)] let stat_after = stat_unchecked(new_start, new_path, FollowSymlinks::No); #[cfg(racy_asserts)] check_symlink_dir( old_path, new_start, new_path, &stat_before, &result, &stat_after, ); result } #[cfg(all(not(windows), racy_asserts))] #[allow(clippy::enum_glob_use)] fn check_symlink( old_path: &Path, new_start: &fs::File, new_path: &Path, stat_before: &io::Result, result: &io::Result<()>, stat_after: &io::Result, ) { use io::ErrorKind::*; match ( map_result(stat_before), map_result(result), map_result(stat_after), ) { (Err((NotFound, _)), Ok(()), Ok(metadata)) => { assert!(metadata.file_type().is_symlink()); let canon = manually::canonicalize_with(new_start, new_path, FollowSymlinks::No).unwrap(); assert_same_file_metadata!( &stat_unchecked(new_start, &canon, FollowSymlinks::No).unwrap(), &metadata ); } (Ok(metadata_before), Err((AlreadyExists, _)), Ok(metadata_after)) => { assert_same_file_metadata!(&metadata_before, &metadata_after); } (_, Err((_kind, _message)), _) => match map_result(&canonicalize(new_start, new_path)) { Ok(canon) => match map_result(&symlink_unchecked(old_path, new_start, &canon)) { Err((_unchecked_kind, _unchecked_message)) => { /* TODO: Check error messages. assert_eq!( kind, unchecked_kind, "unexpected error kind from symlink new_start='{:?}', \ new_path='{}':\nstat_before={:#?}\nresult={:#?}\nstat_after={:#?}", new_start, new_path.display(), stat_before, result, stat_after ); assert_eq!(message, unchecked_message); */ } _ => panic!("unsandboxed symlink success"), }, Err((_canon_kind, _canon_message)) => { /* TODO: Check error messages. assert_eq!(kind, canon_kind); assert_eq!(message, canon_message); */ } }, _other => { /* TODO: Check error messages. panic!( "inconsistent symlink checks: new_start='{:?}' new_path='{}':\n{:#?}", new_start, new_path.display(), other, ) */ } } } #[cfg(all(windows, racy_asserts))] #[allow(clippy::enum_glob_use)] fn check_symlink_file( old_path: &Path, new_start: &fs::File, new_path: &Path, stat_before: &io::Result, result: &io::Result<()>, stat_after: &io::Result, ) { use io::ErrorKind::*; match ( map_result(stat_before), map_result(result), map_result(stat_after), ) { (Err((NotFound, _)), Ok(()), Ok(metadata)) => { assert!(metadata.file_type().is_symlink()); let canon = manually::canonicalize_with(new_start, new_path, FollowSymlinks::No).unwrap(); assert_same_file_metadata!( &stat_unchecked(new_start, &canon, FollowSymlinks::No).unwrap(), &metadata ); } (Ok(metadata_before), Err((AlreadyExists, _)), Ok(metadata_after)) => { assert_same_file_metadata!(&metadata_before, &metadata_after); } (_, Err((_kind, _message)), _) => match map_result(&canonicalize(new_start, new_path)) { Ok(canon) => match map_result(&symlink_file_unchecked(old_path, new_start, &canon)) { Err((_unchecked_kind, _unchecked_message)) => { /* TODO: Check error messages. assert_eq!( kind, unchecked_kind, "unexpected error kind from symlink new_start='{:?}', \ new_path='{}':\nstat_before={:#?}\nresult={:#?}\nstat_after={:#?}", new_start, new_path.display(), stat_before, result, stat_after ); assert_eq!(message, unchecked_message); */ } _ => panic!("unsandboxed symlink success"), }, Err((_canon_kind, _canon_message)) => { /* TODO: Check error messages. assert_eq!(kind, canon_kind); assert_eq!(message, canon_message); */ } }, _other => { /* TODO: Check error messages. panic!( "inconsistent symlink checks: new_start='{:?}' new_path='{}':\n{:#?}", new_start, new_path.display(), other, ) */ } } } #[cfg(all(windows, racy_asserts))] #[allow(clippy::enum_glob_use)] fn check_symlink_dir( old_path: &Path, new_start: &fs::File, new_path: &Path, stat_before: &io::Result, result: &io::Result<()>, stat_after: &io::Result, ) { use io::ErrorKind::*; match ( map_result(stat_before), map_result(result), map_result(stat_after), ) { (Err((NotFound, _)), Ok(()), Ok(metadata)) => { assert!(metadata.file_type().is_symlink()); let canon = manually::canonicalize_with(new_start, new_path, FollowSymlinks::No).unwrap(); assert_same_file_metadata!( &stat_unchecked(new_start, &canon, FollowSymlinks::No).unwrap(), &metadata ); } (Ok(metadata_before), Err((AlreadyExists, _)), Ok(metadata_after)) => { assert_same_file_metadata!(&metadata_before, &metadata_after); } (_, Err((_kind, _message)), _) => match map_result(&canonicalize(new_start, new_path)) { Ok(canon) => match map_result(&symlink_dir_unchecked(old_path, new_start, &canon)) { Err((_unchecked_kind, _unchecked_message)) => { /* TODO: Check error messages. assert_eq!( kind, unchecked_kind, "unexpected error kind from symlink new_start='{:?}', \ new_path='{}':\nstat_before={:#?}\nresult={:#?}\nstat_after={:#?}", new_start, new_path.display(), stat_before, result, stat_after ); assert_eq!(message, unchecked_message); */ } _ => panic!("unsandboxed symlink success"), }, Err((_canon_kind, _canon_message)) => { /* TODO: Check error messages. assert_eq!(kind, canon_kind); assert_eq!(message, canon_message); */ } }, _other => { /* TODO: Check error messages. panic!( "inconsistent symlink checks: new_start='{:?}' new_path='{}':\n{:#?}", new_start, new_path.display(), other, ) */ } } } cap-primitives-3.4.1/src/fs/system_time_spec.rs000064400000000000000000000026421046102023000177120ustar 00000000000000use crate::time::SystemTime; /// A value for specifying a time. #[derive(Debug)] #[allow(clippy::module_name_repetitions)] pub enum SystemTimeSpec { /// A value which always represents the current time, in symbolic form, so /// that even as time elapses, it continues to represent the current time. SymbolicNow, /// An absolute time value. Absolute(SystemTime), } impl SystemTimeSpec { /// Constructs a new instance of `Self` from the given /// [`fs_set_times::SystemTimeSpec`]. // TODO: Make this a `const fn` once `SystemTime::from_std` is a `const fn`. #[inline] pub fn from_std(std: fs_set_times::SystemTimeSpec) -> Self { match std { fs_set_times::SystemTimeSpec::SymbolicNow => Self::SymbolicNow, fs_set_times::SystemTimeSpec::Absolute(time) => { Self::Absolute(SystemTime::from_std(time)) } } } /// Constructs a new instance of [`fs_set_times::SystemTimeSpec`] from the /// given `Self`. #[inline] pub const fn into_std(self) -> fs_set_times::SystemTimeSpec { match self { Self::SymbolicNow => fs_set_times::SystemTimeSpec::SymbolicNow, Self::Absolute(time) => fs_set_times::SystemTimeSpec::Absolute(time.into_std()), } } } impl From for SystemTimeSpec { #[inline] fn from(time: SystemTime) -> Self { Self::Absolute(time) } } cap-primitives-3.4.1/src/fs/via_parent/access.rs000064400000000000000000000011041046102023000177170ustar 00000000000000use super::open_parent; use crate::fs::{access_unchecked, AccessType, FollowSymlinks, MaybeOwnedFile}; use std::path::Path; use std::{fs, io}; /// Implement `access` by `open`ing up the parent component of the path and /// then calling `access_unchecked` on the last component. pub(crate) fn access( start: &fs::File, path: &Path, type_: AccessType, follow: FollowSymlinks, ) -> io::Result<()> { let start = MaybeOwnedFile::borrowed(start); let (dir, basename) = open_parent(start, path)?; access_unchecked(&dir, basename.as_ref(), type_, follow) } cap-primitives-3.4.1/src/fs/via_parent/create_dir.rs000064400000000000000000000014011046102023000205570ustar 00000000000000use super::open_parent; use crate::fs::{create_dir_unchecked, strip_dir_suffix, DirOptions, MaybeOwnedFile}; use std::path::Path; use std::{fs, io}; /// Implement `create_dir` by `open`ing up the parent component of the path and /// then calling `create_dir_unchecked` on the last component. pub(crate) fn create_dir(start: &fs::File, path: &Path, options: &DirOptions) -> io::Result<()> { let start = MaybeOwnedFile::borrowed(start); // As a special case, `create_dir` ignores a trailing slash rather than // treating it as equivalent to a trailing slash-dot, so strip any trailing // slashes. let path = strip_dir_suffix(path); let (dir, basename) = open_parent(start, &path)?; create_dir_unchecked(&dir, basename.as_ref(), options) } cap-primitives-3.4.1/src/fs/via_parent/hard_link.rs000064400000000000000000000014221046102023000204140ustar 00000000000000use super::open_parent; use crate::fs::{hard_link_unchecked, MaybeOwnedFile}; use std::path::Path; use std::{fs, io}; /// Implement `hard_link` by `open`ing up the parent component of the path and /// then calling `hard_link_unchecked` on the last component. pub(crate) fn hard_link( old_start: &fs::File, old_path: &Path, new_start: &fs::File, new_path: &Path, ) -> io::Result<()> { let old_start = MaybeOwnedFile::borrowed(old_start); let new_start = MaybeOwnedFile::borrowed(new_start); let (old_dir, old_basename) = open_parent(old_start, old_path)?; let (new_dir, new_basename) = open_parent(new_start, new_path)?; hard_link_unchecked( &old_dir, old_basename.as_ref(), &new_dir, new_basename.as_ref(), ) } cap-primitives-3.4.1/src/fs/via_parent/mod.rs000064400000000000000000000024251046102023000172440ustar 00000000000000//! In many operations, the last component of a path is special. For example, //! in `create_dir`, the last component names the path to be created, while the //! rest of the components just name the place to create it in. mod access; mod create_dir; mod hard_link; mod open_parent; #[cfg(not(windows))] // doesn't work on windows; use a windows-specific impl mod read_link; mod remove_dir; mod remove_file; mod rename; #[cfg(windows)] mod set_permissions; #[cfg(not(target_os = "wasi"))] mod set_symlink_permissions; #[cfg(not(windows))] mod set_times_nofollow; mod symlink; use open_parent::open_parent; pub(crate) use access::access; pub(crate) use create_dir::create_dir; pub(crate) use hard_link::hard_link; #[cfg(not(windows))] // doesn't work on windows; use a windows-specific impl pub(crate) use read_link::read_link; pub(crate) use remove_dir::remove_dir; pub(crate) use remove_file::remove_file; pub(crate) use rename::rename; #[cfg(windows)] pub(crate) use set_permissions::set_permissions; #[cfg(not(target_os = "wasi"))] pub(crate) use set_symlink_permissions::set_symlink_permissions; #[cfg(not(windows))] pub(crate) use set_times_nofollow::set_times_nofollow; #[cfg(not(windows))] pub(crate) use symlink::symlink; #[cfg(windows)] pub(crate) use symlink::{symlink_dir, symlink_file}; cap-primitives-3.4.1/src/fs/via_parent/open_parent.rs000064400000000000000000000077001046102023000210000ustar 00000000000000/// `open_parent` is the key building block in all `via_parent` functions. /// It opens the parent directory of the given path, and returns the basename, /// so that all the `via_parent` functions need to do is make sure they /// don't follow symlinks in the basename. use crate::fs::{errors, open_dir, path_requires_dir, MaybeOwnedFile}; use std::ffi::OsStr; use std::io; use std::path::{Component, Path}; /// Open the "parent" of `path`, relative to `start`. The return value on /// success is a tuple of the newly opened directory and an `OsStr` referencing /// the single remaining path component. This last component will not be `..`, /// though it may be `.` or a symbolic link to anywhere (possibly /// including `..` or an absolute path). pub(super) fn open_parent<'path, 'borrow>( start: MaybeOwnedFile<'borrow>, path: &'path Path, ) -> io::Result<(MaybeOwnedFile<'borrow>, &'path OsStr)> { let (dirname, basename) = split_parent(path).ok_or_else(errors::no_such_file_or_directory)?; let dir = if dirname.as_os_str().is_empty() { start } else { MaybeOwnedFile::owned(open_dir(&start, dirname)?) }; Ok((dir, basename.as_os_str())) } /// Split `path` into parent and basename parts. Return `None` if `path` /// is empty. /// /// This differs from `path.parent()` and `path.file_name()` in several /// respects: /// - Treat paths ending in `/` or `/.` as implying a directory. /// - Treat the path `.` as a normal component rather than a parent. /// - Append a `.` to a path with a trailing `..` to avoid requiring our /// callers to special-case `..`. /// - Bare absolute paths are ok. fn split_parent(path: &Path) -> Option<(&Path, Component)> { if path.as_os_str().is_empty() { return None; } if !path_requires_dir(path) { let mut comps = path.components(); if let Some(p) = comps.next_back() { match p { Component::Normal(_) | Component::CurDir => return Some((comps.as_path(), p)), _ => (), } } } Some((path, Component::CurDir)) } #[test] fn split_parent_basics() { assert_eq!( split_parent(Path::new("foo/bar/qux")).unwrap(), ( Path::new("foo/bar"), Component::Normal(Path::new("qux").as_ref()) ) ); assert_eq!( split_parent(Path::new("foo/bar")).unwrap(), ( Path::new("foo"), Component::Normal(Path::new("bar").as_ref()) ) ); assert_eq!( split_parent(Path::new("foo")).unwrap(), (Path::new(""), Component::Normal(Path::new("foo").as_ref())) ); } #[test] fn split_parent_special_cases() { assert!(split_parent(Path::new("")).is_none()); assert_eq!( split_parent(Path::new("foo/")).unwrap(), (Path::new("foo"), Component::CurDir) ); assert_eq!( split_parent(Path::new("foo/.")).unwrap(), (Path::new("foo"), Component::CurDir) ); assert_eq!( split_parent(Path::new(".")).unwrap(), (Path::new(""), Component::CurDir) ); assert_eq!( split_parent(Path::new("..")).unwrap(), (Path::new(".."), Component::CurDir) ); assert_eq!( split_parent(Path::new("../..")).unwrap(), (Path::new("../.."), Component::CurDir) ); assert_eq!( split_parent(Path::new("../foo")).unwrap(), ( Path::new(".."), Component::Normal(Path::new("foo").as_ref()) ) ); assert_eq!( split_parent(Path::new("foo/..")).unwrap(), (Path::new("foo/.."), Component::CurDir) ); assert_eq!( split_parent(Path::new("/foo")).unwrap(), (Path::new("/"), Component::Normal(Path::new("foo").as_ref())) ); assert_eq!( split_parent(Path::new("/foo/")).unwrap(), (Path::new("/foo"), Component::CurDir) ); assert_eq!( split_parent(Path::new("/")).unwrap(), (Path::new("/"), Component::CurDir) ); } cap-primitives-3.4.1/src/fs/via_parent/read_link.rs000064400000000000000000000016771046102023000204250ustar 00000000000000use super::open_parent; use crate::fs::{read_link_unchecked, MaybeOwnedFile}; use std::path::{Path, PathBuf}; use std::{fs, io}; /// Implement `read_link` by `open`ing up the parent component of the path and /// then calling `read_link_unchecked` on the last component. /// /// This technique doesn't work in all cases on Windows. In particular, a /// directory symlink such as `C:\Documents and Settings` may not grant any /// access other than what is needed to resolve the symlink, so `open_parent`'s /// technique of returning a relative path of `.` from that point doesn't work, /// because opening `.` within such a directory is denied. Consequently, we use /// a different implementation on Windows. pub(crate) fn read_link(start: &fs::File, path: &Path) -> io::Result { let start = MaybeOwnedFile::borrowed(start); let (dir, basename) = open_parent(start, path)?; read_link_unchecked(&dir, basename.as_ref(), PathBuf::new()) } cap-primitives-3.4.1/src/fs/via_parent/remove_dir.rs000064400000000000000000000007571046102023000206260ustar 00000000000000use super::open_parent; use crate::fs::{remove_dir_unchecked, MaybeOwnedFile}; use std::path::Path; use std::{fs, io}; /// Implement `remove_dir` by `open`ing up the parent component of the path and /// then calling `remove_dir_unchecked` on the last component. pub(crate) fn remove_dir(start: &fs::File, path: &Path) -> io::Result<()> { let start = MaybeOwnedFile::borrowed(start); let (dir, basename) = open_parent(start, path)?; remove_dir_unchecked(&dir, basename.as_ref()) } cap-primitives-3.4.1/src/fs/via_parent/remove_file.rs000064400000000000000000000007641046102023000207650ustar 00000000000000use super::open_parent; use crate::fs::{remove_file_unchecked, MaybeOwnedFile}; use std::path::Path; use std::{fs, io}; /// Implement `remove_file` by `open`ing up the parent component of the path /// and then calling `remove_file_unchecked` on the last component. pub(crate) fn remove_file(start: &fs::File, path: &Path) -> io::Result<()> { let start = MaybeOwnedFile::borrowed(start); let (dir, basename) = open_parent(start, path)?; remove_file_unchecked(&dir, basename.as_ref()) } cap-primitives-3.4.1/src/fs/via_parent/rename.rs000064400000000000000000000032201046102023000177260ustar 00000000000000use super::open_parent; #[cfg(unix)] use crate::fs::{append_dir_suffix, path_has_trailing_slash}; use crate::fs::{rename_unchecked, strip_dir_suffix, MaybeOwnedFile}; use std::path::Path; use std::{fs, io}; /// Implement `rename` by `open`ing up the parent component of the path and /// then calling `rename_unchecked` on the last component. pub(crate) fn rename( old_start: &fs::File, old_path: &Path, new_start: &fs::File, new_path: &Path, ) -> io::Result<()> { let old_start = MaybeOwnedFile::borrowed(old_start); let new_start = MaybeOwnedFile::borrowed(new_start); // As a special case, `rename` ignores a trailing slash rather than treating // it as equivalent to a trailing slash-dot, so strip any trailing slashes // for the purposes of `open_parent`. // // And on Unix, remember whether the source started with a slash so that we // can still fail if it is and the source is a regular file. #[cfg(unix)] let old_starts_with_slash = path_has_trailing_slash(old_path); let old_path = strip_dir_suffix(old_path); let new_path = strip_dir_suffix(new_path); let (old_dir, old_basename) = open_parent(old_start, &old_path)?; let (new_dir, new_basename) = open_parent(new_start, &new_path)?; // On Unix, re-append a slash if needed. #[cfg(unix)] let concat; #[cfg(unix)] let old_basename = if old_starts_with_slash { concat = append_dir_suffix(old_basename.to_owned().into()); concat.as_os_str() } else { old_basename }; rename_unchecked( &old_dir, old_basename.as_ref(), &new_dir, new_basename.as_ref(), ) } cap-primitives-3.4.1/src/fs/via_parent/set_permissions.rs000064400000000000000000000006401046102023000217100ustar 00000000000000use super::open_parent; use crate::fs::{set_permissions_unchecked, MaybeOwnedFile, Permissions}; use std::path::Path; use std::{fs, io}; #[inline] pub(crate) fn set_permissions(start: &fs::File, path: &Path, perm: Permissions) -> io::Result<()> { let start = MaybeOwnedFile::borrowed(start); let (dir, basename) = open_parent(start, &path)?; set_permissions_unchecked(&dir, basename.as_ref(), perm) } cap-primitives-3.4.1/src/fs/via_parent/set_symlink_permissions.rs000064400000000000000000000007061046102023000234610ustar 00000000000000use super::open_parent; use crate::fs::{set_symlink_permissions_unchecked, MaybeOwnedFile, Permissions}; use std::path::Path; use std::{fs, io}; #[inline] pub(crate) fn set_symlink_permissions( start: &fs::File, path: &Path, perm: Permissions, ) -> io::Result<()> { let start = MaybeOwnedFile::borrowed(start); let (dir, basename) = open_parent(start, path)?; set_symlink_permissions_unchecked(&dir, basename.as_ref(), perm) } cap-primitives-3.4.1/src/fs/via_parent/set_times_nofollow.rs000064400000000000000000000007611046102023000224010ustar 00000000000000use super::open_parent; use crate::fs::{set_times_nofollow_unchecked, MaybeOwnedFile, SystemTimeSpec}; use std::path::Path; use std::{fs, io}; #[inline] pub(crate) fn set_times_nofollow( start: &fs::File, path: &Path, atime: Option, mtime: Option, ) -> io::Result<()> { let start = MaybeOwnedFile::borrowed(start); let (dir, basename) = open_parent(start, path)?; set_times_nofollow_unchecked(&dir, basename.as_ref(), atime, mtime) } cap-primitives-3.4.1/src/fs/via_parent/symlink.rs000064400000000000000000000031121046102023000201450ustar 00000000000000use super::open_parent; use crate::fs::MaybeOwnedFile; use std::path::Path; use std::{fs, io}; /// Implement `symlink` by `open`ing up the parent component of the path and /// then calling `symlink_unchecked` on the last component. #[cfg(not(windows))] pub(crate) fn symlink(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { use crate::fs::symlink_unchecked; let new_start = MaybeOwnedFile::borrowed(new_start); let (new_dir, new_basename) = open_parent(new_start, new_path)?; symlink_unchecked(old_path, &new_dir, new_basename.as_ref()) } /// Implement `symlink_file` by `open`ing up the parent component of the path /// and then calling `symlink_file` on the last component. #[cfg(windows)] pub(crate) fn symlink_file( old_path: &Path, new_start: &fs::File, new_path: &Path, ) -> io::Result<()> { use crate::fs::symlink_file_unchecked; let new_start = MaybeOwnedFile::borrowed(new_start); let (new_dir, new_basename) = open_parent(new_start, new_path)?; symlink_file_unchecked(old_path, &new_dir, new_basename.as_ref()) } /// Implement `symlink_dir` by `open`ing up the parent component of the path /// and then calling `symlink_dir` on the last component. #[cfg(windows)] pub(crate) fn symlink_dir( old_path: &Path, new_start: &fs::File, new_path: &Path, ) -> io::Result<()> { use crate::fs::symlink_dir_unchecked; let new_start = MaybeOwnedFile::borrowed(new_start); let (new_dir, new_basename) = open_parent(new_start, new_path)?; symlink_dir_unchecked(old_path, &new_dir, new_basename.as_ref()) } cap-primitives-3.4.1/src/lib.rs000064400000000000000000000020031046102023000144630ustar 00000000000000//! Capability-based primitives. #![deny(missing_docs)] #![deny(unsafe_code)] #![allow(stable_features)] #![cfg_attr(target_os = "wasi", feature(wasi_ext))] #![cfg_attr(all(windows, windows_by_handle), feature(windows_by_handle))] #![cfg_attr(all(windows, windows_file_type_ext), feature(windows_file_type_ext))] #![doc( html_logo_url = "https://raw.githubusercontent.com/bytecodealliance/cap-std/main/media/cap-std.svg" )] #![doc( html_favicon_url = "https://raw.githubusercontent.com/bytecodealliance/cap-std/main/media/cap-std.ico" )] #![cfg_attr(io_lifetimes_use_std, feature(io_safety))] #![cfg_attr(io_error_more, feature(io_error_more))] #![cfg_attr(io_error_uncategorized, feature(io_error_uncategorized))] #[cfg(not(windows))] mod rustix; #[cfg(windows)] mod windows; pub mod fs; pub mod net; pub mod time; #[doc(hidden)] pub use ambient_authority::ambient_authority_known_at_compile_time; pub use ambient_authority::{ambient_authority, AmbientAuthority}; // This is part of our public API. pub use ipnet; cap-primitives-3.4.1/src/net/mod.rs000064400000000000000000000000671046102023000152720ustar 00000000000000//! Networking utilities. mod pool; pub use pool::*; cap-primitives-3.4.1/src/net/pool.rs000064400000000000000000000202571046102023000154670ustar 00000000000000#[cfg(test)] use crate::ambient_authority; use crate::net::pool::net::ToSocketAddrs; use crate::AmbientAuthority; use ipnet::IpNet; #[cfg(test)] use std::str::FromStr; use std::{io, net}; // TODO: Perhaps we should have our own version of `ToSocketAddrs` which // returns hostnames rather than parsing them, so we can add unresolved // hostnames to the pool. #[derive(Clone)] enum AddrSet { Net(IpNet), } impl AddrSet { fn contains(&self, addr: net::IpAddr) -> bool { match self { Self::Net(ip_net) => ip_net.contains(&addr), } } } #[derive(Clone)] struct IpGrant { set: AddrSet, ports_start: u16, ports_end: Option, } impl IpGrant { fn contains(&self, addr: &net::SocketAddr) -> bool { if !self.set.contains(addr.ip()) { return false; } let port = addr.port(); if port < self.ports_start { return false; } if let Some(ports_end) = self.ports_end { if port >= ports_end { return false; } } true } } /// A representation of a set of network resources that may be accessed. /// /// This is presently a very simple concept, though it could grow in /// sophistication in the future. /// /// `Pool` implements `Clone`, which creates new independent entities that /// carry the full authority of the originals. This means that in a borrow /// of a `Pool`, the scope of the authority is not necessarily limited to /// the scope of the borrow. /// /// Similarly, the [`cap_net_ext::PoolExt`] class allows creating "binder" /// and "connecter" objects which represent capabilities to bind and /// connect to addresses. /// /// [`cap_net_ext::PoolExt`]: https://docs.rs/cap-net-ext/latest/cap_net_ext/trait.PoolExt.html #[derive(Clone, Default)] pub struct Pool { // TODO: when compiling for WASI, use WASI-specific handle instead grants: Vec, } impl Pool { /// Construct a new empty pool. pub fn new() -> Self { Self { grants: Vec::new() } } /// Add addresses to the pool. /// /// # Ambient Authority /// /// This function allows ambient access to any IP address. pub fn insert( &mut self, addrs: A, ambient_authority: AmbientAuthority, ) -> io::Result<()> { for addr in addrs.to_socket_addrs()? { self.insert_socket_addr(addr, ambient_authority); } Ok(()) } /// Add a specific [`net::SocketAddr`] to the pool. /// /// # Ambient Authority /// /// This function allows ambient access to any IP address. pub fn insert_socket_addr( &mut self, addr: net::SocketAddr, ambient_authority: AmbientAuthority, ) { self.insert_ip_net(addr.ip().into(), addr.port(), ambient_authority) } /// Add a range of network addresses, accepting any port, to the pool. /// /// # Ambient Authority /// /// This function allows ambient access to any IP address. pub fn insert_ip_net_port_any( &mut self, ip_net: ipnet::IpNet, ambient_authority: AmbientAuthority, ) { self.insert_ip_net_port_range(ip_net, 0, None, ambient_authority) } /// Add a range of network addresses, accepting a range of ports, to the /// pool. /// /// This grants access to the port range starting at `ports_start` and, /// if `ports_end` is provided, ending before `ports_end`. /// /// # Ambient Authority /// /// This function allows ambient access to any IP address. pub fn insert_ip_net_port_range( &mut self, ip_net: ipnet::IpNet, ports_start: u16, ports_end: Option, ambient_authority: AmbientAuthority, ) { let _ = ambient_authority; self.grants.push(IpGrant { set: AddrSet::Net(ip_net), ports_start, ports_end, }) } /// Add a range of network addresses with a specific port to the pool. /// /// # Ambient Authority /// /// This function allows ambient access to any IP address. pub fn insert_ip_net( &mut self, ip_net: ipnet::IpNet, port: u16, ambient_authority: AmbientAuthority, ) { self.insert_ip_net_port_range(ip_net, port, port.checked_add(1), ambient_authority) } /// Check whether the given address is within the pool. pub fn check_addr(&self, addr: &net::SocketAddr) -> io::Result<()> { if self.grants.iter().any(|grant| grant.contains(addr)) { Ok(()) } else { Err(io::Error::new( io::ErrorKind::PermissionDenied, "An address was outside the pool", )) } } } /// An empty array of `SocketAddr`s. pub const NO_SOCKET_ADDRS: &[net::SocketAddr] = &[]; /// Return an error for reporting that no socket addresses were available. #[cold] pub fn no_socket_addrs() -> io::Error { std::net::TcpListener::bind(NO_SOCKET_ADDRS).unwrap_err() } #[test] fn test_empty() { let p = Pool::new(); p.check_addr(&net::SocketAddr::from_str("[::1]:0").unwrap()) .unwrap_err(); p.check_addr(&net::SocketAddr::from_str("[::1]:1023").unwrap()) .unwrap_err(); p.check_addr(&net::SocketAddr::from_str("[::1]:1024").unwrap()) .unwrap_err(); p.check_addr(&net::SocketAddr::from_str("[::1]:8080").unwrap()) .unwrap_err(); p.check_addr(&net::SocketAddr::from_str("[::1]:65535").unwrap()) .unwrap_err(); } #[test] fn test_port_any() { let mut p = Pool::new(); p.insert_ip_net_port_any( IpNet::new(net::IpAddr::V6(net::Ipv6Addr::LOCALHOST), 48).unwrap(), ambient_authority(), ); p.check_addr(&net::SocketAddr::from_str("[::1]:0").unwrap()) .unwrap(); p.check_addr(&net::SocketAddr::from_str("[::1]:1023").unwrap()) .unwrap(); p.check_addr(&net::SocketAddr::from_str("[::1]:1024").unwrap()) .unwrap(); p.check_addr(&net::SocketAddr::from_str("[::1]:8080").unwrap()) .unwrap(); p.check_addr(&net::SocketAddr::from_str("[::1]:65535").unwrap()) .unwrap(); } #[test] fn test_port_range() { let mut p = Pool::new(); p.insert_ip_net_port_range( IpNet::new(net::IpAddr::V6(net::Ipv6Addr::LOCALHOST), 48).unwrap(), 1024, Some(9000), ambient_authority(), ); p.check_addr(&net::SocketAddr::from_str("[::1]:0").unwrap()) .unwrap_err(); p.check_addr(&net::SocketAddr::from_str("[::1]:1023").unwrap()) .unwrap_err(); p.check_addr(&net::SocketAddr::from_str("[::1]:1024").unwrap()) .unwrap(); p.check_addr(&net::SocketAddr::from_str("[::1]:8080").unwrap()) .unwrap(); p.check_addr(&net::SocketAddr::from_str("[::1]:65535").unwrap()) .unwrap_err(); } #[test] fn test_port_one() { let mut p = Pool::new(); p.insert_ip_net( IpNet::new(net::IpAddr::V6(net::Ipv6Addr::LOCALHOST), 48).unwrap(), 8080, ambient_authority(), ); p.check_addr(&net::SocketAddr::from_str("[::1]:0").unwrap()) .unwrap_err(); p.check_addr(&net::SocketAddr::from_str("[::1]:1023").unwrap()) .unwrap_err(); p.check_addr(&net::SocketAddr::from_str("[::1]:1024").unwrap()) .unwrap_err(); p.check_addr(&net::SocketAddr::from_str("[::1]:8080").unwrap()) .unwrap(); p.check_addr(&net::SocketAddr::from_str("[::1]:65535").unwrap()) .unwrap_err(); } #[test] fn test_addrs() { let mut p = Pool::new(); match p.insert("example.com:80", ambient_authority()) { Ok(()) => (), Err(_) => return, // not all test environments have DNS } p.check_addr(&net::SocketAddr::from_str("[::1]:0").unwrap()) .unwrap_err(); p.check_addr(&net::SocketAddr::from_str("[::1]:1023").unwrap()) .unwrap_err(); p.check_addr(&net::SocketAddr::from_str("[::1]:1024").unwrap()) .unwrap_err(); p.check_addr(&net::SocketAddr::from_str("[::1]:8080").unwrap()) .unwrap_err(); p.check_addr(&net::SocketAddr::from_str("[::1]:65535").unwrap()) .unwrap_err(); for addr in "example.com:80".to_socket_addrs().unwrap() { p.check_addr(&addr).unwrap(); } } cap-primitives-3.4.1/src/rustix/darwin/fs/file_path.rs000064400000000000000000000011711046102023000211070ustar 00000000000000//! `get_path` translation code for macOS derived from Rust's //! library/std/src/sys/unix/fs.rs at revision //! 108e90ca78f052c0c1c49c42a22c85620be19712. use crate::rustix::fs::file_path_by_ttyname_or_seaching; use rustix::fs::getpath; use std::ffi::OsString; use std::fs; #[cfg(unix)] use std::os::unix::ffi::OsStringExt; #[cfg(target_os = "wasi")] use std::os::wasi::ffi::OsStringExt; use std::path::PathBuf; pub(crate) fn file_path(file: &fs::File) -> Option { if let Ok(path) = getpath(file) { return Some(OsString::from_vec(path.into_bytes()).into()); } file_path_by_ttyname_or_seaching(file) } cap-primitives-3.4.1/src/rustix/darwin/fs/mod.rs000064400000000000000000000000551046102023000177330ustar 00000000000000mod file_path; pub(crate) use file_path::*; cap-primitives-3.4.1/src/rustix/darwin/mod.rs000064400000000000000000000000231046102023000173160ustar 00000000000000pub(crate) mod fs; cap-primitives-3.4.1/src/rustix/freebsd/fs/check.rs000064400000000000000000000022021046102023000203530ustar 00000000000000use rustix::cstr; use rustix::fs::{openat, statat, AtFlags, Mode, OFlags, CWD}; use rustix::io::Errno; use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; static WORKING: AtomicBool = AtomicBool::new(false); static CHECKED: AtomicBool = AtomicBool::new(false); #[inline] pub(crate) fn beneath_supported() -> bool { if WORKING.load(Relaxed) { return true; } if CHECKED.load(Relaxed) { return false; } check_beneath_supported() } #[cold] fn check_beneath_supported() -> bool { // `RESOLVE_BENEATH` was introduced in FreeBSD 13, but opening `..` within // the root directory re-opened the root directory. In FreeBSD 14, it fails // as cap-std expects. if let Ok(root) = openat( CWD, cstr!("/"), OFlags::RDONLY | OFlags::CLOEXEC, Mode::empty(), ) { // Unknown O_ flags get ignored but AT_ flags have strict checks, so we use that. if let Err(Errno::NOTCAPABLE) = statat(root, cstr!(".."), AtFlags::RESOLVE_BENEATH) { WORKING.store(true, Relaxed); return true; } } CHECKED.store(true, Relaxed); false } cap-primitives-3.4.1/src/rustix/freebsd/fs/mod.rs000064400000000000000000000011541046102023000200620ustar 00000000000000mod check; mod open_entry_impl; mod open_impl; mod remove_dir_impl; mod remove_file_impl; mod set_permissions_impl; mod set_times_impl; mod stat_impl; pub(crate) use crate::fs::manually::canonicalize as canonicalize_impl; pub(crate) use check::beneath_supported; pub(crate) use open_entry_impl::open_entry_impl; pub(crate) use open_impl::open_impl; pub(crate) use remove_dir_impl::remove_dir_impl; pub(crate) use remove_file_impl::remove_file_impl; pub(crate) use set_permissions_impl::set_permissions_impl; pub(crate) use set_times_impl::{set_times_impl, set_times_nofollow_impl}; pub(crate) use stat_impl::stat_impl; cap-primitives-3.4.1/src/rustix/freebsd/fs/open_entry_impl.rs000064400000000000000000000004211046102023000225020ustar 00000000000000use crate::fs::{open_impl, OpenOptions}; use std::ffi::OsStr; use std::{fs, io}; #[inline(always)] pub(crate) fn open_entry_impl( start: &fs::File, path: &OsStr, options: &OpenOptions, ) -> io::Result { open_impl(start, path.as_ref(), options) } cap-primitives-3.4.1/src/rustix/freebsd/fs/open_impl.rs000064400000000000000000000015651046102023000212730ustar 00000000000000use super::super::super::fs::compute_oflags; use crate::fs::{errors, manually, OpenOptions}; use io_lifetimes::FromFd; use rustix::fs::{openat, Mode, OFlags, RawMode}; use std::path::Path; use std::{fs, io}; pub(crate) fn open_impl( start: &fs::File, path: &Path, options: &OpenOptions, ) -> io::Result { if !super::beneath_supported() { return manually::open(start, path, options); } let oflags = compute_oflags(options)? | OFlags::RESOLVE_BENEATH; let mode = if oflags.contains(OFlags::CREATE) { Mode::from_bits((options.ext.mode & 0o7777) as RawMode).unwrap() } else { Mode::empty() }; match openat(start, path, oflags, mode) { Ok(file) => Ok(fs::File::from_into_fd(file)), Err(rustix::io::Errno::NOTCAPABLE) => Err(errors::escape_attempt()), Err(err) => Err(err.into()), } } cap-primitives-3.4.1/src/rustix/freebsd/fs/remove_dir_impl.rs000064400000000000000000000006111046102023000224540ustar 00000000000000use crate::fs::via_parent; use rustix::fs::{unlinkat, AtFlags}; use std::path::Path; use std::{fs, io}; pub(crate) fn remove_dir_impl(start: &fs::File, path: &Path) -> io::Result<()> { if !super::beneath_supported() { return via_parent::remove_dir(start, path); } Ok(unlinkat( start, path, AtFlags::RESOLVE_BENEATH | AtFlags::REMOVEDIR, )?) } cap-primitives-3.4.1/src/rustix/freebsd/fs/remove_file_impl.rs000064400000000000000000000005271046102023000226230ustar 00000000000000use crate::fs::via_parent; use rustix::fs::{unlinkat, AtFlags}; use std::path::Path; use std::{fs, io}; pub(crate) fn remove_file_impl(start: &fs::File, path: &Path) -> io::Result<()> { if !super::beneath_supported() { return via_parent::remove_file(start, path); } Ok(unlinkat(start, path, AtFlags::RESOLVE_BENEATH)?) } cap-primitives-3.4.1/src/rustix/freebsd/fs/set_permissions_impl.rs000064400000000000000000000010021046102023000235420ustar 00000000000000use crate::fs::{Permissions, PermissionsExt}; use rustix::fs::{chmodat, AtFlags, Mode}; use std::path::Path; use std::{fs, io}; pub(crate) fn set_permissions_impl( start: &fs::File, path: &Path, perm: Permissions, ) -> io::Result<()> { if !super::beneath_supported() { return super::super::super::fs::set_permissions_manually(start, path, perm); } Ok(chmodat( start, path, Mode::from_raw_mode(perm.mode() as _), AtFlags::RESOLVE_BENEATH, )?) } cap-primitives-3.4.1/src/rustix/freebsd/fs/set_times_impl.rs000064400000000000000000000022431046102023000223200ustar 00000000000000use crate::fs::{to_timespec, via_parent, SystemTimeSpec}; use rustix::fs::{utimensat, AtFlags, Timestamps}; use std::path::Path; use std::{fs, io}; pub(crate) fn set_times_impl( start: &fs::File, path: &Path, atime: Option, mtime: Option, ) -> io::Result<()> { if !super::beneath_supported() { return super::super::super::fs::set_times_manually(start, path, atime, mtime); } let times = Timestamps { last_access: to_timespec(atime)?, last_modification: to_timespec(mtime)?, }; Ok(utimensat(start, path, ×, AtFlags::RESOLVE_BENEATH)?) } pub(crate) fn set_times_nofollow_impl( start: &fs::File, path: &Path, atime: Option, mtime: Option, ) -> io::Result<()> { if !super::beneath_supported() { return via_parent::set_times_nofollow(start, path, atime, mtime); } let times = Timestamps { last_access: to_timespec(atime)?, last_modification: to_timespec(mtime)?, }; Ok(utimensat( start, path, ×, AtFlags::RESOLVE_BENEATH | AtFlags::SYMLINK_NOFOLLOW, )?) } cap-primitives-3.4.1/src/rustix/freebsd/fs/stat_impl.rs000064400000000000000000000011431046102023000212750ustar 00000000000000use crate::fs::{manually, FollowSymlinks, ImplMetadataExt, Metadata}; use rustix::fs::{statat, AtFlags}; use std::path::Path; use std::{fs, io}; pub(crate) fn stat_impl( start: &fs::File, path: &Path, follow: FollowSymlinks, ) -> io::Result { if !super::beneath_supported() { return manually::stat(start, path, follow); } let flags = AtFlags::RESOLVE_BENEATH | if follow == FollowSymlinks::Yes { AtFlags::empty() } else { AtFlags::SYMLINK_NOFOLLOW }; Ok(ImplMetadataExt::from_rustix(statat(start, path, flags)?)) } cap-primitives-3.4.1/src/rustix/freebsd/mod.rs000064400000000000000000000000231046102023000174440ustar 00000000000000pub(crate) mod fs; cap-primitives-3.4.1/src/rustix/fs/access_unchecked.rs000064400000000000000000000020221046102023000211360ustar 00000000000000use crate::fs::{AccessType, FollowSymlinks}; use rustix::fs::{Access, AtFlags}; use std::path::Path; use std::{fs, io}; /// *Unsandboxed* function similar to `access`, but which does not perform /// sandboxing. pub(crate) fn access_unchecked( start: &fs::File, path: &Path, type_: AccessType, follow: FollowSymlinks, ) -> io::Result<()> { let mut access_type = Access::empty(); match type_ { AccessType::Exists => access_type |= Access::EXISTS, AccessType::Access(modes) => { if modes.readable { access_type |= Access::READ_OK; } if modes.writable { access_type |= Access::WRITE_OK; } if modes.executable { access_type |= Access::EXEC_OK; } } } let atflags = match follow { FollowSymlinks::Yes => AtFlags::empty(), FollowSymlinks::No => AtFlags::SYMLINK_NOFOLLOW, }; rustix::fs::accessat(start, path, access_type, atflags)?; Ok(()) } cap-primitives-3.4.1/src/rustix/fs/copy_impl.rs000064400000000000000000000176651046102023000177020ustar 00000000000000// Implementation derived from `copy` in Rust's // library/std/src/sys/unix/fs.rs at revision // 108e90ca78f052c0c1c49c42a22c85620be19712. use crate::fs::{open, OpenOptions}; #[cfg(any(target_os = "android", target_os = "linux"))] use rustix::fs::copy_file_range; #[cfg(any(target_os = "macos", target_os = "ios"))] use rustix::fs::{ copyfile_state_alloc, copyfile_state_free, copyfile_state_get_copied, copyfile_state_t, fclonefileat, fcopyfile, CloneFlags, CopyfileFlags, }; use std::path::Path; use std::{fs, io}; fn open_from(start: &fs::File, path: &Path) -> io::Result<(fs::File, fs::Metadata)> { let reader = open(start, path, OpenOptions::new().read(true))?; let metadata = reader.metadata()?; if !metadata.is_file() { return Err(io::Error::new( io::ErrorKind::InvalidInput, "the source path is not an existing regular file", )); } Ok((reader, metadata)) } #[cfg(not(target_os = "wasi"))] fn open_to_and_set_permissions( start: &fs::File, path: &Path, reader_metadata: fs::Metadata, ) -> io::Result<(fs::File, fs::Metadata)> { use crate::fs::OpenOptionsExt; use std::os::unix::fs::PermissionsExt; let perm = reader_metadata.permissions(); let writer = open( start, path, OpenOptions::new() // create the file with the correct mode right away .mode(perm.mode()) .write(true) .create(true) .truncate(true), )?; let writer_metadata = writer.metadata()?; if writer_metadata.is_file() { // Set the correct file permissions, in case the file already existed. // Don't set the permissions on already existing non-files like // pipes/FIFOs or device nodes. writer.set_permissions(perm)?; } Ok((writer, writer_metadata)) } #[cfg(target_os = "wasi")] fn open_to_and_set_permissions( start: &fs::File, path: &Path, reader_metadata: fs::Metadata, ) -> io::Result<(fs::File, fs::Metadata)> { let writer = open( start, path, OpenOptions::new() // create the file with the correct mode right away .write(true) .create(true) .truncate(true), )?; let writer_metadata = writer.metadata()?; Ok((writer, writer_metadata)) } #[cfg(not(any( target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios" )))] pub(crate) fn copy_impl( from_start: &fs::File, from_path: &Path, to_start: &fs::File, to_path: &Path, ) -> io::Result { let (mut reader, reader_metadata) = open_from(from_start, from_path)?; let (mut writer, _) = open_to_and_set_permissions(to_start, to_path, reader_metadata)?; io::copy(&mut reader, &mut writer) } #[cfg(any(target_os = "android", target_os = "linux"))] pub(crate) fn copy_impl( from_start: &fs::File, from_path: &Path, to_start: &fs::File, to_path: &Path, ) -> io::Result { use std::cmp; use std::sync::atomic::{AtomicBool, Ordering}; // Kernel prior to 4.5 don't have copy_file_range // We store the availability in a global to avoid unnecessary syscalls static HAS_COPY_FILE_RANGE: AtomicBool = AtomicBool::new(true); let (mut reader, reader_metadata) = open_from(from_start, from_path)?; let len = reader_metadata.len(); let (mut writer, _) = open_to_and_set_permissions(to_start, to_path, reader_metadata)?; let has_copy_file_range = HAS_COPY_FILE_RANGE.load(Ordering::Relaxed); let mut written = 0_u64; while written < len { let copy_result = if has_copy_file_range { let bytes_to_copy = cmp::min(len - written, usize::MAX as u64); // `copy_file_range` takes a `usize`; convert with saturation so // that we copy as many bytes as we can. let bytes_to_copy = usize::try_from(bytes_to_copy).unwrap_or(usize::MAX); // We actually don't have to adjust the offsets, // because copy_file_range adjusts the file offset automatically let copy_result = copy_file_range(&reader, None, &writer, None, bytes_to_copy); if let Err(copy_err) = copy_result { match copy_err { rustix::io::Errno::NOSYS | rustix::io::Errno::PERM => { HAS_COPY_FILE_RANGE.store(false, Ordering::Relaxed); } _ => {} } } copy_result } else { Err(rustix::io::Errno::NOSYS) }; match copy_result { Ok(ret) => written += ret as u64, Err(err) => { match err { rustix::io::Errno::NOSYS | rustix::io::Errno::XDEV | rustix::io::Errno::INVAL | rustix::io::Errno::PERM => { // Try fallback io::copy if either: // - Kernel version is < 4.5 (ENOSYS) // - Files are mounted on different fs (EXDEV) // - copy_file_range is disallowed, for example by seccomp (EPERM) // - copy_file_range cannot be used with pipes or device nodes (EINVAL) assert_eq!(written, 0); return io::copy(&mut reader, &mut writer); } _ => return Err(err.into()), } } } } Ok(written) } #[cfg(any(target_os = "macos", target_os = "ios"))] #[allow(non_upper_case_globals)] #[allow(unsafe_code)] pub(crate) fn copy_impl( from_start: &fs::File, from_path: &Path, to_start: &fs::File, to_path: &Path, ) -> io::Result { use std::sync::atomic::{AtomicBool, Ordering}; struct FreeOnDrop(copyfile_state_t); impl Drop for FreeOnDrop { fn drop(&mut self) { // Safety: This is the only place where we free the state, and we // never let it escape. unsafe { copyfile_state_free(self.0).ok(); } } } // MacOS prior to 10.12 don't support `fclonefileat` // We store the availability in a global to avoid unnecessary syscalls static HAS_FCLONEFILEAT: AtomicBool = AtomicBool::new(true); let (reader, reader_metadata) = open_from(from_start, from_path)?; // Opportunistically attempt to create a copy-on-write clone of `from_path` // using `fclonefileat`. if HAS_FCLONEFILEAT.load(Ordering::Relaxed) { let clonefile_result = fclonefileat(&reader, to_start, to_path, CloneFlags::empty()); match clonefile_result { Ok(_) => return Ok(reader_metadata.len()), Err(err) => match err { // `fclonefileat` will fail on non-APFS volumes, if the // destination already exists, or if the source and destination // are on different devices. In all these cases `fcopyfile` // should succeed. rustix::io::Errno::NOTSUP | rustix::io::Errno::EXIST | rustix::io::Errno::XDEV => { () } rustix::io::Errno::NOSYS => HAS_FCLONEFILEAT.store(false, Ordering::Relaxed), _ => return Err(err.into()), }, } } // Fall back to using `fcopyfile` if `fclonefileat` does not succeed. let (writer, writer_metadata) = open_to_and_set_permissions(to_start, to_path, reader_metadata)?; // We ensure that `FreeOnDrop` never contains a null pointer so it is // always safe to call `copyfile_state_free` let state = { let state = copyfile_state_alloc()?; FreeOnDrop(state) }; let flags = if writer_metadata.is_file() { CopyfileFlags::ALL } else { CopyfileFlags::DATA }; // Safety: We allocated `state` above so it's still live here. unsafe { fcopyfile(&reader, &writer, state.0, flags)?; Ok(copyfile_state_get_copied(state.0)?) } } cap-primitives-3.4.1/src/rustix/fs/create_dir_unchecked.rs000064400000000000000000000010261046102023000220010ustar 00000000000000use crate::fs::DirOptions; use rustix::fs::{mkdirat, Mode, RawMode}; use std::path::Path; use std::{fs, io}; /// *Unsandboxed* function similar to `create_dir`, but which does not perform /// sandboxing. pub(crate) fn create_dir_unchecked( start: &fs::File, path: &Path, options: &DirOptions, ) -> io::Result<()> { #[cfg(not(target_os = "wasi"))] let raw_mode = options.ext.mode as RawMode; #[cfg(target_os = "wasi")] let raw_mode = 0; Ok(mkdirat(start, path, Mode::from_bits(raw_mode).unwrap())?) } cap-primitives-3.4.1/src/rustix/fs/cvt.rs000064400000000000000000000005251046102023000164660ustar 00000000000000use std::io; #[allow(dead_code)] pub(crate) fn cvt_i32(t: i32) -> io::Result { if t == -1 { Err(io::Error::last_os_error()) } else { Ok(t) } } #[allow(dead_code)] pub(crate) fn cvt_i64(t: i64) -> io::Result { if t == -1 { Err(io::Error::last_os_error()) } else { Ok(t) } } cap-primitives-3.4.1/src/rustix/fs/dir_entry_inner.rs000064400000000000000000000062471046102023000210730ustar 00000000000000use crate::fs::{ FileType, FollowSymlinks, Metadata, MetadataExt, OpenOptions, ReadDir, ReadDirInner, }; use rustix::fs::DirEntry; use std::ffi::{OsStr, OsString}; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; #[cfg(target_os = "wasi")] use std::os::wasi::ffi::OsStrExt; use std::{fmt, fs, io}; pub(crate) struct DirEntryInner { pub(super) rustix: DirEntry, pub(super) read_dir: ReadDirInner, } impl DirEntryInner { #[inline] pub(crate) fn open(&self, options: &OpenOptions) -> io::Result { self.read_dir.open(self.file_name_bytes(), options) } #[inline] pub(crate) fn metadata(&self) -> io::Result { self.read_dir.metadata(self.file_name_bytes()) } #[inline] pub(crate) fn remove_file(&self) -> io::Result<()> { self.read_dir.remove_file(self.file_name_bytes()) } #[inline] pub(crate) fn read_dir(&self, follow: FollowSymlinks) -> io::Result { self.read_dir.read_dir(self.file_name_bytes(), follow) } #[inline] pub(crate) fn remove_dir(&self) -> io::Result<()> { self.read_dir.remove_dir(self.file_name_bytes()) } #[cfg(not(any(target_os = "illumos", target_os = "solaris")))] #[inline] #[allow(clippy::unnecessary_wraps)] pub(crate) fn file_type(&self) -> io::Result { use crate::fs::ImplFileTypeExt; Ok(match self.rustix.file_type() { rustix::fs::FileType::Directory => FileType::dir(), rustix::fs::FileType::RegularFile => FileType::file(), rustix::fs::FileType::Symlink => FileType::ext(ImplFileTypeExt::symlink()), #[cfg(not(target_os = "wasi"))] rustix::fs::FileType::Fifo => FileType::ext(ImplFileTypeExt::fifo()), #[cfg(not(target_os = "wasi"))] rustix::fs::FileType::Socket => FileType::ext(ImplFileTypeExt::socket()), rustix::fs::FileType::CharacterDevice => FileType::ext(ImplFileTypeExt::char_device()), rustix::fs::FileType::BlockDevice => FileType::ext(ImplFileTypeExt::block_device()), rustix::fs::FileType::Unknown => FileType::unknown(), }) } #[cfg(any(target_os = "illumos", target_os = "solaris"))] #[inline] pub(crate) fn file_type(&self) -> io::Result { // These platforms don't have the file type on the dirent, so we must lstat the file. self.metadata().map(|m| m.file_type()) } #[inline] pub(crate) fn file_name(&self) -> OsString { self.file_name_bytes().to_os_string() } #[inline] pub(crate) fn ino(&self) -> u64 { self.rustix.ino() } #[inline] pub(crate) fn is_same_file(&self, metadata: &Metadata) -> io::Result { let self_md = self.metadata()?; Ok(self_md.ino() == metadata.ino() && self_md.dev() == metadata.dev()) } fn file_name_bytes(&self) -> &OsStr { OsStr::from_bytes(self.rustix.file_name().to_bytes()) } } impl fmt::Debug for DirEntryInner { // Like libstd's version, but doesn't print the path. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("DirEntry").field(&self.file_name()).finish() } } cap-primitives-3.4.1/src/rustix/fs/dir_options_ext.rs000064400000000000000000000007501046102023000211030ustar 00000000000000#[derive(Debug, Clone)] pub(crate) struct DirOptionsExt { pub(super) mode: u32, } impl DirOptionsExt { pub(crate) const fn new() -> Self { Self { // The default value; see // mode: 0o777, } } } impl crate::fs::DirBuilderExt for DirOptionsExt { fn mode(&mut self, mode: u32) -> &mut Self { self.mode = mode; self } } cap-primitives-3.4.1/src/rustix/fs/dir_utils.rs000064400000000000000000000174701046102023000176770ustar 00000000000000use crate::fs::OpenOptions; use ambient_authority::AmbientAuthority; use rustix::fs::OFlags; use std::ffi::{OsStr, OsString}; use std::ops::Deref; #[cfg(unix)] use std::os::unix::{ ffi::{OsStrExt, OsStringExt}, fs::OpenOptionsExt, }; #[cfg(target_os = "wasi")] use std::os::wasi::{ ffi::{OsStrExt, OsStringExt}, fs::OpenOptionsExt, }; use std::path::{Path, PathBuf}; use std::{fs, io}; /// Rust's `Path` implicitly strips redundant slashes, however they aren't /// redundant in one case: at the end of a path they indicate that a path is /// expected to name a directory. pub(crate) fn path_requires_dir(path: &Path) -> bool { let bytes = path.as_os_str().as_bytes(); // If a path ends with '/' or '.', it's a directory. These aren't the only // cases, but they are the only cases that Rust's `Path` implicitly // normalizes away. bytes.ends_with(b"/") || bytes.ends_with(b"/.") } /// Rust's `Path` implicitly strips trailing `.` components, however they /// aren't redundant in one case: at the end of a path they are the final path /// component, which has different path lookup behavior. pub(crate) fn path_has_trailing_dot(path: &Path) -> bool { let mut bytes = path.as_os_str().as_bytes(); // If a path ends with '.' followed by any number of '/'s, it's a trailing dot. while let Some((last, rest)) = bytes.split_last() { if *last == b'/' { bytes = rest; } else { break; } } bytes.ends_with(b"/.") || bytes == b"." } /// Rust's `Path` implicitly strips trailing `/`s, however they aren't /// redundant in one case: at the end of a path they are the final path /// component, which has different path lookup behavior. pub(crate) fn path_has_trailing_slash(path: &Path) -> bool { let bytes = path.as_os_str().as_bytes(); bytes.ends_with(b"/") } /// Append a trailing `/`. This can be used to require that the given `path` /// names a directory. pub(crate) fn append_dir_suffix(path: PathBuf) -> PathBuf { let mut bytes = path.into_os_string().into_vec(); bytes.push(b'/'); OsString::from_vec(bytes).into() } /// Strip trailing `/`s, unless this reduces `path` to `/` itself. This is /// used by `create_dir` and others to prevent paths like `foo/` from /// canonicalizing to `foo/.` since these syscalls treat these differently. #[allow(clippy::indexing_slicing)] pub(crate) fn strip_dir_suffix(path: &Path) -> impl Deref + '_ { let mut bytes = path.as_os_str().as_bytes(); while bytes.len() > 1 && *bytes.last().unwrap() == b'/' { bytes = &bytes[..bytes.len() - 1]; } OsStr::from_bytes(bytes).as_ref() } /// Return an `OpenOptions` for opening directories. pub(crate) fn dir_options() -> OpenOptions { OpenOptions::new().read(true).dir_required(true).clone() } /// Like `dir_options`, but additionally request the ability to read the /// directory entries. pub(crate) fn readdir_options() -> OpenOptions { OpenOptions::new() .read(true) .dir_required(true) .readdir_required(true) .clone() } /// Return an `OpenOptions` for canonicalizing paths. pub(crate) fn canonicalize_options() -> OpenOptions { OpenOptions::new().read(true).clone() } /// Open a directory named by a bare path, using the host process' ambient /// authority. /// /// # Ambient Authority /// /// This function is not sandboxed and may trivially access any path that the /// host process has access to. pub(crate) fn open_ambient_dir_impl( path: &Path, ambient_authority: AmbientAuthority, ) -> io::Result { let _ = ambient_authority; let mut options = fs::OpenOptions::new(); options.read(true); #[cfg(not(target_os = "wasi"))] // This is for `std::fs`, so we don't have `dir_required`, so set // `O_DIRECTORY` manually. options.custom_flags((OFlags::DIRECTORY | target_o_path()).bits() as i32); #[cfg(target_os = "wasi")] options.directory(true); options.open(path) } /// Use `O_PATH` on platforms which have it, or none otherwise. #[inline] pub(crate) const fn target_o_path() -> OFlags { #[cfg(any( target_os = "android", target_os = "emscripten", target_os = "freebsd", target_os = "fuchsia", target_os = "linux", target_os = "redox", ))] { OFlags::PATH } #[cfg(any( target_os = "dragonfly", target_os = "ios", target_os = "macos", target_os = "netbsd", target_os = "openbsd", target_os = "wasi", target_os = "illumos", target_os = "solaris", ))] { OFlags::empty() } } #[cfg(racy_asserts)] #[test] fn append_dir_suffix_tests() { assert!(append_dir_suffix(Path::new("foo").to_path_buf()) .display() .to_string() .ends_with('/')); } #[test] fn strip_dir_suffix_tests() { assert_eq!(&*strip_dir_suffix(Path::new("/foo//")), Path::new("/foo")); assert_eq!(&*strip_dir_suffix(Path::new("/foo/")), Path::new("/foo")); assert_eq!(&*strip_dir_suffix(Path::new("foo/")), Path::new("foo")); assert_eq!(&*strip_dir_suffix(Path::new("foo")), Path::new("foo")); assert_eq!(&*strip_dir_suffix(Path::new("/")), Path::new("/")); assert_eq!(&*strip_dir_suffix(Path::new("//")), Path::new("/")); assert_eq!(&*strip_dir_suffix(Path::new("/.")), Path::new("/.")); assert_eq!(&*strip_dir_suffix(Path::new("//.")), Path::new("/.")); assert_eq!(&*strip_dir_suffix(Path::new(".")), Path::new(".")); assert_eq!(&*strip_dir_suffix(Path::new("foo/.")), Path::new("foo/.")); } #[test] fn test_path_requires_dir() { assert!(!path_requires_dir(Path::new("."))); assert!(path_requires_dir(Path::new("/"))); assert!(path_requires_dir(Path::new("//"))); assert!(path_requires_dir(Path::new("/./."))); assert!(path_requires_dir(Path::new("foo/"))); assert!(path_requires_dir(Path::new("foo//"))); assert!(path_requires_dir(Path::new("foo//."))); assert!(path_requires_dir(Path::new("foo/./."))); assert!(path_requires_dir(Path::new("foo/./"))); assert!(path_requires_dir(Path::new("foo/.//"))); } #[test] fn test_path_has_trailing_dot() { assert!(!path_has_trailing_dot(Path::new("foo"))); assert!(!path_has_trailing_dot(Path::new("foo."))); assert!(!path_has_trailing_dot(Path::new("/./foo"))); assert!(!path_has_trailing_dot(Path::new(".."))); assert!(!path_has_trailing_dot(Path::new("/.."))); assert!(!path_has_trailing_dot(Path::new("/"))); assert!(!path_has_trailing_dot(Path::new("//"))); assert!(!path_has_trailing_dot(Path::new("foo//"))); assert!(!path_has_trailing_dot(Path::new("foo/"))); assert!(path_has_trailing_dot(Path::new("."))); assert!(path_has_trailing_dot(Path::new("/./."))); assert!(path_has_trailing_dot(Path::new("foo//."))); assert!(path_has_trailing_dot(Path::new("foo/./."))); assert!(path_has_trailing_dot(Path::new("foo/./"))); assert!(path_has_trailing_dot(Path::new("foo/.//"))); } #[test] fn test_path_has_trailing_slash() { assert!(path_has_trailing_slash(Path::new("/"))); assert!(path_has_trailing_slash(Path::new("//"))); assert!(path_has_trailing_slash(Path::new("foo//"))); assert!(path_has_trailing_slash(Path::new("foo/"))); assert!(path_has_trailing_slash(Path::new("foo/./"))); assert!(path_has_trailing_slash(Path::new("foo/.//"))); assert!(!path_has_trailing_slash(Path::new("foo"))); assert!(!path_has_trailing_slash(Path::new("foo."))); assert!(!path_has_trailing_slash(Path::new("/./foo"))); assert!(!path_has_trailing_slash(Path::new(".."))); assert!(!path_has_trailing_slash(Path::new("/.."))); assert!(!path_has_trailing_slash(Path::new("."))); assert!(!path_has_trailing_slash(Path::new("/./."))); assert!(!path_has_trailing_slash(Path::new("foo//."))); assert!(!path_has_trailing_slash(Path::new("foo/./."))); } cap-primitives-3.4.1/src/rustix/fs/errors.rs000064400000000000000000000010431046102023000172020ustar 00000000000000use std::io; #[cfg(any(target_os = "android", target_os = "linux"))] #[cold] pub(crate) fn invalid_flags() -> io::Error { rustix::io::Errno::INVAL.into() } #[cold] pub(crate) fn no_such_file_or_directory() -> io::Error { rustix::io::Errno::NOENT.into() } #[cold] pub(crate) fn is_directory() -> io::Error { rustix::io::Errno::ISDIR.into() } #[cold] pub(crate) fn is_not_directory() -> io::Error { rustix::io::Errno::NOTDIR.into() } #[cold] pub(crate) fn too_many_symlinks() -> io::Error { rustix::io::Errno::LOOP.into() } cap-primitives-3.4.1/src/rustix/fs/file_path.rs000064400000000000000000000012341046102023000176230ustar 00000000000000use crate::fs::file_path_by_searching; #[cfg(not(any(target_os = "wasi", target_os = "fuchsia")))] use rustix::termios::ttyname; #[cfg(not(any(target_os = "wasi", target_os = "fuchsia")))] use std::ffi::OsString; use std::fs; #[cfg(unix)] use std::os::unix::ffi::OsStringExt; use std::path::PathBuf; pub(crate) fn file_path_by_ttyname_or_seaching(file: &fs::File) -> Option { // If it happens to be a tty, we can look up its name. #[cfg(not(any(target_os = "wasi", target_os = "fuchsia")))] if let Ok(name) = ttyname(file, Vec::new()) { return Some(OsString::from_vec(name.into_bytes()).into()); } file_path_by_searching(file) } cap-primitives-3.4.1/src/rustix/fs/file_type_ext.rs000064400000000000000000000072301046102023000205320ustar 00000000000000use crate::fs::FileType; use rustix::fs::RawMode; use std::{fs, io}; /// A type that implements `FileTypeExt` for this platform. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub(crate) enum ImplFileTypeExt { Symlink, BlockDevice, CharDevice, Fifo, Socket, } impl ImplFileTypeExt { /// Constructs a new instance of `FileType` from the given /// [`std::fs::File`] and [`std::fs::FileType`]. #[inline] #[allow(clippy::unnecessary_wraps)] pub(crate) fn from(_file: &fs::File, metadata: &fs::Metadata) -> io::Result { // On `rustix`-style platforms, the `Metadata` has everything we need. Ok(Self::from_just_metadata(metadata)) } /// Constructs a new instance of `FileType` from the given /// [`std::fs::Metadata`]. #[inline] pub(crate) fn from_just_metadata(metadata: &fs::Metadata) -> FileType { let std = metadata.file_type(); Self::from_std(std) } /// Constructs a new instance of `Self` from the given /// [`std::fs::FileType`]. #[inline] pub(crate) fn from_std(std: fs::FileType) -> FileType { use rustix::fs::FileTypeExt; if std.is_file() { FileType::file() } else if std.is_dir() { FileType::dir() } else if std.is_symlink() { FileType::ext(Self::Symlink) } else if std.is_block_device() { FileType::ext(Self::BlockDevice) } else if std.is_char_device() { FileType::ext(Self::CharDevice) } else { #[cfg(not(target_os = "wasi"))] if std.is_fifo() { return FileType::ext(Self::Fifo); } #[cfg(not(target_os = "wasi"))] if std.is_socket() { return FileType::ext(Self::Socket); } FileType::unknown() } } /// Constructs a new instance of `FileType` from the given /// [`RawMode`]. #[inline] pub(crate) const fn from_raw_mode(mode: RawMode) -> FileType { match rustix::fs::FileType::from_raw_mode(mode) { rustix::fs::FileType::RegularFile => FileType::file(), rustix::fs::FileType::Directory => FileType::dir(), rustix::fs::FileType::Symlink => FileType::ext(Self::symlink()), #[cfg(not(target_os = "wasi"))] rustix::fs::FileType::Fifo => FileType::ext(Self::fifo()), rustix::fs::FileType::CharacterDevice => FileType::ext(Self::char_device()), rustix::fs::FileType::BlockDevice => FileType::ext(Self::block_device()), #[cfg(not(target_os = "wasi"))] rustix::fs::FileType::Socket => FileType::ext(Self::socket()), _ => FileType::unknown(), } } /// Creates a `FileType` for which `is_symlink()` returns `true`. #[inline] pub(crate) const fn symlink() -> Self { Self::Symlink } /// Creates a `FileType` for which `is_block_device()` returns `true`. #[inline] pub(crate) const fn block_device() -> Self { Self::BlockDevice } /// Creates a `FileType` for which `is_char_device()` returns `true`. #[inline] pub(crate) const fn char_device() -> Self { Self::CharDevice } /// Creates a `FileType` for which `is_fifo()` returns `true`. #[inline] pub(crate) const fn fifo() -> Self { Self::Fifo } /// Creates a `FileType` for which `is_socket()` returns `true`. #[inline] pub(crate) const fn socket() -> Self { Self::Socket } /// Tests whether this file type represents a symbolic link. #[inline] pub(crate) fn is_symlink(&self) -> bool { *self == Self::Symlink } } cap-primitives-3.4.1/src/rustix/fs/hard_link_unchecked.rs000064400000000000000000000011701046102023000216330ustar 00000000000000use rustix::fs::{linkat, AtFlags}; use std::path::Path; use std::{fs, io}; /// *Unsandboxed* function similar to `hard_link`, but which does not perform /// sandboxing. /// /// Even though POSIX `linkat` has the ability to follow symlinks in /// `old_path`, using `AT_SYMLINK_FOLLOW`, Rust's `hard_link` doesn't need /// that, so we don't expose it here. pub(crate) fn hard_link_unchecked( old_start: &fs::File, old_path: &Path, new_start: &fs::File, new_path: &Path, ) -> io::Result<()> { Ok(linkat( old_start, old_path, new_start, new_path, AtFlags::empty(), )?) } cap-primitives-3.4.1/src/rustix/fs/is_file_read_write_impl.rs000064400000000000000000000002731046102023000225320ustar 00000000000000use rustix::fs::is_file_read_write; use std::{fs, io}; #[inline] pub(crate) fn is_file_read_write_impl(file: &fs::File) -> io::Result<(bool, bool)> { Ok(is_file_read_write(file)?) } cap-primitives-3.4.1/src/rustix/fs/is_root_dir.rs000064400000000000000000000003451046102023000202060ustar 00000000000000use crate::fs::{Metadata, ReadDir}; use std::{fs, io}; pub(crate) fn is_root_dir(dir: &fs::File, parent_iter: &ReadDir) -> io::Result { Ok(Metadata::from_file(dir)?.is_same_file(&parent_iter.inner.self_metadata()?)) } cap-primitives-3.4.1/src/rustix/fs/is_same_file.rs000064400000000000000000000023621046102023000203120ustar 00000000000000use crate::fs::{Metadata, MetadataExt}; use std::{fs, io}; /// Determine if `a` and `b` refer to the same inode on the same device. pub(crate) fn is_same_file(a: &fs::File, b: &fs::File) -> io::Result { let a_metadata = Metadata::from_file(a)?; let b_metadata = Metadata::from_file(b)?; is_same_file_metadata(&a_metadata, &b_metadata) } /// Determine if `a` and `b` are metadata for the same inode on the same /// device. pub(crate) fn is_same_file_metadata(a: &Metadata, b: &Metadata) -> io::Result { Ok(a.dev() == b.dev() && a.ino() == b.ino()) } /// Determine if `a` and `b` definitely refer to different inodes. /// /// This is similar to `is_same_file`, but is conservative, and doesn't depend /// on nightly-only features. #[allow(dead_code)] pub(crate) fn is_different_file(a: &fs::File, b: &fs::File) -> io::Result { is_same_file(a, b).map(|same| !same) } /// Determine if `a` and `b` are metadata for definitely different inodes. /// /// This is similar to `is_same_file_metadata`, but is conservative, and /// doesn't depend on nightly-only features. #[allow(dead_code)] pub(crate) fn is_different_file_metadata(a: &Metadata, b: &Metadata) -> io::Result { is_same_file_metadata(a, b).map(|same| !same) } cap-primitives-3.4.1/src/rustix/fs/metadata_ext.rs000064400000000000000000000332231046102023000203330ustar 00000000000000#![allow(clippy::useless_conversion)] use crate::fs::{ImplFileTypeExt, ImplPermissionsExt, Metadata}; use crate::time::{Duration, SystemClock, SystemTime}; #[cfg(target_os = "linux")] use rustix::fs::{makedev, Statx, StatxFlags}; use rustix::fs::{RawMode, Stat}; use std::{fs, io}; #[derive(Debug, Clone)] pub(crate) struct ImplMetadataExt { dev: u64, ino: u64, #[cfg(not(target_os = "wasi"))] mode: u32, nlink: u64, #[cfg(not(target_os = "wasi"))] uid: u32, #[cfg(not(target_os = "wasi"))] gid: u32, #[cfg(not(target_os = "wasi"))] rdev: u64, size: u64, #[cfg(not(target_os = "wasi"))] atime: i64, #[cfg(not(target_os = "wasi"))] atime_nsec: i64, #[cfg(not(target_os = "wasi"))] mtime: i64, #[cfg(not(target_os = "wasi"))] mtime_nsec: i64, #[cfg(not(target_os = "wasi"))] ctime: i64, #[cfg(not(target_os = "wasi"))] ctime_nsec: i64, #[cfg(not(target_os = "wasi"))] blksize: u64, #[cfg(not(target_os = "wasi"))] blocks: u64, #[cfg(target_os = "wasi")] atim: u64, #[cfg(target_os = "wasi")] mtim: u64, #[cfg(target_os = "wasi")] ctim: u64, } impl ImplMetadataExt { /// Constructs a new instance of `Self` from the given [`std::fs::File`] /// and [`std::fs::Metadata`]. #[inline] #[allow(clippy::unnecessary_wraps)] pub(crate) fn from(_file: &fs::File, std: &fs::Metadata) -> io::Result { // On `rustix`-style platforms, the `Metadata` has everything we need. Ok(Self::from_just_metadata(std)) } /// Constructs a new instance of `Self` from the given /// [`std::fs::Metadata`]. #[inline] pub(crate) fn from_just_metadata(std: &fs::Metadata) -> Self { use rustix::fs::MetadataExt; Self { dev: std.dev(), ino: std.ino(), #[cfg(not(target_os = "wasi"))] mode: std.mode(), nlink: std.nlink(), #[cfg(not(target_os = "wasi"))] uid: std.uid(), #[cfg(not(target_os = "wasi"))] gid: std.gid(), #[cfg(not(target_os = "wasi"))] rdev: std.rdev(), size: std.size(), #[cfg(not(target_os = "wasi"))] atime: std.atime(), #[cfg(not(target_os = "wasi"))] atime_nsec: std.atime_nsec(), #[cfg(not(target_os = "wasi"))] mtime: std.mtime(), #[cfg(not(target_os = "wasi"))] mtime_nsec: std.mtime_nsec(), #[cfg(not(target_os = "wasi"))] ctime: std.ctime(), #[cfg(not(target_os = "wasi"))] ctime_nsec: std.ctime_nsec(), #[cfg(not(target_os = "wasi"))] blksize: std.blksize(), #[cfg(not(target_os = "wasi"))] blocks: std.blocks(), #[cfg(target_os = "wasi")] atim: std.atim(), #[cfg(target_os = "wasi")] mtim: std.mtim(), #[cfg(target_os = "wasi")] ctim: std.ctim(), } } /// Constructs a new instance of `Metadata` from the given `Stat`. #[inline] #[allow(unused_comparisons)] // NB: rust-lang/rust#115823 requires this here instead of on `st_dev` processing below pub(crate) fn from_rustix(stat: Stat) -> Metadata { #[cfg(not(target_os = "wasi"))] use rustix::fs::StatExt; Metadata { file_type: ImplFileTypeExt::from_raw_mode(stat.st_mode as RawMode), len: u64::try_from(stat.st_size).unwrap(), #[cfg(not(target_os = "wasi"))] permissions: ImplPermissionsExt::from_raw_mode(stat.st_mode as RawMode), #[cfg(target_os = "wasi")] permissions: ImplPermissionsExt::default(), #[cfg(not(any(target_os = "netbsd", target_os = "wasi")))] modified: system_time_from_rustix( stat.mtime().try_into().unwrap(), stat.st_mtime_nsec as _, ), #[cfg(not(any(target_os = "netbsd", target_os = "wasi")))] accessed: system_time_from_rustix( stat.atime().try_into().unwrap(), stat.st_atime_nsec as _, ), #[cfg(target_os = "netbsd")] modified: system_time_from_rustix( stat.st_mtime.try_into().unwrap(), stat.st_mtimensec as _, ), #[cfg(target_os = "netbsd")] accessed: system_time_from_rustix( stat.st_atime.try_into().unwrap(), stat.st_atimensec as _, ), #[cfg(target_os = "wasi")] modified: system_time_from_rustix(stat.st_mtim.tv_sec, stat.st_mtim.tv_nsec as _), #[cfg(target_os = "wasi")] accessed: system_time_from_rustix(stat.st_atim.tv_sec, stat.st_atim.tv_nsec as _), #[cfg(any( target_os = "freebsd", target_os = "openbsd", target_os = "macos", target_os = "ios" ))] created: system_time_from_rustix( stat.st_birthtime.try_into().unwrap(), stat.st_birthtime_nsec as _, ), #[cfg(target_os = "netbsd")] created: system_time_from_rustix( stat.st_birthtime.try_into().unwrap(), stat.st_birthtimensec as _, ), // `stat.st_ctime` is the latest status change; we want the creation. #[cfg(not(any( target_os = "freebsd", target_os = "openbsd", target_os = "macos", target_os = "ios", target_os = "netbsd" )))] created: None, ext: Self { // The type of `st_dev` is `dev_t` which is signed on some // platforms and unsigned on other platforms. A `u64` is enough // to work for all unsigned platforms, and for signed platforms // perform a sign extension to `i64` and then view that as an // unsigned 64-bit number instead. // // Note that the `unused_comparisons` is ignored here for // platforms where it's unsigned since the first branch here // will never be taken. dev: if stat.st_dev < 0 { i64::try_from(stat.st_dev).unwrap() as u64 } else { u64::try_from(stat.st_dev).unwrap() }, ino: stat.st_ino.into(), #[cfg(not(target_os = "wasi"))] mode: u32::from(stat.st_mode), nlink: u64::from(stat.st_nlink), #[cfg(not(target_os = "wasi"))] uid: stat.st_uid, #[cfg(not(target_os = "wasi"))] gid: stat.st_gid, #[cfg(not(target_os = "wasi"))] rdev: u64::try_from(stat.st_rdev).unwrap(), size: u64::try_from(stat.st_size).unwrap(), #[cfg(not(target_os = "wasi"))] atime: i64::try_from(stat.atime()).unwrap(), #[cfg(not(any(target_os = "netbsd", target_os = "wasi")))] atime_nsec: stat.st_atime_nsec as _, #[cfg(target_os = "netbsd")] atime_nsec: stat.st_atimensec as _, #[cfg(not(target_os = "wasi"))] mtime: i64::try_from(stat.mtime()).unwrap(), #[cfg(not(any(target_os = "netbsd", target_os = "wasi")))] mtime_nsec: stat.st_mtime_nsec as _, #[cfg(target_os = "netbsd")] mtime_nsec: stat.st_mtimensec as _, #[cfg(not(target_os = "wasi"))] ctime: i64::try_from(stat.ctime()).unwrap(), #[cfg(not(any(target_os = "netbsd", target_os = "wasi")))] ctime_nsec: stat.st_ctime_nsec as _, #[cfg(target_os = "netbsd")] ctime_nsec: stat.st_ctimensec as _, #[cfg(not(target_os = "wasi"))] blksize: u64::try_from(stat.st_blksize).unwrap(), #[cfg(not(target_os = "wasi"))] blocks: u64::try_from(stat.st_blocks).unwrap(), #[cfg(target_os = "wasi")] atim: u64::try_from( stat.st_atim.tv_sec as u64 * 1000000000 + stat.st_atim.tv_nsec as u64, ) .unwrap(), #[cfg(target_os = "wasi")] mtim: u64::try_from( stat.st_mtim.tv_sec as u64 * 1000000000 + stat.st_mtim.tv_nsec as u64, ) .unwrap(), #[cfg(target_os = "wasi")] ctim: u64::try_from( stat.st_ctim.tv_sec as u64 * 1000000000 + stat.st_ctim.tv_nsec as u64, ) .unwrap(), }, } } /// Constructs a new instance of `Metadata` from the given `Statx`. #[cfg(target_os = "linux")] #[inline] pub(crate) fn from_rustix_statx(statx: Statx) -> Metadata { Metadata { file_type: ImplFileTypeExt::from_raw_mode(RawMode::from(statx.stx_mode)), len: u64::try_from(statx.stx_size).unwrap(), permissions: ImplPermissionsExt::from_raw_mode(RawMode::from(statx.stx_mode)), modified: if statx.stx_mask & StatxFlags::MTIME.bits() != 0 { system_time_from_rustix(statx.stx_mtime.tv_sec, statx.stx_mtime.tv_nsec as _) } else { None }, accessed: if statx.stx_mask & StatxFlags::ATIME.bits() != 0 { system_time_from_rustix(statx.stx_atime.tv_sec, statx.stx_atime.tv_nsec as _) } else { None }, created: if statx.stx_mask & StatxFlags::BTIME.bits() != 0 { system_time_from_rustix(statx.stx_btime.tv_sec, statx.stx_btime.tv_nsec as _) } else { None }, ext: Self { dev: makedev(statx.stx_dev_major, statx.stx_dev_minor), ino: statx.stx_ino.into(), mode: u32::from(statx.stx_mode), nlink: u64::from(statx.stx_nlink), uid: statx.stx_uid, gid: statx.stx_gid, rdev: makedev(statx.stx_rdev_major, statx.stx_rdev_minor), size: statx.stx_size, atime: i64::from(statx.stx_atime.tv_sec), atime_nsec: statx.stx_atime.tv_nsec as _, mtime: i64::from(statx.stx_mtime.tv_sec), mtime_nsec: statx.stx_mtime.tv_nsec as _, ctime: i64::from(statx.stx_ctime.tv_sec), ctime_nsec: statx.stx_ctime.tv_nsec as _, blksize: u64::from(statx.stx_blksize), blocks: statx.stx_blocks, }, } } /// Determine if `self` and `other` refer to the same inode on the same /// device. pub(crate) const fn is_same_file(&self, other: &Self) -> bool { self.dev == other.dev && self.ino == other.ino } } #[allow(clippy::similar_names)] fn system_time_from_rustix(sec: i64, nsec: u64) -> Option { if sec >= 0 { SystemClock::UNIX_EPOCH.checked_add(Duration::new(u64::try_from(sec).unwrap(), nsec as _)) } else { SystemClock::UNIX_EPOCH .checked_sub(Duration::new(u64::try_from(-sec).unwrap(), 0)) .map(|t| t.checked_add(Duration::new(0, nsec as u32))) .flatten() } } impl crate::fs::MetadataExt for ImplMetadataExt { #[inline] fn dev(&self) -> u64 { self.dev } #[inline] fn ino(&self) -> u64 { self.ino } #[cfg(not(target_os = "wasi"))] #[inline] fn mode(&self) -> u32 { self.mode } #[inline] fn nlink(&self) -> u64 { self.nlink } #[cfg(not(target_os = "wasi"))] #[inline] fn uid(&self) -> u32 { self.uid } #[cfg(not(target_os = "wasi"))] #[inline] fn gid(&self) -> u32 { self.gid } #[cfg(not(target_os = "wasi"))] #[inline] fn rdev(&self) -> u64 { self.rdev } #[inline] fn size(&self) -> u64 { self.size } #[cfg(not(target_os = "wasi"))] #[inline] fn atime(&self) -> i64 { self.atime } #[cfg(not(target_os = "wasi"))] #[inline] fn atime_nsec(&self) -> i64 { self.atime_nsec } #[cfg(not(target_os = "wasi"))] #[inline] fn mtime(&self) -> i64 { self.mtime } #[cfg(not(target_os = "wasi"))] #[inline] fn mtime_nsec(&self) -> i64 { self.mtime_nsec } #[cfg(not(target_os = "wasi"))] #[inline] fn ctime(&self) -> i64 { self.ctime } #[cfg(not(target_os = "wasi"))] #[inline] fn ctime_nsec(&self) -> i64 { self.ctime_nsec } #[cfg(not(target_os = "wasi"))] #[inline] fn blksize(&self) -> u64 { self.blksize } #[cfg(not(target_os = "wasi"))] #[inline] fn blocks(&self) -> u64 { self.blocks } #[cfg(target_os = "wasi")] fn atim(&self) -> u64 { self.atim } #[cfg(target_os = "wasi")] fn mtim(&self) -> u64 { self.mtim } #[cfg(target_os = "wasi")] fn ctim(&self) -> u64 { self.ctim } } /// It should be possible to represent times before the Epoch. /// https://github.com/bytecodealliance/cap-std/issues/328 #[test] fn negative_time() { let system_time = system_time_from_rustix(-1, 1).unwrap(); let d = SystemClock::UNIX_EPOCH.duration_since(system_time).unwrap(); assert_eq!(d.as_secs(), 0); if !cfg!(emulate_second_only_system) { assert_eq!(d.subsec_nanos(), 999999999); } } cap-primitives-3.4.1/src/rustix/fs/mod.rs000064400000000000000000000146571046102023000164640ustar 00000000000000mod access_unchecked; mod copy_impl; mod create_dir_unchecked; mod dir_entry_inner; #[cfg(not(target_os = "wasi"))] mod dir_options_ext; mod dir_utils; #[cfg(not(any(target_os = "android", target_os = "linux")))] mod file_path; mod file_type_ext; mod hard_link_unchecked; mod is_file_read_write_impl; mod is_root_dir; mod is_same_file; mod metadata_ext; mod oflags; mod open_options_ext; mod open_unchecked; mod permissions_ext; mod read_dir_inner; mod read_link_unchecked; mod remove_dir_all_impl; mod remove_dir_unchecked; mod remove_file_unchecked; mod remove_open_dir_by_searching; mod rename_unchecked; mod reopen_impl; #[cfg(not(any(target_os = "android", target_os = "linux", target_os = "wasi")))] mod set_permissions_impl; #[cfg(not(target_os = "wasi"))] mod set_symlink_permissions_unchecked; #[cfg(not(any(target_os = "android", target_os = "linux")))] mod set_times_impl; mod stat_unchecked; mod symlink_unchecked; mod times; pub(crate) mod errors; // On Linux, use optimized implementations based on // `openat2` and `O_PATH` when available. // // On FreeBSD, use optimized implementations based on // `O_RESOLVE_BENEATH`/`AT_RESOLVE_BENEATH` and `O_PATH` when available. #[cfg(any(target_os = "macos", target_os = "ios"))] pub(crate) use crate::rustix::darwin::fs::*; #[cfg(target_os = "freebsd")] pub(crate) use crate::rustix::freebsd::fs::*; #[cfg(any(target_os = "android", target_os = "linux"))] pub(crate) use crate::rustix::linux::fs::*; #[cfg(not(any(target_os = "android", target_os = "linux", target_os = "freebsd")))] #[rustfmt::skip] pub(crate) use crate::fs::{ manually::open_entry as open_entry_impl, manually::open as open_impl, manually::stat as stat_impl, manually::canonicalize as canonicalize_impl, via_parent::set_times_nofollow as set_times_nofollow_impl, }; #[cfg(any(target_os = "macos", target_os = "ios"))] pub(super) use file_path::file_path_by_ttyname_or_seaching; #[cfg(not(any( target_os = "android", target_os = "linux", target_os = "macos", target_os = "ios" )))] pub(crate) use file_path::file_path_by_ttyname_or_seaching as file_path; #[cfg(not(any( target_os = "android", target_os = "linux", target_os = "freebsd", target_os = "wasi" )))] pub(crate) use set_permissions_impl::set_permissions_impl; #[cfg(target_os = "freebsd")] pub(crate) use set_permissions_impl::set_permissions_impl as set_permissions_manually; #[cfg(not(target_os = "wasi"))] pub(crate) use set_symlink_permissions_unchecked::set_symlink_permissions_unchecked; #[cfg(not(any(target_os = "android", target_os = "linux", target_os = "freebsd")))] pub(crate) use set_times_impl::set_times_impl; #[cfg(target_os = "freebsd")] pub(crate) use set_times_impl::set_times_impl as set_times_manually; #[rustfmt::skip] pub(crate) use crate::fs::{ via_parent::access as access_impl, via_parent::hard_link as hard_link_impl, via_parent::create_dir as create_dir_impl, via_parent::read_link as read_link_impl, via_parent::rename as rename_impl, via_parent::symlink as symlink_impl, remove_open_dir_by_searching as remove_open_dir_impl, }; #[cfg(not(target_os = "wasi"))] pub(crate) use crate::fs::via_parent::set_symlink_permissions as set_symlink_permissions_impl; #[cfg(not(target_os = "freebsd"))] #[rustfmt::skip] pub(crate) use crate::fs::{ via_parent::remove_dir as remove_dir_impl, via_parent::remove_file as remove_file_impl, }; pub(crate) use access_unchecked::access_unchecked; pub(crate) use copy_impl::copy_impl; pub(crate) use create_dir_unchecked::create_dir_unchecked; pub(crate) use dir_entry_inner::DirEntryInner; #[cfg(not(target_os = "wasi"))] pub(crate) use dir_options_ext::DirOptionsExt; pub(crate) use dir_utils::*; pub(crate) use file_type_ext::ImplFileTypeExt; pub(crate) use hard_link_unchecked::hard_link_unchecked; pub(crate) use is_file_read_write_impl::is_file_read_write_impl; pub(crate) use is_root_dir::is_root_dir; #[allow(unused_imports)] pub(crate) use is_same_file::{is_different_file, is_different_file_metadata, is_same_file}; pub(crate) use metadata_ext::ImplMetadataExt; pub(crate) use open_options_ext::ImplOpenOptionsExt; pub(crate) use open_unchecked::{open_ambient_impl, open_unchecked}; pub(crate) use permissions_ext::ImplPermissionsExt; pub(crate) use read_dir_inner::ReadDirInner; pub(crate) use read_link_unchecked::read_link_unchecked; pub(crate) use remove_dir_all_impl::{remove_dir_all_impl, remove_open_dir_all_impl}; pub(crate) use remove_dir_unchecked::remove_dir_unchecked; pub(crate) use remove_file_unchecked::remove_file_unchecked; pub(crate) use remove_open_dir_by_searching::remove_open_dir_by_searching; pub(crate) use rename_unchecked::rename_unchecked; pub(crate) use reopen_impl::reopen_impl; pub(crate) use stat_unchecked::stat_unchecked; pub(crate) use symlink_unchecked::symlink_unchecked; #[allow(unused_imports)] pub(crate) use times::{set_times_follow_unchecked, set_times_nofollow_unchecked, to_timespec}; // On Linux, there is a limit of 40 symlink expansions. // Source: pub(crate) const MAX_SYMLINK_EXPANSIONS: u8 = 40; pub(super) use oflags::*; /// Test that `file_path` works on a tty path. #[test] fn tty_path() { #[cfg(unix)] use std::os::unix::fs::FileTypeExt; let paths: &[&str] = if cfg!(target_os = "freebsd") { // On FreeBSD, /dev/{tty,stdin,stdout,stderr} are aliases to different // real devices. &["/dev/ttyv0", "/dev/pts/0"] } else if cfg!(target_os = "illumos") { // On illumos, /dev/std{in,out,err} only exist if they're open. &["/dev/tty", "/dev/pts/0"] } else { &["/dev/tty", "/dev/stdin", "/dev/stdout", "/dev/stderr"] }; for path in paths { // Not all host configurations have these, so only test them if we can // open and canonicalize them, and if they're not FIFOs, which some // OS's use for stdin/stdout/stderr. if let Ok(file) = std::fs::File::open(path) { if !file.metadata().unwrap().file_type().is_fifo() { if let Ok(canonical) = std::fs::canonicalize(path) { assert_eq!( file_path(&file) .as_ref() .map(std::fs::canonicalize) .map(Result::unwrap), Some(canonical), "for path {path}, file_path matches canonicalized path" ); } } } } } cap-primitives-3.4.1/src/rustix/fs/oflags.rs000064400000000000000000000062221046102023000171450ustar 00000000000000use crate::fs::{target_o_path, FollowSymlinks, OpenOptions}; use rustix::fs::OFlags; use std::io; pub(in super::super) fn compute_oflags(options: &OpenOptions) -> io::Result { let mut oflags = OFlags::CLOEXEC; oflags |= get_access_mode(options)?; oflags |= get_creation_mode(options)?; if options.follow == FollowSymlinks::No { oflags |= OFlags::NOFOLLOW; } if options.sync { oflags |= OFlags::SYNC; } if options.dsync { #[cfg(not(target_os = "freebsd"))] { oflags |= OFlags::DSYNC; } // Where needed, approximate `DSYNC` with `SYNC`. #[cfg(target_os = "freebsd")] { oflags |= OFlags::SYNC; } } #[cfg(not(any( target_os = "ios", target_os = "macos", target_os = "freebsd", target_os = "fuchsia" )))] if options.rsync { oflags |= OFlags::RSYNC; } if options.nonblock { oflags |= OFlags::NONBLOCK; } if options.dir_required { oflags |= OFlags::DIRECTORY; // If the target has `O_PATH`, we don't need to read the directory // entries, and we're not requesting write access (which need to // fail on a directory), use it. if !options.readdir_required && !options.write && !options.append { oflags |= target_o_path(); } } // Use `RWMODE` here instead of `ACCMODE` so that we preserve the `O_PATH` // flag. #[cfg(not(target_os = "wasi"))] { oflags |= OFlags::from_bits(options.ext.custom_flags as _).expect("unrecognized OFlags") & !OFlags::RWMODE; } Ok(oflags) } // `OpenOptions` translation code derived from Rust's // library/std/src/sys/unix/fs.rs at revision // 108e90ca78f052c0c1c49c42a22c85620be19712. pub(crate) fn get_access_mode(options: &OpenOptions) -> io::Result { match (options.read, options.write, options.append) { (true, false, false) => Ok(OFlags::RDONLY), (false, true, false) => Ok(OFlags::WRONLY), (true, true, false) => Ok(OFlags::RDWR), (false, _, true) => Ok(OFlags::WRONLY | OFlags::APPEND), (true, _, true) => Ok(OFlags::RDWR | OFlags::APPEND), (false, false, false) => Err(rustix::io::Errno::INVAL.into()), } } pub(crate) fn get_creation_mode(options: &OpenOptions) -> io::Result { match (options.write, options.append) { (true, false) => {} (false, false) => { if options.truncate || options.create || options.create_new { return Err(rustix::io::Errno::INVAL.into()); } } (_, true) => { if options.truncate && !options.create_new { return Err(rustix::io::Errno::INVAL.into()); } } } Ok( match (options.create, options.truncate, options.create_new) { (false, false, false) => OFlags::empty(), (true, false, false) => OFlags::CREATE, (false, true, false) => OFlags::TRUNC, (true, true, false) => OFlags::CREATE | OFlags::TRUNC, (_, _, true) => OFlags::CREATE | OFlags::EXCL, }, ) } cap-primitives-3.4.1/src/rustix/fs/open_options_ext.rs000064400000000000000000000007751046102023000212750ustar 00000000000000#[derive(Debug, Clone)] pub(crate) struct ImplOpenOptionsExt { pub(crate) mode: u32, pub(crate) custom_flags: i32, } impl ImplOpenOptionsExt { pub(crate) const fn new() -> Self { Self { mode: 0o666, custom_flags: 0, } } pub(crate) fn mode(&mut self, mode: u32) -> &mut Self { self.mode = mode; self } pub(crate) fn custom_flags(&mut self, flags: i32) -> &mut Self { self.custom_flags = flags; self } } cap-primitives-3.4.1/src/rustix/fs/open_unchecked.rs000064400000000000000000000047401046102023000206470ustar 00000000000000use super::compute_oflags; use crate::fs::{stat_unchecked, OpenOptions, OpenUncheckedError}; use crate::AmbientAuthority; use io_lifetimes::AsFilelike; use rustix::fs::{openat, Mode, CWD}; use rustix::io; use std::fs; use std::path::Path; /// *Unsandboxed* function similar to `open`, but which does not perform /// sandboxing. pub(crate) fn open_unchecked( start: &fs::File, path: &Path, options: &OpenOptions, ) -> Result { let oflags = compute_oflags(options).map_err(OpenUncheckedError::Other)?; #[allow(clippy::useless_conversion)] #[cfg(not(target_os = "wasi"))] let mode = Mode::from_bits_truncate(options.ext.mode as _); #[cfg(target_os = "wasi")] let mode = Mode::empty(); let err = match openat(start, path, oflags, mode) { Ok(file) => { return Ok(fs::File::from(file)); } Err(err) => err, }; match err { // `ELOOP` is the POSIX standard and most widely used error code to // indicate that a symlink was found when `O_NOFOLLOW` was set. #[cfg(not(any(target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd")))] io::Errno::LOOP => Err(OpenUncheckedError::Symlink(err.into(), ())), // FreeBSD and similar (but not Darwin) use `EMLINK`. #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] io::Errno::MLINK => Err(OpenUncheckedError::Symlink(err.into(), ())), // NetBSD uses `EFTYPE`. #[cfg(target_os = "netbsd")] io::Errno::FTYPE => Err(OpenUncheckedError::Symlink(err.into(), ())), io::Errno::NOENT => Err(OpenUncheckedError::NotFound(err.into())), io::Errno::NOTDIR => { if options.dir_required && stat_unchecked(start, path, options.follow) .map(|m| m.file_type().is_symlink()) .unwrap_or(false) { Err(OpenUncheckedError::Symlink(err.into(), ())) } else { Err(OpenUncheckedError::NotFound(err.into())) } } _ => Err(OpenUncheckedError::Other(err.into())), } } /// *Unsandboxed* function similar to `open`, but which does not perform /// sandboxing. #[inline] pub(crate) fn open_ambient_impl( path: &Path, options: &OpenOptions, ambient_authority: AmbientAuthority, ) -> Result { let _ = ambient_authority; open_unchecked(&CWD.as_filelike_view::(), path, options) } cap-primitives-3.4.1/src/rustix/fs/permissions_ext.rs000064400000000000000000000036271046102023000211330ustar 00000000000000use crate::fs::Permissions; use rustix::fs::RawMode; use std::fs; #[derive(Debug, Clone, Eq, PartialEq)] pub(crate) struct ImplPermissionsExt { #[cfg(not(target_os = "wasi"))] mode: RawMode, } #[cfg(not(target_os = "wasi"))] impl ImplPermissionsExt { /// Constructs a new instance of `Self` from the given /// [`std::fs::Permissions`]. #[inline] pub(crate) fn from_std(std: fs::Permissions) -> Self { use std::os::unix::fs::PermissionsExt; Self { mode: std.mode() as RawMode, } } /// Constructs a new instance of `Permissions` from the given /// `RawMode`. #[inline] pub(crate) const fn from_raw_mode(mode: RawMode) -> Permissions { Permissions { readonly: Self::readonly(mode), ext: Self { mode }, } } /// Test whether the given `RawMode` lacks write permissions. #[inline] pub(crate) const fn readonly(mode: RawMode) -> bool { mode & 0o222 == 0 } /// Test whether the given `RawMode` lacks write permissions. #[inline] pub(crate) fn set_readonly(&mut self, readonly: bool) { if readonly { // remove write permission for all classes; equivalent to `chmod a-w ` self.mode &= !0o222; } else { // add write permission for all classes; equivalent to `chmod a+w ` self.mode |= 0o222; } } } #[cfg(not(target_os = "wasi"))] impl crate::fs::PermissionsExt for ImplPermissionsExt { fn mode(&self) -> u32 { self.mode as u32 } fn set_mode(&mut self, mode: u32) { self.mode = mode as RawMode & 0o7777; } fn from_mode(mode: u32) -> Self { Self { mode: mode as RawMode & 0o7777, } } } #[cfg(target_os = "wasi")] impl ImplPermissionsExt { pub(crate) fn default() -> Permissions { Permissions { readonly: false } } } cap-primitives-3.4.1/src/rustix/fs/read_dir_inner.rs000064400000000000000000000113301046102023000206320ustar 00000000000000use crate::fs::{ open_dir_for_reading, open_dir_for_reading_unchecked, open_entry_impl, read_dir_unchecked, remove_dir_unchecked, remove_file_unchecked, stat_unchecked, DirEntryInner, FollowSymlinks, Metadata, OpenOptions, ReadDir, }; use io_extras::os::rustix::{AsRawFd, FromRawFd, RawFd}; use io_lifetimes::AsFd; use rustix::fd::OwnedFd; use rustix::fs::Dir; use std::ffi::OsStr; use std::mem::ManuallyDrop; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; #[cfg(target_os = "wasi")] use std::os::wasi::ffi::OsStrExt; use std::path::{Component, Path}; use std::sync::{Arc, Mutex}; use std::{fmt, fs, io}; pub(crate) struct ReadDirInner { raw_fd: RawFd, // `Dir` doesn't implement `AsFd`, because libc `fdopendir` has UB if the // file descriptor is used in almost any way, so we hold a separate // `OwnedFd` that we can do `as_fd()` on. rustix: Arc>, } impl ReadDirInner { pub(crate) fn new(start: &fs::File, path: &Path, follow: FollowSymlinks) -> io::Result { let fd = open_dir_for_reading(start, path, follow)?; let dir = Dir::read_from(fd.as_fd())?; Ok(Self { raw_fd: fd.as_fd().as_raw_fd(), rustix: Arc::new(Mutex::new((dir, OwnedFd::from(fd)))), }) } pub(crate) fn read_base_dir(start: &fs::File) -> io::Result { // Open ".", to obtain a new independent file descriptor. Don't use // `dup` since in that case the resulting file descriptor would share // a current position with the original, and `read_dir` calls after // the first `read_dir` call wouldn't start from the beginning. let fd = open_dir_for_reading_unchecked(start, Component::CurDir.as_ref(), FollowSymlinks::No)?; let dir = Dir::read_from(fd.as_fd())?; Ok(Self { raw_fd: fd.as_fd().as_raw_fd(), rustix: Arc::new(Mutex::new((dir, fd.into()))), }) } pub(crate) fn new_unchecked( start: &fs::File, path: &Path, follow: FollowSymlinks, ) -> io::Result { let fd = open_dir_for_reading_unchecked(start, path, follow)?; let dir = Dir::read_from(fd.as_fd())?; Ok(Self { raw_fd: fd.as_fd().as_raw_fd(), rustix: Arc::new(Mutex::new((dir, fd.into()))), }) } pub(super) fn open(&self, file_name: &OsStr, options: &OpenOptions) -> io::Result { open_entry_impl(&self.as_file_view(), file_name, options) } pub(super) fn metadata(&self, file_name: &OsStr) -> io::Result { stat_unchecked(&self.as_file_view(), file_name.as_ref(), FollowSymlinks::No) } pub(super) fn remove_file(&self, file_name: &OsStr) -> io::Result<()> { remove_file_unchecked(&self.as_file_view(), file_name.as_ref()) } pub(super) fn remove_dir(&self, file_name: &OsStr) -> io::Result<()> { remove_dir_unchecked(&self.as_file_view(), file_name.as_ref()) } pub(super) fn self_metadata(&self) -> io::Result { Metadata::from_file(&self.as_file_view()) } pub(super) fn read_dir( &self, file_name: &OsStr, follow: FollowSymlinks, ) -> io::Result { read_dir_unchecked(&self.as_file_view(), file_name.as_ref(), follow) } #[allow(unsafe_code)] fn as_file_view(&self) -> ManuallyDrop { // Safety: `self.rustix` owns the file descriptor. We just hold a // copy outside so that we can read it without taking a lock. ManuallyDrop::new(unsafe { fs::File::from_raw_fd(self.raw_fd) }) } } impl Iterator for ReadDirInner { type Item = io::Result; fn next(&mut self) -> Option { loop { let entry = self.rustix.lock().unwrap().0.read()?; let entry = match entry { Ok(entry) => entry, Err(e) => return Some(Err(e.into())), }; let file_name = entry.file_name().to_bytes(); if file_name != Component::CurDir.as_os_str().as_bytes() && file_name != Component::ParentDir.as_os_str().as_bytes() { let clone = Arc::clone(&self.rustix); return Some(Ok(DirEntryInner { rustix: entry, read_dir: Self { raw_fd: self.raw_fd, rustix: clone, }, })); } } } } impl fmt::Debug for ReadDirInner { // Like libstd's version, but doesn't print the path. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut b = f.debug_struct("ReadDir"); b.field("raw_fd", &self.raw_fd); b.finish() } } cap-primitives-3.4.1/src/rustix/fs/read_link_unchecked.rs000064400000000000000000000010711046102023000216300ustar 00000000000000use rustix::fs::readlinkat; use std::ffi::OsString; #[cfg(unix)] use std::os::unix::ffi::OsStringExt; #[cfg(target_os = "wasi")] use std::os::wasi::ffi::OsStringExt; use std::path::{Path, PathBuf}; use std::{fs, io}; /// *Unsandboxed* function similar to `read_link`, but which does not perform /// sandboxing. pub(crate) fn read_link_unchecked( start: &fs::File, path: &Path, reuse: PathBuf, ) -> io::Result { Ok(readlinkat(start, path, reuse.into_os_string().into_vec()) .map(|path| OsString::from_vec(path.into_bytes()).into())?) } cap-primitives-3.4.1/src/rustix/fs/remove_dir_all_impl.rs000064400000000000000000000024071046102023000216770ustar 00000000000000use crate::fs::{ read_dir_nofollow, read_dir_unchecked, remove_dir, remove_file, remove_open_dir, stat, FollowSymlinks, ReadDir, }; use std::path::{Component, Path}; use std::{fs, io}; pub(crate) fn remove_dir_all_impl(start: &fs::File, path: &Path) -> io::Result<()> { // Code adapted from `remove_dir_all` in Rust's // library/std/src/sys_common/fs.rs at revision // 108e90ca78f052c0c1c49c42a22c85620be19712. let filetype = stat(start, path, FollowSymlinks::No)?.file_type(); if filetype.is_symlink() { remove_file(start, path) } else { remove_dir_all_recursive(read_dir_nofollow(start, path)?)?; remove_dir(start, path) } } pub(crate) fn remove_open_dir_all_impl(dir: fs::File) -> io::Result<()> { remove_dir_all_recursive(read_dir_unchecked( &dir, Component::CurDir.as_ref(), FollowSymlinks::No, )?)?; remove_open_dir(dir) } fn remove_dir_all_recursive(children: ReadDir) -> io::Result<()> { for child in children { let child = child?; if child.file_type()?.is_dir() { remove_dir_all_recursive(child.inner.read_dir(FollowSymlinks::No)?)?; child.remove_dir()?; } else { child.remove_file()?; } } Ok(()) } cap-primitives-3.4.1/src/rustix/fs/remove_dir_unchecked.rs000064400000000000000000000004701046102023000220350ustar 00000000000000use rustix::fs::{unlinkat, AtFlags}; use std::path::Path; use std::{fs, io}; /// *Unsandboxed* function similar to `remove_dir`, but which does not perform /// sandboxing. pub(crate) fn remove_dir_unchecked(start: &fs::File, path: &Path) -> io::Result<()> { Ok(unlinkat(start, path, AtFlags::REMOVEDIR)?) } cap-primitives-3.4.1/src/rustix/fs/remove_file_unchecked.rs000064400000000000000000000004701046102023000221760ustar 00000000000000use rustix::fs::{unlinkat, AtFlags}; use std::path::Path; use std::{fs, io}; /// *Unsandboxed* function similar to `remove_file`, but which does not perform /// sandboxing. pub(crate) fn remove_file_unchecked(start: &fs::File, path: &Path) -> io::Result<()> { Ok(unlinkat(start, path, AtFlags::empty())?) } cap-primitives-3.4.1/src/rustix/fs/remove_open_dir_by_searching.rs000064400000000000000000000026721046102023000235700ustar 00000000000000use crate::fs::{errors, is_root_dir, read_dir_unchecked, FollowSymlinks, Metadata}; use std::path::Component; use std::{fs, io}; /// Delete the directory referenced by the given handle by searching for it in /// its `..`. This requires search permission in `..`, but that's usually /// available. pub(crate) fn remove_open_dir_by_searching(dir: fs::File) -> io::Result<()> { let metadata = Metadata::from_file(&dir)?; let mut iter = read_dir_unchecked(&dir, Component::ParentDir.as_ref(), FollowSymlinks::No)?; while let Some(child) = iter.next() { let child = child?; // Test if the child we found by iteration matches the directory we're // looking for. Ignore `NotFound` errors, which can happen if another // process removes a different directory in the same parent. let same = match child.is_same_file(&metadata) { Ok(same) => same, Err(err) if err.kind() == std::io::ErrorKind::NotFound => false, Err(err) => Err(err)?, }; if same { return child.remove_dir(); } } // We didn't find the directory among its parent's children. Check for the // root directory and handle it specially -- removal will probably fail, so // we'll get the appropriate error code. if is_root_dir(&dir, &iter)? { fs::remove_dir(Component::RootDir.as_os_str()) } else { Err(errors::no_such_file_or_directory()) } } cap-primitives-3.4.1/src/rustix/fs/rename_unchecked.rs000064400000000000000000000005601046102023000211510ustar 00000000000000use rustix::fs::renameat; use std::path::Path; use std::{fs, io}; /// *Unsandboxed* function similar to `rename`, but which does not perform /// sandboxing. pub(crate) fn rename_unchecked( old_start: &fs::File, old_path: &Path, new_start: &fs::File, new_path: &Path, ) -> io::Result<()> { Ok(renameat(old_start, old_path, new_start, new_path)?) } cap-primitives-3.4.1/src/rustix/fs/reopen_impl.rs000064400000000000000000000010341046102023000201770ustar 00000000000000use crate::fs::{open_unchecked, OpenOptions}; use crate::rustix::fs::file_path; use io_lifetimes::AsFilelike; use rustix::fs::CWD; use std::{fs, io}; /// Implementation of `reopen`. pub(crate) fn reopen_impl(file: &fs::File, options: &OpenOptions) -> io::Result { if let Some(path) = file_path(file) { Ok(open_unchecked( &CWD.as_filelike_view::(), &path, options, )?) } else { Err(io::Error::new(io::ErrorKind::Other, "Couldn't reopen file")) } } cap-primitives-3.4.1/src/rustix/fs/set_permissions_impl.rs000064400000000000000000000034711046102023000221440ustar 00000000000000use crate::fs::{open, OpenOptions, Permissions}; use rustix::fs::{fchmod, Mode}; use rustix::io::Errno; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; use std::path::Path; use std::{fs, io}; /// This sounds like it should be a job for `fchmodat`, however `fchmodat` /// handles symlinks in an incompatible way. It either follows symlinks /// without guaranteeing to stay in the sandbox, or with `AT_SYMLINK_NOFOLLOW` /// it attempts to change the permissions of symlinks themselves. What we'd /// need is for it to fail if it encounters a symlink, like `O_NOFOLLOW` does. pub(crate) fn set_permissions_impl( start: &fs::File, path: &Path, perm: Permissions, ) -> io::Result<()> { let std_perm = perm.into_std(start)?; // Try `fchmod` with a normal handle. Normal handles need some kind of // access, so first try read. match open(start, path, OpenOptions::new().read(true)) { Ok(file) => return set_file_permissions(&file, std_perm), Err(err) => match Errno::from_io_error(&err) { Some(Errno::ACCESS) => (), _ => return Err(err), }, } // Next try write. match open(start, path, OpenOptions::new().write(true)) { Ok(file) => return set_file_permissions(&file, std_perm), Err(err) => match Errno::from_io_error(&err) { Some(Errno::ACCESS) | Some(Errno::ISDIR) => (), _ => return Err(err), }, } // If neither of those worked, we're out of luck. Err(Errno::NOTSUP.into()) } pub(crate) fn set_file_permissions(file: &fs::File, perm: fs::Permissions) -> io::Result<()> { // Use `from_bits_truncate` for compatibility with std, which allows // non-permission bits to propagate through. let mode = Mode::from_bits_truncate(perm.mode().try_into().unwrap()); Ok(fchmod(file, mode)?) } cap-primitives-3.4.1/src/rustix/fs/set_symlink_permissions_unchecked.rs000064400000000000000000000007311046102023000246760ustar 00000000000000use crate::fs::Permissions; #[cfg(unix)] use crate::fs::PermissionsExt; use rustix::fs::{chmodat, AtFlags, Mode}; use std::path::Path; use std::{fs, io}; /// This can just use `AT_SYMLINK_NOFOLLOW`. pub(crate) fn set_symlink_permissions_unchecked( start: &fs::File, path: &Path, perm: Permissions, ) -> io::Result<()> { let mode = Mode::from_bits_truncate(perm.mode().try_into().unwrap()); Ok(chmodat(start, path, mode, AtFlags::SYMLINK_NOFOLLOW)?) } cap-primitives-3.4.1/src/rustix/fs/set_times_impl.rs000064400000000000000000000036011046102023000207050ustar 00000000000000//! This module consists of helper types and functions for dealing //! with setting the file times. use crate::fs::{open, OpenOptions, SystemTimeSpec}; use rustix::io::Errno; use std::path::Path; use std::{fs, io}; pub(crate) fn set_times_impl( start: &fs::File, path: &Path, atime: Option, mtime: Option, ) -> io::Result<()> { // Try `futimens` with a normal handle. Normal handles need some kind of // access, so first try write. match open(start, path, OpenOptions::new().write(true)) { Ok(file) => { return fs_set_times::SetTimes::set_times( &file, atime.map(SystemTimeSpec::into_std), mtime.map(SystemTimeSpec::into_std), ) } Err(err) => match Errno::from_io_error(&err) { Some(Errno::ACCESS) | Some(Errno::ISDIR) => (), _ => return Err(err), }, } // Next try read. match open(start, path, OpenOptions::new().read(true)) { Ok(file) => { return fs_set_times::SetTimes::set_times( &file, atime.map(SystemTimeSpec::into_std), mtime.map(SystemTimeSpec::into_std), ) } Err(err) => match Errno::from_io_error(&err) { Some(Errno::ACCESS) => (), _ => return Err(err), }, } // It's not possible to do anything else with generic POSIX. Plain // `utimensat` has two options: // - Follow symlinks, which would open up a race in which a concurrent // modification of the symlink could point outside the sandbox and we // wouldn't be able to detect it, or // - Don't follow symlinks, which would modify the timestamp of the symlink // instead of the file we're trying to get to. // // So neither does what we need. Err(Errno::NOTSUP.into()) } cap-primitives-3.4.1/src/rustix/fs/stat_unchecked.rs000064400000000000000000000060151046102023000206560ustar 00000000000000use crate::fs::{FollowSymlinks, ImplMetadataExt, Metadata}; use rustix::fs::{statat, AtFlags}; use std::path::Path; use std::{fs, io}; #[cfg(target_os = "linux")] use rustix::fs::{statx, StatxFlags}; #[cfg(target_os = "linux")] use std::sync::atomic::{AtomicU8, Ordering}; /// *Unsandboxed* function similar to `stat`, but which does not perform /// sandboxing. pub(crate) fn stat_unchecked( start: &fs::File, path: &Path, follow: FollowSymlinks, ) -> io::Result { let atflags = match follow { FollowSymlinks::Yes => AtFlags::empty(), FollowSymlinks::No => AtFlags::SYMLINK_NOFOLLOW, }; // `statx` is preferred on regular Linux because it can return creation // times. Linux kernels prior to 4.11 don't have `statx` and return // `ENOSYS`. Older versions of Docker/seccomp would return `EPERM` for // `statx`; see . We store // the availability in a global to avoid unnecessary syscalls. // // On Android, the [seccomp policy] prevents us from even // detecting whether `statx` is supported, so don't even try. // // [seccomp policy]: https://android-developers.googleblog.com/2017/07/seccomp-filter-in-android-o.html #[cfg(target_os = "linux")] { // 0: Unknown // 1: Not available // 2: Available static STATX_STATE: AtomicU8 = AtomicU8::new(0); let state = STATX_STATE.load(Ordering::Relaxed); if state != 1 { let statx_result = statx( start, path, atflags, StatxFlags::BASIC_STATS | StatxFlags::BTIME, ); match statx_result { Ok(statx) => { if state == 0 { STATX_STATE.store(2, Ordering::Relaxed); } return Ok(ImplMetadataExt::from_rustix_statx(statx)); } Err(rustix::io::Errno::NOSYS) => STATX_STATE.store(1, Ordering::Relaxed), Err(rustix::io::Errno::PERM) if state == 0 => { // This is an unlikely case, as `statx` doesn't normally // return `PERM` errors. One way this can happen is when // running on old versions of seccomp/Docker. If `statx` on // the current working directory returns a similar error, // then stop using `statx`. if let Err(rustix::io::Errno::PERM) = statx( rustix::fs::CWD, "", AtFlags::EMPTY_PATH, StatxFlags::empty(), ) { STATX_STATE.store(1, Ordering::Relaxed); } else { return Err(rustix::io::Errno::PERM.into()); } } Err(e) => return Err(e.into()), } } } Ok(statat(start, path, atflags).map(ImplMetadataExt::from_rustix)?) } cap-primitives-3.4.1/src/rustix/fs/symlink_unchecked.rs000064400000000000000000000005171046102023000213720ustar 00000000000000use rustix::fs::symlinkat; use std::path::Path; use std::{fs, io}; /// *Unsandboxed* function similar to `symlink`, but which does not perform /// sandboxing. pub(crate) fn symlink_unchecked( old_path: &Path, new_start: &fs::File, new_path: &Path, ) -> io::Result<()> { Ok(symlinkat(old_path, new_start, new_path)?) } cap-primitives-3.4.1/src/rustix/fs/times.rs000064400000000000000000000037041046102023000170150ustar 00000000000000use crate::fs::SystemTimeSpec; use crate::time::SystemClock; use io_lifetimes::BorrowedFd; use rustix::fs::{utimensat, AtFlags, Timestamps, UTIME_NOW, UTIME_OMIT}; use rustix::time::Timespec; use std::path::Path; use std::{fs, io}; #[allow(clippy::useless_conversion)] pub(crate) fn to_timespec(ft: Option) -> io::Result { Ok(match ft { None => Timespec { tv_sec: 0, tv_nsec: UTIME_OMIT.into(), }, Some(SystemTimeSpec::SymbolicNow) => Timespec { tv_sec: 0, tv_nsec: UTIME_NOW.into(), }, Some(SystemTimeSpec::Absolute(ft)) => { let duration = ft.duration_since(SystemClock::UNIX_EPOCH).unwrap(); let nanoseconds = duration.subsec_nanos(); assert_ne!(i64::from(nanoseconds), i64::from(UTIME_OMIT)); assert_ne!(i64::from(nanoseconds), i64::from(UTIME_NOW)); Timespec { tv_sec: duration .as_secs() .try_into() .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?, tv_nsec: nanoseconds.try_into().unwrap(), } } }) } #[allow(dead_code)] pub(crate) fn set_times_nofollow_unchecked( start: &fs::File, path: &Path, atime: Option, mtime: Option, ) -> io::Result<()> { let times = Timestamps { last_access: to_timespec(atime)?, last_modification: to_timespec(mtime)?, }; Ok(utimensat(start, path, ×, AtFlags::SYMLINK_NOFOLLOW)?) } #[allow(dead_code)] pub(crate) fn set_times_follow_unchecked( start: BorrowedFd<'_>, path: &Path, atime: Option, mtime: Option, ) -> io::Result<()> { let times = Timestamps { last_access: to_timespec(atime)?, last_modification: to_timespec(mtime)?, }; Ok(utimensat(start, path, ×, AtFlags::empty())?) } cap-primitives-3.4.1/src/rustix/linux/fs/canonicalize_impl.rs000064400000000000000000000050131046102023000225060ustar 00000000000000//! Path canonicalization using `/proc/self/fd`. use super::procfs::get_path_from_proc_self_fd; use crate::fs::OpenOptionsExt; use crate::fs::{manually, open_beneath, FollowSymlinks, OpenOptions}; use rustix::fs::OFlags; use std::path::{Component, Path, PathBuf}; use std::{fs, io}; /// Implement `canonicalize` by using readlink on `/proc/self/fd/*`. pub(crate) fn canonicalize_impl(start: &fs::File, path: &Path) -> io::Result { // Open the path with `O_PATH`. Use `read(true)` even though we don't need // `read` permissions, because Rust's libstd requires an access mode, and // Linux ignores `O_RDONLY` with `O_PATH`. let result = open_beneath( start, path, OpenOptions::new() .read(true) .follow(FollowSymlinks::Yes) .custom_flags(OFlags::PATH.bits() as i32), ); // If that worked, call `readlink`. match result { Ok(file) => { if let Ok(start_path) = get_path_from_proc_self_fd(start) { if let Ok(file_path) = get_path_from_proc_self_fd(&file) { if let Ok(canonical_path) = file_path.strip_prefix(start_path) { #[cfg(racy_asserts)] if canonical_path.as_os_str().is_empty() { assert_eq!( Component::CurDir.as_os_str(), manually::canonicalize(start, path).unwrap() ); } else { assert_eq!( canonical_path, manually::canonicalize(start, path).unwrap() ); } let mut path_buf = canonical_path.to_path_buf(); // Replace "" with ".", since "" as a relative path is // interpreted as an error. if path_buf.as_os_str().is_empty() { path_buf.push(Component::CurDir); } return Ok(path_buf); } } } } Err(err) => match rustix::io::Errno::from_io_error(&err) { // `ENOSYS` from `open_beneath` means `openat2` is unavailable // and we should use a fallback. Some(rustix::io::Errno::NOSYS) => (), _ => return Err(err), }, } // Use a fallback. manually::canonicalize(start, path) } cap-primitives-3.4.1/src/rustix/linux/fs/file_metadata.rs000064400000000000000000000022251046102023000216070ustar 00000000000000use crate::fs::{ImplMetadataExt, Metadata}; use rustix::fs::{statat, AtFlags}; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering::Relaxed; use std::{fs, io}; /// Like `file.metadata()`, but works with `O_PATH` descriptors on old (pre /// 3.6) versions of Linux too. pub(super) fn file_metadata(file: &fs::File) -> io::Result { // Record whether we've seen an `EBADF` from an `fstat` on an `O_PATH` // file descriptor, meaning we're on a Linux that doesn't support it. static FSTAT_PATH_BADF: AtomicBool = AtomicBool::new(false); if !FSTAT_PATH_BADF.load(Relaxed) { match Metadata::from_file(file) { Ok(metadata) => return Ok(metadata), Err(err) => match rustix::io::Errno::from_io_error(&err) { // Before Linux 3.6, `fstat` with `O_PATH` returned `EBADF`. Some(rustix::io::Errno::BADF) => FSTAT_PATH_BADF.store(true, Relaxed), _ => return Err(err), }, } } // If `fstat` with `O_PATH` isn't supported, use `statat` with `AT_EMPTY_PATH`. Ok(statat(file, "", AtFlags::EMPTY_PATH).map(ImplMetadataExt::from_rustix)?) } cap-primitives-3.4.1/src/rustix/linux/fs/file_path.rs000064400000000000000000000013151046102023000207620ustar 00000000000000use super::procfs::get_path_from_proc_self_fd; use std::fs; use std::path::PathBuf; pub(crate) fn file_path(file: &fs::File) -> Option { use std::os::unix::fs::MetadataExt; // Ignore paths that don't start with '/', which are things like // `socket:[3556564]` or similar. let path = get_path_from_proc_self_fd(file) .ok() .filter(|path| path.starts_with("/"))?; // Linux appends the string " (deleted)" when a file is deleted; avoid // treating that as the actual name. Check this after doing the `readlink` // above so that we're conservative about concurrent deletions. if file.metadata().ok()?.nlink() == 0 { return None; } Some(path) } cap-primitives-3.4.1/src/rustix/linux/fs/mod.rs000064400000000000000000000024351046102023000176120ustar 00000000000000#[cfg(target_os = "linux")] mod canonicalize_impl; #[cfg(target_os = "linux")] mod file_metadata; mod file_path; #[cfg(target_os = "linux")] mod open_entry_impl; mod open_impl; mod procfs; mod set_permissions_impl; mod set_times_impl; #[cfg(target_os = "linux")] mod stat_impl; #[cfg(target_os = "android")] pub(crate) use crate::fs::manually::canonicalize as canonicalize_impl; #[cfg(target_os = "android")] pub(crate) use crate::fs::manually::open_entry as open_entry_impl; #[cfg(target_os = "android")] pub(crate) use crate::fs::manually::stat as stat_impl; pub(crate) use crate::fs::via_parent::set_times_nofollow as set_times_nofollow_impl; #[cfg(target_os = "linux")] pub(crate) use canonicalize_impl::canonicalize_impl; pub(crate) use file_path::file_path; #[cfg(target_os = "linux")] pub(crate) use open_entry_impl::open_entry_impl; #[cfg(target_os = "linux")] pub(crate) use open_impl::open_beneath; pub(crate) use open_impl::open_impl; pub(crate) use set_permissions_impl::set_permissions_impl; pub(crate) use set_times_impl::set_times_impl; #[cfg(target_os = "linux")] pub(crate) use stat_impl::stat_impl; // In theory we could optimize `link` using `openat2` with `O_PATH` and // `linkat` with `AT_EMPTY_PATH`, however that requires `CAP_DAC_READ_SEARCH`, // so it isn't very widely applicable. cap-primitives-3.4.1/src/rustix/linux/fs/open_entry_impl.rs000064400000000000000000000010301046102023000222240ustar 00000000000000use crate::fs::{manually, open_beneath, OpenOptions}; use std::ffi::OsStr; use std::{fs, io}; pub(crate) fn open_entry_impl( start: &fs::File, path: &OsStr, options: &OpenOptions, ) -> io::Result { let result = open_beneath(start, path.as_ref(), options); match result { Ok(file) => Ok(file), Err(err) => match rustix::io::Errno::from_io_error(&err) { Some(rustix::io::Errno::NOSYS) => manually::open_entry(start, path, options), _ => Err(err), }, } } cap-primitives-3.4.1/src/rustix/linux/fs/open_impl.rs000064400000000000000000000126501046102023000210150ustar 00000000000000//! Linux 5.6 and later have a syscall `openat2`, with flags that allow it to //! enforce the sandboxing property we want. See the [LWN article] for an //! overview and the [`openat2` documentation] for details. //! //! [LWN article]: https://lwn.net/Articles/796868/ //! [`openat2` documentation]: https://man7.org/linux/man-pages/man2/openat2.2.html //! //! On older Linux, fall back to `manually::open`. #[cfg(racy_asserts)] use crate::fs::is_same_file; use crate::fs::{manually, OpenOptions}; use std::path::Path; use std::{fs, io}; #[cfg(target_os = "linux")] use { super::super::super::fs::compute_oflags, crate::fs::errors, io_lifetimes::FromFd, rustix::fs::{openat2, Mode, OFlags, RawMode, ResolveFlags}, rustix::path::Arg, std::sync::atomic::AtomicBool, std::sync::atomic::Ordering::Relaxed, }; /// Call the `openat2` system call, or use a fallback if that's unavailable. pub(crate) fn open_impl( start: &fs::File, path: &Path, options: &OpenOptions, ) -> io::Result { // On regular Linux, attempt to use `openat2` to accelerate sandboxed // lookups. On Android, the [seccomp policy] prevents us from even // detecting whether `openat2` is supported, so don't even try. // // [seccomp policy]: https://android-developers.googleblog.com/2017/07/seccomp-filter-in-android-o.html #[cfg(target_os = "linux")] { let result = open_beneath(start, path, options); // If we got anything other than a `ENOSYS` error, that's our result. match result { Err(err) if err.raw_os_error() == Some(rustix::io::Errno::NOSYS.raw_os_error()) => {} Err(err) => return Err(err), Ok(fd) => return Ok(fd), } } manually::open(start, path, options) } /// Call the `openat2` system call with `RESOLVE_BENEATH`. If the syscall is /// unavailable, mark it so for future calls. If `openat2` is unavailable /// either permanently or temporarily, return `ENOSYS`. #[cfg(target_os = "linux")] pub(crate) fn open_beneath( start: &fs::File, path: &Path, options: &OpenOptions, ) -> io::Result { static INVALID: AtomicBool = AtomicBool::new(false); if INVALID.load(Relaxed) { // `openat2` is permanently unavailable. return Err(rustix::io::Errno::NOSYS.into()); } let oflags = compute_oflags(options)?; // Do two `contains` checks because `TMPFILE` may be represented with // multiple flags and we need to ensure they're all set. let mode = if oflags.contains(OFlags::CREATE) || oflags.contains(OFlags::TMPFILE) { Mode::from_bits((options.ext.mode & 0o7777) as RawMode).unwrap() } else { Mode::empty() }; // We know `openat2` needs a `&CStr` internally; to avoid allocating on // each iteration of the loop below, allocate the `CString` now. path.into_with_c_str(|path_c_str| { // `openat2` fails with `EAGAIN` if a rename happens anywhere on the host // while it's running, so use a loop to retry it a few times. But not too many // times, because there's no limit on how often this can happen. The actual // number here is currently an arbitrarily chosen guess. for _ in 0..4 { match openat2( start, path_c_str, oflags, mode, ResolveFlags::BENEATH | ResolveFlags::NO_MAGICLINKS, ) { Ok(file) => { let file = fs::File::from_into_fd(file); #[cfg(racy_asserts)] check_open(start, path, options, &file); return Ok(file); } Err(err) => match err { // A rename or similar happened. Try again. rustix::io::Errno::AGAIN => continue, // `EPERM` is used by some `seccomp` sandboxes to indicate // that `openat2` is unimplemented: // // // However, `EPERM` may also indicate a failed `O_NOATIME` // or a file seal prevented the operation, and it's complex // to detect those cases, so exit the loop and use the // fallback. rustix::io::Errno::PERM => break, // `ENOSYS` means `openat2` is permanently unavailable; // mark it so and exit the loop. rustix::io::Errno::NOSYS => { INVALID.store(true, Relaxed); break; } _ => return Err(err), }, } } Err(rustix::io::Errno::NOSYS) }) .map_err(|err| match err { rustix::io::Errno::XDEV => errors::escape_attempt(), err => err.into(), }) } #[cfg(racy_asserts)] fn check_open(start: &fs::File, path: &Path, options: &OpenOptions, file: &fs::File) { let check = manually::open( start, path, options .clone() .create(false) .create_new(false) .truncate(false), ) .expect("manually::open failed when open_openat2 succeeded"); assert!( is_same_file(file, &check).unwrap(), "manually::open should open the same inode as open_openat2" ); } cap-primitives-3.4.1/src/rustix/linux/fs/procfs.rs000064400000000000000000000053151046102023000203270ustar 00000000000000//! Utilities for working with `/proc`, where Linux's `procfs` is typically //! mounted. `/proc` serves as an adjunct to Linux's main syscall surface area, //! providing additional features with an awkward interface. //! //! This module does a considerable amount of work to determine whether `/proc` //! is mounted, with actual `procfs`, and without any additional mount points //! on top of the paths we open. use crate::fs::OpenOptionsExt; use crate::fs::{ errors, open, read_link_unchecked, set_times_follow_unchecked, OpenOptions, SystemTimeSpec, }; use io_lifetimes::{AsFd, AsFilelike}; use rustix::fs::{chmodat, AtFlags, Mode, OFlags, RawMode}; use rustix::path::DecInt; use rustix::procfs::proc_self_fd; use std::os::unix::fs::PermissionsExt; use std::path::{Path, PathBuf}; use std::{fs, io}; pub(crate) fn get_path_from_proc_self_fd(file: &fs::File) -> io::Result { read_link_unchecked( &proc_self_fd()?.as_filelike_view::(), DecInt::from_fd(file).as_ref(), PathBuf::new(), ) } /// Linux's `fchmodat` doesn't support `AT_NOFOLLOW_SYMLINK`, so we can't trust /// that it won't follow a symlink outside the sandbox. As an alternative, the /// symlinks in Linux's /proc/self/fd/* aren't ordinary symlinks, they're /// "magic links", which are more transparent, to the point of allowing chmod /// to work. So we open the file with `O_PATH` and then do `fchmodat` on the /// corresponding /proc/self/fd/* link. pub(crate) fn set_permissions_through_proc_self_fd( start: &fs::File, path: &Path, perm: fs::Permissions, ) -> io::Result<()> { let opath = open( start, path, OpenOptions::new() .read(true) .custom_flags(OFlags::PATH.bits() as i32), )?; let dirfd = proc_self_fd()?; let mode = Mode::from_bits(perm.mode() as RawMode).ok_or_else(errors::invalid_flags)?; Ok(chmodat( dirfd, DecInt::from_fd(&opath), mode, AtFlags::empty(), )?) } pub(crate) fn set_times_through_proc_self_fd( start: &fs::File, path: &Path, atime: Option, mtime: Option, ) -> io::Result<()> { let opath = open( start, path, OpenOptions::new() .read(true) .custom_flags(OFlags::PATH.bits() as i32), )?; // Don't pass `AT_SYMLINK_NOFOLLOW`, because we do actually want to follow // the first symlink. We don't want to follow any subsequent symlinks, but // omitting `O_NOFOLLOW` above ensures that the destination of the link // isn't a symlink. set_times_follow_unchecked( proc_self_fd()?.as_fd(), DecInt::from_fd(&opath).as_ref(), atime, mtime, ) } cap-primitives-3.4.1/src/rustix/linux/fs/set_permissions_impl.rs000064400000000000000000000041141046102023000232760ustar 00000000000000use super::procfs::set_permissions_through_proc_self_fd; use crate::fs::{open, OpenOptions, Permissions}; use rustix::fs::{fchmod, Mode, RawMode}; use std::os::unix::fs::PermissionsExt; use std::path::Path; use std::{fs, io}; pub(crate) fn set_permissions_impl( start: &fs::File, path: &Path, perm: Permissions, ) -> io::Result<()> { let std_perm = perm.into_std(start)?; // First try using `O_PATH` and doing a chmod on `/proc/self/fd/{}` // (`fchmod` doesn't work on `O_PATH` file descriptors). // // This may fail, due to older Linux versions not supporting `O_PATH`, or // due to procfs being unavailable, but if it does work, go with it, as // `O_PATH` tells Linux that we don't actually need to read or write the // file, which may avoid side effects associated with opening device files. let result = set_permissions_through_proc_self_fd(start, path, std_perm.clone()); if let Ok(()) = result { return Ok(()); } // Then try `fchmod` with a normal handle. Normal handles need some kind of // access, so first try read. match open(start, path, OpenOptions::new().read(true)) { Ok(file) => return set_file_permissions(&file, std_perm), Err(err) => match rustix::io::Errno::from_io_error(&err) { Some(rustix::io::Errno::ACCESS) => (), _ => return Err(err), }, } // Next try write. match open(start, path, OpenOptions::new().write(true)) { Ok(file) => return set_file_permissions(&file, std_perm), Err(err) => match rustix::io::Errno::from_io_error(&err) { Some(rustix::io::Errno::ACCESS) | Some(rustix::io::Errno::ISDIR) => (), _ => return Err(err), }, } // Nothing worked, so just return the original error. result } fn set_file_permissions(file: &fs::File, perm: fs::Permissions) -> io::Result<()> { // Use `from_bits_truncate` for compatibility with std, which allows // non-permission bits to propagate through. let mode = Mode::from_bits_truncate(perm.mode() as RawMode); Ok(fchmod(file, mode)?) } cap-primitives-3.4.1/src/rustix/linux/fs/set_times_impl.rs000064400000000000000000000031341046102023000220450ustar 00000000000000//! This module consists of helper types and functions for dealing //! with setting the file times specific to Linux. use super::procfs::set_times_through_proc_self_fd; use crate::fs::{open, OpenOptions, SystemTimeSpec}; use std::path::Path; use std::{fs, io}; pub(crate) fn set_times_impl( start: &fs::File, path: &Path, atime: Option, mtime: Option, ) -> io::Result<()> { // Try `futimens` with a normal handle. Normal handles need some kind of // access, so first try write. match open(start, path, OpenOptions::new().write(true)) { Ok(file) => { return fs_set_times::SetTimes::set_times( &file, atime.map(SystemTimeSpec::into_std), mtime.map(SystemTimeSpec::into_std), ) } Err(err) => match rustix::io::Errno::from_io_error(&err) { Some(rustix::io::Errno::ACCESS) | Some(rustix::io::Errno::ISDIR) => (), _ => return Err(err), }, } // Next try read. match open(start, path, OpenOptions::new().read(true)) { Ok(file) => { return fs_set_times::SetTimes::set_times( &file, atime.map(SystemTimeSpec::into_std), mtime.map(SystemTimeSpec::into_std), ) } Err(err) => match rustix::io::Errno::from_io_error(&err) { Some(rustix::io::Errno::ACCESS) => (), _ => return Err(err), }, } // If neither of those worked, turn to `/proc`. set_times_through_proc_self_fd(start, path, atime, mtime) } cap-primitives-3.4.1/src/rustix/linux/fs/stat_impl.rs000064400000000000000000000040261046102023000210250ustar 00000000000000//! Linux has an `O_PATH` flag which allows opening a file without necessary //! having read or write access to it; we can use that with `openat2` and //! `fstat` to perform a fast sandboxed `stat`. use super::file_metadata::file_metadata; use crate::fs::{manually, open_beneath, FollowSymlinks, Metadata, OpenOptions}; use rustix::fs::OFlags; use std::path::Path; use std::{fs, io}; /// Use `openat2` with `O_PATH` and `fstat`. If that's not available, fallback /// to `manually::stat`. pub(crate) fn stat_impl( start: &fs::File, path: &Path, follow: FollowSymlinks, ) -> io::Result { use crate::fs::{stat_unchecked, OpenOptionsExt}; use std::path::Component; // Optimization: if path has exactly one component and it's not ".." and // we're not following symlinks we can go straight to `stat_unchecked`, // which is faster than doing an open with a separate fstat. if follow == FollowSymlinks::No { let mut components = path.components(); if let Some(component) = components.next() { if components.next().is_none() && component != Component::ParentDir { return stat_unchecked(start, component.as_ref(), FollowSymlinks::No); } } } // Open the path with `O_PATH`. Use `read(true)` even though we don't need // `read` permissions, because Rust's libstd requires an access mode, and // Linux ignores `O_RDONLY` with `O_PATH`. let result = open_beneath( start, path, OpenOptions::new() .read(true) .follow(follow) .custom_flags(OFlags::PATH.bits() as i32), ); // If that worked, call `fstat`. match result { Ok(file) => file_metadata(&file), Err(err) => match rustix::io::Errno::from_io_error(&err) { // `ENOSYS` from `open_beneath` means `openat2` is unavailable // and we should use a fallback. Some(rustix::io::Errno::NOSYS) => manually::stat(start, path, follow), _ => Err(err), }, } } cap-primitives-3.4.1/src/rustix/linux/mod.rs000064400000000000000000000002561046102023000172010ustar 00000000000000//! Following [`std`], we don't carry workarounds for Linux versions //! older than 2.6.32. //! //! [`std`]: https://github.com/rust-lang/rust/pull/74163 pub(crate) mod fs; cap-primitives-3.4.1/src/rustix/mod.rs000064400000000000000000000004621046102023000160410ustar 00000000000000//! The `rustix` module contains code specific to the Posix-ish platforms //! supported by the `rustix` crate. pub(crate) mod fs; #[cfg(any(target_os = "macos", target_os = "ios"))] mod darwin; #[cfg(target_os = "freebsd")] mod freebsd; #[cfg(any(target_os = "android", target_os = "linux"))] mod linux; cap-primitives-3.4.1/src/time/instant.rs000064400000000000000000000100111046102023000163310ustar 00000000000000use crate::time::Duration; use std::ops::{Add, AddAssign, Sub, SubAssign}; use std::{fmt, time}; /// A measurement of a monotonically nondecreasing clock. /// /// This corresponds to [`std::time::Instant`]. /// /// This `Instant` has no `now` or `elapsed` methods. To obtain the current /// time or measure the duration to the current time, first obtain a /// [`MonotonicClock`], and then call [`MonotonicClock::now`] or /// [`MonotonicClock::elapsed`] instead. /// /// [`MonotonicClock`]: crate::time::MonotonicClock /// [`MonotonicClock::now`]: crate::time::MonotonicClock::now /// [`MonotonicClock::elapsed`]: crate::time::MonotonicClock::elapsed #[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)] pub struct Instant { pub(crate) std: time::Instant, } impl Instant { /// Constructs a new instance of `Self` from the given /// [`std::time::Instant`]. #[inline] pub const fn from_std(std: time::Instant) -> Self { Self { std } } /// Returns the amount of time elapsed from another instant to this one. /// /// This corresponds to [`std::time::Instant::duration_since`]. #[inline] pub fn duration_since(&self, earlier: Self) -> Duration { self.std.duration_since(earlier.std) } /// Returns the amount of time elapsed from another instant to this one, or /// None if that instant is later than this one. /// /// This corresponds to [`std::time::Instant::checked_duration_since`]. #[inline] pub fn checked_duration_since(&self, earlier: Self) -> Option { self.std.checked_duration_since(earlier.std) } /// Returns the amount of time elapsed from another instant to this one, or /// zero duration if that instant is later than this one. /// /// This corresponds to [`std::time::Instant::saturating_duration_since`]. #[inline] pub fn saturating_duration_since(&self, earlier: Self) -> Duration { self.std.saturating_duration_since(earlier.std) } /// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be /// represented as `Instant` (which means it's inside the bounds of the /// underlying data structure), `None` otherwise. /// /// This corresponds to [`std::time::Instant::checked_add`]. #[inline] pub fn checked_add(&self, duration: Duration) -> Option { self.std.checked_add(duration).map(Self::from_std) } /// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be /// represented as `Instant` (which means it's inside the bounds of the /// underlying data structure), `None` otherwise. /// /// This corresponds to [`std::time::Instant::checked_sub`]. #[inline] pub fn checked_sub(&self, duration: Duration) -> Option { self.std.checked_sub(duration).map(Self::from_std) } } impl Add for Instant { type Output = Self; /// # Panics /// /// This function may panic if the resulting point in time cannot be /// represented by the underlying data structure. See /// [`Instant::checked_add`] for a version without panic. #[inline] fn add(self, other: Duration) -> Self { self.checked_add(other) .expect("overflow when adding duration to instant") } } impl AddAssign for Instant { #[inline] fn add_assign(&mut self, other: Duration) { *self = *self + other; } } impl Sub for Instant { type Output = Self; #[inline] fn sub(self, other: Duration) -> Self { self.checked_sub(other) .expect("overflow when subtracting duration from instant") } } impl SubAssign for Instant { #[inline] fn sub_assign(&mut self, other: Duration) { *self = *self - other; } } impl Sub for Instant { type Output = Duration; #[inline] fn sub(self, other: Self) -> Duration { self.duration_since(other) } } impl fmt::Debug for Instant { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.std.fmt(f) } } cap-primitives-3.4.1/src/time/mod.rs000064400000000000000000000004231046102023000154360ustar 00000000000000//! Time utilities. mod instant; mod monotonic_clock; mod system_clock; mod system_time; pub use instant::Instant; pub use monotonic_clock::MonotonicClock; pub use system_clock::SystemClock; pub use system_time::SystemTime; pub use std::time::{Duration, SystemTimeError}; cap-primitives-3.4.1/src/time/monotonic_clock.rs000064400000000000000000000022651046102023000200450ustar 00000000000000use crate::time::{Duration, Instant}; use ambient_authority::AmbientAuthority; use std::time; /// A reference to a monotonically nondecreasing clock. /// /// This does not directly correspond to anything in `std`, however its methods /// correspond to [methods in `std::time::Instant`]. /// /// [methods in `std::time::Instant`]: https://doc.rust-lang.org/std/time/struct.Instant.html#impl pub struct MonotonicClock(()); impl MonotonicClock { /// Constructs a new instance of `Self`. /// /// # Ambient Authority /// /// This uses ambient authority to accesses clocks. #[inline] pub const fn new(ambient_authority: AmbientAuthority) -> Self { let _ = ambient_authority; Self(()) } /// Returns an instant corresponding to "now". /// /// This corresponds to [`std::time::Instant::now`]. #[inline] pub fn now(&self) -> Instant { Instant::from_std(time::Instant::now()) } /// Returns the amount of time elapsed since this instant was created. /// /// This corresponds to [`std::time::Instant::elapsed`]. #[inline] pub fn elapsed(&self, instant: Instant) -> Duration { instant.std.elapsed() } } cap-primitives-3.4.1/src/time/system_clock.rs000064400000000000000000000031741046102023000173640ustar 00000000000000use crate::time::{Duration, SystemTime, SystemTimeError}; use ambient_authority::AmbientAuthority; use std::time; /// A reference to a system clock, useful for talking to external entities like /// the file system or other processes. /// /// This does not directly correspond to anything in `std`, however its methods /// correspond to [methods in `std::time::SystemTime`]. /// /// [methods in `std::time::SystemTime`]: https://doc.rust-lang.org/std/time/struct.SystemTime.html#impl pub struct SystemClock(()); impl SystemClock { /// An anchor in time which can be used to create new `SystemTime` /// instances or learn about where in time a `SystemTime` lies. /// /// This corresponds to [`std::time::SystemTime::UNIX_EPOCH`]. pub const UNIX_EPOCH: SystemTime = SystemTime { std: time::SystemTime::UNIX_EPOCH, }; /// Constructs a new instance of `Self`. /// /// # Ambient Authority /// /// This uses ambient authority to accesses clocks. #[inline] pub const fn new(ambient_authority: AmbientAuthority) -> Self { let _ = ambient_authority; Self(()) } /// Returns an instant corresponding to "now". /// /// This corresponds to [`std::time::SystemTime::now`]. #[inline] pub fn now(&self) -> SystemTime { SystemTime::from_std(time::SystemTime::now()) } /// Returns the amount of time elapsed since this instant was created. /// /// This corresponds to [`std::time::SystemTime::elapsed`]. #[inline] pub fn elapsed(&self, system_time: SystemTime) -> Result { system_time.std.elapsed() } } cap-primitives-3.4.1/src/time/system_time.rs000064400000000000000000000114501046102023000172230ustar 00000000000000use crate::time::{Duration, SystemTimeError}; use std::ops::{Add, AddAssign, Sub, SubAssign}; use std::{fmt, time}; /// A measurement of the system clock, useful for talking to external entities /// like the file system or other processes. /// /// This corresponds to [`std::time::SystemTime`]. /// /// This `SystemTime` has no `now`, `elapsed` methods. To obtain the current /// time or measure the duration to the current time, first obtain a /// [`SystemClock`], and then call [`SystemClock::now`] or /// [`SystemClock::elapsed`] instead. The `UNIX_EPOCH` constant is at /// [`SystemClock::UNIX_EPOCH`]. /// /// Similar to the [`filetime` crate], when /// `RUSTFLAGS=--cfg emulate_second_only_system` is set, `SystemTime` will /// round times from the operating system down to the second. This emulates /// the behavior of some file systems, mostly /// [HFS], allowing debugging on other hardware. /// /// [`SystemClock`]: crate::time::SystemClock /// [`SystemClock::now`]: crate::time::SystemClock::now /// [`SystemClock::elapsed`]: crate::time::SystemClock::elapsed /// [`SystemClock::UNIX_EPOCH`]: crate::time::SystemClock::UNIX_EPOCH /// [`filetime` crate]: https://crates.io/crates/filetime /// [HFS]: https://en.wikipedia.org/wiki/HFS_Plus #[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd)] pub struct SystemTime { pub(crate) std: time::SystemTime, } impl SystemTime { /// Constructs a new instance of `Self` from the given /// [`std::time::SystemTime`]. // TODO: Make this a `const fn` once `time::Duration::checked_add` is a `const fn`. #[inline] pub fn from_std(std: time::SystemTime) -> Self { if cfg!(emulate_second_only_system) { match std.duration_since(time::SystemTime::UNIX_EPOCH) { Ok(duration) => { let secs = time::Duration::from_secs(duration.as_secs()); Self { std: time::SystemTime::UNIX_EPOCH.checked_add(secs).unwrap(), } } Err(_) => { let duration = time::SystemTime::UNIX_EPOCH.duration_since(std).unwrap(); let secs = time::Duration::from_secs(duration.as_secs()); Self { std: time::SystemTime::UNIX_EPOCH.checked_sub(secs).unwrap(), } } } } else { Self { std } } } /// Constructs a new instance of [`std::time::SystemTime`] from the given /// `Self`. #[inline] pub const fn into_std(self) -> time::SystemTime { self.std } /// Returns the amount of time elapsed from another instant to this one. /// /// This corresponds to [`std::time::SystemTime::duration_since`]. #[inline] pub fn duration_since(&self, earlier: Self) -> Result { self.std.duration_since(earlier.std) } /// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be /// represented as `SystemTime` (which means it's inside the bounds of the /// underlying data structure), `None` otherwise. /// /// This corresponds to [`std::time::SystemTime::checked_add`]. #[inline] pub fn checked_add(&self, duration: Duration) -> Option { self.std.checked_add(duration).map(Self::from_std) } /// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be /// represented as `SystemTime` (which means it's inside the bounds of the /// underlying data structure), `None` otherwise. /// /// This corresponds to [`std::time::SystemTime::checked_sub`]. #[inline] pub fn checked_sub(&self, duration: Duration) -> Option { self.std.checked_sub(duration).map(Self::from_std) } } impl Add for SystemTime { type Output = Self; /// # Panics /// /// This function may panic if the resulting point in time cannot be /// represented by the underlying data structure. See /// [`SystemTime::checked_add`] for a version without panic. #[inline] fn add(self, dur: Duration) -> Self { self.checked_add(dur) .expect("overflow when adding duration to instant") } } impl AddAssign for SystemTime { #[inline] fn add_assign(&mut self, other: Duration) { *self = *self + other; } } impl Sub for SystemTime { type Output = Self; #[inline] fn sub(self, dur: Duration) -> Self { self.checked_sub(dur) .expect("overflow when subtracting duration from instant") } } impl SubAssign for SystemTime { #[inline] fn sub_assign(&mut self, other: Duration) { *self = *self - other; } } impl fmt::Debug for SystemTime { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.std.fmt(f) } } cap-primitives-3.4.1/src/windows/fs/access_unchecked.rs000064400000000000000000000015241046102023000213000ustar 00000000000000use crate::fs::{open, AccessType, FollowSymlinks, OpenOptions}; use std::path::Path; use std::{fs, io}; /// *Unsandboxed* function similar to `access`, but which does not perform /// sandboxing. pub(crate) fn access_unchecked( start: &fs::File, path: &Path, type_: AccessType, follow: FollowSymlinks, ) -> io::Result<()> { let mut options = OpenOptions::new(); options.follow(follow); match type_ { AccessType::Exists => { options.read(true); } AccessType::Access(modes) => { if modes.readable { options.read(true); } if modes.writable { options.write(true); } if modes.executable { options.read(true); } } } open(start, path, &options).map(|_| ()) } cap-primitives-3.4.1/src/windows/fs/copy.rs000064400000000000000000000007711046102023000170030ustar 00000000000000use super::get_path::get_path; use crate::fs::{open, OpenOptions}; use std::path::Path; use std::{fs, io}; pub(crate) fn copy_impl( from_start: &fs::File, from_path: &Path, to_start: &fs::File, to_path: &Path, ) -> io::Result { let from = open(from_start, from_path, OpenOptions::new().read(true))?; let to = open( to_start, to_path, OpenOptions::new().create(true).truncate(true).write(true), )?; fs::copy(get_path(&from)?, get_path(&to)?) } cap-primitives-3.4.1/src/windows/fs/create_dir_unchecked.rs000064400000000000000000000007671046102023000221500ustar 00000000000000use super::get_path::concatenate; use crate::fs::DirOptions; use std::path::Path; use std::{fs, io}; /// *Unsandboxed* function similar to `create_dir`, but which does not perform /// sandboxing. /// /// Windows doesn't have any extra flags in `DirOptions`, so the `options` /// parameter is ignored. pub(crate) fn create_dir_unchecked( start: &fs::File, path: &Path, _options: &DirOptions, ) -> io::Result<()> { let out_path = concatenate(start, path)?; fs::create_dir(out_path) } cap-primitives-3.4.1/src/windows/fs/create_file_at_w.rs000064400000000000000000000265621046102023000213130ustar 00000000000000#![allow(unsafe_code)] use std::mem; use std::os::windows::io::HandleOrInvalid; use std::ptr::null_mut; use windows_sys::Wdk::Foundation::OBJECT_ATTRIBUTES; use windows_sys::Wdk::Storage::FileSystem::{ NtCreateFile, FILE_CREATE, FILE_DELETE_ON_CLOSE, FILE_NON_DIRECTORY_FILE, FILE_NO_INTERMEDIATE_BUFFERING, FILE_OPEN, FILE_OPEN_FOR_BACKUP_INTENT, FILE_OPEN_IF, FILE_OPEN_REPARSE_POINT, FILE_OVERWRITE, FILE_OVERWRITE_IF, FILE_RANDOM_ACCESS, FILE_SEQUENTIAL_ONLY, FILE_SYNCHRONOUS_IO_NONALERT, FILE_WRITE_THROUGH, }; use windows_sys::Win32::Foundation::{ RtlNtStatusToDosError, SetLastError, ERROR_ALREADY_EXISTS, ERROR_FILE_EXISTS, ERROR_INVALID_NAME, ERROR_INVALID_PARAMETER, ERROR_NOT_SUPPORTED, GENERIC_ALL, GENERIC_READ, GENERIC_WRITE, HANDLE, INVALID_HANDLE_VALUE, STATUS_OBJECT_NAME_COLLISION, STATUS_PENDING, STATUS_SUCCESS, SUCCESS, UNICODE_STRING, }; use windows_sys::Win32::Security::{ SECURITY_ATTRIBUTES, SECURITY_DYNAMIC_TRACKING, SECURITY_QUALITY_OF_SERVICE, SECURITY_STATIC_TRACKING, }; use windows_sys::Win32::Storage::FileSystem::{ CREATE_ALWAYS, CREATE_NEW, DELETE, FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_COMPRESSED, FILE_ATTRIBUTE_DEVICE, FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_EA, FILE_ATTRIBUTE_ENCRYPTED, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_INTEGRITY_STREAM, FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, FILE_ATTRIBUTE_NO_SCRUB_DATA, FILE_ATTRIBUTE_OFFLINE, FILE_ATTRIBUTE_PINNED, FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS, FILE_ATTRIBUTE_RECALL_ON_OPEN, FILE_ATTRIBUTE_REPARSE_POINT, FILE_ATTRIBUTE_SPARSE_FILE, FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_TEMPORARY, FILE_ATTRIBUTE_UNPINNED, FILE_ATTRIBUTE_VIRTUAL, FILE_CREATION_DISPOSITION, FILE_FLAGS_AND_ATTRIBUTES, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_DELETE_ON_CLOSE, FILE_FLAG_NO_BUFFERING, FILE_FLAG_OPEN_NO_RECALL, FILE_FLAG_OPEN_REPARSE_POINT, FILE_FLAG_OVERLAPPED, FILE_FLAG_POSIX_SEMANTICS, FILE_FLAG_RANDOM_ACCESS, FILE_FLAG_SEQUENTIAL_SCAN, FILE_FLAG_SESSION_AWARE, FILE_FLAG_WRITE_THROUGH, FILE_READ_ATTRIBUTES, FILE_SHARE_MODE, OPEN_ALWAYS, OPEN_EXISTING, SECURITY_CONTEXT_TRACKING, SECURITY_EFFECTIVE_ONLY, SECURITY_SQOS_PRESENT, SYNCHRONIZE, TRUNCATE_EXISTING, }; use windows_sys::Win32::System::Kernel::{OBJ_CASE_INSENSITIVE, OBJ_INHERIT}; use windows_sys::Win32::System::WindowsProgramming::{ FILE_OPENED, FILE_OPEN_NO_RECALL, FILE_OPEN_REMOTE_INSTANCE, FILE_OVERWRITTEN, }; use windows_sys::Win32::System::IO::IO_STATUS_BLOCK; // All currently known `FILE_ATTRIBUTE_*` constants, according to // windows-sys' documentation. const FILE_ATTRIBUTE_VALID_FLAGS: FILE_FLAGS_AND_ATTRIBUTES = FILE_ATTRIBUTE_EA | FILE_ATTRIBUTE_DEVICE | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_PINNED | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_OFFLINE | FILE_ATTRIBUTE_VIRTUAL | FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_UNPINNED | FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_ENCRYPTED | FILE_ATTRIBUTE_TEMPORARY | FILE_ATTRIBUTE_COMPRESSED | FILE_ATTRIBUTE_SPARSE_FILE | FILE_ATTRIBUTE_NO_SCRUB_DATA | FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_RECALL_ON_OPEN | FILE_ATTRIBUTE_INTEGRITY_STREAM | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS; /// Like Windows' `CreateFileW`, but takes a `dir` argument to use as the /// root directory. /// /// Also, the `lpfilename` is a Rust slice instead of a C-style NUL-terminated /// array, because that's what our callers have and it's closer to what /// `NtCreatePath` takes. #[allow(non_snake_case)] pub unsafe fn CreateFileAtW( dir: HANDLE, lpfilename: &[u16], dwdesiredaccess: u32, dwsharemode: FILE_SHARE_MODE, lpsecurityattributes: *const SECURITY_ATTRIBUTES, dwcreationdisposition: FILE_CREATION_DISPOSITION, dwflagsandattributes: FILE_FLAGS_AND_ATTRIBUTES, htemplatefile: HANDLE, ) -> HandleOrInvalid { // Absolute paths are not yet implemented here. // // It seems like `NtCreatePath` needs the apparently NT-internal `\??\` // prefix prepended to absolute paths. It's possible it needs other // path transforms as well. `RtlDosPathNameToNtPathName_U` may be a // function that does these things, though it's not available in // windows-sys and not documented, though one can find // [unofficial blog posts], though even they say things like "I`m // sorry that I cannot give more details on these functions". // // [unofficial blog posts]: https://mecanik.dev/en/posts/convert-dos-and-nt-paths-using-rtl-functions/ assert!(dir != 0); // Extended attributes are not implemented yet. if htemplatefile != 0 { SetLastError(ERROR_NOT_SUPPORTED); return HandleOrInvalid::from_raw_handle(INVALID_HANDLE_VALUE as _); } // Convert `dwcreationdisposition` to the `createdisposition` argument // to `NtCreateFile`. Do this before converting `lpfilename` so that // we can return early on failure. let createdisposition = match dwcreationdisposition { CREATE_NEW => FILE_CREATE, CREATE_ALWAYS => FILE_OVERWRITE_IF, OPEN_EXISTING => FILE_OPEN, OPEN_ALWAYS => FILE_OPEN_IF, TRUNCATE_EXISTING => FILE_OVERWRITE, _ => { SetLastError(ERROR_INVALID_PARAMETER); return HandleOrInvalid::from_raw_handle(INVALID_HANDLE_VALUE as _); } }; // Convert `lpfilename` to a `UNICODE_STRING`. let byte_length = lpfilename.len() * mem::size_of::(); let length: u16 = match byte_length.try_into() { Ok(length) => length, Err(_) => { SetLastError(ERROR_INVALID_NAME); return HandleOrInvalid::from_raw_handle(INVALID_HANDLE_VALUE as _); } }; let mut unicode_string = UNICODE_STRING { Buffer: lpfilename.as_ptr() as *mut u16, Length: length, MaximumLength: length, }; let mut handle = INVALID_HANDLE_VALUE; // Convert `dwdesiredaccess` and `dwflagsandattributes` to the // `desiredaccess` argument to `NtCreateFile`. let mut desiredaccess = dwdesiredaccess | SYNCHRONIZE | FILE_READ_ATTRIBUTES; if dwflagsandattributes & FILE_FLAG_DELETE_ON_CLOSE != 0 { desiredaccess |= DELETE; } // Compute `objectattributes`' `Attributes` field. Case-insensitive is // the expected behavior on Windows. let mut attributes = 0; if dwflagsandattributes & FILE_FLAG_POSIX_SEMANTICS != 0 { attributes |= OBJ_CASE_INSENSITIVE as u32; }; if !lpsecurityattributes.is_null() && (*lpsecurityattributes).bInheritHandle != 0 { attributes |= OBJ_INHERIT as u32; } // Compute the `objectattributes` argument to `NtCreateFile`. let mut objectattributes = mem::zeroed::(); objectattributes.Length = mem::size_of::() as _; objectattributes.RootDirectory = dir; objectattributes.ObjectName = &mut unicode_string; objectattributes.Attributes = attributes; if !lpsecurityattributes.is_null() { objectattributes.SecurityDescriptor = (*lpsecurityattributes).lpSecurityDescriptor; } // If needed, set `objectattributes`' `SecurityQualityOfService` field. let mut qos; if dwflagsandattributes & SECURITY_SQOS_PRESENT != 0 { qos = mem::zeroed::(); qos.Length = mem::size_of::() as _; qos.ImpersonationLevel = ((dwflagsandattributes >> 16) & 0x3) as _; qos.ContextTrackingMode = if dwflagsandattributes & SECURITY_CONTEXT_TRACKING != 0 { SECURITY_DYNAMIC_TRACKING } else { SECURITY_STATIC_TRACKING }; qos.EffectiveOnly = ((dwflagsandattributes & SECURITY_EFFECTIVE_ONLY) != 0) as _; objectattributes.SecurityQualityOfService = (&mut qos as *mut SECURITY_QUALITY_OF_SERVICE).cast(); } let mut iostatusblock = mem::zeroed::(); iostatusblock.Anonymous.Status = STATUS_PENDING; // Compute the `fileattributes` argument to `NtCreateFile`. Mask off // unrecognized flags. let mut fileattributes = dwflagsandattributes & FILE_ATTRIBUTE_VALID_FLAGS; if fileattributes == 0 { fileattributes = FILE_ATTRIBUTE_NORMAL; } // Compute the `createoptions` argument to `NtCreateFile`. let mut createoptions = 0; if dwflagsandattributes & FILE_FLAG_BACKUP_SEMANTICS == 0 { createoptions |= FILE_NON_DIRECTORY_FILE; } else { if dwdesiredaccess & GENERIC_ALL != 0 { createoptions |= FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REMOTE_INSTANCE; } else { if dwdesiredaccess & GENERIC_READ != 0 { createoptions |= FILE_OPEN_FOR_BACKUP_INTENT; } if dwdesiredaccess & GENERIC_WRITE != 0 { createoptions |= FILE_OPEN_REMOTE_INSTANCE; } } } if dwflagsandattributes & FILE_FLAG_DELETE_ON_CLOSE != 0 { createoptions |= FILE_DELETE_ON_CLOSE; } if dwflagsandattributes & FILE_FLAG_NO_BUFFERING != 0 { createoptions |= FILE_NO_INTERMEDIATE_BUFFERING; } if dwflagsandattributes & FILE_FLAG_OPEN_NO_RECALL != 0 { createoptions |= FILE_OPEN_NO_RECALL; } if dwflagsandattributes & FILE_FLAG_OPEN_REPARSE_POINT != 0 { createoptions |= FILE_OPEN_REPARSE_POINT; } if dwflagsandattributes & FILE_FLAG_OVERLAPPED == 0 { createoptions |= FILE_SYNCHRONOUS_IO_NONALERT; } // FILE_FLAG_POSIX_SEMANTICS is handled above. if dwflagsandattributes & FILE_FLAG_RANDOM_ACCESS != 0 { createoptions |= FILE_RANDOM_ACCESS; } if dwflagsandattributes & FILE_FLAG_SESSION_AWARE != 0 { // TODO: How should we handle FILE_FLAG_SESSION_AWARE? SetLastError(ERROR_NOT_SUPPORTED); return HandleOrInvalid::from_raw_handle(INVALID_HANDLE_VALUE as _); } if dwflagsandattributes & FILE_FLAG_SEQUENTIAL_SCAN != 0 { createoptions |= FILE_SEQUENTIAL_ONLY; } if dwflagsandattributes & FILE_FLAG_WRITE_THROUGH != 0 { createoptions |= FILE_WRITE_THROUGH; } // Ok, we have what we need to call `NtCreateFile` now! let status = NtCreateFile( &mut handle, desiredaccess, &mut objectattributes, &mut iostatusblock, null_mut(), fileattributes, dwsharemode, createdisposition, createoptions, null_mut(), 0, ); // Check for errors. if status != STATUS_SUCCESS { handle = INVALID_HANDLE_VALUE; if status == STATUS_OBJECT_NAME_COLLISION { SetLastError(ERROR_FILE_EXISTS); } else { SetLastError(RtlNtStatusToDosError(status)); } } else if (dwcreationdisposition == CREATE_ALWAYS && iostatusblock.Information == FILE_OVERWRITTEN as _) || (dwcreationdisposition == OPEN_ALWAYS && iostatusblock.Information == FILE_OPENED as _) { // Set `ERROR_ALREADY_EXISTS` according to the table for // `dwCreationDisposition` in the [`CreateFileW` docs]. // // [`CreateFileW` docs]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew SetLastError(ERROR_ALREADY_EXISTS); } else { // Otherwise indicate that we succeeded. SetLastError(SUCCESS); } HandleOrInvalid::from_raw_handle(handle as _) } cap-primitives-3.4.1/src/windows/fs/dir_entry_inner.rs000064400000000000000000000075621046102023000212300ustar 00000000000000use super::open_options_to_std; use crate::ambient_authority; use crate::fs::OpenOptionsExt; use crate::fs::{ open, open_ambient_dir, FileType, FollowSymlinks, ImplFileTypeExt, Metadata, OpenOptions, ReadDir, ReadDirInner, }; use std::ffi::OsString; use std::{fmt, fs, io}; use windows_sys::Win32::Storage::FileSystem::{ FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, }; pub(crate) struct DirEntryInner { std: fs::DirEntry, } impl DirEntryInner { #[inline] pub(crate) fn open(&self, options: &OpenOptions) -> io::Result { match options.follow { FollowSymlinks::No => { let (opts, manually_trunc) = open_options_to_std(options); let file = opts.open(self.std.path())?; if manually_trunc { // Unwrap is ok because 0 never overflows, and we'll only // have `manually_trunc` set when the file is opened for // writing. file.set_len(0).unwrap(); } Ok(file) } FollowSymlinks::Yes => { let path = self.std.path(); open( &open_ambient_dir(path.parent().unwrap(), ambient_authority())?, path.file_name().unwrap().as_ref(), options, ) } } } #[inline] pub(crate) fn metadata(&self) -> io::Result { self.std.metadata().map(Metadata::from_just_metadata) } #[inline] pub(crate) fn full_metadata(&self) -> io::Result { // If we can open the file, we can get a more complete Metadata which // includes `file_index`, `volume_serial_number`, and // `number_of_links`. let mut opts = OpenOptions::new(); opts.access_mode(0); opts.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS); opts.follow(FollowSymlinks::No); let opened = self.open(&opts)?; Metadata::from_file(&opened) } #[inline] pub(crate) fn remove_file(&self) -> io::Result<()> { fs::remove_file(self.std.path()) } #[inline] pub(crate) fn remove_dir(&self) -> io::Result<()> { fs::remove_dir(self.std.path()) } #[inline] pub(crate) fn read_dir(&self, follow: FollowSymlinks) -> io::Result { assert_eq!( follow, FollowSymlinks::Yes, "`read_dir` without following symlinks is not implemented yet" ); let std = fs::read_dir(self.std.path())?; let inner = ReadDirInner::from_std(std); Ok(ReadDir { inner }) } #[inline] pub(crate) fn file_type(&self) -> io::Result { self.std.file_type().map(ImplFileTypeExt::from_std) } #[inline] pub(crate) fn file_name(&self) -> OsString { self.std.file_name() } #[inline] #[cfg(windows_by_handle)] pub(crate) fn is_same_file(&self, metadata: &Metadata) -> io::Result { // Don't use `self.metadata()`, because that doesn't include the // volume serial number which we need. // Ok(Metadata::from_just_metadata(fs::metadata(self.std.path())?).is_same_file(metadata)) } #[inline] pub(super) fn from_std(std: fs::DirEntry) -> Self { Self { std } } } impl fmt::Debug for DirEntryInner { // Like libstd's version, but doesn't print the path. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("DirEntry").field(&self.file_name()).finish() } } #[doc(hidden)] impl crate::fs::_WindowsDirEntryExt for crate::fs::DirEntry { #[inline] fn full_metadata(&self) -> io::Result { DirEntryInner::full_metadata(&self.inner) } } cap-primitives-3.4.1/src/windows/fs/dir_options_ext.rs000064400000000000000000000002211046102023000212300ustar 00000000000000#[derive(Debug, Clone)] pub(crate) struct DirOptionsExt {} impl DirOptionsExt { pub(crate) const fn new() -> Self { Self {} } } cap-primitives-3.4.1/src/windows/fs/dir_utils.rs000064400000000000000000000202101046102023000200150ustar 00000000000000use crate::fs::OpenOptionsExt; use crate::fs::{errors, OpenOptions}; use crate::AmbientAuthority; use std::ffi::OsString; use std::ops::Deref; use std::os::windows::ffi::{OsStrExt, OsStringExt}; use std::path::{Path, PathBuf}; use std::{fs, io}; use windows_sys::Win32::Storage::FileSystem::{ FILE_FLAG_BACKUP_SEMANTICS, FILE_SHARE_READ, FILE_SHARE_WRITE, }; /// Rust's `Path` implicitly strips redundant slashes, however they aren't /// redundant in one case: at the end of a path they indicate that a path is /// expected to name a directory. pub(crate) fn path_requires_dir(path: &Path) -> bool { let wide: Vec = path.as_os_str().encode_wide().collect(); wide.ends_with(&['/' as u16]) || wide.ends_with(&['/' as u16, '.' as _]) || wide.ends_with(&['\\' as u16]) || wide.ends_with(&['\\' as u16, '.' as _]) } /// Windows treats `foo/.` as equivalent to `foo` even if `foo` does not /// exist or is not a directory. So we don't do the special trailing-dot /// handling that we do on Posix-ish platforms. pub(crate) fn path_has_trailing_dot(_path: &Path) -> bool { false } /// For the purposes of emulating Windows symlink resolution, we sometimes /// need to know whether a path really does end in a trailing dot though. pub(crate) fn path_really_has_trailing_dot(path: &Path) -> bool { let wide: Vec = path.as_os_str().encode_wide().collect(); wide.ends_with(&['/' as u16, '.' as u16]) || wide.ends_with(&['\\' as u16, '.' as u16]) } /// Rust's `Path` implicitly strips trailing `/`s, however they aren't /// redundant in one case: at the end of a path they are the final path /// component, which has different path lookup behavior. pub(crate) fn path_has_trailing_slash(path: &Path) -> bool { let wide: Vec = path.as_os_str().encode_wide().collect(); wide.ends_with(&['/' as u16]) || wide.ends_with(&['\\' as u16]) } /// Strip trailing `/`s, unless this reduces `path` to `/` itself. This is /// used by `create_dir` and others to prevent paths like `foo/` from /// canonicalizing to `foo/.` since these syscalls treat these differently. pub(crate) fn strip_dir_suffix(path: &Path) -> impl Deref + '_ { let mut wide: Vec = path.as_os_str().encode_wide().collect(); while wide.len() > 1 && (*wide.last().unwrap() == '/' as u16 || *wide.last().unwrap() == '\\' as u16) { wide.pop(); } PathBuf::from(OsString::from_wide(&wide)) } /// Return an `OpenOptions` for opening directories. pub(crate) fn dir_options() -> OpenOptions { // Set `FILE_FLAG_BACKUP_SEMANTICS` so that we can open directories. Unset // `FILE_SHARE_DELETE` so that directories can't be renamed or deleted // underneath us, since we use paths to implement many directory operations. OpenOptions::new() .read(true) .dir_required(true) .custom_flags(FILE_FLAG_BACKUP_SEMANTICS) .share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE) .clone() } /// Like `dir_options`, but additionally request the ability to read the /// directory entries. pub(crate) fn readdir_options() -> OpenOptions { dir_options().readdir_required(true).clone() } /// Return an `OpenOptions` for canonicalizing paths. pub(crate) fn canonicalize_options() -> OpenOptions { OpenOptions::new() .read(true) .custom_flags(FILE_FLAG_BACKUP_SEMANTICS) .clone() } /// Open a directory named by a bare path, using the host process' ambient /// authority. /// /// # Ambient Authority /// /// This function is not sandboxed and may trivially access any path that the /// host process has access to. pub(crate) fn open_ambient_dir_impl( path: &Path, ambient_authority: AmbientAuthority, ) -> io::Result { use std::os::windows::fs::OpenOptionsExt; let _ = ambient_authority; // Set `FILE_FLAG_BACKUP_SEMANTICS` so that we can open directories. Unset // `FILE_SHARE_DELETE` so that directories can't be renamed or deleted // underneath us, since we use paths to implement many directory operations. let dir = fs::OpenOptions::new() .read(true) .custom_flags(FILE_FLAG_BACKUP_SEMANTICS) .share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE) .open(path)?; // Require a directory. It may seem possible to eliminate this `metadata()` // call by appending a slash to the path before opening it so that the OS // requires a directory for us, however on Windows in some circumstances // this leads to "The filename, directory name, or volume label syntax is // incorrect." errors. if !dir.metadata()?.is_dir() { return Err(errors::is_not_directory()); } Ok(dir) } #[test] fn strip_dir_suffix_tests() { assert_eq!(&*strip_dir_suffix(Path::new("/foo//")), Path::new("/foo")); assert_eq!(&*strip_dir_suffix(Path::new("/foo/")), Path::new("/foo")); assert_eq!(&*strip_dir_suffix(Path::new("foo/")), Path::new("foo")); assert_eq!(&*strip_dir_suffix(Path::new("foo")), Path::new("foo")); assert_eq!(&*strip_dir_suffix(Path::new("/")), Path::new("/")); assert_eq!(&*strip_dir_suffix(Path::new("//")), Path::new("/")); assert_eq!( &*strip_dir_suffix(Path::new("\\foo\\\\")), Path::new("\\foo") ); assert_eq!(&*strip_dir_suffix(Path::new("\\foo\\")), Path::new("\\foo")); assert_eq!(&*strip_dir_suffix(Path::new("foo\\")), Path::new("foo")); assert_eq!(&*strip_dir_suffix(Path::new("foo")), Path::new("foo")); assert_eq!(&*strip_dir_suffix(Path::new("\\")), Path::new("\\")); assert_eq!(&*strip_dir_suffix(Path::new("\\\\")), Path::new("\\")); } #[test] fn test_path_requires_dir() { assert!(!path_requires_dir(Path::new("."))); assert!(path_requires_dir(Path::new("/"))); assert!(path_requires_dir(Path::new("//"))); assert!(path_requires_dir(Path::new("/./."))); assert!(path_requires_dir(Path::new("foo/"))); assert!(path_requires_dir(Path::new("foo//"))); assert!(path_requires_dir(Path::new("foo//."))); assert!(path_requires_dir(Path::new("foo/./."))); assert!(path_requires_dir(Path::new("foo/./"))); assert!(path_requires_dir(Path::new("foo/.//"))); assert!(path_requires_dir(Path::new("\\"))); assert!(path_requires_dir(Path::new("\\\\"))); assert!(path_requires_dir(Path::new("\\.\\."))); assert!(path_requires_dir(Path::new("foo\\"))); assert!(path_requires_dir(Path::new("foo\\\\"))); assert!(path_requires_dir(Path::new("foo\\\\."))); assert!(path_requires_dir(Path::new("foo\\.\\."))); assert!(path_requires_dir(Path::new("foo\\.\\"))); assert!(path_requires_dir(Path::new("foo\\.\\\\"))); } #[test] fn test_path_has_trailing_slash() { assert!(path_has_trailing_slash(Path::new("/"))); assert!(path_has_trailing_slash(Path::new("//"))); assert!(path_has_trailing_slash(Path::new("foo/"))); assert!(path_has_trailing_slash(Path::new("foo//"))); assert!(path_has_trailing_slash(Path::new("foo/./"))); assert!(path_has_trailing_slash(Path::new("foo/.//"))); assert!(path_has_trailing_slash(Path::new("\\"))); assert!(path_has_trailing_slash(Path::new("\\\\"))); assert!(path_has_trailing_slash(Path::new("foo\\"))); assert!(path_has_trailing_slash(Path::new("foo\\\\"))); assert!(path_has_trailing_slash(Path::new("foo\\.\\"))); assert!(path_has_trailing_slash(Path::new("foo\\.\\\\"))); assert!(!path_has_trailing_slash(Path::new("foo"))); assert!(!path_has_trailing_slash(Path::new("foo."))); assert!(!path_has_trailing_slash(Path::new("/./foo"))); assert!(!path_has_trailing_slash(Path::new(".."))); assert!(!path_has_trailing_slash(Path::new("/.."))); assert!(!path_has_trailing_slash(Path::new("\\.\\foo"))); assert!(!path_has_trailing_slash(Path::new(".."))); assert!(!path_has_trailing_slash(Path::new("\\.."))); assert!(!path_has_trailing_slash(Path::new("/./."))); assert!(!path_has_trailing_slash(Path::new("foo//."))); assert!(!path_has_trailing_slash(Path::new("foo/./."))); assert!(!path_has_trailing_slash(Path::new("."))); assert!(!path_has_trailing_slash(Path::new("\\.\\."))); assert!(!path_has_trailing_slash(Path::new("foo\\\\."))); assert!(!path_has_trailing_slash(Path::new("foo\\.\\."))); } cap-primitives-3.4.1/src/windows/fs/errors.rs000064400000000000000000000011171046102023000173400ustar 00000000000000use std::io; use windows_sys::Win32::Foundation; #[cold] pub(crate) fn no_such_file_or_directory() -> io::Error { io::Error::from_raw_os_error(Foundation::ERROR_FILE_NOT_FOUND as i32) } #[cold] pub(crate) fn is_directory() -> io::Error { io::Error::from_raw_os_error(Foundation::ERROR_DIRECTORY_NOT_SUPPORTED as i32) } #[cold] pub(crate) fn is_not_directory() -> io::Error { io::Error::from_raw_os_error(Foundation::ERROR_DIRECTORY as i32) } #[cold] pub(crate) fn too_many_symlinks() -> io::Error { io::Error::from_raw_os_error(Foundation::ERROR_TOO_MANY_LINKS as i32) } cap-primitives-3.4.1/src/windows/fs/file_type_ext.rs000064400000000000000000000064011046102023000206650ustar 00000000000000use crate::fs::FileType; use std::{fs, io}; /// A type that implements `FileTypeExt` for this platform. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub(crate) enum ImplFileTypeExt { CharacterDevice, Fifo, #[cfg(windows_file_type_ext)] SymlinkFile, #[cfg(windows_file_type_ext)] SymlinkDir, SymlinkUnknown, } impl ImplFileTypeExt { /// Constructs a new instance of `Self` from the given [`std::fs::File`] /// and [`std::fs::Metadata`]. pub(crate) fn from(file: &fs::File, metadata: &fs::Metadata) -> io::Result { // Check for the things we can do with just metadata. let file_type = Self::from_just_metadata(metadata); if file_type != FileType::unknown() { return Ok(file_type); } // Use the open file to check for one of the exotic file types. let file_type = winx::winapi_util::file::typ(file)?; if file_type.is_char() { return Ok(FileType::ext(ImplFileTypeExt::CharacterDevice)); } if file_type.is_pipe() { return Ok(FileType::ext(ImplFileTypeExt::Fifo)); } Ok(FileType::unknown()) } /// Constructs a new instance of `Self` from the given /// [`std::fs::Metadata`]. #[inline] pub(crate) fn from_just_metadata(metadata: &fs::Metadata) -> FileType { let std = metadata.file_type(); Self::from_std(std) } /// Constructs a new instance of `Self` from the given /// [`std::fs::FileType`]. #[inline] pub(crate) fn from_std(std: fs::FileType) -> FileType { if std.is_file() { return FileType::file(); } if std.is_dir() { return FileType::dir(); } #[cfg(windows_file_type_ext)] { use std::os::windows::fs::FileTypeExt; if std.is_symlink_file() { return FileType::ext(Self::SymlinkFile); } if std.is_symlink_dir() { return FileType::ext(Self::SymlinkDir); } } if std.is_symlink() { return FileType::ext(Self::SymlinkUnknown); } FileType::unknown() } /// Creates a `FileType` for which `is_symlink_file()` returns `true`. #[cfg(windows_file_type_ext)] #[inline] pub(crate) const fn symlink_file() -> Self { Self::SymlinkFile } /// Creates a `FileType` for which `is_symlink_dir()` returns `true`. #[cfg(windows_file_type_ext)] #[inline] pub(crate) const fn symlink_dir() -> Self { Self::SymlinkDir } #[inline] pub(crate) fn is_symlink(&self) -> bool { match self { #[cfg(windows_file_type_ext)] Self::SymlinkFile | Self::SymlinkDir => true, Self::SymlinkUnknown => true, _ => false, } } } #[doc(hidden)] impl crate::fs::_WindowsFileTypeExt for crate::fs::FileType { #[inline] fn is_block_device(&self) -> bool { false } #[inline] fn is_char_device(&self) -> bool { *self == FileType::ext(ImplFileTypeExt::CharacterDevice) } #[inline] fn is_fifo(&self) -> bool { *self == FileType::ext(ImplFileTypeExt::Fifo) } #[inline] fn is_socket(&self) -> bool { false } } cap-primitives-3.4.1/src/windows/fs/get_path.rs000064400000000000000000000021161046102023000176170ustar 00000000000000use std::ffi::OsString; use std::os::windows::ffi::{OsStrExt, OsStringExt}; use std::path::{Path, PathBuf}; use std::{fs, io}; /// Calculates system path of `file`. /// /// This function will automatically strip the extended prefix from the /// resultant path to allow for joining this resultant path with relative /// components. pub(crate) fn get_path(file: &fs::File) -> io::Result { // get system path to the handle let path = winx::file::get_file_path(file)?; // strip extended prefix; otherwise we will error out on any relative // components with `out_path` let wide: Vec<_> = path.as_os_str().encode_wide().collect(); let wide_final = if wide.starts_with(&['\\' as u16, '\\' as _, '?' as _, '\\' as _]) { &wide[4..] } else { &wide }; Ok(PathBuf::from(OsString::from_wide(wide_final))) } /// Convenience function for calling `get_path` and concatenating the result /// with `path`. pub(super) fn concatenate(file: &fs::File, path: &Path) -> io::Result { let file_path = get_path(file)?; Ok(file_path.join(path)) } cap-primitives-3.4.1/src/windows/fs/hard_link_unchecked.rs000064400000000000000000000007501046102023000217720ustar 00000000000000use super::get_path::concatenate; use std::path::Path; use std::{fs, io}; /// *Unsandboxed* function similar to `hard_link`, but which does not perform /// sandboxing. pub(crate) fn hard_link_unchecked( old_start: &fs::File, old_path: &Path, new_start: &fs::File, new_path: &Path, ) -> io::Result<()> { let old_full_path = concatenate(old_start, old_path)?; let new_full_path = concatenate(new_start, new_path)?; fs::hard_link(old_full_path, new_full_path) } cap-primitives-3.4.1/src/windows/fs/is_file_read_write_impl.rs000064400000000000000000000007531046102023000226710ustar 00000000000000use io_lifetimes::AsHandle; use std::{fs, io}; pub(crate) fn is_file_read_write_impl(file: &fs::File) -> io::Result<(bool, bool)> { let handle = file.as_handle(); let access_mode = winx::file::query_access_information(handle)?; let read = access_mode.contains(winx::file::AccessMode::FILE_READ_DATA); let write = access_mode.contains(winx::file::AccessMode::FILE_WRITE_DATA) || access_mode.contains(winx::file::AccessMode::FILE_APPEND_DATA); Ok((read, write)) } cap-primitives-3.4.1/src/windows/fs/is_same_file.rs000064400000000000000000000036131046102023000204460ustar 00000000000000use crate::fs::ImplMetadataExt; #[cfg(windows_by_handle)] use crate::fs::Metadata; use std::{fs, io}; /// Determine if `a` and `b` refer to the same inode on the same device. pub(crate) fn is_same_file(a: &fs::File, b: &fs::File) -> io::Result { let a_metadata = ImplMetadataExt::from(a, &a.metadata()?)?; let b_metadata = ImplMetadataExt::from(b, &b.metadata()?)?; Ok(a_metadata.is_same_file(&b_metadata)) } /// Determine if `a` and `b` are metadata for the same inode on the same /// device. #[cfg(windows_by_handle)] #[allow(dead_code)] pub(crate) fn is_same_file_metadata(a: &Metadata, b: &Metadata) -> io::Result { use crate::fs::MetadataExt; Ok(a.volume_serial_number() == b.volume_serial_number() && a.file_index() == b.file_index()) } /// Determine if `a` and `b` definitely refer to different inodes. /// /// This is similar to `is_same_file`, but is conservative, and doesn't depend /// on nightly-only features. #[cfg(racy_asserts)] pub(crate) fn is_different_file(a: &fs::File, b: &fs::File) -> io::Result { #[cfg(windows_by_handle)] { is_same_file(a, b).map(|same| !same) } #[cfg(not(windows_by_handle))] { let a_metadata = Metadata::from_std(a.metadata()?); let b_metadata = Metadata::from_std(b.metadata()?); is_different_file_metadata(&a_metadata, &b_metadata) } } /// Determine if `a` and `b` are metadata for definitely different inodes. /// /// This is similar to `is_same_file_metadata`, but is conservative, and /// doesn't depend on nightly-only features. #[cfg(racy_asserts)] pub(crate) fn is_different_file_metadata(a: &Metadata, b: &Metadata) -> io::Result { #[cfg(windows_by_handle)] { is_same_file_metadata(a, b).map(|same| !same) } #[cfg(not(windows_by_handle))] { // Conservatively just compare creation times. Ok(a.created()? != b.created()?) } } cap-primitives-3.4.1/src/windows/fs/metadata_ext.rs000064400000000000000000000146021046102023000204670ustar 00000000000000#![allow(clippy::useless_conversion)] use crate::fs::MetadataExt; use std::{fs, io}; #[derive(Debug, Clone)] pub(crate) struct ImplMetadataExt { file_attributes: u32, creation_time: u64, last_access_time: u64, last_write_time: u64, file_size: u64, volume_serial_number: Option, number_of_links: Option, file_index: Option, } impl ImplMetadataExt { /// Constructs a new instance of `Self` from the given [`std::fs::File`] /// and [`std::fs::Metadata`]. #[inline] #[allow(unused_variables)] pub(crate) fn from(file: &fs::File, std: &fs::Metadata) -> io::Result { let (mut volume_serial_number, mut number_of_links, mut file_index) = (None, None, None); #[cfg(windows_by_handle)] { use std::os::windows::fs::MetadataExt; if let Some(some) = std.volume_serial_number() { volume_serial_number = Some(some); } if let Some(some) = std.number_of_links() { number_of_links = Some(some); } if let Some(some) = std.file_index() { file_index = Some(some); } } #[cfg(not(windows_by_handle))] if volume_serial_number.is_none() || number_of_links.is_none() || file_index.is_none() { let fileinfo = winx::winapi_util::file::information(file)?; if volume_serial_number.is_none() { let t64: u64 = fileinfo.volume_serial_number(); let t32: u32 = t64.try_into().unwrap(); volume_serial_number = Some(t32); } if number_of_links.is_none() { let t64: u64 = fileinfo.number_of_links(); let t32: u32 = t64.try_into().unwrap(); number_of_links = Some(t32); } if file_index.is_none() { file_index = Some(fileinfo.file_index()); } } Ok(Self::from_parts( std, volume_serial_number, number_of_links, file_index, )) } /// Constructs a new instance of `Self` from the given /// [`std::fs::Metadata`]. /// /// As with the comments in [`std::fs::Metadata::volume_serial_number`] and /// nearby functions, some fields of the resulting metadata will be `None`. /// /// [`std::fs::Metadata::volume_serial_number`]: https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html#tymethod.volume_serial_number #[inline] #[allow(unused_mut)] pub(crate) fn from_just_metadata(std: &fs::Metadata) -> Self { let (mut volume_serial_number, mut number_of_links, mut file_index) = (None, None, None); #[cfg(windows_by_handle)] { use std::os::windows::fs::MetadataExt; if let Some(some) = std.volume_serial_number() { volume_serial_number = Some(some); } if let Some(some) = std.number_of_links() { number_of_links = Some(some); } if let Some(some) = std.file_index() { file_index = Some(some); } } Self::from_parts(std, volume_serial_number, number_of_links, file_index) } #[inline] fn from_parts( std: &fs::Metadata, volume_serial_number: Option, number_of_links: Option, file_index: Option, ) -> Self { use std::os::windows::fs::MetadataExt; Self { file_attributes: std.file_attributes(), creation_time: std.creation_time(), last_access_time: std.last_access_time(), last_write_time: std.last_write_time(), file_size: std.file_size(), volume_serial_number, number_of_links, file_index, } } /// Determine if `self` and `other` refer to the same inode on the same /// device. pub(crate) fn is_same_file(&self, other: &Self) -> bool { // From [MSDN]: // The identifier (low and high parts) and the volume serial number // uniquely identify a file on a single computer. To determine whether // two open handles represent the same file, combine the identifier // and the volume serial number for each file and compare them. // [MSDN]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/ns-fileapi-by_handle_file_information let self_vsn = self .volume_serial_number .expect("could extract volume serial number of `self`"); let other_vsn = other .volume_serial_number .expect("could extract volume serial number of `other`"); let self_file_index = self.file_index.expect("could extract file index `self`"); let other_file_index = other.file_index.expect("could extract file index `other`"); self_vsn == other_vsn && self_file_index == other_file_index } /// `MetadataExt` requires nightly to be implemented, but we sometimes /// just need the file attributes. #[inline] pub(crate) fn file_attributes(&self) -> u32 { self.file_attributes } } impl MetadataExt for ImplMetadataExt { fn file_attributes(&self) -> u32 { self.file_attributes } #[inline] fn creation_time(&self) -> u64 { self.creation_time } #[inline] fn last_access_time(&self) -> u64 { self.last_access_time } #[inline] fn last_write_time(&self) -> u64 { self.last_write_time } #[inline] fn file_size(&self) -> u64 { self.file_size } #[inline] #[cfg(windows_by_handle)] fn volume_serial_number(&self) -> Option { self.volume_serial_number } #[inline] #[cfg(windows_by_handle)] fn number_of_links(&self) -> Option { self.number_of_links } #[inline] #[cfg(windows_by_handle)] fn file_index(&self) -> Option { self.file_index } } #[doc(hidden)] impl crate::fs::_WindowsByHandle for crate::fs::Metadata { #[inline] fn file_attributes(&self) -> u32 { self.ext.file_attributes } #[inline] fn volume_serial_number(&self) -> Option { self.ext.volume_serial_number } #[inline] fn number_of_links(&self) -> Option { self.ext.number_of_links } #[inline] fn file_index(&self) -> Option { self.ext.file_index } } cap-primitives-3.4.1/src/windows/fs/mod.rs000064400000000000000000000052051046102023000166050ustar 00000000000000mod access_unchecked; mod copy; mod create_dir_unchecked; mod create_file_at_w; mod dir_entry_inner; mod dir_options_ext; mod dir_utils; mod file_type_ext; mod get_path; mod hard_link_unchecked; mod is_file_read_write_impl; mod is_same_file; mod metadata_ext; mod oflags; mod open_impl; mod open_options_ext; mod open_unchecked; mod read_dir_inner; mod read_link_impl; mod read_link_unchecked; mod remove_dir_all_impl; mod remove_dir_unchecked; mod remove_file_unchecked; mod remove_open_dir_impl; mod rename_unchecked; mod reopen_impl; mod set_permissions_unchecked; mod set_symlink_permissions_unchecked; mod set_times_impl; mod stat_unchecked; mod symlink_unchecked; pub(crate) mod errors; #[rustfmt::skip] pub(crate) use crate::fs::{ manually::canonicalize as canonicalize_impl, via_parent::access as access_impl, via_parent::hard_link as hard_link_impl, via_parent::create_dir as create_dir_impl, via_parent::rename as rename_impl, via_parent::remove_dir as remove_dir_impl, via_parent::set_permissions as set_permissions_impl, via_parent::set_symlink_permissions as set_symlink_permissions_impl, manually::stat as stat_impl, via_parent::symlink_dir as symlink_dir_impl, via_parent::symlink_file as symlink_file_impl, via_parent::remove_file as remove_file_impl, }; pub(crate) use access_unchecked::*; pub(crate) use copy::*; pub(crate) use create_dir_unchecked::*; pub(crate) use dir_entry_inner::*; pub(crate) use dir_options_ext::*; pub(crate) use dir_utils::*; pub(crate) use file_type_ext::*; pub(crate) use hard_link_unchecked::*; pub(crate) use is_file_read_write_impl::*; pub(crate) use is_same_file::*; pub(crate) use metadata_ext::*; pub(crate) use open_impl::open_impl; pub(crate) use open_options_ext::*; pub(crate) use open_unchecked::*; pub(crate) use read_dir_inner::*; pub(crate) use read_link_impl::*; pub(crate) use read_link_unchecked::*; pub(crate) use remove_dir_all_impl::*; pub(crate) use remove_dir_unchecked::*; pub(crate) use remove_file_unchecked::*; pub(crate) use remove_open_dir_impl::*; pub(crate) use rename_unchecked::*; pub(crate) use reopen_impl::reopen_impl; pub(crate) use set_permissions_unchecked::*; pub(crate) use set_symlink_permissions_unchecked::*; pub(crate) use set_times_impl::*; pub(crate) use stat_unchecked::*; pub(crate) use symlink_unchecked::*; // On Windows, there is a limit of 63 reparse points on any given path. // pub(crate) const MAX_SYMLINK_EXPANSIONS: u8 = 63; pub(crate) fn file_path(file: &std::fs::File) -> Option { get_path::get_path(file).ok() } pub(super) use oflags::*; cap-primitives-3.4.1/src/windows/fs/oflags.rs000064400000000000000000000060631046102023000173040ustar 00000000000000use crate::fs::{FollowSymlinks, OpenOptions, OpenOptionsExt}; use std::fs; use windows_sys::Win32::Storage::FileSystem::{ FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_FLAG_WRITE_THROUGH, FILE_SHARE_DELETE, }; /// Adjust an `OpenOptions` after all the flags are set, in preparation /// for the to call a Windows API `open` function. Also return a bool /// indicating that the `trunc` flag was requested but could not be set, /// so the file should be truncated manually after opening. pub(in super::super) fn prepare_open_options_for_open(opts: &mut OpenOptions) -> bool { let mut trunc = opts.truncate; let mut manually_trunc = false; let mut custom_flags = match opts.follow { FollowSymlinks::Yes => opts.ext.custom_flags, FollowSymlinks::No => { if trunc && !opts.create_new && !opts.append && opts.write { // On Windows, truncating overwrites a symlink with a // non-symlink. manually_trunc = true; trunc = false; } opts.ext.custom_flags | FILE_FLAG_OPEN_REPARSE_POINT } }; let mut share_mode = opts.ext.share_mode; if opts.maybe_dir { custom_flags |= FILE_FLAG_BACKUP_SEMANTICS; // Only allow `FILE_SHARE_READ` and `FILE_SHARE_WRITE`; this mirrors // the values in `dir_options()` and is done to prevent directories // from being deleted or renamed underneath cap-std's sandboxed path // lookups on Windows. share_mode &= !FILE_SHARE_DELETE; } // This matches system-interface's `set_fd_flags` interpretation of these // flags on Windows. if opts.sync || opts.dsync { custom_flags |= FILE_FLAG_WRITE_THROUGH; } opts.truncate(trunc) .share_mode(share_mode) .custom_flags(custom_flags); manually_trunc } /// Translate the given `cap_std` into `std` options. Also return a bool /// indicating that the `trunc` flag was requested but could not be set, /// so the file should be truncated manually after opening. pub(in super::super) fn open_options_to_std(opts: &OpenOptions) -> (fs::OpenOptions, bool) { use std::os::windows::fs::OpenOptionsExt; let mut opts = opts.clone(); let manually_trunc = prepare_open_options_for_open(&mut opts); let mut std_opts = fs::OpenOptions::new(); std_opts .read(opts.read) .write(opts.write) .append(opts.append) .truncate(opts.truncate) .create(opts.create) .create_new(opts.create_new) .share_mode(opts.ext.share_mode) .custom_flags(opts.ext.custom_flags) .attributes(opts.ext.attributes); // Calling `sequence_qos_flags` with a value of 0 has the side effect // of setting `SECURITY_SQOS_PRESENT`, so don't call it if we don't // have any flags. if opts.ext.security_qos_flags != 0 { std_opts.security_qos_flags(opts.ext.security_qos_flags); } if let Some(access_mode) = opts.ext.access_mode { std_opts.access_mode(access_mode); } (std_opts, manually_trunc) } cap-primitives-3.4.1/src/windows/fs/open_impl.rs000064400000000000000000000021611046102023000200060ustar 00000000000000use crate::fs::{manually, OpenOptions}; use std::path::Path; use std::{fs, io}; use windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND; pub(crate) fn open_impl( start: &fs::File, path: &Path, options: &OpenOptions, ) -> io::Result { // Windows reserves several special device paths. Disallow opening any // of them. // See: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions if let Some(stem) = path.file_stem() { if let Some(stemstr) = stem.to_str() { match stemstr.to_uppercase().as_str() { "CON" | "PRN" | "AUX" | "NUL" | "COM0" | "COM1" | "COM2" | "COM3" | "COM4" | "COM5" | "COM6" | "COM7" | "COM8" | "COM9" | "COM¹" | "COM²" | "COM³" | "LPT0" | "LPT1" | "LPT2" | "LPT3" | "LPT4" | "LPT5" | "LPT6" | "LPT7" | "LPT8" | "LPT9" | "LPT¹" | "LPT²" | "LPT³" => { return Err(io::Error::from_raw_os_error(ERROR_FILE_NOT_FOUND as i32)); } _ => {} } } } manually::open(start, path, options) } cap-primitives-3.4.1/src/windows/fs/open_options_ext.rs000064400000000000000000000074461046102023000214330ustar 00000000000000#![allow(unsafe_code)] use crate::fs::OpenOptions; use std::io; use std::ptr::null_mut; use windows_sys::Win32::Foundation::{ERROR_INVALID_PARAMETER, GENERIC_READ, GENERIC_WRITE}; use windows_sys::Win32::Security::SECURITY_ATTRIBUTES; use windows_sys::Win32::Storage::FileSystem::{ CREATE_ALWAYS, CREATE_NEW, FILE_FLAG_OPEN_REPARSE_POINT, FILE_GENERIC_WRITE, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_WRITE_DATA, OPEN_ALWAYS, OPEN_EXISTING, SECURITY_SQOS_PRESENT, TRUNCATE_EXISTING, }; #[derive(Debug, Clone)] pub(crate) struct ImplOpenOptionsExt { pub(super) access_mode: Option, pub(super) share_mode: u32, pub(super) custom_flags: u32, pub(super) attributes: u32, pub(super) security_attributes: *mut SECURITY_ATTRIBUTES, pub(super) security_qos_flags: u32, } unsafe impl Send for ImplOpenOptionsExt {} unsafe impl Sync for ImplOpenOptionsExt {} impl ImplOpenOptionsExt { pub(crate) const fn new() -> Self { Self { access_mode: None, share_mode: FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, custom_flags: 0, attributes: 0, security_attributes: null_mut(), security_qos_flags: 0, } } pub(crate) fn access_mode(&mut self, mode: u32) -> &mut Self { self.access_mode = Some(mode); self } pub(crate) fn share_mode(&mut self, share: u32) -> &mut Self { self.share_mode = share; self } pub(crate) fn custom_flags(&mut self, flags: u32) -> &mut Self { self.custom_flags = flags; self } pub(crate) fn attributes(&mut self, attributes: u32) -> &mut Self { self.attributes = attributes; self } pub(crate) fn security_qos_flags(&mut self, flags: u32) -> &mut Self { self.security_qos_flags = flags | SECURITY_SQOS_PRESENT; self } } pub(crate) fn get_access_mode(options: &OpenOptions) -> io::Result { match ( options.read, options.write, options.append, options.ext.access_mode, ) { (.., Some(mode)) => Ok(mode), (true, false, false, None) => Ok(GENERIC_READ), (false, true, false, None) => Ok(GENERIC_WRITE), (true, true, false, None) => Ok(GENERIC_READ | GENERIC_WRITE), (false, _, true, None) => Ok(FILE_GENERIC_WRITE & !FILE_WRITE_DATA), (true, _, true, None) => Ok(GENERIC_READ | (FILE_GENERIC_WRITE & !FILE_WRITE_DATA)), (false, false, false, None) => { Err(io::Error::from_raw_os_error(ERROR_INVALID_PARAMETER as i32)) } } } pub(crate) fn get_flags_and_attributes(options: &OpenOptions) -> u32 { options.ext.custom_flags | options.ext.attributes | options.ext.security_qos_flags | if options.create_new { FILE_FLAG_OPEN_REPARSE_POINT } else { 0 } } pub(crate) fn get_creation_mode(options: &OpenOptions) -> io::Result { const ERROR_INVALID_PARAMETER: i32 = 87; match (options.write, options.append) { (true, false) => {} (false, false) => { if options.truncate || options.create || options.create_new { return Err(io::Error::from_raw_os_error(ERROR_INVALID_PARAMETER)); } } (_, true) => { if options.truncate && !options.create_new { return Err(io::Error::from_raw_os_error(ERROR_INVALID_PARAMETER)); } } } Ok( match (options.create, options.truncate, options.create_new) { (false, false, false) => OPEN_EXISTING, (true, false, false) => OPEN_ALWAYS, (false, true, false) => TRUNCATE_EXISTING, (true, true, false) => CREATE_ALWAYS, (_, _, true) => CREATE_NEW, }, ) } cap-primitives-3.4.1/src/windows/fs/open_unchecked.rs000064400000000000000000000217211046102023000210010ustar 00000000000000//! Windows implementation of `openat` functionality. #![allow(unsafe_code)] use super::create_file_at_w::CreateFileAtW; use super::{open_options_to_std, prepare_open_options_for_open}; use crate::fs::{ errors, file_path, get_access_mode, get_creation_mode, get_flags_and_attributes, FollowSymlinks, OpenOptions, OpenUncheckedError, SymlinkKind, }; use crate::{ambient_authority, AmbientAuthority}; use std::ffi::OsStr; use std::os::windows::ffi::OsStrExt; use std::os::windows::fs::MetadataExt; use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle}; use std::path::{Component, Path, PathBuf}; use std::{fs, io}; use windows_sys::Win32::Foundation::{self, ERROR_ACCESS_DENIED, HANDLE, INVALID_HANDLE_VALUE}; use windows_sys::Win32::Storage::FileSystem::{ CreateFileW, FILE_ATTRIBUTE_DIRECTORY, FILE_FLAG_OPEN_REPARSE_POINT, }; /// *Unsandboxed* function similar to `open`, but which does not perform /// sandboxing. pub(crate) fn open_unchecked( start: &fs::File, path: &Path, options: &OpenOptions, ) -> Result { let _ = ambient_authority; // We have the final `OpenOptions`; now prepare it for an `open`. let mut prepared_opts = options.clone(); let manually_trunc = prepare_open_options_for_open(&mut prepared_opts); handle_open_result( open_at(start, path, &prepared_opts), options, manually_trunc, ) } // The following is derived from Rust's library/std/src/sys/windows/fs.rs // at revision 56888c1e9b4135b511abd2d8e907099003d12281, except with a // directory `start` parameter added and using `CreateFileAtW` instead of // `CreateFileW`. fn open_at(start: &fs::File, path: &Path, opts: &OpenOptions) -> io::Result { let mut dir = start.as_raw_handle() as HANDLE; // `PathCchCanonicalizeEx` and friends don't seem to work with relative // paths. Or at least, when I tried it, they canonicalized "a" to "", // which isn't what we want. So we manually canonicalize `..` and `.`. // Hopefully there aren't other mysterious Windows path conventions that // we're missing here. let mut rebuilt = PathBuf::new(); for component in path.components() { match component { Component::Prefix(_) | Component::RootDir => { rebuilt.push(component); dir = 0; } Component::Normal(_) => { rebuilt.push(component); } Component::ParentDir => { if !rebuilt.pop() { // We popped past the beginning of `path`. Substitute in // the path of `start` and convert this to an ambient // path by dropping the directory base. It's ok to do // this because we're not sandboxing at this level of the // code. if dir == 0 { return Err(io::Error::from_raw_os_error(ERROR_ACCESS_DENIED as _)); } rebuilt = match file_path(start) { Some(path) => path, None => { return Err(io::Error::from_raw_os_error(ERROR_ACCESS_DENIED as _)); } }; dir = 0; // And then pop the last component of that. let _ = rebuilt.pop(); } } Component::CurDir => (), } } let mut wide = OsStr::encode_wide(rebuilt.as_os_str()).collect::>(); // If we ended up re-rooting, use Windows' `CreateFileW` instead of our // own `CreateFileAtW` so that it does the requisite magic for absolute // paths. if dir == 0 { // We're calling the windows-sys `CreateFileW` which expects a // NUL-terminated filename, so add a NUL terminator. wide.push(0); let handle = unsafe { CreateFileW( wide.as_ptr(), get_access_mode(opts)?, opts.ext.share_mode, opts.ext.security_attributes, get_creation_mode(opts)?, get_flags_and_attributes(opts), 0 as HANDLE, ) }; if handle != INVALID_HANDLE_VALUE { Ok(unsafe { fs::File::from_raw_handle(handle as _) }) } else { Err(io::Error::last_os_error()) } } else { // Our own `CreateFileAtW` is similar to `CreateFileW` except it // takes the filename as a Rust slice directly, so we can skip // the NUL terminator. let handle = unsafe { CreateFileAtW( dir, &wide, get_access_mode(opts)?, opts.ext.share_mode, opts.ext.security_attributes, get_creation_mode(opts)?, get_flags_and_attributes(opts), 0 as HANDLE, ) }; if let Ok(handle) = handle.try_into() { Ok(>::from(handle)) } else { Err(io::Error::last_os_error()) } } } /// *Unsandboxed* function similar to `open_unchecked`, but which just operates /// on a bare path, rather than starting with a handle. pub(crate) fn open_ambient_impl( path: &Path, options: &OpenOptions, ambient_authority: AmbientAuthority, ) -> Result { let _ = ambient_authority; let (std_opts, manually_trunc) = open_options_to_std(options); handle_open_result(std_opts.open(path), options, manually_trunc) } fn handle_open_result( result: io::Result, options: &OpenOptions, manually_trunc: bool, ) -> Result { match result { Ok(f) => { let enforce_dir = options.dir_required; let enforce_nofollow = options.follow == FollowSymlinks::No && (options.ext.custom_flags & FILE_FLAG_OPEN_REPARSE_POINT) == 0; if enforce_dir || enforce_nofollow { let metadata = f.metadata().map_err(OpenUncheckedError::Other)?; if enforce_dir { // Require a directory. It may seem possible to eliminate // this `metadata()` call by appending a slash to the path // before opening it so that the OS requires a directory // for us, however on Windows in some circumstances this // leads to "The filename, directory name, or volume label // syntax is incorrect." errors. // // We check `file_attributes()` instead of using `is_dir()` // since the latter returns false if we're looking at a // directory symlink. if metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY == 0 { return Err(OpenUncheckedError::Other(errors::is_not_directory())); } } if enforce_nofollow { // Windows doesn't have a way to return errors like // `O_NOFOLLOW`, so if we're not following symlinks and // we're not using `FILE_FLAG_OPEN_REPARSE_POINT` manually // to open a symlink itself, check for symlinks and report // them as a distinct error. if metadata.file_type().is_symlink() { return Err(OpenUncheckedError::Symlink( io::Error::from_raw_os_error( Foundation::ERROR_STOPPED_ON_SYMLINK as i32, ), if metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY { SymlinkKind::Dir } else { SymlinkKind::File }, )); } } } // Windows truncates symlinks into normal files, so truncation // may be disabled above; do it manually if needed. if manually_trunc { // Unwrap is ok because 0 never overflows, and we'll only // have `manually_trunc` set when the file is opened for // writing. f.set_len(0).unwrap(); } Ok(f) } Err(e) if e.kind() == io::ErrorKind::NotFound => Err(OpenUncheckedError::NotFound(e)), Err(e) => match e.raw_os_error() { Some(code) => match code as u32 { Foundation::ERROR_FILE_NOT_FOUND | Foundation::ERROR_PATH_NOT_FOUND => { Err(OpenUncheckedError::NotFound(e)) } _ => Err(OpenUncheckedError::Other(e)), }, None => Err(OpenUncheckedError::Other(e)), }, } } cap-primitives-3.4.1/src/windows/fs/read_dir_inner.rs000064400000000000000000000031551046102023000207740ustar 00000000000000use super::get_path::concatenate; use crate::fs::{open_dir, DirEntryInner, FollowSymlinks}; use std::path::{Component, Path}; use std::{fmt, fs, io}; pub(crate) struct ReadDirInner { std: fs::ReadDir, } impl ReadDirInner { pub(crate) fn new(start: &fs::File, path: &Path, follow: FollowSymlinks) -> io::Result { assert_eq!( follow, FollowSymlinks::Yes, "`read_dir` without following symlinks is not implemented yet" ); let dir = open_dir(start, path)?; Self::new_unchecked(&dir, Component::CurDir.as_ref()) } pub(crate) fn read_base_dir(start: &fs::File) -> io::Result { Self::new_unchecked(&start, Component::CurDir.as_ref()) } pub(crate) fn new_unchecked(start: &fs::File, path: &Path) -> io::Result { let full_path = concatenate(start, path)?; Ok(Self { std: fs::read_dir(full_path)?, }) } pub(super) fn from_std(std: fs::ReadDir) -> Self { Self { std } } } impl Iterator for ReadDirInner { type Item = io::Result; fn next(&mut self) -> Option { self.std .next() .map(|result| result.map(DirEntryInner::from_std)) } } impl fmt::Debug for ReadDirInner { // Like libstd's version, but doesn't print the path. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut b = f.debug_struct("ReadDir"); // `fs::ReadDir`'s `Debug` just prints the path, and since we're not // printing that, we don't have anything else to print. b.finish() } } cap-primitives-3.4.1/src/windows/fs/read_link_impl.rs000064400000000000000000000015541046102023000210020ustar 00000000000000use crate::fs::{open, FollowSymlinks, OpenOptions, OpenOptionsExt}; use std::path::{Path, PathBuf}; use std::{fs, io}; use windows_sys::Win32::Storage::FileSystem::{ FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, }; /// *Unsandboxed* function similar to `read_link`, but which does not perform /// sandboxing. pub(crate) fn read_link_impl(start: &fs::File, path: &Path) -> io::Result { // Open the link with no access mode, instead of generic read. // By default FILE_LIST_DIRECTORY is denied for the junction "C:\Documents and // Settings", so this is needed for a common case. let mut opts = OpenOptions::new(); opts.access_mode(0); opts.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS); opts.follow(FollowSymlinks::No); let file = open(start, path, &opts)?; winx::file::read_link(&file) } cap-primitives-3.4.1/src/windows/fs/read_link_unchecked.rs000064400000000000000000000005741046102023000217730ustar 00000000000000use super::get_path::concatenate; use std::path::{Path, PathBuf}; use std::{fs, io}; /// *Unsandboxed* function similar to `read_link`, but which does not perform /// sandboxing. pub(crate) fn read_link_unchecked( start: &fs::File, path: &Path, _reuse: PathBuf, ) -> io::Result { let full_path = concatenate(start, path)?; fs::read_link(full_path) } cap-primitives-3.4.1/src/windows/fs/remove_dir_all_impl.rs000064400000000000000000000017451046102023000220370ustar 00000000000000use super::get_path::get_path; use crate::fs::{open_dir, open_dir_nofollow, remove_dir, stat, FollowSymlinks}; use std::path::Path; use std::{fs, io}; pub(crate) fn remove_dir_all_impl(start: &fs::File, path: &Path) -> io::Result<()> { // Open the directory, following symlinks, to make sure it is a directory. let file = open_dir(start, path)?; // Test whether the path is a symlink. let md = stat(start, path, FollowSymlinks::No)?; drop(file); if md.is_symlink() { // If so, just remove the link. remove_dir(start, path) } else { // Otherwise, remove the tree. let dir = open_dir_nofollow(start, path)?; remove_open_dir_all_impl(dir) } } pub(crate) fn remove_open_dir_all_impl(dir: fs::File) -> io::Result<()> { // Close the directory so that we can delete it. This is racy; see the // comments in `remove_open_dir_impl` for details. let path = get_path(&dir)?; drop(dir); fs::remove_dir_all(&path) } cap-primitives-3.4.1/src/windows/fs/remove_dir_unchecked.rs000064400000000000000000000005171046102023000221730ustar 00000000000000use super::get_path::concatenate; use std::path::Path; use std::{fs, io}; /// *Unsandboxed* function similar to `remove_dir`, but which does not perform /// sandboxing. pub(crate) fn remove_dir_unchecked(start: &fs::File, path: &Path) -> io::Result<()> { let full_path = concatenate(start, path)?; fs::remove_dir(full_path) } cap-primitives-3.4.1/src/windows/fs/remove_file_unchecked.rs000064400000000000000000000005221046102023000223300ustar 00000000000000use super::get_path::concatenate; use std::path::Path; use std::{fs, io}; /// *Unsandboxed* function similar to `remove_file`, but which does not perform /// sandboxing. pub(crate) fn remove_file_unchecked(start: &fs::File, path: &Path) -> io::Result<()> { let full_path = concatenate(start, path)?; fs::remove_file(full_path) } cap-primitives-3.4.1/src/windows/fs/remove_open_dir_impl.rs000064400000000000000000000020251046102023000222200ustar 00000000000000use super::get_path::get_path; use std::{fs, io}; pub(crate) fn remove_open_dir_impl(dir: fs::File) -> io::Result<()> { let path = get_path(&dir)?; // Drop the directory before removing it, since we open directories without // `FILE_SHARE_DELETE`, and removing it requires accessing via its name // rather than its handle. // // There is a window here in which another process could remove or rename // a directory with this path after the handle is dropped, however it's // unlikely to happen by accident, and unlikely to cause major problems. // It may cause spurious failures, or failures with different error codes, // but this appears to be unavoidable. // // Even if we did have `FILE_SHARE_DELETE` and we kept the handle open // while doing the `remove_dir, `FILE_SHARE_DELETE` would grant other // processes the right to remove or rename the directory. So there // doesn't seem to be a race-free way of removing opened directories. drop(dir); fs::remove_dir(path) } cap-primitives-3.4.1/src/windows/fs/rename_unchecked.rs000064400000000000000000000007371046102023000213130ustar 00000000000000use super::get_path::concatenate; use std::path::Path; use std::{fs, io}; /// *Unsandboxed* function similar to `rename`, but which does not perform /// sandboxing. pub(crate) fn rename_unchecked( old_start: &fs::File, old_path: &Path, new_start: &fs::File, new_path: &Path, ) -> io::Result<()> { let old_full_path = concatenate(old_start, old_path)?; let new_full_path = concatenate(new_start, new_path)?; fs::rename(old_full_path, new_full_path) } cap-primitives-3.4.1/src/windows/fs/reopen_impl.rs000064400000000000000000000062451046102023000203440ustar 00000000000000use crate::fs::{get_access_mode, get_flags_and_attributes, OpenOptions}; use io_lifetimes::AsHandle; use std::{fs, io}; use windows_sys::Win32::Foundation::{GENERIC_READ, GENERIC_WRITE}; use windows_sys::Win32::Storage::FileSystem::{ FILE_FLAG_DELETE_ON_CLOSE, FILE_FLAG_WRITE_THROUGH, FILE_GENERIC_READ, FILE_GENERIC_WRITE, SECURITY_CONTEXT_TRACKING, SECURITY_DELEGATION, SECURITY_EFFECTIVE_ONLY, SECURITY_IDENTIFICATION, SECURITY_IMPERSONATION, }; use winx::file::{AccessMode, Flags, ShareMode}; /// Implementation of `reopen`. pub(crate) fn reopen_impl(file: &fs::File, options: &OpenOptions) -> io::Result { let old_access_mode = winx::file::query_access_information(file.as_handle())?; let new_access_mode = get_access_mode(options)?; let flags = get_flags_and_attributes(options); let new_access_mode = AccessMode::from_bits(new_access_mode).unwrap(); let flags = Flags::from_bits(flags).unwrap(); // Disallow custom access modes might imply or require more access than we // have. if new_access_mode .intersects(AccessMode::from_bits(GENERIC_WRITE | FILE_GENERIC_WRITE).unwrap()) && !old_access_mode .intersects(AccessMode::from_bits(GENERIC_WRITE | FILE_GENERIC_WRITE).unwrap()) { return Err(io::Error::new( io::ErrorKind::PermissionDenied, "Can't reopen file", )); } if new_access_mode.intersects(AccessMode::from_bits(GENERIC_READ | FILE_GENERIC_READ).unwrap()) && !old_access_mode .intersects(AccessMode::from_bits(GENERIC_READ | FILE_GENERIC_READ).unwrap()) { return Err(io::Error::new( io::ErrorKind::PermissionDenied, "Can't reopen file", )); } // Disallow custom flags which might imply or require more access than we have. if flags .intersects(Flags::from_bits(FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_WRITE_THROUGH).unwrap()) && !old_access_mode .intersects(AccessMode::from_bits(GENERIC_WRITE | FILE_GENERIC_WRITE).unwrap()) { return Err(io::Error::new( io::ErrorKind::PermissionDenied, "Can't reopen file", )); } // For now, disallow all non-anonymous security modes. /* if flags.intersects( Flags::from_bits( SECURITY_CONTEXT_TRACKING | SECURITY_DELEGATION | SECURITY_EFFECTIVE_ONLY | SECURITY_IDENTIFICATION | SECURITY_IMPERSONATION, ) .unwrap(), ) { return Err(io::Error::new(io::ErrorKind::Other, "Can't reopen file")); } */ // And for now, do the bit tests manually. if (flags.bits() & (SECURITY_CONTEXT_TRACKING | SECURITY_DELEGATION | SECURITY_EFFECTIVE_ONLY | SECURITY_IDENTIFICATION | SECURITY_IMPERSONATION)) != 0 { return Err(io::Error::new(io::ErrorKind::Other, "Can't reopen file")); } let new_share_mode = ShareMode::FILE_SHARE_READ | ShareMode::FILE_SHARE_WRITE | ShareMode::FILE_SHARE_DELETE; winx::file::reopen_file(file.as_handle(), new_access_mode, new_share_mode, flags) } cap-primitives-3.4.1/src/windows/fs/set_permissions_unchecked.rs000064400000000000000000000016141046102023000232650ustar 00000000000000use super::get_path::concatenate; use crate::fs::Permissions; use std::path::Path; use std::{fs, io}; /// *Unsandboxed* function similar to `set_permissions`, but which does not /// perform sandboxing. pub(crate) fn set_permissions_unchecked( start: &fs::File, path: &Path, perm: Permissions, ) -> io::Result<()> { // According to [Rust's documentation], `fs::set_permissions` uses // `SetFileAttributes`, and according to [Windows' documentation] // `SetFileAttributes` does not follow symbolic links. // // [Windows' documentation]: https://docs.microsoft.com/en-us/windows/win32/fileio/symbolic-link-effects-on-file-systems-functions#setfileattributes // [Rust's documentation]: https://doc.rust-lang.org/std/fs/fn.set_permissions.html#platform-specific-behavior let out_path = concatenate(start, path)?; fs::set_permissions(out_path, perm.into_std(start)?) } cap-primitives-3.4.1/src/windows/fs/set_symlink_permissions_unchecked.rs000064400000000000000000000015351046102023000250350ustar 00000000000000use super::get_path::concatenate; use crate::fs::Permissions; use std::path::Path; use std::{fs, io}; /// This can just use `AT_SYMLINK_NOFOLLOW`. pub(crate) fn set_symlink_permissions_unchecked( start: &fs::File, path: &Path, perm: Permissions, ) -> io::Result<()> { // According to [Rust's documentation], `fs::set_permissions` uses // `SetFileAttributes`, and according to [Windows' documentation] // `SetFileAttributes` does not follow symbolic links. // // [Windows' documentation]: https://docs.microsoft.com/en-us/windows/win32/fileio/symbolic-link-effects-on-file-systems-functions#setfileattributes // [Rust's documentation]: https://doc.rust-lang.org/std/fs/fn.set_permissions.html#platform-specific-behavior let out_path = concatenate(start, path)?; fs::set_permissions(out_path, perm.into_std(start)?) } cap-primitives-3.4.1/src/windows/fs/set_times_impl.rs000064400000000000000000000024021046102023000210370ustar 00000000000000use crate::fs::{open, OpenOptions, OpenOptionsExt, SystemTimeSpec}; use std::path::Path; use std::{fs, io}; use windows_sys::Win32::Storage::FileSystem::{ FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, }; #[inline] pub(crate) fn set_times_impl( start: &fs::File, path: &Path, atime: Option, mtime: Option, ) -> io::Result<()> { set_times_inner(start, path, atime, mtime, 0) } #[inline] pub(crate) fn set_times_nofollow_impl( start: &fs::File, path: &Path, atime: Option, mtime: Option, ) -> io::Result<()> { set_times_inner(start, path, atime, mtime, FILE_FLAG_OPEN_REPARSE_POINT) } fn set_times_inner( start: &fs::File, path: &Path, atime: Option, mtime: Option, custom_flags: u32, ) -> io::Result<()> { let custom_flags = custom_flags | FILE_FLAG_BACKUP_SEMANTICS; // On Windows, `set_times` requires write permissions. let file = open( start, path, OpenOptions::new().write(true).custom_flags(custom_flags), )?; fs_set_times::SetTimes::set_times( &file, atime.map(SystemTimeSpec::into_std), mtime.map(SystemTimeSpec::into_std), ) } cap-primitives-3.4.1/src/windows/fs/stat_unchecked.rs000064400000000000000000000021731046102023000210130ustar 00000000000000use crate::fs::OpenOptionsExt; use crate::fs::{open_unchecked, FollowSymlinks, Metadata, OpenOptions}; use std::path::Path; use std::{fs, io}; use windows_sys::Win32::Storage::FileSystem::{ FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, }; /// *Unsandboxed* function similar to `stat`, but which does not perform /// sandboxing. pub(crate) fn stat_unchecked( start: &fs::File, path: &Path, follow: FollowSymlinks, ) -> io::Result { // Attempt to open the file to get the metadata that way, as that gives // us all the info. let mut opts = OpenOptions::new(); // Explicitly request no access, because we're just querying metadata. opts.access_mode(0); match follow { FollowSymlinks::Yes => { opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS); opts.follow(FollowSymlinks::Yes); } FollowSymlinks::No => { opts.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS); opts.follow(FollowSymlinks::No); } } let file = open_unchecked(start, path, &opts)?; Metadata::from_file(&file) } cap-primitives-3.4.1/src/windows/fs/symlink_unchecked.rs000064400000000000000000000014051046102023000215230ustar 00000000000000use super::get_path::concatenate; use std::path::Path; use std::{fs, io}; /// *Unsandboxed* function similar to `symlink_file`, but which does not /// perform sandboxing. pub(crate) fn symlink_file_unchecked( old_path: &Path, new_start: &fs::File, new_path: &Path, ) -> io::Result<()> { let new_full_path = concatenate(new_start, new_path)?; std::os::windows::fs::symlink_file(old_path, new_full_path) } /// *Unsandboxed* function similar to `symlink_dir`, but which does not perform /// sandboxing. pub(crate) fn symlink_dir_unchecked( old_path: &Path, new_start: &fs::File, new_path: &Path, ) -> io::Result<()> { let new_full_path = concatenate(new_start, new_path)?; std::os::windows::fs::symlink_dir(old_path, new_full_path) } cap-primitives-3.4.1/src/windows/mod.rs000064400000000000000000000001601046102023000161700ustar 00000000000000//! The `winx` module contains code specific to Windows, supported by the //! `winx` crate. pub(crate) mod fs;