├── .cirrus.yml ├── .github ├── actions │ └── install-rust │ │ ├── README.md │ │ ├── action.yml │ │ └── main.js └── workflows │ └── main.yml ├── .gitignore ├── .rustfmt.toml ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── COPYRIGHT ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-Apache-2.0_WITH_LLVM-exception ├── LICENSE-MIT ├── ORG_CODE_OF_CONDUCT.md ├── README.md ├── SECURITY.md ├── benches └── mod.rs ├── build.rs ├── cap-async-std ├── COPYRIGHT ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-Apache-2.0_WITH_LLVM-exception ├── LICENSE-MIT ├── README.md ├── build.rs └── src │ ├── fs │ ├── dir.rs │ ├── dir_entry.rs │ ├── file.rs │ ├── mod.rs │ └── read_dir.rs │ ├── fs_utf8 │ ├── dir.rs │ ├── dir_entry.rs │ ├── file.rs │ ├── mod.rs │ └── read_dir.rs │ ├── lib.rs │ ├── net │ ├── incoming.rs │ ├── mod.rs │ ├── pool.rs │ ├── tcp_listener.rs │ ├── tcp_stream.rs │ └── udp_socket.rs │ ├── os │ ├── mod.rs │ └── unix │ │ ├── mod.rs │ │ └── net │ │ ├── incoming.rs │ │ ├── mod.rs │ │ ├── unix_datagram.rs │ │ ├── unix_listener.rs │ │ └── unix_stream.rs │ └── time │ └── mod.rs ├── cap-directories ├── COPYRIGHT ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-Apache-2.0_WITH_LLVM-exception ├── LICENSE-MIT ├── README.md └── src │ ├── lib.rs │ ├── project_dirs.rs │ └── user_dirs.rs ├── cap-fs-ext ├── COPYRIGHT ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-Apache-2.0_WITH_LLVM-exception ├── LICENSE-MIT ├── README.md ├── build.rs └── src │ ├── dir_entry_ext.rs │ ├── dir_ext.rs │ ├── file_type_ext.rs │ ├── is_file_read_write.rs │ ├── lib.rs │ ├── metadata_ext.rs │ ├── open_options_follow_ext.rs │ ├── open_options_maybe_dir_ext.rs │ ├── open_options_sync_ext.rs │ └── reopen.rs ├── cap-net-ext ├── COPYRIGHT ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-Apache-2.0_WITH_LLVM-exception ├── LICENSE-MIT ├── README.md └── src │ └── lib.rs ├── cap-primitives ├── COPYRIGHT ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-Apache-2.0_WITH_LLVM-exception ├── LICENSE-MIT ├── README.md ├── build.rs └── src │ ├── fs │ ├── access.rs │ ├── assert_same_file.rs │ ├── canonicalize.rs │ ├── copy.rs │ ├── create_dir.rs │ ├── dir_builder.rs │ ├── dir_entry.rs │ ├── dir_options.rs │ ├── errors.rs │ ├── file.rs │ ├── file_path_by_searching.rs │ ├── file_type.rs │ ├── follow_symlinks.rs │ ├── hard_link.rs │ ├── is_file_read_write.rs │ ├── manually │ │ ├── canonical_path.rs │ │ ├── canonicalize.rs │ │ ├── cow_component.rs │ │ ├── mod.rs │ │ ├── open.rs │ │ ├── open_entry.rs │ │ └── read_link_one.rs │ ├── maybe_owned_file.rs │ ├── metadata.rs │ ├── mod.rs │ ├── open.rs │ ├── open_ambient.rs │ ├── open_dir.rs │ ├── open_options.rs │ ├── open_unchecked_error.rs │ ├── permissions.rs │ ├── read_dir.rs │ ├── read_link.rs │ ├── remove_dir.rs │ ├── remove_dir_all.rs │ ├── remove_file.rs │ ├── remove_open_dir.rs │ ├── rename.rs │ ├── reopen.rs │ ├── set_permissions.rs │ ├── set_times.rs │ ├── stat.rs │ ├── symlink.rs │ ├── system_time_spec.rs │ └── via_parent │ │ ├── access.rs │ │ ├── create_dir.rs │ │ ├── hard_link.rs │ │ ├── mod.rs │ │ ├── open_parent.rs │ │ ├── read_link.rs │ │ ├── remove_dir.rs │ │ ├── remove_file.rs │ │ ├── rename.rs │ │ ├── set_permissions.rs │ │ ├── set_symlink_permissions.rs │ │ ├── set_times_nofollow.rs │ │ └── symlink.rs │ ├── lib.rs │ ├── net │ ├── mod.rs │ └── pool.rs │ ├── rustix │ ├── darwin │ │ ├── fs │ │ │ ├── file_path.rs │ │ │ └── mod.rs │ │ └── mod.rs │ ├── freebsd │ │ ├── fs │ │ │ ├── check.rs │ │ │ ├── mod.rs │ │ │ ├── open_entry_impl.rs │ │ │ ├── open_impl.rs │ │ │ ├── remove_dir_impl.rs │ │ │ ├── remove_file_impl.rs │ │ │ ├── set_permissions_impl.rs │ │ │ ├── set_times_impl.rs │ │ │ └── stat_impl.rs │ │ └── mod.rs │ ├── fs │ │ ├── access_unchecked.rs │ │ ├── copy_impl.rs │ │ ├── create_dir_unchecked.rs │ │ ├── cvt.rs │ │ ├── dir_entry_inner.rs │ │ ├── dir_options_ext.rs │ │ ├── dir_utils.rs │ │ ├── errors.rs │ │ ├── file_path.rs │ │ ├── file_type_ext.rs │ │ ├── hard_link_unchecked.rs │ │ ├── is_file_read_write_impl.rs │ │ ├── is_root_dir.rs │ │ ├── is_same_file.rs │ │ ├── metadata_ext.rs │ │ ├── mod.rs │ │ ├── oflags.rs │ │ ├── open_options_ext.rs │ │ ├── open_unchecked.rs │ │ ├── permissions_ext.rs │ │ ├── read_dir_inner.rs │ │ ├── read_link_unchecked.rs │ │ ├── remove_dir_all_impl.rs │ │ ├── remove_dir_unchecked.rs │ │ ├── remove_file_unchecked.rs │ │ ├── remove_open_dir_by_searching.rs │ │ ├── rename_unchecked.rs │ │ ├── reopen_impl.rs │ │ ├── set_permissions_impl.rs │ │ ├── set_symlink_permissions_unchecked.rs │ │ ├── set_times_impl.rs │ │ ├── stat_unchecked.rs │ │ ├── symlink_unchecked.rs │ │ └── times.rs │ ├── linux │ │ ├── fs │ │ │ ├── canonicalize_impl.rs │ │ │ ├── file_metadata.rs │ │ │ ├── file_path.rs │ │ │ ├── mod.rs │ │ │ ├── open_entry_impl.rs │ │ │ ├── open_impl.rs │ │ │ ├── procfs.rs │ │ │ ├── set_permissions_impl.rs │ │ │ ├── set_times_impl.rs │ │ │ └── stat_impl.rs │ │ └── mod.rs │ └── mod.rs │ ├── time │ ├── instant.rs │ ├── mod.rs │ ├── monotonic_clock.rs │ ├── system_clock.rs │ └── system_time.rs │ └── windows │ ├── fs │ ├── access_unchecked.rs │ ├── copy.rs │ ├── create_dir_unchecked.rs │ ├── create_file_at_w.rs │ ├── dir_entry_inner.rs │ ├── dir_options_ext.rs │ ├── dir_utils.rs │ ├── errors.rs │ ├── file_type_ext.rs │ ├── get_path.rs │ ├── hard_link_unchecked.rs │ ├── is_file_read_write_impl.rs │ ├── is_same_file.rs │ ├── metadata_ext.rs │ ├── mod.rs │ ├── oflags.rs │ ├── open_impl.rs │ ├── open_options_ext.rs │ ├── open_unchecked.rs │ ├── read_dir_inner.rs │ ├── read_link_impl.rs │ ├── read_link_unchecked.rs │ ├── remove_dir_all_impl.rs │ ├── remove_dir_unchecked.rs │ ├── remove_file_unchecked.rs │ ├── remove_open_dir_impl.rs │ ├── rename_unchecked.rs │ ├── reopen_impl.rs │ ├── set_permissions_unchecked.rs │ ├── set_symlink_permissions_unchecked.rs │ ├── set_times_impl.rs │ ├── stat_unchecked.rs │ └── symlink_unchecked.rs │ └── mod.rs ├── cap-rand ├── COPYRIGHT ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-Apache-2.0_WITH_LLVM-exception ├── LICENSE-MIT ├── README.md └── src │ └── lib.rs ├── cap-std ├── COPYRIGHT ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-Apache-2.0_WITH_LLVM-exception ├── LICENSE-MIT ├── README.md ├── build.rs └── src │ ├── fs │ ├── dir.rs │ ├── dir_entry.rs │ ├── file.rs │ ├── mod.rs │ └── read_dir.rs │ ├── fs_utf8 │ ├── dir.rs │ ├── dir_entry.rs │ ├── file.rs │ ├── mod.rs │ └── read_dir.rs │ ├── lib.rs │ ├── net │ ├── incoming.rs │ ├── mod.rs │ ├── pool.rs │ ├── tcp_listener.rs │ ├── tcp_stream.rs │ └── udp_socket.rs │ ├── os │ ├── mod.rs │ └── unix │ │ ├── mod.rs │ │ └── net │ │ ├── incoming.rs │ │ ├── mod.rs │ │ ├── unix_datagram.rs │ │ ├── unix_listener.rs │ │ └── unix_stream.rs │ └── time │ └── mod.rs ├── cap-tempfile ├── COPYRIGHT ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-Apache-2.0_WITH_LLVM-exception ├── LICENSE-MIT ├── README.md └── src │ ├── lib.rs │ ├── tempfile.rs │ └── utf8.rs ├── cap-time-ext ├── COPYRIGHT ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-Apache-2.0_WITH_LLVM-exception ├── LICENSE-MIT ├── README.md └── src │ ├── lib.rs │ ├── monotonic_clock.rs │ ├── system_clock.rs │ └── timezone.rs ├── examples ├── async_std_fs_misc.rs ├── kv-cli.rs └── std_fs_misc.rs ├── fuzz ├── .gitignore ├── Cargo.toml ├── README.md └── fuzz_targets │ └── cap-primitives.rs ├── media ├── cap-std.ico └── cap-std.svg └── tests ├── canonicalize.rs ├── cap-basics.rs ├── cap-net-ext-tcp-split.rs ├── cap-net-ext-tcp.rs ├── cap-net-ext-udp-split.rs ├── cap-net-ext-udp.rs ├── dir-entry-ext.rs ├── dir-ext.rs ├── file-type-ext.rs ├── fs.rs ├── fs_additional.rs ├── fs_utf8.rs ├── is-file-read-write.rs ├── issue-22577.rs ├── metadata-ext.rs ├── net-tcp.rs ├── net-udp.rs ├── net.rs ├── open-ambient.rs ├── paths-containing-nul.rs ├── rand.rs ├── readdir.rs ├── rename-directory.rs ├── rename.rs ├── reopen.rs ├── reopendir.rs ├── root.rs ├── set.rs ├── set_times.rs ├── symlinks.rs ├── sys ├── mod.rs └── unix │ ├── mod.rs │ └── weak.rs ├── sys_common ├── io.rs ├── mod.rs └── symlink_junction.rs ├── time.rs ├── windows-open.rs └── windows_symlinks.rs /.cirrus.yml: -------------------------------------------------------------------------------- 1 | # Implementation derived from `.cirrus.yml` in Rust's libc bindings 2 | # at revision 7f4774e76bd5cb9ccb7140d71ef9be9c16009cdf. 3 | 4 | task: 5 | name: stable x86_64-unknown-freebsd-15-snap 6 | freebsd_instance: 7 | image_family: freebsd-15-0-snap 8 | setup_script: 9 | - curl https://sh.rustup.rs -sSf --output rustup.sh 10 | - sh rustup.sh --default-toolchain stable -y --profile=minimal 11 | - . $HOME/.cargo/env 12 | - rustup default stable 13 | test_script: 14 | - . $HOME/.cargo/env 15 | - cargo test --features=fs_utf8 --workspace 16 | 17 | task: 18 | name: stable x86_64-unknown-freebsd-14 19 | freebsd_instance: 20 | image_family: freebsd-14-0 21 | setup_script: 22 | - curl https://sh.rustup.rs -sSf --output rustup.sh 23 | - sh rustup.sh --default-toolchain stable -y --profile=minimal 24 | - . $HOME/.cargo/env 25 | - rustup default stable 26 | test_script: 27 | - . $HOME/.cargo/env 28 | - cargo test --features=fs_utf8 --workspace 29 | 30 | task: 31 | name: stable x86_64-unknown-freebsd-13 32 | freebsd_instance: 33 | image_family: freebsd-13-3 34 | setup_script: 35 | - curl https://sh.rustup.rs -sSf --output rustup.sh 36 | - sh rustup.sh --default-toolchain stable -y --profile=minimal 37 | - . $HOME/.cargo/env 38 | - rustup default stable 39 | test_script: 40 | - . $HOME/.cargo/env 41 | - cargo test --features=fs_utf8 --workspace 42 | -------------------------------------------------------------------------------- /.github/actions/install-rust/README.md: -------------------------------------------------------------------------------- 1 | # install-rust 2 | 3 | A small github action to install `rustup` and a Rust toolchain. This is 4 | generally expressed inline, but it was repeated enough in this repository it 5 | seemed worthwhile to extract. 6 | 7 | Some gotchas: 8 | 9 | * Can't `--self-update` on Windows due to permission errors (a bug in Github 10 | Actions) 11 | * `rustup` isn't installed on macOS (a bug in Github Actions) 12 | 13 | When the above are fixed we should delete this action and just use this inline: 14 | 15 | ```yml 16 | - run: rustup update $toolchain && rustup default $toolchain 17 | shell: bash 18 | ``` 19 | -------------------------------------------------------------------------------- /.github/actions/install-rust/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Install Rust toolchain' 2 | description: 'Install both `rustup` and a Rust toolchain' 3 | 4 | inputs: 5 | toolchain: 6 | description: 'Default toolchan to install' 7 | required: false 8 | default: 'stable' 9 | 10 | runs: 11 | using: node20 12 | main: 'main.js' 13 | -------------------------------------------------------------------------------- /.github/actions/install-rust/main.js: -------------------------------------------------------------------------------- 1 | const child_process = require('child_process'); 2 | const toolchain = process.env.INPUT_TOOLCHAIN; 3 | const fs = require('fs'); 4 | 5 | function set_env(name, val) { 6 | fs.appendFileSync(process.env['GITHUB_ENV'], `${name}=${val}\n`) 7 | } 8 | 9 | // Needed for now to get 1.24.2 which fixes a bug in 1.24.1 that causes issues 10 | // on Windows. 11 | if (process.platform === 'win32') { 12 | child_process.execFileSync('rustup', ['self', 'update']); 13 | } 14 | 15 | child_process.execFileSync('rustup', ['set', 'profile', 'minimal']); 16 | child_process.execFileSync('rustup', ['update', toolchain, '--no-self-update']); 17 | child_process.execFileSync('rustup', ['default', toolchain]); 18 | 19 | // Deny warnings on CI to keep our code warning-free as it lands in-tree. Don't 20 | // do this on nightly though since there's a fair amount of warning churn there. 21 | if (!toolchain.startsWith('nightly')) { 22 | set_env("RUSTFLAGS", "-D warnings"); 23 | } 24 | 25 | // Save disk space by avoiding incremental compilation, and also we don't use 26 | // any caching so incremental wouldn't help anyway. 27 | set_env("CARGO_INCREMENTAL", "0"); 28 | 29 | // Turn down debuginfo from 2 to 1 to help save disk space 30 | set_env("CARGO_PROFILE_DEV_DEBUG", "1"); 31 | set_env("CARGO_PROFILE_TEST_DEBUG", "1"); 32 | 33 | if (process.platform === 'darwin') { 34 | set_env("CARGO_PROFILE_DEV_SPLIT_DEBUGINFO", "unpacked"); 35 | set_env("CARGO_PROFILE_TEST_SPLIT_DEBUGINFO", "unpacked"); 36 | } 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | # This file tells tools we use rustfmt. We use the default settings. 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | 4 | before_install: 5 | - target=x86_64-unknown-linux-musl 6 | - curl -L https://github.com/mozilla/sccache/releases/download/0.2.13/sccache-0.2.13-$target.tar.gz | tar xzf - 7 | - export PATH=$PATH:`pwd`/sccache-0.2.13-$target 8 | - export RUSTC_WRAPPER=sccache 9 | 10 | before_script: 11 | - export RUST_BACKTRACE=1 12 | 13 | after_script: 14 | - sccache -s 15 | 16 | matrix: 17 | include: 18 | - name: "Precise" 19 | rust: stable 20 | dist: precise 21 | 22 | script: 23 | - cargo test --no-default-features --features=fs_utf8 --workspace 24 | 25 | notifications: 26 | email: 27 | on_success: never 28 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to `cap-std` 2 | 3 | `cap-std` follows common [PR]-oriented development, and uses common Rust tooling, 4 | including [`rustfmt`]. 5 | 6 | [PR]: https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests 7 | [rustfmt]: https://github.com/rust-lang/rustfmt#quick-start 8 | 9 | ## Tests 10 | 11 | `cargo test`. 12 | 13 | ## Fuzzing 14 | 15 | There is a simple fuzzer for the `cap-primitives` crate which constructs 16 | random paths and attempts random filesystem operations on them. 17 | 18 | Caution is recommended when running this fuzzer, since it is a filesystem 19 | fuzzer which in a critical path may result in data loss. 20 | 21 | For more details on our fuzzer, see [fuzz/README.md]. 22 | 23 | [fuzz/README.md]: https://github.com/bytecodealliance/cap-std/blob/main/fuzz/README.md 24 | 25 | ## Benchmarking 26 | 27 | There are several micro-benchmarks for the `cap-std` crate which stress-test 28 | specific API features. As micro-benchmarks, they aren't representative of 29 | real-world use, but they are useful for development of `cap-std`. 30 | 31 | To run the `cap-std` benchmarks, run: 32 | 33 | ``` 34 | cargo +nightly bench 35 | ``` 36 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Short version for non-lawyers: 2 | 3 | `cap-std` is triple-licensed under Apache 2.0 with the LLVM Exception, 4 | Apache 2.0, and MIT terms. 5 | 6 | 7 | Longer version: 8 | 9 | Copyrights in the `cap-std` project are retained by their contributors. 10 | No copyright assignment is required to contribute to the `cap-std` 11 | project. 12 | 13 | Some files include code derived from Rust's `libstd`; see the comments in 14 | the code for details. 15 | 16 | Except as otherwise noted (below and/or in individual files), `cap-std` 17 | is licensed under: 18 | 19 | - the Apache License, Version 2.0, with the LLVM Exception 20 | or 21 | 22 | - the Apache License, Version 2.0 23 | or 24 | , 25 | - or the MIT license 26 | or 27 | , 28 | 29 | at your option. 30 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | Building secure foundations for software development is at the core of what we do in the Bytecode Alliance. Contributions of external security researchers are a vital part of that. 4 | 5 | ## Scope 6 | 7 | If you believe you've found a security issue in any website, service, or software owned or operated by the Bytecode Alliance, we encourage you to notify us. 8 | 9 | ## How to Submit a Report 10 | 11 | To submit a vulnerability report to the Bytecode Alliance, please contact us at [security@bytecodealliance.org](mailto:security@bytecodealliance.org). Your submission will be reviewed and validated by a member of our security team. 12 | 13 | ## Safe Harbor 14 | 15 | The Bytecode Alliance supports safe harbor for security researchers who: 16 | 17 | * Make a good faith effort to avoid privacy violations, destruction of data, and interruption or degradation of our services. 18 | * Only interact with accounts you own or with explicit permission of the account holder. If you do encounter Personally Identifiable Information (PII) contact us immediately, do not proceed with access, and immediately purge any local information. 19 | * Provide us with a reasonable amount of time to resolve vulnerabilities prior to any disclosure to the public or a third-party. 20 | 21 | We will consider activities conducted consistent with this policy to constitute "authorized" conduct and will not pursue civil action or initiate a complaint to law enforcement. We will help to the extent we can if legal action is initiated by a third party against you. 22 | 23 | Please submit a report to us before engaging in conduct that may be inconsistent with or unaddressed by this policy. 24 | 25 | ## Preferences 26 | 27 | * Please provide detailed reports with reproducible steps and a clearly defined impact. 28 | * Submit one vulnerability per report. 29 | * Social engineering (e.g. phishing, vishing, smishing) is prohibited. 30 | -------------------------------------------------------------------------------- /cap-async-std/COPYRIGHT: -------------------------------------------------------------------------------- 1 | Short version for non-lawyers: 2 | 3 | `cap-async-std` is triple-licensed under Apache 2.0 with the LLVM Exception, 4 | Apache 2.0, and MIT terms. 5 | 6 | 7 | Longer version: 8 | 9 | Copyrights in the `cap-async-std` project are retained by their contributors. 10 | No copyright assignment is required to contribute to the `cap-async-std` 11 | project. 12 | 13 | Some files include code derived from Rust's `libstd`; see the comments in 14 | the code for details. 15 | 16 | Except as otherwise noted (below and/or in individual files), `cap-async-std` 17 | is licensed under: 18 | 19 | - the Apache License, Version 2.0, with the LLVM Exception 20 | or 21 | 22 | - the Apache License, Version 2.0 23 | or 24 | , 25 | - or the MIT license 26 | or 27 | , 28 | 29 | at your option. 30 | -------------------------------------------------------------------------------- /cap-async-std/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cap-async-std" 3 | version = "3.4.4" 4 | description = "Capability-based version of async-std" 5 | authors = [ 6 | "Dan Gohman ", 7 | "Jakub Konka ", 8 | ] 9 | license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" 10 | keywords = ["network", "file", "async", "future", "await"] 11 | categories = ["filesystem", "network-programming", "asynchronous", "concurrency"] 12 | repository = "https://github.com/bytecodealliance/cap-std" 13 | edition = "2021" 14 | 15 | [dependencies] 16 | arf-strings = { version = "0.7.0", optional = true } 17 | async-std = { version = "1.13.0", features = ["attributes", "io_safety"] } 18 | cap-primitives = { path = "../cap-primitives", version = "^3.4.4" } 19 | io-lifetimes = { version = "2.0.0", default-features = false, features = ["async-std"] } 20 | io-extras = { version = "0.18.3", features = ["use_async_std"] } 21 | camino = { version = "1.0.5", optional = true } 22 | 23 | [target.'cfg(not(windows))'.dependencies] 24 | rustix = { version = "1.0.0", features = ["fs"] } 25 | 26 | [features] 27 | default = [] 28 | fs_utf8 = ["camino"] 29 | arf_strings = ["fs_utf8", "arf-strings"] 30 | tokio1 = ["async-std/tokio1"] 31 | -------------------------------------------------------------------------------- /cap-async-std/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /cap-async-std/README.md: -------------------------------------------------------------------------------- 1 |
2 |

cap-async-std

3 | 4 |

5 | Capability-based version of `async-std` 6 |

7 | 8 |

9 | Github Actions CI Status 10 | crates.io page 11 | docs.rs docs 12 |

13 |
14 | 15 | This crate provides a capability-based version of [`async-std`]. See the 16 | [toplevel README.md] for more information about capability-based security. 17 | 18 | [`async-std`]: https://crates.io/crates/async-std 19 | [toplevel README.md]: https://github.com/bytecodealliance/cap-std/blob/main/README.md 20 | -------------------------------------------------------------------------------- /cap-async-std/src/fs/mod.rs: -------------------------------------------------------------------------------- 1 | //! A capability-based filesystem API modeled after [`async_std::fs`]. 2 | //! 3 | //! This corresponds to [`async_std::fs`]. 4 | //! 5 | //! Instead of [`async_std::fs`'s free functions] and [`async_std::fs::File`]'s 6 | //! constructors which operate on bare paths, this crate has methods on [`Dir`] 7 | //! which operate on paths which must be relative to the directory. 8 | //! 9 | //! Where `async_std` says "the filesystem", this API says "a filesystem", as 10 | //! it doesn't assume that there's a single global filesystem namespace. 11 | //! 12 | //! Since all functions which expose raw file descriptors are `unsafe`, I/O 13 | //! handles in this API are unforgeable (unsafe code notwithstanding). This 14 | //! combined with a lack of absolute paths provides a natural capability-based 15 | //! interface. 16 | //! 17 | //! This crate uses the existing `async_std::path::Path` rather than having its 18 | //! own path type, however while `async_std::path::Path` is mostly just a pure 19 | //! datatype, it includes aliases for several `async_std::fs` functions. To 20 | //! preserve the capability-based interface, avoid using 21 | //! `async_std::path::Path`'s `canonicalize`, `read_link`, `read_dir`, 22 | //! `metadata`, and `symlink_metadata` functions. 23 | //! 24 | //! [`async_std::fs`'s free functions]: https://docs.rs/async-std/latest/async_std/fs/#functions 25 | 26 | mod dir; 27 | mod dir_entry; 28 | mod file; 29 | mod read_dir; 30 | 31 | pub use dir::Dir; 32 | pub use dir_entry::DirEntry; 33 | pub use file::File; 34 | pub use read_dir::ReadDir; 35 | 36 | // Re-export things from `cap_primitives` that we can use as-is. 37 | #[cfg(not(target_os = "wasi"))] 38 | pub use cap_primitives::fs::{DirBuilder, FileType, Metadata, OpenOptions, Permissions}; 39 | 40 | // Re-export conditional types from `cap_primitives`. 41 | #[cfg(any(unix, target_os = "vxworks", all(windows, windows_file_type_ext)))] 42 | pub use cap_primitives::fs::FileTypeExt; 43 | #[cfg(unix)] 44 | pub use cap_primitives::fs::{DirBuilderExt, PermissionsExt}; 45 | pub use cap_primitives::fs::{FileExt, MetadataExt, OpenOptionsExt}; 46 | 47 | // Re-export things from `async_std` that we can use as-is. 48 | #[cfg(target_os = "wasi")] 49 | pub use async_std::fs::{DirBuilder, FileType, Metadata, OpenOptions, Permissions}; 50 | -------------------------------------------------------------------------------- /cap-async-std/src/fs/read_dir.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::DirEntry; 2 | use async_std::io; 3 | use std::fmt; 4 | 5 | /// Iterator over the entries in a directory. 6 | /// 7 | /// This corresponds to [`async_std::fs::ReadDir`]. 8 | /// 9 | /// There is no `from_std` method, as `async_std::fs::ReadDir` doesn't provide 10 | /// a way to construct a `ReadDir` without opening directories by ambient 11 | /// paths. 12 | pub struct ReadDir { 13 | pub(crate) inner: cap_primitives::fs::ReadDir, 14 | } 15 | 16 | impl Iterator for ReadDir { 17 | type Item = io::Result; 18 | 19 | #[inline] 20 | fn next(&mut self) -> Option { 21 | self.inner 22 | .next() 23 | .map(|inner| inner.map(|inner| DirEntry { inner })) 24 | } 25 | } 26 | 27 | impl fmt::Debug for ReadDir { 28 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 29 | self.inner.fmt(f) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cap-async-std/src/fs_utf8/read_dir.rs: -------------------------------------------------------------------------------- 1 | use crate::fs_utf8::DirEntry; 2 | use std::{fmt, io}; 3 | 4 | /// Iterator over the entries in a directory. 5 | /// 6 | /// This corresponds to [`async_std::fs::ReadDir`]. 7 | /// 8 | /// There is no `from_std` method, as `async_std::fs::ReadDir` doesn't provide 9 | /// a way to construct a `ReadDir` without opening directories by ambient 10 | /// paths. 11 | pub struct ReadDir { 12 | cap_std: crate::fs::ReadDir, 13 | } 14 | 15 | impl ReadDir { 16 | /// Constructs a new instance of `Self` from the given `cap_std::fs::File`. 17 | #[inline] 18 | pub fn from_cap_std(cap_std: crate::fs::ReadDir) -> Self { 19 | Self { cap_std } 20 | } 21 | } 22 | 23 | impl Iterator for ReadDir { 24 | type Item = io::Result; 25 | 26 | #[inline] 27 | fn next(&mut self) -> Option { 28 | self.cap_std 29 | .next() 30 | .map(|result| result.map(DirEntry::from_cap_std)) 31 | } 32 | } 33 | 34 | impl fmt::Debug for ReadDir { 35 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 36 | self.cap_std.fmt(f) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /cap-async-std/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A capability-based API modeled after [`async_std`]. 2 | //! 3 | //! This corresponds to [`async_std`]. 4 | //! 5 | //! Capability-based APIs represent access to external resources as values 6 | //! which can be passed around between different parts of a program. 7 | //! 8 | //! Two notable features are the [`Dir`] and [`Pool`] types: 9 | //! - `Dir` represents an open directory in a filesystem. Instead of opening 10 | //! files by absolute paths or paths relative to the current working 11 | //! directory, files are opened via paths relative to a `Dir`. The concepts 12 | //! of a process-wide "current working directory" and a single global 13 | //! filesystem namespace are de-emphasized. 14 | //! - `Pool` represents a set of network addresses. Instead of allowing 15 | //! applications to request access to any address and then applying 16 | //! process-wide filtering rules, filtering rules are built into pools which 17 | //! may be passed through the program. 18 | //! 19 | //! On WASI, use of this library closely reflects the underlying system 20 | //! API, so it avoids compatibility layers. 21 | //! 22 | //! [`Dir`]: fs::Dir 23 | //! [`Pool`]: net::Pool 24 | 25 | #![deny(missing_docs)] 26 | #![cfg_attr(target_os = "wasi", feature(wasi_ext))] 27 | // async_std doesn't have "can_vector". 28 | // async_std doesn't have "write_all_vectored". 29 | #![doc( 30 | html_logo_url = "https://raw.githubusercontent.com/bytecodealliance/cap-std/main/media/cap-std.svg" 31 | )] 32 | #![doc( 33 | html_favicon_url = "https://raw.githubusercontent.com/bytecodealliance/cap-std/main/media/cap-std.ico" 34 | )] 35 | #![cfg_attr(io_lifetimes_use_std, feature(io_safety))] 36 | 37 | pub mod fs; 38 | #[cfg(feature = "fs_utf8")] 39 | pub mod fs_utf8; 40 | #[cfg(not(target_os = "wasi"))] // Disable `net` on WASI until it has networking support. 41 | pub mod net; 42 | pub mod os; 43 | pub mod time; 44 | #[doc(hidden)] 45 | pub use cap_primitives::ambient_authority_known_at_compile_time; 46 | pub use cap_primitives::{ambient_authority, AmbientAuthority}; 47 | 48 | // Re-export `async_std` to make it easy for users to depend on the same 49 | // version we do, because we use its types in our public API. 50 | pub use async_std; 51 | // And these are also part of our public API 52 | pub use cap_primitives::ipnet; 53 | pub use io_lifetimes; 54 | -------------------------------------------------------------------------------- /cap-async-std/src/net/incoming.rs: -------------------------------------------------------------------------------- 1 | use crate::net::TcpStream; 2 | use async_std::stream::Stream; 3 | use async_std::task::{Context, Poll}; 4 | use async_std::{io, net}; 5 | use std::fmt; 6 | use std::pin::Pin; 7 | 8 | /// An iterator that infinitely `accept`s connections on a [`TcpListener`]. 9 | /// 10 | /// This corresponds to [`async_std::net::Incoming`]. 11 | /// 12 | /// [`TcpListener`]: struct.TcpListener.html 13 | pub struct Incoming<'a> { 14 | std: net::Incoming<'a>, 15 | } 16 | 17 | impl<'a> Incoming<'a> { 18 | /// Constructs a new instance of `Self` from the given 19 | /// `async_std::net::Incoming`. 20 | /// 21 | /// This grants access the resources the `async_std::net::Incoming` 22 | /// instance already has access to. 23 | #[inline] 24 | pub fn from_std(std: net::Incoming<'a>) -> Self { 25 | Self { std } 26 | } 27 | } 28 | 29 | impl<'a> Stream for Incoming<'a> { 30 | type Item = io::Result; 31 | 32 | #[inline] 33 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { 34 | Stream::poll_next(Pin::new(&mut self.std), cx).map(|poll| { 35 | poll.map(|result| { 36 | let tcp_stream = result?; 37 | Ok(TcpStream::from_std(tcp_stream)) 38 | }) 39 | }) 40 | } 41 | 42 | #[inline] 43 | fn size_hint(&self) -> (usize, Option) { 44 | self.std.size_hint() 45 | } 46 | } 47 | 48 | impl<'a> fmt::Debug for Incoming<'a> { 49 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 50 | self.std.fmt(f) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /cap-async-std/src/net/mod.rs: -------------------------------------------------------------------------------- 1 | //! A capability-based network API modeled after [`async_std::net`]. 2 | //! 3 | //! This corresponds to [`async_std::net`]. 4 | //! 5 | //! Instead of [`async_std::net`]'s constructor methods which take an address 6 | //! to connect to, this crates has methods on [`Pool`] which operate on 7 | //! addresses which must be present in the pool. 8 | //! 9 | //! [`Pool`]: struct.Pool.html 10 | 11 | mod incoming; 12 | mod pool; 13 | mod tcp_listener; 14 | mod tcp_stream; 15 | mod udp_socket; 16 | 17 | pub use incoming::*; 18 | pub use pool::*; 19 | pub use tcp_listener::*; 20 | pub use tcp_stream::*; 21 | pub use udp_socket::*; 22 | 23 | // Re-export things from `async_std::net` that we can use as-is. 24 | pub use async_std::net::{ 25 | AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, SocketAddrV4, SocketAddrV6, 26 | ToSocketAddrs, 27 | }; 28 | 29 | // TODO: re-export experimental Ipv6MulticastScope? 30 | -------------------------------------------------------------------------------- /cap-async-std/src/os/mod.rs: -------------------------------------------------------------------------------- 1 | //! OS-specific extensions. 2 | //! 3 | //! This corresponds to [`async_std::os`]. 4 | 5 | #[cfg(unix)] 6 | pub mod unix; 7 | -------------------------------------------------------------------------------- /cap-async-std/src/os/unix/mod.rs: -------------------------------------------------------------------------------- 1 | //! Platform-specific extensions for Unix platforms. 2 | //! 3 | //! This corresponds to [`async_std::os::unix`]. 4 | //! 5 | //! [`async_std::os::unix`]: https://docs.rs/async-std/latest/async_std/os/unix/ 6 | 7 | pub mod net; 8 | -------------------------------------------------------------------------------- /cap-async-std/src/os/unix/net/incoming.rs: -------------------------------------------------------------------------------- 1 | use crate::os::unix::net::UnixStream; 2 | use async_std::io; 3 | use async_std::os::unix; 4 | use async_std::stream::Stream; 5 | use async_std::task::{Context, Poll}; 6 | use std::fmt; 7 | use std::pin::Pin; 8 | 9 | /// An iterator over incoming connections to a [`UnixListener`]. 10 | /// 11 | /// This corresponds to [`async_std::os::unix::net::Incoming`]. 12 | /// 13 | /// [`async_std::os::unix::net::Incoming`]: https://docs.rs/async-std/latest/async_std/os/unix/net/struct.Incoming.html 14 | /// [`UnixListener`]: struct.UnixListener.html 15 | pub struct Incoming<'a> { 16 | std: unix::net::Incoming<'a>, 17 | } 18 | 19 | impl<'a> Incoming<'a> { 20 | /// Constructs a new instance of `Self` from the given 21 | /// `async_std::os::unix::net::Incoming`. 22 | /// 23 | /// This grants access the resources the 24 | /// `async_std::os::unix::net::Incoming` instance already has access to, 25 | /// without any sandboxing. 26 | #[inline] 27 | pub fn from_std(std: unix::net::Incoming<'a>) -> Self { 28 | Self { std } 29 | } 30 | } 31 | 32 | impl<'a> Stream for Incoming<'a> { 33 | type Item = io::Result; 34 | 35 | #[inline] 36 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { 37 | Stream::poll_next(Pin::new(&mut self.std), cx).map(|poll| { 38 | poll.map(|result| { 39 | let unix_stream = result?; 40 | Ok(UnixStream::from_std(unix_stream)) 41 | }) 42 | }) 43 | } 44 | 45 | #[inline] 46 | fn size_hint(&self) -> (usize, Option) { 47 | self.std.size_hint() 48 | } 49 | } 50 | 51 | impl<'a> fmt::Debug for Incoming<'a> { 52 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 53 | self.std.fmt(f) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /cap-async-std/src/os/unix/net/mod.rs: -------------------------------------------------------------------------------- 1 | //! Unix-specific networking functionality 2 | //! 3 | //! This corresponds to [`async_std::os::unix::net`]. 4 | //! 5 | //! [`async_std::os::unix::net`]: https://docs.rs/async-std/latest/async_std/os/unix/net/ 6 | 7 | mod incoming; 8 | mod unix_datagram; 9 | mod unix_listener; 10 | mod unix_stream; 11 | 12 | pub use incoming::*; 13 | pub use unix_datagram::*; 14 | pub use unix_listener::*; 15 | pub use unix_stream::*; 16 | 17 | pub use async_std::os::unix::net::SocketAddr; 18 | -------------------------------------------------------------------------------- /cap-async-std/src/time/mod.rs: -------------------------------------------------------------------------------- 1 | //! A capability-based clock API modeled after [`std::time`]. 2 | //! 3 | //! This corresponds to [`std::time`]. 4 | //! 5 | //! Instead of [`std::time`]'s methods which return the current time, this 6 | //! crate has methods on [`SystemClock`] and [`MonotonicClock`]. 7 | 8 | pub use cap_primitives::time::{ 9 | Duration, Instant, MonotonicClock, SystemClock, SystemTime, SystemTimeError, 10 | }; 11 | -------------------------------------------------------------------------------- /cap-directories/COPYRIGHT: -------------------------------------------------------------------------------- 1 | Short version for non-lawyers: 2 | 3 | `cap-directories` is triple-licensed under Apache 2.0 with the LLVM Exception, 4 | Apache 2.0, and MIT terms. 5 | 6 | 7 | Longer version: 8 | 9 | Copyrights in the `cap-directories` project are retained by their contributors. 10 | No copyright assignment is required to contribute to the `cap-directories` 11 | project. 12 | 13 | Some files include code derived from Rust's `libstd`; see the comments in 14 | the code for details. 15 | 16 | Except as otherwise noted (below and/or in individual files), `cap-directories` 17 | is licensed under: 18 | 19 | - the Apache License, Version 2.0, with the LLVM Exception 20 | or 21 | 22 | - the Apache License, Version 2.0 23 | or 24 | , 25 | - or the MIT license 26 | or 27 | , 28 | 29 | at your option. 30 | -------------------------------------------------------------------------------- /cap-directories/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cap-directories" 3 | version = "3.4.4" 4 | description = "Capability-based standard directories for config, cache and other data" 5 | authors = [ 6 | "Dan Gohman ", 7 | "Jakub Konka ", 8 | ] 9 | license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" 10 | keywords = ["app_dirs", "path", "folder", "xdg", "basedir"] 11 | categories = ["filesystem"] 12 | repository = "https://github.com/bytecodealliance/cap-std" 13 | edition = "2021" 14 | 15 | [dependencies] 16 | cap-std = { path = "../cap-std", version = "^3.4.4" } 17 | directories-next = "2.0.0" 18 | 19 | [target.'cfg(not(windows))'.dependencies] 20 | rustix = { version = "1.0.0" } 21 | 22 | [target.'cfg(windows)'.dependencies.windows-sys] 23 | version = ">=0.52, <=0.59" 24 | features = [ 25 | "Win32_Foundation", 26 | ] 27 | -------------------------------------------------------------------------------- /cap-directories/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /cap-directories/README.md: -------------------------------------------------------------------------------- 1 |
2 |

cap-directories

3 | 4 |

5 | Capability-based standard directories for config, cache and other data 6 |

7 | 8 |

9 | Github Actions CI Status 10 | crates.io page 11 | docs.rs docs 12 |

13 |
14 | 15 | The `cap-directories` crate provides utilities for accessing standard 16 | directories via the [`directories-next`] crate, but which provide [`Dir`]s instead of 17 | `Path`s. 18 | 19 | [`directories-next`]: https://crates.io/crates/directories-next 20 | [`Dir`]: https://docs.rs/cap-std/latest/cap_std/fs/struct.Dir.html 21 | -------------------------------------------------------------------------------- /cap-directories/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Capability-based standard directories. 2 | 3 | #![deny(missing_docs)] 4 | #![forbid(unsafe_code)] 5 | #![doc( 6 | html_logo_url = "https://raw.githubusercontent.com/bytecodealliance/cap-std/main/media/cap-std.svg" 7 | )] 8 | #![doc( 9 | html_favicon_url = "https://raw.githubusercontent.com/bytecodealliance/cap-std/main/media/cap-std.ico" 10 | )] 11 | 12 | use std::io; 13 | 14 | mod project_dirs; 15 | mod user_dirs; 16 | 17 | #[doc(hidden)] 18 | pub use cap_std::ambient_authority_known_at_compile_time; 19 | pub use cap_std::{ambient_authority, AmbientAuthority}; 20 | pub use project_dirs::ProjectDirs; 21 | pub use user_dirs::UserDirs; 22 | 23 | #[cfg(not(windows))] 24 | pub(crate) fn not_found() -> io::Error { 25 | rustix::io::Errno::NOENT.into() 26 | } 27 | 28 | #[cfg(windows)] 29 | pub(crate) fn not_found() -> io::Error { 30 | io::Error::from_raw_os_error(windows_sys::Win32::Foundation::ERROR_PATH_NOT_FOUND as i32) 31 | } 32 | -------------------------------------------------------------------------------- /cap-fs-ext/COPYRIGHT: -------------------------------------------------------------------------------- 1 | Short version for non-lawyers: 2 | 3 | `cap-fs-ext` is triple-licensed under Apache 2.0 with the LLVM Exception, 4 | Apache 2.0, and MIT terms. 5 | 6 | 7 | Longer version: 8 | 9 | Copyrights in the `cap-fs-ext` project are retained by their contributors. 10 | No copyright assignment is required to contribute to the `cap-fs-ext` 11 | project. 12 | 13 | Some files include code derived from Rust's `libstd`; see the comments in 14 | the code for details. 15 | 16 | Except as otherwise noted (below and/or in individual files), `cap-fs-ext` 17 | is licensed under: 18 | 19 | - the Apache License, Version 2.0, with the LLVM Exception 20 | or 21 | 22 | - the Apache License, Version 2.0 23 | or 24 | , 25 | - or the MIT license 26 | or 27 | , 28 | 29 | at your option. 30 | -------------------------------------------------------------------------------- /cap-fs-ext/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cap-fs-ext" 3 | version = "3.4.4" 4 | description = "Extension traits for `Dir`, `File`, etc." 5 | authors = [ 6 | "Dan Gohman ", 7 | "Jakub Konka ", 8 | ] 9 | license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" 10 | keywords = ["filesystem"] 11 | categories = ["filesystem"] 12 | repository = "https://github.com/bytecodealliance/cap-std" 13 | edition = "2021" 14 | 15 | [dependencies] 16 | arf-strings = { version = "0.7.0", optional = true } 17 | cap-async-std = { path = "../cap-async-std", optional = true, version = "3.4.4" } 18 | cap-std = { path = "../cap-std", optional = true, version = "3.4.4" } 19 | cap-primitives = { path = "../cap-primitives", version = "3.4.4" } 20 | io-lifetimes = { version = "2.0.0", default-features = false } 21 | async-std = { version = "1.13.0", features = ["io_safety", "attributes"], optional = true } 22 | async-trait = { version = "0.1.42", optional = true } 23 | camino = { version = "1.0.5", optional = true } 24 | 25 | [features] 26 | default = ["std"] 27 | fs_utf8 = ["cap-std/fs_utf8", "camino"] 28 | arf_strings = ["cap-std/arf_strings", "fs_utf8", "arf-strings"] 29 | std = ["cap-std"] 30 | async_std = ["cap-async-std", "async-std", "io-lifetimes/async-std", "async-trait"] 31 | async_std_fs_utf8 = ["cap-async-std/fs_utf8", "camino"] 32 | async_std_arf_strings = ["cap-async-std/arf_strings", "async_std_fs_utf8", "arf-strings"] 33 | 34 | [target.'cfg(windows)'.dependencies.windows-sys] 35 | version = ">=0.52, <=0.59" 36 | features = [ 37 | "Win32_Storage_FileSystem", 38 | ] 39 | 40 | [dev-dependencies] 41 | cap-tempfile = { path = "../cap-tempfile" } 42 | -------------------------------------------------------------------------------- /cap-fs-ext/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /cap-fs-ext/README.md: -------------------------------------------------------------------------------- 1 |
2 |

cap-fs-ext

3 | 4 |

5 | Extension traits for `Dir`, `File`, etc. 6 |

7 | 8 |

9 | Github Actions CI Status 10 | crates.io page 11 | docs.rs docs 12 |

13 |
14 | 15 | The `cap-fs-ext` crate provides extension traits adding extra features 16 | to directory types such as [`Dir`], [`File`] and related types. 17 | 18 | [`Dir`]: https://docs.rs/cap-std/latest/cap_std/fs/struct.Dir.html 19 | [`File`]: https://docs.rs/cap-std/latest/cap_std/fs/struct.File.html 20 | -------------------------------------------------------------------------------- /cap-fs-ext/src/is_file_read_write.rs: -------------------------------------------------------------------------------- 1 | use cap_primitives::fs::is_file_read_write; 2 | use io_lifetimes::AsFilelike; 3 | use std::io; 4 | 5 | /// A trait for the `is_file_read_write` function for `File` types. 6 | /// 7 | /// This is only implemented for `File` types; for arbitrary I/O handles, use 8 | /// [`system_interface::io::IsReadWrite`] instead. 9 | /// 10 | /// [`system_interface::io::IsReadWrite`]: https://docs.rs/system-interface/latest/system_interface/io/trait.ReadReady.html 11 | pub trait IsFileReadWrite { 12 | /// Test whether the given file is readable and/or writable. 13 | fn is_file_read_write(&self) -> io::Result<(bool, bool)>; 14 | } 15 | 16 | impl IsFileReadWrite for std::fs::File { 17 | #[inline] 18 | fn is_file_read_write(&self) -> io::Result<(bool, bool)> { 19 | is_file_read_write(self) 20 | } 21 | } 22 | 23 | #[cfg(feature = "std")] 24 | impl IsFileReadWrite for cap_std::fs::File { 25 | #[inline] 26 | fn is_file_read_write(&self) -> io::Result<(bool, bool)> { 27 | is_file_read_write(&self.as_filelike_view::()) 28 | } 29 | } 30 | 31 | #[cfg(all(feature = "std", feature = "fs_utf8"))] 32 | impl IsFileReadWrite for cap_std::fs_utf8::File { 33 | #[inline] 34 | fn is_file_read_write(&self) -> io::Result<(bool, bool)> { 35 | is_file_read_write(&self.as_filelike_view::()) 36 | } 37 | } 38 | 39 | #[cfg(feature = "async_std")] 40 | impl IsFileReadWrite for async_std::fs::File { 41 | #[inline] 42 | fn is_file_read_write(&self) -> io::Result<(bool, bool)> { 43 | is_file_read_write(&self.as_filelike_view::()) 44 | } 45 | } 46 | 47 | #[cfg(feature = "async_std")] 48 | impl IsFileReadWrite for cap_async_std::fs::File { 49 | #[inline] 50 | fn is_file_read_write(&self) -> io::Result<(bool, bool)> { 51 | is_file_read_write(&self.as_filelike_view::()) 52 | } 53 | } 54 | 55 | #[cfg(all(feature = "async_std", feature = "fs_utf8"))] 56 | impl IsFileReadWrite for cap_async_std::fs_utf8::File { 57 | #[inline] 58 | fn is_file_read_write(&self) -> io::Result<(bool, bool)> { 59 | is_file_read_write(&self.as_filelike_view::()) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /cap-fs-ext/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Extension traits for `Dir` 2 | 3 | #![deny(missing_docs)] 4 | #![forbid(unsafe_code)] 5 | #![cfg_attr(all(windows, windows_by_handle), feature(windows_by_handle))] 6 | #![doc( 7 | html_logo_url = "https://raw.githubusercontent.com/bytecodealliance/cap-std/main/media/cap-std.svg" 8 | )] 9 | #![doc( 10 | html_favicon_url = "https://raw.githubusercontent.com/bytecodealliance/cap-std/main/media/cap-std.ico" 11 | )] 12 | 13 | mod dir_entry_ext; 14 | mod dir_ext; 15 | mod file_type_ext; 16 | mod is_file_read_write; 17 | mod metadata_ext; 18 | mod open_options_follow_ext; 19 | mod open_options_maybe_dir_ext; 20 | mod open_options_sync_ext; 21 | mod reopen; 22 | 23 | pub use dir_entry_ext::DirEntryExt; 24 | #[cfg(feature = "async_std")] 25 | pub use dir_ext::AsyncDirExt; 26 | #[cfg(all(feature = "async_std", feature = "fs_utf8"))] 27 | pub use dir_ext::AsyncDirExtUtf8; 28 | #[cfg(all(any(feature = "std", feature = "async_std"), feature = "fs_utf8"))] 29 | pub use dir_ext::DirExtUtf8; 30 | pub use dir_ext::{AccessType, DirExt, SystemTimeSpec}; 31 | pub use file_type_ext::FileTypeExt; 32 | pub use is_file_read_write::IsFileReadWrite; 33 | pub use metadata_ext::MetadataExt; 34 | pub use open_options_follow_ext::OpenOptionsFollowExt; 35 | pub use open_options_maybe_dir_ext::OpenOptionsMaybeDirExt; 36 | pub use open_options_sync_ext::OpenOptionsSyncExt; 37 | pub use reopen::Reopen; 38 | 39 | /// Re-export these to allow them to be used with `Reuse`. 40 | pub use cap_primitives::fs::{ 41 | FollowSymlinks, Metadata, MetadataExt as OsMetadataExt, OpenOptions, OpenOptionsExt, 42 | }; 43 | 44 | #[doc(hidden)] 45 | pub use cap_primitives::ambient_authority_known_at_compile_time; 46 | pub use cap_primitives::{ambient_authority, AmbientAuthority}; 47 | -------------------------------------------------------------------------------- /cap-fs-ext/src/open_options_follow_ext.rs: -------------------------------------------------------------------------------- 1 | use crate::FollowSymlinks; 2 | 3 | /// Extension trait for `cap_primitives::fs::OpenOptions` which adds 4 | /// `follow`, a function for controlling whether a symlink in the last 5 | /// component of a path is followed. 6 | pub trait OpenOptionsFollowExt { 7 | /// Sets the option for following symlinks in the last component of a path. 8 | /// 9 | /// This option, when set to `FollowSymlinks::Yes`, will indicate that a 10 | /// symbolic link in the last component of a path will be followed. When 11 | /// set to `FollowSymlinks::No`, it will indicate that attempting to 12 | /// resolve a path which ends in a symbolic link will fail. 13 | fn follow(&mut self, follow: FollowSymlinks) -> &mut Self; 14 | } 15 | 16 | impl OpenOptionsFollowExt for cap_primitives::fs::OpenOptions { 17 | #[inline] 18 | fn follow(&mut self, follow: FollowSymlinks) -> &mut Self { 19 | // `follow` functionality is implemented within `cap_primitives`; we're 20 | // just exposing it here since `OpenOptions` is re-exported by 21 | // `cap_std` etc. and `follow` isn't in `std`. 22 | self._cap_fs_ext_follow(follow) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /cap-fs-ext/src/open_options_maybe_dir_ext.rs: -------------------------------------------------------------------------------- 1 | /// Extension trait for `cap_primitives::fs::OpenOptions` which adds 2 | /// `maybe_dir`, a function for controlling whether an open should attempt to 3 | /// succeed on a directory. On Posix-ish platforms, opening a directory always 4 | /// succeeds, but on Windows, opening a directory needs this option. 5 | pub trait OpenOptionsMaybeDirExt { 6 | /// Sets the option for disabling an error that might be generated by the 7 | /// opened object being a directory. 8 | /// 9 | /// On some platforms, this may prevent the directory from being deleted 10 | /// or renamed while the handle is open. 11 | fn maybe_dir(&mut self, maybe_dir: bool) -> &mut Self; 12 | } 13 | 14 | impl OpenOptionsMaybeDirExt for cap_primitives::fs::OpenOptions { 15 | #[inline] 16 | fn maybe_dir(&mut self, maybe_dir: bool) -> &mut Self { 17 | // `maybe_dir` functionality is implemented within `cap_primitives`; 18 | // we're just exposing it here since `OpenOptions` is re-exported by 19 | // `cap_std` etc. and `maybe_dir` isn't in `std`. 20 | self._cap_fs_ext_maybe_dir(maybe_dir) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /cap-net-ext/COPYRIGHT: -------------------------------------------------------------------------------- 1 | Short version for non-lawyers: 2 | 3 | `cap-net-ext` is triple-licensed under Apache 2.0 with the LLVM Exception, 4 | Apache 2.0, and MIT terms. 5 | 6 | 7 | Longer version: 8 | 9 | Copyrights in the `cap-net-ext` project are retained by their contributors. 10 | No copyright assignment is required to contribute to the `cap-net-ext` 11 | project. 12 | 13 | Some files include code derived from Rust's `libstd`; see the comments in 14 | the code for details. 15 | 16 | Except as otherwise noted (below and/or in individual files), `cap-net-ext` 17 | is licensed under: 18 | 19 | - the Apache License, Version 2.0, with the LLVM Exception 20 | or 21 | 22 | - the Apache License, Version 2.0 23 | or 24 | , 25 | - or the MIT license 26 | or 27 | , 28 | 29 | at your option. 30 | -------------------------------------------------------------------------------- /cap-net-ext/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cap-net-ext" 3 | version = "3.4.4" 4 | description = "Extension traits for `TcpListener`, `Pool`, etc." 5 | authors = [ 6 | "Dan Gohman ", 7 | "Jakub Konka ", 8 | ] 9 | license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" 10 | keywords = ["filesystem"] 11 | categories = ["filesystem"] 12 | repository = "https://github.com/bytecodealliance/cap-std" 13 | edition = "2021" 14 | 15 | [dependencies] 16 | cap-std = { path = "../cap-std", version = "^3.4.4" } 17 | cap-primitives = { path = "../cap-primitives", version = "^3.4.4" } 18 | rustix = { version = "1.0.0", features = ["net"] } 19 | smallvec = "1.10" 20 | -------------------------------------------------------------------------------- /cap-net-ext/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /cap-net-ext/README.md: -------------------------------------------------------------------------------- 1 |
2 |

cap-net-ext

3 | 4 |

5 | Extension traits for `TcpListener`, `UdpSocket`, `Pool`, etc. 6 |

7 | 8 |

9 | Github Actions CI Status 10 | crates.io page 11 | docs.rs docs 12 |

13 |
14 | 15 | The `cap-net-ext` crate provides extension traits adding extra features 16 | to types such as [`TcpListener`], [`UdpSocket`], and [`Pool`]. 17 | 18 | It provides split-out operations corresponding to `socket`, `bind`, `listen`, 19 | `accept`, and `connect`, and it exposes more options for enabling non-blocking 20 | mode. 21 | 22 | [`TcpListener`]: https://docs.rs/cap-std/latest/cap_std/net/struct.TcpListener.html 23 | [`UdpSocket`]: https://docs.rs/cap-std/latest/cap_std/net/struct.UdpSocket.html 24 | [`Pool`]: https://docs.rs/cap-std/latest/cap_std/net/struct.Pool.html 25 | -------------------------------------------------------------------------------- /cap-primitives/COPYRIGHT: -------------------------------------------------------------------------------- 1 | Short version for non-lawyers: 2 | 3 | `cap-primitives` is triple-licensed under Apache 2.0 with the LLVM Exception, 4 | Apache 2.0, and MIT terms. 5 | 6 | 7 | Longer version: 8 | 9 | Copyrights in the `cap-primitives` project are retained by their contributors. 10 | No copyright assignment is required to contribute to the `cap-primitives` 11 | project. 12 | 13 | Some files include code derived from Rust's `libstd`; see the comments in 14 | the code for details. 15 | 16 | Except as otherwise noted (below and/or in individual files), `cap-primitives` 17 | is licensed under: 18 | 19 | - the Apache License, Version 2.0, with the LLVM Exception 20 | or 21 | 22 | - the Apache License, Version 2.0 23 | or 24 | , 25 | - or the MIT license 26 | or 27 | , 28 | 29 | at your option. 30 | -------------------------------------------------------------------------------- /cap-primitives/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cap-primitives" 3 | version = "3.4.4" 4 | description = "Capability-based primitives" 5 | authors = [ 6 | "Dan Gohman ", 7 | "Jakub Konka ", 8 | ] 9 | license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" 10 | keywords = ["api", "network", "file"] 11 | categories = ["filesystem", "network-programming"] 12 | repository = "https://github.com/bytecodealliance/cap-std" 13 | edition = "2021" 14 | 15 | [dependencies] 16 | ambient-authority = "0.0.2" 17 | arbitrary = { version = "1.0.0", optional = true, features = ["derive"] } 18 | ipnet = "2.5.0" 19 | maybe-owned = "0.3.4" 20 | fs-set-times = "0.20.0" 21 | io-extras = "0.18.3" 22 | io-lifetimes = { version = "2.0.0", default-features = false } 23 | 24 | [dev-dependencies] 25 | cap-tempfile = { path = "../cap-tempfile" } 26 | 27 | [target.'cfg(not(windows))'.dependencies] 28 | rustix = { version = "1.0.0", features = ["fs", "process", "termios", "time"] } 29 | 30 | [target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies] 31 | rustix-linux-procfs = "0.1.1" 32 | 33 | [target.'cfg(windows)'.dependencies] 34 | winx = "0.36.0" 35 | 36 | [target.'cfg(windows)'.dependencies.windows-sys] 37 | version = ">=0.52, <=0.59" 38 | features = [ 39 | "Win32_Foundation", 40 | "Win32_Security", 41 | "Win32_Storage_FileSystem", 42 | "Win32_System_Kernel", 43 | "Win32_System_WindowsProgramming", 44 | "Win32_System_IO", 45 | "Wdk_Storage_FileSystem", 46 | "Wdk_Foundation", 47 | ] 48 | -------------------------------------------------------------------------------- /cap-primitives/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /cap-primitives/README.md: -------------------------------------------------------------------------------- 1 |
2 |

cap-primitives

3 | 4 |

5 | Capability-based primitives 6 |

7 | 8 |

9 | Github Actions CI Status 10 | crates.io page 11 | docs.rs docs 12 |

13 |
14 | 15 | The `cap-primitives` crate provides primitive sandboxing operations that 16 | [`cap-std`] and [`cap-async-std`] are built on. 17 | 18 | The filesystem module [`cap_primitives::fs`], the networking module 19 | [`cap_primitives::net`], and time module [`cap_primitives::time`] currently 20 | support Linux, macOS, FreeBSD, and Windows. WASI support is in development, 21 | though not yet usable. 22 | 23 | [`cap-std`]: https://github.com/bytecodealliance/cap-std/blob/main/cap-std/README.md 24 | [`cap-async-std`]: https://github.com/bytecodealliance/cap-std/blob/main/cap-async-std/README.md 25 | [`cap_primitives::fs`]: https://docs.rs/cap-primitives/latest/cap_primitives/fs/index.html 26 | [`cap_primitives::net`]: https://docs.rs/cap-primitives/latest/cap_primitives/net/index.html 27 | [`cap_primitives::time`]: https://docs.rs/cap-primitives/latest/cap_primitives/time/index.html 28 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/copy.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::copy_impl; 2 | use std::path::Path; 3 | use std::{fs, io}; 4 | 5 | /// Copies the contents of one file to another. 6 | #[inline] 7 | pub fn copy( 8 | from_start: &fs::File, 9 | from_path: &Path, 10 | to_start: &fs::File, 11 | to_path: &Path, 12 | ) -> io::Result { 13 | // In theory we could do extra sanity checks here, but `copy_impl` 14 | // implementations use other sandboxed routines to open the files, 15 | // so it'd be mostly redundant. 16 | copy_impl(from_start, from_path, to_start, to_path) 17 | } 18 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/dir_options.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(target_os = "wasi"))] 2 | use crate::fs::DirOptionsExt; 3 | 4 | /// Options and flags which can be used to configure how a directory is 5 | /// created. 6 | /// 7 | /// This is to `create_dir` what to `OpenOptions` is to `open`. 8 | #[derive(Debug, Clone)] 9 | pub struct DirOptions { 10 | #[cfg(not(target_os = "wasi"))] 11 | #[allow(dead_code)] 12 | pub(crate) ext: DirOptionsExt, 13 | } 14 | 15 | impl DirOptions { 16 | /// Creates a blank new set of options ready for configuration. 17 | #[allow(clippy::new_without_default)] 18 | #[inline] 19 | pub const fn new() -> Self { 20 | Self { 21 | #[cfg(not(target_os = "wasi"))] 22 | ext: DirOptionsExt::new(), 23 | } 24 | } 25 | } 26 | 27 | #[cfg(unix)] 28 | impl crate::fs::DirBuilderExt for DirOptions { 29 | #[inline] 30 | fn mode(&mut self, mode: u32) -> &mut Self { 31 | self.ext.mode(mode); 32 | self 33 | } 34 | } 35 | 36 | #[cfg(target_os = "vxworks")] 37 | impl crate::fs::DirBuilderExt for DirOptions { 38 | #[inline] 39 | fn mode(&mut self, mode: u32) -> &mut Self { 40 | self.ext.mode(mode); 41 | self 42 | } 43 | } 44 | 45 | #[cfg(feature = "arbitrary")] 46 | impl arbitrary::Arbitrary<'_> for DirOptions { 47 | fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { 48 | #[cfg(any(unix, target_os = "vxworks"))] 49 | use crate::fs::DirBuilderExt; 50 | 51 | #[allow(unused_mut)] 52 | let mut dir_options = Self::new(); 53 | 54 | #[cfg(any(unix, target_os = "vxworks"))] 55 | dir_options.mode(u.int_in_range(0..=0o777)?); 56 | 57 | // Unix is currently the only platform with a `DirBuilderExt`. 58 | #[cfg(not(any(unix, target_os = "vxworks")))] 59 | let _ = u; 60 | 61 | Ok(dir_options) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/errors.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | #[cfg(not(windows))] 4 | pub(crate) use crate::rustix::fs::errors::*; 5 | #[cfg(windows)] 6 | pub(crate) use crate::windows::fs::errors::*; 7 | 8 | #[cold] 9 | pub(crate) fn escape_attempt() -> io::Error { 10 | io::Error::new( 11 | io::ErrorKind::PermissionDenied, 12 | "a path led outside of the filesystem", 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/file_path_by_searching.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::{ 2 | is_root_dir, open_dir_unchecked, read_dir_unchecked, FollowSymlinks, MaybeOwnedFile, Metadata, 3 | }; 4 | use std::fs; 5 | use std::path::{Component, PathBuf}; 6 | 7 | /// Implementation of `file_path` for directories by opening `..` and searching 8 | /// for a directory among `..`'s children to find its name. 9 | pub(crate) fn file_path_by_searching(file: &fs::File) -> Option { 10 | // Use the `_noassert` functions because the asserts depend on `file_path`, 11 | // which is what we're implementing here. 12 | let mut base = MaybeOwnedFile::borrowed_noassert(file); 13 | let mut components = Vec::new(); 14 | 15 | // Iterate with `..` until we reach the root directory. 16 | 'next_component: loop { 17 | // Open `..`. 18 | let mut iter = 19 | read_dir_unchecked(&base, Component::ParentDir.as_ref(), FollowSymlinks::No).ok()?; 20 | let metadata = Metadata::from_file(&*base).ok()?; 21 | 22 | // Search the children until we find one with matching metadata, and 23 | // then record its name. 24 | while let Some(child) = iter.next() { 25 | let child = child.ok()?; 26 | if child.is_same_file(&metadata).ok()? { 27 | // Found a match. Record the name and continue to the next component. 28 | components.push(child.file_name()); 29 | base = MaybeOwnedFile::owned_noassert( 30 | open_dir_unchecked(&base, Component::ParentDir.as_ref()).ok()?, 31 | ); 32 | continue 'next_component; 33 | } 34 | } 35 | 36 | // We didn't find the directory among its parent's children. If we're at 37 | // the root directory, we're done. 38 | if is_root_dir(&base, &iter).ok()? { 39 | break; 40 | } 41 | 42 | // Otherwise, something went wrong and we can't determine the path. 43 | return None; 44 | } 45 | 46 | let mut path = PathBuf::new(); 47 | path.push(Component::RootDir); 48 | for component in components.iter().rev() { 49 | path.push(component); 50 | } 51 | Some(path) 52 | } 53 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/follow_symlinks.rs: -------------------------------------------------------------------------------- 1 | /// Should symlinks be followed in the last component of a path? 2 | /// 3 | /// This doesn't affect path components other than the last. So for example in 4 | /// "foo/bar/baz", if "foo" or "bar" are symlinks, they will always be 5 | /// followed. This enum value only determines whether "baz" is followed. 6 | /// 7 | /// Instead of passing bare `bool`s as parameters, pass a distinct enum so that 8 | /// the intent is clear. 9 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 10 | #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] 11 | pub enum FollowSymlinks { 12 | /// Yes, do follow symlinks in the last component of a path. 13 | Yes, 14 | 15 | /// No, do not follow symlinks in the last component of a path. 16 | No, 17 | } 18 | 19 | impl FollowSymlinks { 20 | /// Convert a bool where true means "follow" and false means "don't follow" 21 | /// to a `FollowSymlinks`. 22 | #[inline] 23 | pub const fn follow(follow: bool) -> Self { 24 | if follow { 25 | Self::Yes 26 | } else { 27 | Self::No 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/is_file_read_write.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::is_file_read_write_impl; 2 | use std::{fs, io}; 3 | 4 | /// Return a pair of booleans indicating whether the given file is opened 5 | /// for reading and writing, respectively. 6 | #[inline] 7 | pub fn is_file_read_write(file: &fs::File) -> io::Result<(bool, bool)> { 8 | is_file_read_write_impl(file) 9 | } 10 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/manually/cow_component.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::ffi::OsStr; 3 | use std::path::Component; 4 | 5 | /// Like `std::path::Component` except we combine `Prefix` and `RootDir` since 6 | /// we don't support absolute paths, and `Normal` has a `Cow` instead of a 7 | /// plain `OsStr` reference, so it can optionally own its own string. 8 | pub(super) enum CowComponent<'borrow> { 9 | PrefixOrRootDir, 10 | CurDir, 11 | ParentDir, 12 | Normal(Cow<'borrow, OsStr>), 13 | } 14 | 15 | impl<'borrow> CowComponent<'borrow> { 16 | /// Convert a `Component` into a `CowComponent` which borrows strings. 17 | pub(super) fn borrowed(component: Component<'borrow>) -> Self { 18 | match component { 19 | Component::Prefix(_) | Component::RootDir => Self::PrefixOrRootDir, 20 | Component::CurDir => Self::CurDir, 21 | Component::ParentDir => Self::ParentDir, 22 | Component::Normal(os_str) => Self::Normal(os_str.into()), 23 | } 24 | } 25 | 26 | /// Convert a `Component` into a `CowComponent` which owns strings. 27 | pub(super) fn owned(component: Component) -> Self { 28 | match component { 29 | Component::Prefix(_) | Component::RootDir => Self::PrefixOrRootDir, 30 | Component::CurDir => Self::CurDir, 31 | Component::ParentDir => Self::ParentDir, 32 | Component::Normal(os_str) => Self::Normal(os_str.to_os_string().into()), 33 | } 34 | } 35 | 36 | /// Test whether `self` is `Component::Normal`. 37 | #[cfg(windows)] 38 | pub(super) fn is_normal(&self) -> bool { 39 | match self { 40 | CowComponent::Normal(_) => true, 41 | _ => false, 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/manually/mod.rs: -------------------------------------------------------------------------------- 1 | //! Functions that perform path lookup manually, one component 2 | //! at a time, with manual symlink resolution. 3 | 4 | mod canonical_path; 5 | mod canonicalize; 6 | mod cow_component; 7 | mod open; 8 | #[cfg(not(any(windows, target_os = "freebsd")))] 9 | mod open_entry; 10 | mod read_link_one; 11 | 12 | use canonical_path::CanonicalPath; 13 | use cow_component::CowComponent; 14 | use open::internal_open; 15 | use read_link_one::read_link_one; 16 | 17 | #[cfg(racy_asserts)] 18 | pub(super) use canonicalize::canonicalize_with; 19 | 20 | pub(crate) use canonicalize::canonicalize; 21 | pub(crate) use open::{open, stat}; 22 | #[cfg(not(any(windows, target_os = "freebsd")))] 23 | pub(crate) use open_entry::open_entry; 24 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/manually/open_entry.rs: -------------------------------------------------------------------------------- 1 | use super::{internal_open, read_link_one}; 2 | use crate::fs::{open_unchecked, FollowSymlinks, MaybeOwnedFile, OpenOptions, OpenUncheckedError}; 3 | use std::ffi::OsStr; 4 | use std::path::PathBuf; 5 | use std::{fs, io}; 6 | 7 | pub(crate) fn open_entry( 8 | start: &fs::File, 9 | path: &OsStr, 10 | options: &OpenOptions, 11 | ) -> io::Result { 12 | match open_unchecked( 13 | start, 14 | path.as_ref(), 15 | options.clone().follow(FollowSymlinks::No), 16 | ) { 17 | Ok(file) => Ok(file), 18 | Err(OpenUncheckedError::Symlink(_, _)) if options.follow == FollowSymlinks::Yes => { 19 | let mut symlink_count = 0; 20 | let destination = read_link_one(start, path, &mut symlink_count, PathBuf::new())?; 21 | let maybe = MaybeOwnedFile::borrowed(start); 22 | internal_open(maybe, &destination, options, &mut symlink_count, None) 23 | .map(MaybeOwnedFile::unwrap_owned) 24 | } 25 | Err(OpenUncheckedError::NotFound(err)) 26 | | Err(OpenUncheckedError::Other(err)) 27 | | Err(OpenUncheckedError::Symlink(err, _)) => Err(err), 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/manually/read_link_one.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::{errors, read_link_unchecked, MAX_SYMLINK_EXPANSIONS}; 2 | use std::ffi::OsStr; 3 | use std::path::{Path, PathBuf}; 4 | use std::{fs, io}; 5 | 6 | /// This is a wrapper around `read_link_unchecked` which performs a single 7 | /// symlink expansion on a single path component, and which enforces the 8 | /// recursion limit. 9 | pub(super) fn read_link_one( 10 | base: &fs::File, 11 | name: &OsStr, 12 | symlink_count: &mut u8, 13 | reuse: PathBuf, 14 | ) -> io::Result { 15 | let name: &Path = name.as_ref(); 16 | assert!( 17 | name.as_os_str().is_empty() || name.file_name().is_some(), 18 | "read_link_one expects a single normal path component, got '{}'", 19 | name.display() 20 | ); 21 | assert!( 22 | name.as_os_str().is_empty() || name.parent().unwrap().as_os_str().is_empty(), 23 | "read_link_one expects a single normal path component, got '{}'", 24 | name.display() 25 | ); 26 | 27 | if *symlink_count == MAX_SYMLINK_EXPANSIONS { 28 | return Err(errors::too_many_symlinks()); 29 | } 30 | 31 | let destination = read_link_unchecked(base, name, reuse)?; 32 | 33 | *symlink_count += 1; 34 | 35 | Ok(destination) 36 | } 37 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/open_ambient.rs: -------------------------------------------------------------------------------- 1 | //! This defines `open_ambient`, for unsandboxed file opening. 2 | 3 | use crate::fs::{open_ambient_impl, OpenOptions}; 4 | use crate::AmbientAuthority; 5 | use std::path::Path; 6 | use std::{fs, io}; 7 | 8 | /// Open a file named by a bare path, using the host process' ambient 9 | /// authority. 10 | /// 11 | /// # Ambient Authority 12 | /// 13 | /// This function is not sandboxed and may trivially access any path that the 14 | /// host process has access to. 15 | #[inline] 16 | pub fn open_ambient( 17 | path: &Path, 18 | options: &OpenOptions, 19 | ambient_authority: AmbientAuthority, 20 | ) -> io::Result { 21 | Ok(open_ambient_impl(path, options, ambient_authority)?) 22 | } 23 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/open_unchecked_error.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | #[derive(Debug)] 4 | pub(crate) enum OpenUncheckedError { 5 | Other(io::Error), 6 | Symlink(io::Error, SymlinkKind), 7 | NotFound(io::Error), 8 | } 9 | 10 | #[cfg(not(windows))] 11 | pub(crate) type SymlinkKind = (); 12 | 13 | #[cfg(windows)] 14 | #[derive(Debug)] 15 | pub(crate) enum SymlinkKind { 16 | File, 17 | Dir, 18 | } 19 | 20 | impl OpenUncheckedError { 21 | #[allow(dead_code)] 22 | pub(crate) fn kind(&self) -> io::ErrorKind { 23 | match self { 24 | Self::Other(err) | Self::Symlink(err, _) | Self::NotFound(err) => err.kind(), 25 | } 26 | } 27 | } 28 | 29 | impl From for io::Error { 30 | fn from(error: OpenUncheckedError) -> Self { 31 | match error { 32 | OpenUncheckedError::Other(err) 33 | | OpenUncheckedError::Symlink(err, _) 34 | | OpenUncheckedError::NotFound(err) => err, 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/read_dir.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::{DirEntry, FollowSymlinks, ReadDirInner}; 2 | use std::path::Path; 3 | use std::{fmt, fs, io}; 4 | 5 | /// Construct a `ReadDir` to iterate over the contents of a directory, 6 | /// ensuring that the resolution of the path never escapes the directory 7 | /// tree rooted at `start`. 8 | #[inline] 9 | pub fn read_dir(start: &fs::File, path: &Path) -> io::Result { 10 | Ok(ReadDir { 11 | inner: ReadDirInner::new(start, path, FollowSymlinks::Yes)?, 12 | }) 13 | } 14 | 15 | /// Like `read_dir`, but fails if `path` names a symlink. 16 | #[inline] 17 | #[cfg(not(windows))] 18 | pub(crate) fn read_dir_nofollow(start: &fs::File, path: &Path) -> io::Result { 19 | Ok(ReadDir { 20 | inner: ReadDirInner::new(start, path, FollowSymlinks::No)?, 21 | }) 22 | } 23 | 24 | /// Like `read_dir` but operates on the base directory itself, rather than 25 | /// on a path based on it. 26 | #[inline] 27 | pub fn read_base_dir(start: &fs::File) -> io::Result { 28 | Ok(ReadDir { 29 | inner: ReadDirInner::read_base_dir(start)?, 30 | }) 31 | } 32 | 33 | /// Like `read_dir`, but doesn't perform sandboxing. 34 | #[inline] 35 | #[cfg(not(windows))] 36 | pub(crate) fn read_dir_unchecked( 37 | start: &fs::File, 38 | path: &Path, 39 | follow: FollowSymlinks, 40 | ) -> io::Result { 41 | Ok(ReadDir { 42 | inner: ReadDirInner::new_unchecked(start, path, follow)?, 43 | }) 44 | } 45 | 46 | /// Iterator over the entries in a directory. 47 | /// 48 | /// This corresponds to [`std::fs::ReadDir`]. 49 | /// 50 | /// There is no `from_std` method, as `std::fs::ReadDir` doesn't provide a way 51 | /// to construct a `ReadDir` without opening directories by ambient paths. 52 | pub struct ReadDir { 53 | pub(crate) inner: ReadDirInner, 54 | } 55 | 56 | impl Iterator for ReadDir { 57 | type Item = io::Result; 58 | 59 | #[inline] 60 | fn next(&mut self) -> Option { 61 | self.inner 62 | .next() 63 | .map(|inner| inner.map(|inner| DirEntry { inner })) 64 | } 65 | } 66 | 67 | impl fmt::Debug for ReadDir { 68 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 69 | self.inner.fmt(f) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/remove_dir_all.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::remove_dir_all_impl; 2 | use std::path::Path; 3 | use std::{fs, io}; 4 | 5 | /// Removes a directory and all of its contents. 6 | #[inline] 7 | pub fn remove_dir_all(start: &fs::File, path: &Path) -> io::Result<()> { 8 | remove_dir_all_impl(start, path) 9 | } 10 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/remove_open_dir.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::{remove_open_dir_all_impl, remove_open_dir_impl}; 2 | use std::{fs, io}; 3 | 4 | /// Given an open directory handle, delete the directory. 5 | #[inline] 6 | pub fn remove_open_dir(dir: fs::File) -> io::Result<()> { 7 | remove_open_dir_impl(dir) 8 | } 9 | 10 | /// Given an open directory handle, recursively delete the contents of the 11 | /// directory plus the directory itself. 12 | #[allow(clippy::module_name_repetitions)] 13 | #[inline] 14 | pub fn remove_open_dir_all(dir: fs::File) -> io::Result<()> { 15 | remove_open_dir_all_impl(dir) 16 | } 17 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/reopen.rs: -------------------------------------------------------------------------------- 1 | //! Re-open a `fs::File` to produce an independent handle. 2 | 3 | use crate::fs::{is_file_read_write, is_same_file, reopen_impl, OpenOptions}; 4 | use std::{fs, io}; 5 | 6 | /// Re-open an `fs::File` to produce an independent handle. 7 | /// 8 | /// This operation isn't supported by all operating systems in all 9 | /// circumstances, or in some operating systems in any circumstances, 10 | /// so it may return an `io::ErrorKind::Other` error if the file 11 | /// cannot be reopened. 12 | #[inline] 13 | pub fn reopen(file: &fs::File, options: &OpenOptions) -> io::Result { 14 | let (read, write) = is_file_read_write(file)?; 15 | 16 | // Don't grant more rights than the original file had. And don't allow 17 | // it to create a file. 18 | if options.create 19 | || options.create_new 20 | || (!read && options.read) 21 | || (!write && (options.write || options.append || options.truncate)) 22 | { 23 | return Err(io::Error::new( 24 | io::ErrorKind::PermissionDenied, 25 | "Couldn't reopen file", 26 | )); 27 | } 28 | 29 | let new = reopen_impl(file, options)?; 30 | 31 | if !is_same_file(file, &new)? { 32 | return Err(io::Error::new(io::ErrorKind::Other, "Couldn't reopen file")); 33 | } 34 | 35 | Ok(new) 36 | } 37 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/set_times.rs: -------------------------------------------------------------------------------- 1 | //! This defines `set_times`, the primary entrypoint to sandboxed 2 | //! filesystem times modification. 3 | //! 4 | //! TODO: `check_set_times` etc. 5 | 6 | use crate::fs::{set_times_impl, set_times_nofollow_impl, SystemTimeSpec}; 7 | use std::path::Path; 8 | use std::{fs, io}; 9 | 10 | /// Perform a `utimensat`-like operation, ensuring that the resolution of the 11 | /// path never escapes the directory tree rooted at `start`. This function 12 | /// follows symlinks. 13 | #[inline] 14 | pub fn set_times( 15 | start: &fs::File, 16 | path: &Path, 17 | atime: Option, 18 | mtime: Option, 19 | ) -> io::Result<()> { 20 | set_times_impl(start, path, atime, mtime) 21 | } 22 | 23 | /// Like `set_times`, but never follows symlinks. 24 | #[inline] 25 | pub fn set_times_nofollow( 26 | start: &fs::File, 27 | path: &Path, 28 | atime: Option, 29 | mtime: Option, 30 | ) -> io::Result<()> { 31 | set_times_nofollow_impl(start, path, atime, mtime) 32 | } 33 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/system_time_spec.rs: -------------------------------------------------------------------------------- 1 | use crate::time::SystemTime; 2 | 3 | /// A value for specifying a time. 4 | #[derive(Debug)] 5 | #[allow(clippy::module_name_repetitions)] 6 | pub enum SystemTimeSpec { 7 | /// A value which always represents the current time, in symbolic form, so 8 | /// that even as time elapses, it continues to represent the current time. 9 | SymbolicNow, 10 | 11 | /// An absolute time value. 12 | Absolute(SystemTime), 13 | } 14 | 15 | impl SystemTimeSpec { 16 | /// Constructs a new instance of `Self` from the given 17 | /// [`fs_set_times::SystemTimeSpec`]. 18 | // TODO: Make this a `const fn` once `SystemTime::from_std` is a `const fn`. 19 | #[inline] 20 | pub fn from_std(std: fs_set_times::SystemTimeSpec) -> Self { 21 | match std { 22 | fs_set_times::SystemTimeSpec::SymbolicNow => Self::SymbolicNow, 23 | fs_set_times::SystemTimeSpec::Absolute(time) => { 24 | Self::Absolute(SystemTime::from_std(time)) 25 | } 26 | } 27 | } 28 | 29 | /// Constructs a new instance of [`fs_set_times::SystemTimeSpec`] from the 30 | /// given `Self`. 31 | #[inline] 32 | pub const fn into_std(self) -> fs_set_times::SystemTimeSpec { 33 | match self { 34 | Self::SymbolicNow => fs_set_times::SystemTimeSpec::SymbolicNow, 35 | Self::Absolute(time) => fs_set_times::SystemTimeSpec::Absolute(time.into_std()), 36 | } 37 | } 38 | } 39 | 40 | impl From for SystemTimeSpec { 41 | #[inline] 42 | fn from(time: SystemTime) -> Self { 43 | Self::Absolute(time) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/via_parent/access.rs: -------------------------------------------------------------------------------- 1 | use super::open_parent; 2 | use crate::fs::{access_unchecked, AccessType, FollowSymlinks, MaybeOwnedFile}; 3 | use std::path::Path; 4 | use std::{fs, io}; 5 | 6 | /// Implement `access` by `open`ing up the parent component of the path and 7 | /// then calling `access_unchecked` on the last component. 8 | pub(crate) fn access( 9 | start: &fs::File, 10 | path: &Path, 11 | type_: AccessType, 12 | follow: FollowSymlinks, 13 | ) -> io::Result<()> { 14 | let start = MaybeOwnedFile::borrowed(start); 15 | 16 | let (dir, basename) = open_parent(start, path)?; 17 | 18 | access_unchecked(&dir, basename.as_ref(), type_, follow) 19 | } 20 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/via_parent/create_dir.rs: -------------------------------------------------------------------------------- 1 | use super::open_parent; 2 | use crate::fs::{create_dir_unchecked, strip_dir_suffix, DirOptions, MaybeOwnedFile}; 3 | use std::path::Path; 4 | use std::{fs, io}; 5 | 6 | /// Implement `create_dir` by `open`ing up the parent component of the path and 7 | /// then calling `create_dir_unchecked` on the last component. 8 | pub(crate) fn create_dir(start: &fs::File, path: &Path, options: &DirOptions) -> io::Result<()> { 9 | let start = MaybeOwnedFile::borrowed(start); 10 | 11 | // As a special case, `create_dir` ignores a trailing slash rather than 12 | // treating it as equivalent to a trailing slash-dot, so strip any trailing 13 | // slashes. 14 | let path = strip_dir_suffix(path); 15 | 16 | let (dir, basename) = open_parent(start, &path)?; 17 | 18 | create_dir_unchecked(&dir, basename.as_ref(), options) 19 | } 20 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/via_parent/hard_link.rs: -------------------------------------------------------------------------------- 1 | use super::open_parent; 2 | use crate::fs::{hard_link_unchecked, MaybeOwnedFile}; 3 | use std::path::Path; 4 | use std::{fs, io}; 5 | 6 | /// Implement `hard_link` by `open`ing up the parent component of the path and 7 | /// then calling `hard_link_unchecked` on the last component. 8 | pub(crate) fn hard_link( 9 | old_start: &fs::File, 10 | old_path: &Path, 11 | new_start: &fs::File, 12 | new_path: &Path, 13 | ) -> io::Result<()> { 14 | let old_start = MaybeOwnedFile::borrowed(old_start); 15 | let new_start = MaybeOwnedFile::borrowed(new_start); 16 | 17 | let (old_dir, old_basename) = open_parent(old_start, old_path)?; 18 | let (new_dir, new_basename) = open_parent(new_start, new_path)?; 19 | 20 | hard_link_unchecked( 21 | &old_dir, 22 | old_basename.as_ref(), 23 | &new_dir, 24 | new_basename.as_ref(), 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/via_parent/mod.rs: -------------------------------------------------------------------------------- 1 | //! In many operations, the last component of a path is special. For example, 2 | //! in `create_dir`, the last component names the path to be created, while the 3 | //! rest of the components just name the place to create it in. 4 | 5 | mod access; 6 | mod create_dir; 7 | mod hard_link; 8 | mod open_parent; 9 | #[cfg(not(windows))] // doesn't work on windows; use a windows-specific impl 10 | mod read_link; 11 | mod remove_dir; 12 | mod remove_file; 13 | mod rename; 14 | #[cfg(windows)] 15 | mod set_permissions; 16 | #[cfg(not(target_os = "wasi"))] 17 | mod set_symlink_permissions; 18 | #[cfg(not(windows))] 19 | mod set_times_nofollow; 20 | mod symlink; 21 | 22 | use open_parent::open_parent; 23 | 24 | pub(crate) use access::access; 25 | pub(crate) use create_dir::create_dir; 26 | pub(crate) use hard_link::hard_link; 27 | #[cfg(not(windows))] // doesn't work on windows; use a windows-specific impl 28 | pub(crate) use read_link::read_link; 29 | pub(crate) use remove_dir::remove_dir; 30 | pub(crate) use remove_file::remove_file; 31 | pub(crate) use rename::rename; 32 | #[cfg(windows)] 33 | pub(crate) use set_permissions::set_permissions; 34 | #[cfg(not(target_os = "wasi"))] 35 | pub(crate) use set_symlink_permissions::set_symlink_permissions; 36 | #[cfg(not(windows))] 37 | pub(crate) use set_times_nofollow::set_times_nofollow; 38 | #[cfg(not(windows))] 39 | pub(crate) use symlink::symlink; 40 | #[cfg(windows)] 41 | pub(crate) use symlink::{symlink_dir, symlink_file}; 42 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/via_parent/read_link.rs: -------------------------------------------------------------------------------- 1 | use super::open_parent; 2 | use crate::fs::{read_link_unchecked, MaybeOwnedFile}; 3 | use std::path::{Path, PathBuf}; 4 | use std::{fs, io}; 5 | 6 | /// Implement `read_link` by `open`ing up the parent component of the path and 7 | /// then calling `read_link_unchecked` on the last component. 8 | /// 9 | /// This technique doesn't work in all cases on Windows. In particular, a 10 | /// directory symlink such as `C:\Documents and Settings` may not grant any 11 | /// access other than what is needed to resolve the symlink, so `open_parent`'s 12 | /// technique of returning a relative path of `.` from that point doesn't work, 13 | /// because opening `.` within such a directory is denied. Consequently, we use 14 | /// a different implementation on Windows. 15 | pub(crate) fn read_link(start: &fs::File, path: &Path) -> io::Result { 16 | let start = MaybeOwnedFile::borrowed(start); 17 | 18 | let (dir, basename) = open_parent(start, path)?; 19 | 20 | read_link_unchecked(&dir, basename.as_ref(), PathBuf::new()) 21 | } 22 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/via_parent/remove_dir.rs: -------------------------------------------------------------------------------- 1 | use super::open_parent; 2 | use crate::fs::{remove_dir_unchecked, MaybeOwnedFile}; 3 | use std::path::Path; 4 | use std::{fs, io}; 5 | 6 | /// Implement `remove_dir` by `open`ing up the parent component of the path and 7 | /// then calling `remove_dir_unchecked` on the last component. 8 | pub(crate) fn remove_dir(start: &fs::File, path: &Path) -> io::Result<()> { 9 | let start = MaybeOwnedFile::borrowed(start); 10 | 11 | let (dir, basename) = open_parent(start, path)?; 12 | 13 | remove_dir_unchecked(&dir, basename.as_ref()) 14 | } 15 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/via_parent/remove_file.rs: -------------------------------------------------------------------------------- 1 | use super::open_parent; 2 | use crate::fs::{remove_file_unchecked, MaybeOwnedFile}; 3 | use std::path::Path; 4 | use std::{fs, io}; 5 | 6 | /// Implement `remove_file` by `open`ing up the parent component of the path 7 | /// and then calling `remove_file_unchecked` on the last component. 8 | pub(crate) fn remove_file(start: &fs::File, path: &Path) -> io::Result<()> { 9 | let start = MaybeOwnedFile::borrowed(start); 10 | 11 | let (dir, basename) = open_parent(start, path)?; 12 | 13 | remove_file_unchecked(&dir, basename.as_ref()) 14 | } 15 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/via_parent/rename.rs: -------------------------------------------------------------------------------- 1 | use super::open_parent; 2 | #[cfg(unix)] 3 | use crate::fs::{append_dir_suffix, path_has_trailing_slash}; 4 | use crate::fs::{rename_unchecked, strip_dir_suffix, MaybeOwnedFile}; 5 | use std::path::Path; 6 | use std::{fs, io}; 7 | 8 | /// Implement `rename` by `open`ing up the parent component of the path and 9 | /// then calling `rename_unchecked` on the last component. 10 | pub(crate) fn rename( 11 | old_start: &fs::File, 12 | old_path: &Path, 13 | new_start: &fs::File, 14 | new_path: &Path, 15 | ) -> io::Result<()> { 16 | let old_start = MaybeOwnedFile::borrowed(old_start); 17 | let new_start = MaybeOwnedFile::borrowed(new_start); 18 | 19 | // As a special case, `rename` ignores a trailing slash rather than treating 20 | // it as equivalent to a trailing slash-dot, so strip any trailing slashes 21 | // for the purposes of `open_parent`. 22 | // 23 | // And on Unix, remember whether the source started with a slash so that we 24 | // can still fail if it is and the source is a regular file. 25 | #[cfg(unix)] 26 | let old_starts_with_slash = path_has_trailing_slash(old_path); 27 | let old_path = strip_dir_suffix(old_path); 28 | let new_path = strip_dir_suffix(new_path); 29 | 30 | let (old_dir, old_basename) = open_parent(old_start, &old_path)?; 31 | let (new_dir, new_basename) = open_parent(new_start, &new_path)?; 32 | 33 | // On Unix, re-append a slash if needed. 34 | #[cfg(unix)] 35 | let concat; 36 | #[cfg(unix)] 37 | let old_basename = if old_starts_with_slash { 38 | concat = append_dir_suffix(old_basename.to_owned().into()); 39 | concat.as_os_str() 40 | } else { 41 | old_basename 42 | }; 43 | 44 | rename_unchecked( 45 | &old_dir, 46 | old_basename.as_ref(), 47 | &new_dir, 48 | new_basename.as_ref(), 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/via_parent/set_permissions.rs: -------------------------------------------------------------------------------- 1 | use super::open_parent; 2 | use crate::fs::{set_permissions_unchecked, MaybeOwnedFile, Permissions}; 3 | use std::path::Path; 4 | use std::{fs, io}; 5 | 6 | #[inline] 7 | pub(crate) fn set_permissions(start: &fs::File, path: &Path, perm: Permissions) -> io::Result<()> { 8 | let start = MaybeOwnedFile::borrowed(start); 9 | 10 | let (dir, basename) = open_parent(start, &path)?; 11 | 12 | set_permissions_unchecked(&dir, basename.as_ref(), perm) 13 | } 14 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/via_parent/set_symlink_permissions.rs: -------------------------------------------------------------------------------- 1 | use super::open_parent; 2 | use crate::fs::{set_symlink_permissions_unchecked, MaybeOwnedFile, Permissions}; 3 | use std::path::Path; 4 | use std::{fs, io}; 5 | 6 | #[inline] 7 | pub(crate) fn set_symlink_permissions( 8 | start: &fs::File, 9 | path: &Path, 10 | perm: Permissions, 11 | ) -> io::Result<()> { 12 | let start = MaybeOwnedFile::borrowed(start); 13 | 14 | let (dir, basename) = open_parent(start, path)?; 15 | 16 | set_symlink_permissions_unchecked(&dir, basename.as_ref(), perm) 17 | } 18 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/via_parent/set_times_nofollow.rs: -------------------------------------------------------------------------------- 1 | use super::open_parent; 2 | use crate::fs::{set_times_nofollow_unchecked, MaybeOwnedFile, SystemTimeSpec}; 3 | use std::path::Path; 4 | use std::{fs, io}; 5 | 6 | #[inline] 7 | pub(crate) fn set_times_nofollow( 8 | start: &fs::File, 9 | path: &Path, 10 | atime: Option, 11 | mtime: Option, 12 | ) -> io::Result<()> { 13 | let start = MaybeOwnedFile::borrowed(start); 14 | 15 | let (dir, basename) = open_parent(start, path)?; 16 | 17 | set_times_nofollow_unchecked(&dir, basename.as_ref(), atime, mtime) 18 | } 19 | -------------------------------------------------------------------------------- /cap-primitives/src/fs/via_parent/symlink.rs: -------------------------------------------------------------------------------- 1 | use super::open_parent; 2 | use crate::fs::MaybeOwnedFile; 3 | use std::path::Path; 4 | use std::{fs, io}; 5 | 6 | /// Implement `symlink` by `open`ing up the parent component of the path and 7 | /// then calling `symlink_unchecked` on the last component. 8 | #[cfg(not(windows))] 9 | pub(crate) fn symlink(old_path: &Path, new_start: &fs::File, new_path: &Path) -> io::Result<()> { 10 | use crate::fs::symlink_unchecked; 11 | let new_start = MaybeOwnedFile::borrowed(new_start); 12 | 13 | let (new_dir, new_basename) = open_parent(new_start, new_path)?; 14 | 15 | symlink_unchecked(old_path, &new_dir, new_basename.as_ref()) 16 | } 17 | 18 | /// Implement `symlink_file` by `open`ing up the parent component of the path 19 | /// and then calling `symlink_file` on the last component. 20 | #[cfg(windows)] 21 | pub(crate) fn symlink_file( 22 | old_path: &Path, 23 | new_start: &fs::File, 24 | new_path: &Path, 25 | ) -> io::Result<()> { 26 | use crate::fs::symlink_file_unchecked; 27 | let new_start = MaybeOwnedFile::borrowed(new_start); 28 | 29 | let (new_dir, new_basename) = open_parent(new_start, new_path)?; 30 | 31 | symlink_file_unchecked(old_path, &new_dir, new_basename.as_ref()) 32 | } 33 | 34 | /// Implement `symlink_dir` by `open`ing up the parent component of the path 35 | /// and then calling `symlink_dir` on the last component. 36 | #[cfg(windows)] 37 | pub(crate) fn symlink_dir( 38 | old_path: &Path, 39 | new_start: &fs::File, 40 | new_path: &Path, 41 | ) -> io::Result<()> { 42 | use crate::fs::symlink_dir_unchecked; 43 | let new_start = MaybeOwnedFile::borrowed(new_start); 44 | 45 | let (new_dir, new_basename) = open_parent(new_start, new_path)?; 46 | 47 | symlink_dir_unchecked(old_path, &new_dir, new_basename.as_ref()) 48 | } 49 | -------------------------------------------------------------------------------- /cap-primitives/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Capability-based primitives. 2 | 3 | #![deny(missing_docs)] 4 | #![deny(unsafe_code)] 5 | #![allow(stable_features)] 6 | #![cfg_attr(target_os = "wasi", feature(wasi_ext))] 7 | #![cfg_attr(all(windows, windows_by_handle), feature(windows_by_handle))] 8 | #![cfg_attr(all(windows, windows_file_type_ext), feature(windows_file_type_ext))] 9 | #![doc( 10 | html_logo_url = "https://raw.githubusercontent.com/bytecodealliance/cap-std/main/media/cap-std.svg" 11 | )] 12 | #![doc( 13 | html_favicon_url = "https://raw.githubusercontent.com/bytecodealliance/cap-std/main/media/cap-std.ico" 14 | )] 15 | #![cfg_attr(io_lifetimes_use_std, feature(io_safety))] 16 | #![cfg_attr(io_error_more, feature(io_error_more))] 17 | #![cfg_attr(io_error_uncategorized, feature(io_error_uncategorized))] 18 | 19 | #[cfg(not(windows))] 20 | mod rustix; 21 | #[cfg(windows)] 22 | mod windows; 23 | 24 | pub mod fs; 25 | pub mod net; 26 | pub mod time; 27 | 28 | #[doc(hidden)] 29 | pub use ambient_authority::ambient_authority_known_at_compile_time; 30 | pub use ambient_authority::{ambient_authority, AmbientAuthority}; 31 | // This is part of our public API. 32 | pub use ipnet; 33 | -------------------------------------------------------------------------------- /cap-primitives/src/net/mod.rs: -------------------------------------------------------------------------------- 1 | //! Networking utilities. 2 | 3 | mod pool; 4 | 5 | pub use pool::*; 6 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/darwin/fs/file_path.rs: -------------------------------------------------------------------------------- 1 | //! `get_path` translation code for macOS derived from Rust's 2 | //! library/std/src/sys/unix/fs.rs at revision 3 | //! 108e90ca78f052c0c1c49c42a22c85620be19712. 4 | 5 | use crate::rustix::fs::file_path_by_ttyname_or_seaching; 6 | use rustix::fs::getpath; 7 | use std::ffi::OsString; 8 | use std::fs; 9 | #[cfg(unix)] 10 | use std::os::unix::ffi::OsStringExt; 11 | #[cfg(target_os = "wasi")] 12 | use std::os::wasi::ffi::OsStringExt; 13 | use std::path::PathBuf; 14 | 15 | pub(crate) fn file_path(file: &fs::File) -> Option { 16 | if let Ok(path) = getpath(file) { 17 | return Some(OsString::from_vec(path.into_bytes()).into()); 18 | } 19 | 20 | file_path_by_ttyname_or_seaching(file) 21 | } 22 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/darwin/fs/mod.rs: -------------------------------------------------------------------------------- 1 | mod file_path; 2 | 3 | pub(crate) use file_path::*; 4 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/darwin/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod fs; 2 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/freebsd/fs/check.rs: -------------------------------------------------------------------------------- 1 | use rustix::cstr; 2 | use rustix::fs::{openat, statat, AtFlags, Mode, OFlags, CWD}; 3 | use rustix::io::Errno; 4 | use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; 5 | 6 | static WORKING: AtomicBool = AtomicBool::new(false); 7 | static CHECKED: AtomicBool = AtomicBool::new(false); 8 | 9 | #[inline] 10 | pub(crate) fn beneath_supported() -> bool { 11 | if WORKING.load(Relaxed) { 12 | return true; 13 | } 14 | if CHECKED.load(Relaxed) { 15 | return false; 16 | } 17 | check_beneath_supported() 18 | } 19 | 20 | #[cold] 21 | fn check_beneath_supported() -> bool { 22 | // `RESOLVE_BENEATH` was introduced in FreeBSD 13, but opening `..` within 23 | // the root directory re-opened the root directory. In FreeBSD 14, it fails 24 | // as cap-std expects. 25 | if let Ok(root) = openat( 26 | CWD, 27 | cstr!("/"), 28 | OFlags::RDONLY | OFlags::CLOEXEC, 29 | Mode::empty(), 30 | ) { 31 | // Unknown O_ flags get ignored but AT_ flags have strict checks, so we use that. 32 | if let Err(Errno::NOTCAPABLE) = statat(root, cstr!(".."), AtFlags::RESOLVE_BENEATH) { 33 | WORKING.store(true, Relaxed); 34 | return true; 35 | } 36 | } 37 | 38 | CHECKED.store(true, Relaxed); 39 | false 40 | } 41 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/freebsd/fs/mod.rs: -------------------------------------------------------------------------------- 1 | mod check; 2 | mod open_entry_impl; 3 | mod open_impl; 4 | mod remove_dir_impl; 5 | mod remove_file_impl; 6 | mod set_permissions_impl; 7 | mod set_times_impl; 8 | mod stat_impl; 9 | 10 | pub(crate) use crate::fs::manually::canonicalize as canonicalize_impl; 11 | pub(crate) use check::beneath_supported; 12 | pub(crate) use open_entry_impl::open_entry_impl; 13 | pub(crate) use open_impl::open_impl; 14 | pub(crate) use remove_dir_impl::remove_dir_impl; 15 | pub(crate) use remove_file_impl::remove_file_impl; 16 | pub(crate) use set_permissions_impl::set_permissions_impl; 17 | pub(crate) use set_times_impl::{set_times_impl, set_times_nofollow_impl}; 18 | pub(crate) use stat_impl::stat_impl; 19 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/freebsd/fs/open_entry_impl.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::{open_impl, OpenOptions}; 2 | use std::ffi::OsStr; 3 | use std::{fs, io}; 4 | 5 | #[inline(always)] 6 | pub(crate) fn open_entry_impl( 7 | start: &fs::File, 8 | path: &OsStr, 9 | options: &OpenOptions, 10 | ) -> io::Result { 11 | open_impl(start, path.as_ref(), options) 12 | } 13 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/freebsd/fs/open_impl.rs: -------------------------------------------------------------------------------- 1 | use super::super::super::fs::compute_oflags; 2 | use crate::fs::{errors, manually, OpenOptions}; 3 | use io_lifetimes::FromFd; 4 | use rustix::fs::{openat, Mode, OFlags, RawMode}; 5 | use std::path::Path; 6 | use std::{fs, io}; 7 | 8 | pub(crate) fn open_impl( 9 | start: &fs::File, 10 | path: &Path, 11 | options: &OpenOptions, 12 | ) -> io::Result { 13 | if !super::beneath_supported() { 14 | return manually::open(start, path, options); 15 | } 16 | 17 | let oflags = compute_oflags(options)? | OFlags::RESOLVE_BENEATH; 18 | 19 | let mode = if oflags.contains(OFlags::CREATE) { 20 | Mode::from_bits((options.ext.mode & 0o7777) as RawMode).unwrap() 21 | } else { 22 | Mode::empty() 23 | }; 24 | 25 | match openat(start, path, oflags, mode) { 26 | Ok(file) => Ok(fs::File::from_into_fd(file)), 27 | Err(rustix::io::Errno::NOTCAPABLE) => Err(errors::escape_attempt()), 28 | Err(err) => Err(err.into()), 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/freebsd/fs/remove_dir_impl.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::via_parent; 2 | use rustix::fs::{unlinkat, AtFlags}; 3 | use std::path::Path; 4 | use std::{fs, io}; 5 | 6 | pub(crate) fn remove_dir_impl(start: &fs::File, path: &Path) -> io::Result<()> { 7 | if !super::beneath_supported() { 8 | return via_parent::remove_dir(start, path); 9 | } 10 | 11 | Ok(unlinkat( 12 | start, 13 | path, 14 | AtFlags::RESOLVE_BENEATH | AtFlags::REMOVEDIR, 15 | )?) 16 | } 17 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/freebsd/fs/remove_file_impl.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::via_parent; 2 | use rustix::fs::{unlinkat, AtFlags}; 3 | use std::path::Path; 4 | use std::{fs, io}; 5 | 6 | pub(crate) fn remove_file_impl(start: &fs::File, path: &Path) -> io::Result<()> { 7 | if !super::beneath_supported() { 8 | return via_parent::remove_file(start, path); 9 | } 10 | 11 | Ok(unlinkat(start, path, AtFlags::RESOLVE_BENEATH)?) 12 | } 13 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/freebsd/fs/set_permissions_impl.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::{Permissions, PermissionsExt}; 2 | use rustix::fs::{chmodat, AtFlags, Mode}; 3 | use std::path::Path; 4 | use std::{fs, io}; 5 | 6 | pub(crate) fn set_permissions_impl( 7 | start: &fs::File, 8 | path: &Path, 9 | perm: Permissions, 10 | ) -> io::Result<()> { 11 | if !super::beneath_supported() { 12 | return super::super::super::fs::set_permissions_manually(start, path, perm); 13 | } 14 | 15 | Ok(chmodat( 16 | start, 17 | path, 18 | Mode::from_raw_mode(perm.mode() as _), 19 | AtFlags::RESOLVE_BENEATH, 20 | )?) 21 | } 22 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/freebsd/fs/set_times_impl.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::{to_timespec, via_parent, SystemTimeSpec}; 2 | use rustix::fs::{utimensat, AtFlags, Timestamps}; 3 | use std::path::Path; 4 | use std::{fs, io}; 5 | 6 | pub(crate) fn set_times_impl( 7 | start: &fs::File, 8 | path: &Path, 9 | atime: Option, 10 | mtime: Option, 11 | ) -> io::Result<()> { 12 | if !super::beneath_supported() { 13 | return super::super::super::fs::set_times_manually(start, path, atime, mtime); 14 | } 15 | 16 | let times = Timestamps { 17 | last_access: to_timespec(atime)?, 18 | last_modification: to_timespec(mtime)?, 19 | }; 20 | 21 | Ok(utimensat(start, path, ×, AtFlags::RESOLVE_BENEATH)?) 22 | } 23 | 24 | pub(crate) fn set_times_nofollow_impl( 25 | start: &fs::File, 26 | path: &Path, 27 | atime: Option, 28 | mtime: Option, 29 | ) -> io::Result<()> { 30 | if !super::beneath_supported() { 31 | return via_parent::set_times_nofollow(start, path, atime, mtime); 32 | } 33 | 34 | let times = Timestamps { 35 | last_access: to_timespec(atime)?, 36 | last_modification: to_timespec(mtime)?, 37 | }; 38 | 39 | Ok(utimensat( 40 | start, 41 | path, 42 | ×, 43 | AtFlags::RESOLVE_BENEATH | AtFlags::SYMLINK_NOFOLLOW, 44 | )?) 45 | } 46 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/freebsd/fs/stat_impl.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::{manually, FollowSymlinks, ImplMetadataExt, Metadata}; 2 | use rustix::fs::{statat, AtFlags}; 3 | use std::path::Path; 4 | use std::{fs, io}; 5 | 6 | pub(crate) fn stat_impl( 7 | start: &fs::File, 8 | path: &Path, 9 | follow: FollowSymlinks, 10 | ) -> io::Result { 11 | if !super::beneath_supported() { 12 | return manually::stat(start, path, follow); 13 | } 14 | 15 | let flags = AtFlags::RESOLVE_BENEATH 16 | | if follow == FollowSymlinks::Yes { 17 | AtFlags::empty() 18 | } else { 19 | AtFlags::SYMLINK_NOFOLLOW 20 | }; 21 | Ok(ImplMetadataExt::from_rustix(statat(start, path, flags)?)) 22 | } 23 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/freebsd/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod fs; 2 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/access_unchecked.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::{AccessType, FollowSymlinks}; 2 | use rustix::fs::{Access, AtFlags}; 3 | use std::path::Path; 4 | use std::{fs, io}; 5 | 6 | /// *Unsandboxed* function similar to `access`, but which does not perform 7 | /// sandboxing. 8 | pub(crate) fn access_unchecked( 9 | start: &fs::File, 10 | path: &Path, 11 | type_: AccessType, 12 | follow: FollowSymlinks, 13 | ) -> io::Result<()> { 14 | let mut access_type = Access::empty(); 15 | match type_ { 16 | AccessType::Exists => access_type |= Access::EXISTS, 17 | AccessType::Access(modes) => { 18 | if modes.readable { 19 | access_type |= Access::READ_OK; 20 | } 21 | if modes.writable { 22 | access_type |= Access::WRITE_OK; 23 | } 24 | if modes.executable { 25 | access_type |= Access::EXEC_OK; 26 | } 27 | } 28 | } 29 | 30 | let atflags = match follow { 31 | FollowSymlinks::Yes => AtFlags::empty(), 32 | FollowSymlinks::No => AtFlags::SYMLINK_NOFOLLOW, 33 | }; 34 | 35 | rustix::fs::accessat(start, path, access_type, atflags)?; 36 | 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/create_dir_unchecked.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::DirOptions; 2 | use rustix::fs::{mkdirat, Mode, RawMode}; 3 | use std::path::Path; 4 | use std::{fs, io}; 5 | 6 | /// *Unsandboxed* function similar to `create_dir`, but which does not perform 7 | /// sandboxing. 8 | pub(crate) fn create_dir_unchecked( 9 | start: &fs::File, 10 | path: &Path, 11 | options: &DirOptions, 12 | ) -> io::Result<()> { 13 | #[cfg(not(target_os = "wasi"))] 14 | let raw_mode = options.ext.mode as RawMode; 15 | #[cfg(target_os = "wasi")] 16 | let raw_mode = 0; 17 | 18 | Ok(mkdirat(start, path, Mode::from_bits(raw_mode).unwrap())?) 19 | } 20 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/cvt.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | #[allow(dead_code)] 4 | pub(crate) fn cvt_i32(t: i32) -> io::Result { 5 | if t == -1 { 6 | Err(io::Error::last_os_error()) 7 | } else { 8 | Ok(t) 9 | } 10 | } 11 | 12 | #[allow(dead_code)] 13 | pub(crate) fn cvt_i64(t: i64) -> io::Result { 14 | if t == -1 { 15 | Err(io::Error::last_os_error()) 16 | } else { 17 | Ok(t) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/dir_options_ext.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone)] 2 | pub(crate) struct DirOptionsExt { 3 | pub(super) mode: u32, 4 | } 5 | 6 | impl DirOptionsExt { 7 | pub(crate) const fn new() -> Self { 8 | Self { 9 | // The default value; see 10 | // 11 | mode: 0o777, 12 | } 13 | } 14 | } 15 | 16 | impl crate::fs::DirBuilderExt for DirOptionsExt { 17 | fn mode(&mut self, mode: u32) -> &mut Self { 18 | self.mode = mode; 19 | self 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/errors.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | #[cfg(any(target_os = "android", target_os = "linux"))] 4 | #[cold] 5 | pub(crate) fn invalid_flags() -> io::Error { 6 | rustix::io::Errno::INVAL.into() 7 | } 8 | 9 | #[cold] 10 | pub(crate) fn no_such_file_or_directory() -> io::Error { 11 | rustix::io::Errno::NOENT.into() 12 | } 13 | 14 | #[cold] 15 | pub(crate) fn is_directory() -> io::Error { 16 | rustix::io::Errno::ISDIR.into() 17 | } 18 | 19 | #[cold] 20 | pub(crate) fn is_not_directory() -> io::Error { 21 | rustix::io::Errno::NOTDIR.into() 22 | } 23 | 24 | #[cold] 25 | pub(crate) fn too_many_symlinks() -> io::Error { 26 | rustix::io::Errno::LOOP.into() 27 | } 28 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/file_path.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::file_path_by_searching; 2 | #[cfg(not(any(target_os = "wasi", target_os = "fuchsia")))] 3 | use rustix::termios::ttyname; 4 | #[cfg(not(any(target_os = "wasi", target_os = "fuchsia")))] 5 | use std::ffi::OsString; 6 | use std::fs; 7 | #[cfg(unix)] 8 | use std::os::unix::ffi::OsStringExt; 9 | use std::path::PathBuf; 10 | 11 | pub(crate) fn file_path_by_ttyname_or_seaching(file: &fs::File) -> Option { 12 | // If it happens to be a tty, we can look up its name. 13 | #[cfg(not(any(target_os = "wasi", target_os = "fuchsia")))] 14 | if let Ok(name) = ttyname(file, Vec::new()) { 15 | return Some(OsString::from_vec(name.into_bytes()).into()); 16 | } 17 | 18 | file_path_by_searching(file) 19 | } 20 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/hard_link_unchecked.rs: -------------------------------------------------------------------------------- 1 | use rustix::fs::{linkat, AtFlags}; 2 | use std::path::Path; 3 | use std::{fs, io}; 4 | 5 | /// *Unsandboxed* function similar to `hard_link`, but which does not perform 6 | /// sandboxing. 7 | /// 8 | /// Even though POSIX `linkat` has the ability to follow symlinks in 9 | /// `old_path`, using `AT_SYMLINK_FOLLOW`, Rust's `hard_link` doesn't need 10 | /// that, so we don't expose it here. 11 | pub(crate) fn hard_link_unchecked( 12 | old_start: &fs::File, 13 | old_path: &Path, 14 | new_start: &fs::File, 15 | new_path: &Path, 16 | ) -> io::Result<()> { 17 | Ok(linkat( 18 | old_start, 19 | old_path, 20 | new_start, 21 | new_path, 22 | AtFlags::empty(), 23 | )?) 24 | } 25 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/is_file_read_write_impl.rs: -------------------------------------------------------------------------------- 1 | use rustix::fd::{AsFd, BorrowedFd}; 2 | use rustix::fs::{fcntl_getfl, OFlags}; 3 | use std::{fs, io}; 4 | 5 | #[inline] 6 | pub(crate) fn is_file_read_write_impl(file: &fs::File) -> io::Result<(bool, bool)> { 7 | Ok(is_file_read_write(file)?) 8 | } 9 | 10 | /// `fcntl(fd, F_GETFL) & O_ACCMODE` 11 | /// 12 | /// Returns a pair of booleans indicating whether the file descriptor is 13 | /// readable and/or writable, respectively. This is only reliable on files; for 14 | /// example, it doesn't reflect whether sockets have been shut down; for 15 | /// general I/O handle support, use [`io::is_read_write`]. 16 | #[inline] 17 | fn is_file_read_write(fd: Fd) -> io::Result<(bool, bool)> { 18 | _is_file_read_write(fd.as_fd()) 19 | } 20 | 21 | fn _is_file_read_write(fd: BorrowedFd<'_>) -> io::Result<(bool, bool)> { 22 | let mode = fcntl_getfl(fd)?; 23 | 24 | // Check for `O_PATH`. 25 | #[cfg(any( 26 | target_os = "linux", 27 | target_os = "android", 28 | target_os = "emscripten", 29 | target_os = "fuchsia" 30 | ))] 31 | if mode.contains(OFlags::PATH) { 32 | return Ok((false, false)); 33 | } 34 | 35 | // Use `RWMODE` rather than `ACCMODE` as `ACCMODE` may include `O_PATH`. 36 | // We handled `O_PATH` above. 37 | match mode & OFlags::RWMODE { 38 | OFlags::RDONLY => Ok((true, false)), 39 | OFlags::RDWR => Ok((true, true)), 40 | OFlags::WRONLY => Ok((false, true)), 41 | _ => unreachable!(), 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/is_root_dir.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::{Metadata, ReadDir}; 2 | use std::{fs, io}; 3 | 4 | pub(crate) fn is_root_dir(dir: &fs::File, parent_iter: &ReadDir) -> io::Result { 5 | Ok(Metadata::from_file(dir)?.is_same_file(&parent_iter.inner.self_metadata()?)) 6 | } 7 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/is_same_file.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::{Metadata, MetadataExt}; 2 | use std::{fs, io}; 3 | 4 | /// Determine if `a` and `b` refer to the same inode on the same device. 5 | pub(crate) fn is_same_file(a: &fs::File, b: &fs::File) -> io::Result { 6 | let a_metadata = Metadata::from_file(a)?; 7 | let b_metadata = Metadata::from_file(b)?; 8 | is_same_file_metadata(&a_metadata, &b_metadata) 9 | } 10 | 11 | /// Determine if `a` and `b` are metadata for the same inode on the same 12 | /// device. 13 | pub(crate) fn is_same_file_metadata(a: &Metadata, b: &Metadata) -> io::Result { 14 | Ok(a.dev() == b.dev() && a.ino() == b.ino()) 15 | } 16 | 17 | /// Determine if `a` and `b` definitely refer to different inodes. 18 | /// 19 | /// This is similar to `is_same_file`, but is conservative, and doesn't depend 20 | /// on nightly-only features. 21 | #[allow(dead_code)] 22 | pub(crate) fn is_different_file(a: &fs::File, b: &fs::File) -> io::Result { 23 | is_same_file(a, b).map(|same| !same) 24 | } 25 | 26 | /// Determine if `a` and `b` are metadata for definitely different inodes. 27 | /// 28 | /// This is similar to `is_same_file_metadata`, but is conservative, and 29 | /// doesn't depend on nightly-only features. 30 | #[allow(dead_code)] 31 | pub(crate) fn is_different_file_metadata(a: &Metadata, b: &Metadata) -> io::Result { 32 | is_same_file_metadata(a, b).map(|same| !same) 33 | } 34 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/open_options_ext.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone)] 2 | pub(crate) struct ImplOpenOptionsExt { 3 | pub(crate) mode: u32, 4 | pub(crate) custom_flags: i32, 5 | } 6 | 7 | impl ImplOpenOptionsExt { 8 | pub(crate) const fn new() -> Self { 9 | Self { 10 | mode: 0o666, 11 | custom_flags: 0, 12 | } 13 | } 14 | 15 | pub(crate) fn mode(&mut self, mode: u32) -> &mut Self { 16 | self.mode = mode; 17 | self 18 | } 19 | 20 | pub(crate) fn custom_flags(&mut self, flags: i32) -> &mut Self { 21 | self.custom_flags = flags; 22 | self 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/permissions_ext.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::Permissions; 2 | use rustix::fs::RawMode; 3 | use std::fs; 4 | 5 | #[derive(Debug, Clone, Eq, PartialEq)] 6 | pub(crate) struct ImplPermissionsExt { 7 | #[cfg(not(target_os = "wasi"))] 8 | mode: RawMode, 9 | } 10 | 11 | #[cfg(not(target_os = "wasi"))] 12 | impl ImplPermissionsExt { 13 | /// Constructs a new instance of `Self` from the given 14 | /// [`std::fs::Permissions`]. 15 | #[inline] 16 | pub(crate) fn from_std(std: fs::Permissions) -> Self { 17 | use std::os::unix::fs::PermissionsExt; 18 | Self { 19 | mode: std.mode() as RawMode, 20 | } 21 | } 22 | 23 | /// Constructs a new instance of `Permissions` from the given 24 | /// `RawMode`. 25 | #[inline] 26 | pub(crate) const fn from_raw_mode(mode: RawMode) -> Permissions { 27 | Permissions { 28 | readonly: Self::readonly(mode), 29 | ext: Self { mode }, 30 | } 31 | } 32 | 33 | /// Test whether the given `RawMode` lacks write permissions. 34 | #[inline] 35 | pub(crate) const fn readonly(mode: RawMode) -> bool { 36 | mode & 0o222 == 0 37 | } 38 | 39 | /// Test whether the given `RawMode` lacks write permissions. 40 | #[inline] 41 | pub(crate) fn set_readonly(&mut self, readonly: bool) { 42 | if readonly { 43 | // remove write permission for all classes; equivalent to `chmod a-w ` 44 | self.mode &= !0o222; 45 | } else { 46 | // add write permission for all classes; equivalent to `chmod a+w ` 47 | self.mode |= 0o222; 48 | } 49 | } 50 | } 51 | 52 | #[cfg(not(target_os = "wasi"))] 53 | impl crate::fs::PermissionsExt for ImplPermissionsExt { 54 | fn mode(&self) -> u32 { 55 | self.mode as u32 56 | } 57 | 58 | fn set_mode(&mut self, mode: u32) { 59 | self.mode = mode as RawMode & 0o7777; 60 | } 61 | 62 | fn from_mode(mode: u32) -> Self { 63 | Self { 64 | mode: mode as RawMode & 0o7777, 65 | } 66 | } 67 | } 68 | 69 | #[cfg(target_os = "wasi")] 70 | impl ImplPermissionsExt { 71 | pub(crate) fn default() -> Permissions { 72 | Permissions { readonly: false } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/read_link_unchecked.rs: -------------------------------------------------------------------------------- 1 | use rustix::fs::readlinkat; 2 | use std::ffi::OsString; 3 | #[cfg(unix)] 4 | use std::os::unix::ffi::OsStringExt; 5 | #[cfg(target_os = "wasi")] 6 | use std::os::wasi::ffi::OsStringExt; 7 | use std::path::{Path, PathBuf}; 8 | use std::{fs, io}; 9 | 10 | /// *Unsandboxed* function similar to `read_link`, but which does not perform 11 | /// sandboxing. 12 | pub(crate) fn read_link_unchecked( 13 | start: &fs::File, 14 | path: &Path, 15 | reuse: PathBuf, 16 | ) -> io::Result { 17 | Ok(readlinkat(start, path, reuse.into_os_string().into_vec()) 18 | .map(|path| OsString::from_vec(path.into_bytes()).into())?) 19 | } 20 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/remove_dir_all_impl.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::{ 2 | read_dir_nofollow, read_dir_unchecked, remove_dir, remove_file, remove_open_dir, stat, 3 | FollowSymlinks, ReadDir, 4 | }; 5 | use std::path::{Component, Path}; 6 | use std::{fs, io}; 7 | 8 | pub(crate) fn remove_dir_all_impl(start: &fs::File, path: &Path) -> io::Result<()> { 9 | // Code adapted from `remove_dir_all` in Rust's 10 | // library/std/src/sys_common/fs.rs at revision 11 | // 108e90ca78f052c0c1c49c42a22c85620be19712. 12 | let filetype = stat(start, path, FollowSymlinks::No)?.file_type(); 13 | if filetype.is_symlink() { 14 | remove_file(start, path) 15 | } else { 16 | remove_dir_all_recursive(read_dir_nofollow(start, path)?)?; 17 | remove_dir(start, path) 18 | } 19 | } 20 | 21 | pub(crate) fn remove_open_dir_all_impl(dir: fs::File) -> io::Result<()> { 22 | remove_dir_all_recursive(read_dir_unchecked( 23 | &dir, 24 | Component::CurDir.as_ref(), 25 | FollowSymlinks::No, 26 | )?)?; 27 | remove_open_dir(dir) 28 | } 29 | 30 | fn remove_dir_all_recursive(children: ReadDir) -> io::Result<()> { 31 | for child in children { 32 | let child = child?; 33 | if child.file_type()?.is_dir() { 34 | remove_dir_all_recursive(child.inner.read_dir(FollowSymlinks::No)?)?; 35 | child.remove_dir()?; 36 | } else { 37 | child.remove_file()?; 38 | } 39 | } 40 | Ok(()) 41 | } 42 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/remove_dir_unchecked.rs: -------------------------------------------------------------------------------- 1 | use rustix::fs::{unlinkat, AtFlags}; 2 | use std::path::Path; 3 | use std::{fs, io}; 4 | 5 | /// *Unsandboxed* function similar to `remove_dir`, but which does not perform 6 | /// sandboxing. 7 | pub(crate) fn remove_dir_unchecked(start: &fs::File, path: &Path) -> io::Result<()> { 8 | Ok(unlinkat(start, path, AtFlags::REMOVEDIR)?) 9 | } 10 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/remove_file_unchecked.rs: -------------------------------------------------------------------------------- 1 | use rustix::fs::{unlinkat, AtFlags}; 2 | use std::path::Path; 3 | use std::{fs, io}; 4 | 5 | /// *Unsandboxed* function similar to `remove_file`, but which does not perform 6 | /// sandboxing. 7 | pub(crate) fn remove_file_unchecked(start: &fs::File, path: &Path) -> io::Result<()> { 8 | Ok(unlinkat(start, path, AtFlags::empty())?) 9 | } 10 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/remove_open_dir_by_searching.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::{errors, is_root_dir, read_dir_unchecked, FollowSymlinks, Metadata}; 2 | use std::path::Component; 3 | use std::{fs, io}; 4 | 5 | /// Delete the directory referenced by the given handle by searching for it in 6 | /// its `..`. This requires search permission in `..`, but that's usually 7 | /// available. 8 | pub(crate) fn remove_open_dir_by_searching(dir: fs::File) -> io::Result<()> { 9 | let metadata = Metadata::from_file(&dir)?; 10 | let mut iter = read_dir_unchecked(&dir, Component::ParentDir.as_ref(), FollowSymlinks::No)?; 11 | while let Some(child) = iter.next() { 12 | let child = child?; 13 | 14 | // Test if the child we found by iteration matches the directory we're 15 | // looking for. Ignore `NotFound` errors, which can happen if another 16 | // process removes a different directory in the same parent. 17 | let same = match child.is_same_file(&metadata) { 18 | Ok(same) => same, 19 | Err(err) if err.kind() == std::io::ErrorKind::NotFound => false, 20 | Err(err) => Err(err)?, 21 | }; 22 | 23 | if same { 24 | return child.remove_dir(); 25 | } 26 | } 27 | 28 | // We didn't find the directory among its parent's children. Check for the 29 | // root directory and handle it specially -- removal will probably fail, so 30 | // we'll get the appropriate error code. 31 | if is_root_dir(&dir, &iter)? { 32 | fs::remove_dir(Component::RootDir.as_os_str()) 33 | } else { 34 | Err(errors::no_such_file_or_directory()) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/rename_unchecked.rs: -------------------------------------------------------------------------------- 1 | use rustix::fs::renameat; 2 | use std::path::Path; 3 | use std::{fs, io}; 4 | 5 | /// *Unsandboxed* function similar to `rename`, but which does not perform 6 | /// sandboxing. 7 | pub(crate) fn rename_unchecked( 8 | old_start: &fs::File, 9 | old_path: &Path, 10 | new_start: &fs::File, 11 | new_path: &Path, 12 | ) -> io::Result<()> { 13 | Ok(renameat(old_start, old_path, new_start, new_path)?) 14 | } 15 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/reopen_impl.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::{open_unchecked, OpenOptions}; 2 | use crate::rustix::fs::file_path; 3 | use io_lifetimes::AsFilelike; 4 | use rustix::fs::CWD; 5 | use std::{fs, io}; 6 | 7 | /// Implementation of `reopen`. 8 | pub(crate) fn reopen_impl(file: &fs::File, options: &OpenOptions) -> io::Result { 9 | if let Some(path) = file_path(file) { 10 | Ok(open_unchecked( 11 | &CWD.as_filelike_view::(), 12 | &path, 13 | options, 14 | )?) 15 | } else { 16 | Err(io::Error::new(io::ErrorKind::Other, "Couldn't reopen file")) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/set_permissions_impl.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::{open, OpenOptions, Permissions}; 2 | use rustix::fs::{fchmod, Mode}; 3 | use rustix::io::Errno; 4 | #[cfg(unix)] 5 | use std::os::unix::fs::PermissionsExt; 6 | use std::path::Path; 7 | use std::{fs, io}; 8 | 9 | /// This sounds like it should be a job for `fchmodat`, however `fchmodat` 10 | /// handles symlinks in an incompatible way. It either follows symlinks 11 | /// without guaranteeing to stay in the sandbox, or with `AT_SYMLINK_NOFOLLOW` 12 | /// it attempts to change the permissions of symlinks themselves. What we'd 13 | /// need is for it to fail if it encounters a symlink, like `O_NOFOLLOW` does. 14 | pub(crate) fn set_permissions_impl( 15 | start: &fs::File, 16 | path: &Path, 17 | perm: Permissions, 18 | ) -> io::Result<()> { 19 | let std_perm = perm.into_std(start)?; 20 | 21 | // Try `fchmod` with a normal handle. Normal handles need some kind of 22 | // access, so first try read. 23 | match open(start, path, OpenOptions::new().read(true)) { 24 | Ok(file) => return set_file_permissions(&file, std_perm), 25 | Err(err) => match Errno::from_io_error(&err) { 26 | Some(Errno::ACCESS) => (), 27 | _ => return Err(err), 28 | }, 29 | } 30 | 31 | // Next try write. 32 | match open(start, path, OpenOptions::new().write(true)) { 33 | Ok(file) => return set_file_permissions(&file, std_perm), 34 | Err(err) => match Errno::from_io_error(&err) { 35 | Some(Errno::ACCESS) | Some(Errno::ISDIR) => (), 36 | _ => return Err(err), 37 | }, 38 | } 39 | 40 | // If neither of those worked, we're out of luck. 41 | Err(Errno::NOTSUP.into()) 42 | } 43 | 44 | pub(crate) fn set_file_permissions(file: &fs::File, perm: fs::Permissions) -> io::Result<()> { 45 | // Use `from_bits_truncate` for compatibility with std, which allows 46 | // non-permission bits to propagate through. 47 | let mode = Mode::from_bits_truncate(perm.mode().try_into().unwrap()); 48 | Ok(fchmod(file, mode)?) 49 | } 50 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/set_symlink_permissions_unchecked.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::Permissions; 2 | #[cfg(unix)] 3 | use crate::fs::PermissionsExt; 4 | use rustix::fs::{chmodat, AtFlags, Mode}; 5 | use std::path::Path; 6 | use std::{fs, io}; 7 | 8 | /// This can just use `AT_SYMLINK_NOFOLLOW`. 9 | pub(crate) fn set_symlink_permissions_unchecked( 10 | start: &fs::File, 11 | path: &Path, 12 | perm: Permissions, 13 | ) -> io::Result<()> { 14 | let mode = Mode::from_bits_truncate(perm.mode().try_into().unwrap()); 15 | 16 | Ok(chmodat(start, path, mode, AtFlags::SYMLINK_NOFOLLOW)?) 17 | } 18 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/set_times_impl.rs: -------------------------------------------------------------------------------- 1 | //! This module consists of helper types and functions for dealing 2 | //! with setting the file times. 3 | 4 | use crate::fs::{open, OpenOptions, SystemTimeSpec}; 5 | use rustix::io::Errno; 6 | use std::path::Path; 7 | use std::{fs, io}; 8 | 9 | pub(crate) fn set_times_impl( 10 | start: &fs::File, 11 | path: &Path, 12 | atime: Option, 13 | mtime: Option, 14 | ) -> io::Result<()> { 15 | // Try `futimens` with a normal handle. Normal handles need some kind of 16 | // access, so first try write. 17 | match open(start, path, OpenOptions::new().write(true)) { 18 | Ok(file) => { 19 | return fs_set_times::SetTimes::set_times( 20 | &file, 21 | atime.map(SystemTimeSpec::into_std), 22 | mtime.map(SystemTimeSpec::into_std), 23 | ) 24 | } 25 | Err(err) => match Errno::from_io_error(&err) { 26 | Some(Errno::ACCESS) | Some(Errno::ISDIR) => (), 27 | _ => return Err(err), 28 | }, 29 | } 30 | 31 | // Next try read. 32 | match open(start, path, OpenOptions::new().read(true)) { 33 | Ok(file) => { 34 | return fs_set_times::SetTimes::set_times( 35 | &file, 36 | atime.map(SystemTimeSpec::into_std), 37 | mtime.map(SystemTimeSpec::into_std), 38 | ) 39 | } 40 | Err(err) => match Errno::from_io_error(&err) { 41 | Some(Errno::ACCESS) => (), 42 | _ => return Err(err), 43 | }, 44 | } 45 | 46 | // It's not possible to do anything else with generic POSIX. Plain 47 | // `utimensat` has two options: 48 | // - Follow symlinks, which would open up a race in which a concurrent 49 | // modification of the symlink could point outside the sandbox and we 50 | // wouldn't be able to detect it, or 51 | // - Don't follow symlinks, which would modify the timestamp of the symlink 52 | // instead of the file we're trying to get to. 53 | // 54 | // So neither does what we need. 55 | Err(Errno::NOTSUP.into()) 56 | } 57 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/symlink_unchecked.rs: -------------------------------------------------------------------------------- 1 | use rustix::fs::symlinkat; 2 | use std::path::Path; 3 | use std::{fs, io}; 4 | 5 | /// *Unsandboxed* function similar to `symlink`, but which does not perform 6 | /// sandboxing. 7 | pub(crate) fn symlink_unchecked( 8 | old_path: &Path, 9 | new_start: &fs::File, 10 | new_path: &Path, 11 | ) -> io::Result<()> { 12 | Ok(symlinkat(old_path, new_start, new_path)?) 13 | } 14 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/fs/times.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::SystemTimeSpec; 2 | use crate::time::SystemClock; 3 | use io_lifetimes::BorrowedFd; 4 | use rustix::fs::{utimensat, AtFlags, Timestamps, UTIME_NOW, UTIME_OMIT}; 5 | use rustix::time::Timespec; 6 | use std::path::Path; 7 | use std::{fs, io}; 8 | 9 | #[allow(clippy::useless_conversion)] 10 | pub(crate) fn to_timespec(ft: Option) -> io::Result { 11 | Ok(match ft { 12 | None => Timespec { 13 | tv_sec: 0, 14 | tv_nsec: UTIME_OMIT.into(), 15 | }, 16 | Some(SystemTimeSpec::SymbolicNow) => Timespec { 17 | tv_sec: 0, 18 | tv_nsec: UTIME_NOW.into(), 19 | }, 20 | Some(SystemTimeSpec::Absolute(ft)) => { 21 | let duration = ft.duration_since(SystemClock::UNIX_EPOCH).unwrap(); 22 | let nanoseconds = duration.subsec_nanos(); 23 | assert_ne!(i64::from(nanoseconds), i64::from(UTIME_OMIT)); 24 | assert_ne!(i64::from(nanoseconds), i64::from(UTIME_NOW)); 25 | Timespec { 26 | tv_sec: duration 27 | .as_secs() 28 | .try_into() 29 | .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?, 30 | tv_nsec: nanoseconds.try_into().unwrap(), 31 | } 32 | } 33 | }) 34 | } 35 | 36 | #[allow(dead_code)] 37 | pub(crate) fn set_times_nofollow_unchecked( 38 | start: &fs::File, 39 | path: &Path, 40 | atime: Option, 41 | mtime: Option, 42 | ) -> io::Result<()> { 43 | let times = Timestamps { 44 | last_access: to_timespec(atime)?, 45 | last_modification: to_timespec(mtime)?, 46 | }; 47 | Ok(utimensat(start, path, ×, AtFlags::SYMLINK_NOFOLLOW)?) 48 | } 49 | 50 | #[allow(dead_code)] 51 | pub(crate) fn set_times_follow_unchecked( 52 | start: BorrowedFd<'_>, 53 | path: &Path, 54 | atime: Option, 55 | mtime: Option, 56 | ) -> io::Result<()> { 57 | let times = Timestamps { 58 | last_access: to_timespec(atime)?, 59 | last_modification: to_timespec(mtime)?, 60 | }; 61 | Ok(utimensat(start, path, ×, AtFlags::empty())?) 62 | } 63 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/linux/fs/file_metadata.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::{ImplMetadataExt, Metadata}; 2 | use rustix::fs::{statat, AtFlags}; 3 | use std::sync::atomic::AtomicBool; 4 | use std::sync::atomic::Ordering::Relaxed; 5 | use std::{fs, io}; 6 | 7 | /// Like `file.metadata()`, but works with `O_PATH` descriptors on old (pre 8 | /// 3.6) versions of Linux too. 9 | pub(super) fn file_metadata(file: &fs::File) -> io::Result { 10 | // Record whether we've seen an `EBADF` from an `fstat` on an `O_PATH` 11 | // file descriptor, meaning we're on a Linux that doesn't support it. 12 | static FSTAT_PATH_BADF: AtomicBool = AtomicBool::new(false); 13 | 14 | if !FSTAT_PATH_BADF.load(Relaxed) { 15 | match Metadata::from_file(file) { 16 | Ok(metadata) => return Ok(metadata), 17 | Err(err) => match rustix::io::Errno::from_io_error(&err) { 18 | // Before Linux 3.6, `fstat` with `O_PATH` returned `EBADF`. 19 | Some(rustix::io::Errno::BADF) => FSTAT_PATH_BADF.store(true, Relaxed), 20 | _ => return Err(err), 21 | }, 22 | } 23 | } 24 | 25 | // If `fstat` with `O_PATH` isn't supported, use `statat` with `AT_EMPTY_PATH`. 26 | Ok(statat(file, "", AtFlags::EMPTY_PATH).map(ImplMetadataExt::from_rustix)?) 27 | } 28 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/linux/fs/file_path.rs: -------------------------------------------------------------------------------- 1 | use super::procfs::get_path_from_proc_self_fd; 2 | use std::fs; 3 | use std::path::PathBuf; 4 | 5 | pub(crate) fn file_path(file: &fs::File) -> Option { 6 | use std::os::unix::fs::MetadataExt; 7 | 8 | // Ignore paths that don't start with '/', which are things like 9 | // `socket:[3556564]` or similar. 10 | let path = get_path_from_proc_self_fd(file) 11 | .ok() 12 | .filter(|path| path.starts_with("/"))?; 13 | 14 | // Linux appends the string " (deleted)" when a file is deleted; avoid 15 | // treating that as the actual name. Check this after doing the `readlink` 16 | // above so that we're conservative about concurrent deletions. 17 | if file.metadata().ok()?.nlink() == 0 { 18 | return None; 19 | } 20 | 21 | Some(path) 22 | } 23 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/linux/fs/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "linux")] 2 | mod canonicalize_impl; 3 | #[cfg(target_os = "linux")] 4 | mod file_metadata; 5 | mod file_path; 6 | #[cfg(target_os = "linux")] 7 | mod open_entry_impl; 8 | mod open_impl; 9 | mod procfs; 10 | mod set_permissions_impl; 11 | mod set_times_impl; 12 | #[cfg(target_os = "linux")] 13 | mod stat_impl; 14 | 15 | #[cfg(target_os = "android")] 16 | pub(crate) use crate::fs::manually::canonicalize as canonicalize_impl; 17 | #[cfg(target_os = "android")] 18 | pub(crate) use crate::fs::manually::open_entry as open_entry_impl; 19 | #[cfg(target_os = "android")] 20 | pub(crate) use crate::fs::manually::stat as stat_impl; 21 | pub(crate) use crate::fs::via_parent::set_times_nofollow as set_times_nofollow_impl; 22 | #[cfg(target_os = "linux")] 23 | pub(crate) use canonicalize_impl::canonicalize_impl; 24 | pub(crate) use file_path::file_path; 25 | #[cfg(target_os = "linux")] 26 | pub(crate) use open_entry_impl::open_entry_impl; 27 | #[cfg(target_os = "linux")] 28 | pub(crate) use open_impl::open_beneath; 29 | pub(crate) use open_impl::open_impl; 30 | pub(crate) use set_permissions_impl::set_permissions_impl; 31 | pub(crate) use set_times_impl::set_times_impl; 32 | #[cfg(target_os = "linux")] 33 | pub(crate) use stat_impl::stat_impl; 34 | 35 | // In theory we could optimize `link` using `openat2` with `O_PATH` and 36 | // `linkat` with `AT_EMPTY_PATH`, however that requires `CAP_DAC_READ_SEARCH`, 37 | // so it isn't very widely applicable. 38 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/linux/fs/open_entry_impl.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::{manually, open_beneath, OpenOptions}; 2 | use std::ffi::OsStr; 3 | use std::{fs, io}; 4 | 5 | pub(crate) fn open_entry_impl( 6 | start: &fs::File, 7 | path: &OsStr, 8 | options: &OpenOptions, 9 | ) -> io::Result { 10 | let result = open_beneath(start, path.as_ref(), options); 11 | 12 | match result { 13 | Ok(file) => Ok(file), 14 | Err(err) => match rustix::io::Errno::from_io_error(&err) { 15 | Some(rustix::io::Errno::NOSYS) => manually::open_entry(start, path, options), 16 | _ => Err(err), 17 | }, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/linux/fs/set_permissions_impl.rs: -------------------------------------------------------------------------------- 1 | use super::procfs::set_permissions_through_proc_self_fd; 2 | use crate::fs::{open, OpenOptions, Permissions}; 3 | use rustix::fs::{fchmod, Mode, RawMode}; 4 | use std::os::unix::fs::PermissionsExt; 5 | use std::path::Path; 6 | use std::{fs, io}; 7 | 8 | pub(crate) fn set_permissions_impl( 9 | start: &fs::File, 10 | path: &Path, 11 | perm: Permissions, 12 | ) -> io::Result<()> { 13 | let std_perm = perm.into_std(start)?; 14 | 15 | // First try using `O_PATH` and doing a chmod on `/proc/self/fd/{}` 16 | // (`fchmod` doesn't work on `O_PATH` file descriptors). 17 | // 18 | // This may fail, due to older Linux versions not supporting `O_PATH`, or 19 | // due to procfs being unavailable, but if it does work, go with it, as 20 | // `O_PATH` tells Linux that we don't actually need to read or write the 21 | // file, which may avoid side effects associated with opening device files. 22 | let result = set_permissions_through_proc_self_fd(start, path, std_perm.clone()); 23 | if let Ok(()) = result { 24 | return Ok(()); 25 | } 26 | 27 | // Then try `fchmod` with a normal handle. Normal handles need some kind of 28 | // access, so first try read. 29 | match open(start, path, OpenOptions::new().read(true)) { 30 | Ok(file) => return set_file_permissions(&file, std_perm), 31 | Err(err) => match rustix::io::Errno::from_io_error(&err) { 32 | Some(rustix::io::Errno::ACCESS) => (), 33 | _ => return Err(err), 34 | }, 35 | } 36 | 37 | // Next try write. 38 | match open(start, path, OpenOptions::new().write(true)) { 39 | Ok(file) => return set_file_permissions(&file, std_perm), 40 | Err(err) => match rustix::io::Errno::from_io_error(&err) { 41 | Some(rustix::io::Errno::ACCESS) | Some(rustix::io::Errno::ISDIR) => (), 42 | _ => return Err(err), 43 | }, 44 | } 45 | 46 | // Nothing worked, so just return the original error. 47 | result 48 | } 49 | 50 | fn set_file_permissions(file: &fs::File, perm: fs::Permissions) -> io::Result<()> { 51 | // Use `from_bits_truncate` for compatibility with std, which allows 52 | // non-permission bits to propagate through. 53 | let mode = Mode::from_bits_truncate(perm.mode() as RawMode); 54 | Ok(fchmod(file, mode)?) 55 | } 56 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/linux/fs/set_times_impl.rs: -------------------------------------------------------------------------------- 1 | //! This module consists of helper types and functions for dealing 2 | //! with setting the file times specific to Linux. 3 | 4 | use super::procfs::set_times_through_proc_self_fd; 5 | use crate::fs::{open, OpenOptions, SystemTimeSpec}; 6 | use std::path::Path; 7 | use std::{fs, io}; 8 | 9 | pub(crate) fn set_times_impl( 10 | start: &fs::File, 11 | path: &Path, 12 | atime: Option, 13 | mtime: Option, 14 | ) -> io::Result<()> { 15 | // Try `futimens` with a normal handle. Normal handles need some kind of 16 | // access, so first try write. 17 | match open(start, path, OpenOptions::new().write(true)) { 18 | Ok(file) => { 19 | return fs_set_times::SetTimes::set_times( 20 | &file, 21 | atime.map(SystemTimeSpec::into_std), 22 | mtime.map(SystemTimeSpec::into_std), 23 | ) 24 | } 25 | Err(err) => match rustix::io::Errno::from_io_error(&err) { 26 | Some(rustix::io::Errno::ACCESS) | Some(rustix::io::Errno::ISDIR) => (), 27 | _ => return Err(err), 28 | }, 29 | } 30 | 31 | // Next try read. 32 | match open(start, path, OpenOptions::new().read(true)) { 33 | Ok(file) => { 34 | return fs_set_times::SetTimes::set_times( 35 | &file, 36 | atime.map(SystemTimeSpec::into_std), 37 | mtime.map(SystemTimeSpec::into_std), 38 | ) 39 | } 40 | Err(err) => match rustix::io::Errno::from_io_error(&err) { 41 | Some(rustix::io::Errno::ACCESS) => (), 42 | _ => return Err(err), 43 | }, 44 | } 45 | 46 | // If neither of those worked, turn to `/proc`. 47 | set_times_through_proc_self_fd(start, path, atime, mtime) 48 | } 49 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/linux/fs/stat_impl.rs: -------------------------------------------------------------------------------- 1 | //! Linux has an `O_PATH` flag which allows opening a file without necessary 2 | //! having read or write access to it; we can use that with `openat2` and 3 | //! `fstat` to perform a fast sandboxed `stat`. 4 | 5 | use super::file_metadata::file_metadata; 6 | use crate::fs::{manually, open_beneath, FollowSymlinks, Metadata, OpenOptions}; 7 | use rustix::fs::OFlags; 8 | use std::path::Path; 9 | use std::{fs, io}; 10 | 11 | /// Use `openat2` with `O_PATH` and `fstat`. If that's not available, fallback 12 | /// to `manually::stat`. 13 | pub(crate) fn stat_impl( 14 | start: &fs::File, 15 | path: &Path, 16 | follow: FollowSymlinks, 17 | ) -> io::Result { 18 | use crate::fs::{stat_unchecked, OpenOptionsExt}; 19 | use std::path::Component; 20 | 21 | // Optimization: if path has exactly one component and it's not ".." and 22 | // we're not following symlinks we can go straight to `stat_unchecked`, 23 | // which is faster than doing an open with a separate fstat. 24 | if follow == FollowSymlinks::No { 25 | let mut components = path.components(); 26 | if let Some(component) = components.next() { 27 | if components.next().is_none() && component != Component::ParentDir { 28 | return stat_unchecked(start, component.as_ref(), FollowSymlinks::No); 29 | } 30 | } 31 | } 32 | 33 | // Open the path with `O_PATH`. Use `read(true)` even though we don't need 34 | // `read` permissions, because Rust's libstd requires an access mode, and 35 | // Linux ignores `O_RDONLY` with `O_PATH`. 36 | let result = open_beneath( 37 | start, 38 | path, 39 | OpenOptions::new() 40 | .read(true) 41 | .follow(follow) 42 | .custom_flags(OFlags::PATH.bits() as i32), 43 | ); 44 | 45 | // If that worked, call `fstat`. 46 | match result { 47 | Ok(file) => file_metadata(&file), 48 | Err(err) => match rustix::io::Errno::from_io_error(&err) { 49 | // `ENOSYS` from `open_beneath` means `openat2` is unavailable 50 | // and we should use a fallback. 51 | Some(rustix::io::Errno::NOSYS) => manually::stat(start, path, follow), 52 | _ => Err(err), 53 | }, 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/linux/mod.rs: -------------------------------------------------------------------------------- 1 | //! Following [`std`], we don't carry workarounds for Linux versions 2 | //! older than 2.6.32. 3 | //! 4 | //! [`std`]: https://github.com/rust-lang/rust/pull/74163 5 | 6 | pub(crate) mod fs; 7 | -------------------------------------------------------------------------------- /cap-primitives/src/rustix/mod.rs: -------------------------------------------------------------------------------- 1 | //! The `rustix` module contains code specific to the Posix-ish platforms 2 | //! supported by the `rustix` crate. 3 | 4 | pub(crate) mod fs; 5 | 6 | #[cfg(any( 7 | target_os = "macos", 8 | target_os = "ios", 9 | target_os = "tvos", 10 | target_os = "watchos", 11 | target_os = "visionos", 12 | ))] 13 | mod darwin; 14 | #[cfg(target_os = "freebsd")] 15 | mod freebsd; 16 | #[cfg(any(target_os = "android", target_os = "linux"))] 17 | mod linux; 18 | -------------------------------------------------------------------------------- /cap-primitives/src/time/mod.rs: -------------------------------------------------------------------------------- 1 | //! Time utilities. 2 | 3 | mod instant; 4 | mod monotonic_clock; 5 | mod system_clock; 6 | mod system_time; 7 | 8 | pub use instant::Instant; 9 | pub use monotonic_clock::MonotonicClock; 10 | pub use system_clock::SystemClock; 11 | pub use system_time::SystemTime; 12 | 13 | pub use std::time::{Duration, SystemTimeError}; 14 | -------------------------------------------------------------------------------- /cap-primitives/src/time/monotonic_clock.rs: -------------------------------------------------------------------------------- 1 | use crate::time::{Duration, Instant}; 2 | use ambient_authority::AmbientAuthority; 3 | use std::time; 4 | 5 | /// A reference to a monotonically nondecreasing clock. 6 | /// 7 | /// This does not directly correspond to anything in `std`, however its methods 8 | /// correspond to [methods in `std::time::Instant`]. 9 | /// 10 | /// [methods in `std::time::Instant`]: https://doc.rust-lang.org/std/time/struct.Instant.html#impl 11 | pub struct MonotonicClock(()); 12 | 13 | impl MonotonicClock { 14 | /// Constructs a new instance of `Self`. 15 | /// 16 | /// # Ambient Authority 17 | /// 18 | /// This uses ambient authority to accesses clocks. 19 | #[inline] 20 | pub const fn new(ambient_authority: AmbientAuthority) -> Self { 21 | let _ = ambient_authority; 22 | Self(()) 23 | } 24 | 25 | /// Returns an instant corresponding to "now". 26 | /// 27 | /// This corresponds to [`std::time::Instant::now`]. 28 | #[inline] 29 | pub fn now(&self) -> Instant { 30 | Instant::from_std(time::Instant::now()) 31 | } 32 | 33 | /// Returns the amount of time elapsed since this instant was created. 34 | /// 35 | /// This corresponds to [`std::time::Instant::elapsed`]. 36 | #[inline] 37 | pub fn elapsed(&self, instant: Instant) -> Duration { 38 | instant.std.elapsed() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cap-primitives/src/time/system_clock.rs: -------------------------------------------------------------------------------- 1 | use crate::time::{Duration, SystemTime, SystemTimeError}; 2 | use ambient_authority::AmbientAuthority; 3 | use std::time; 4 | 5 | /// A reference to a system clock, useful for talking to external entities like 6 | /// the file system or other processes. 7 | /// 8 | /// This does not directly correspond to anything in `std`, however its methods 9 | /// correspond to [methods in `std::time::SystemTime`]. 10 | /// 11 | /// [methods in `std::time::SystemTime`]: https://doc.rust-lang.org/std/time/struct.SystemTime.html#impl 12 | pub struct SystemClock(()); 13 | 14 | impl SystemClock { 15 | /// An anchor in time which can be used to create new `SystemTime` 16 | /// instances or learn about where in time a `SystemTime` lies. 17 | /// 18 | /// This corresponds to [`std::time::SystemTime::UNIX_EPOCH`]. 19 | pub const UNIX_EPOCH: SystemTime = SystemTime { 20 | std: time::SystemTime::UNIX_EPOCH, 21 | }; 22 | 23 | /// Constructs a new instance of `Self`. 24 | /// 25 | /// # Ambient Authority 26 | /// 27 | /// This uses ambient authority to accesses clocks. 28 | #[inline] 29 | pub const fn new(ambient_authority: AmbientAuthority) -> Self { 30 | let _ = ambient_authority; 31 | Self(()) 32 | } 33 | 34 | /// Returns an instant corresponding to "now". 35 | /// 36 | /// This corresponds to [`std::time::SystemTime::now`]. 37 | #[inline] 38 | pub fn now(&self) -> SystemTime { 39 | SystemTime::from_std(time::SystemTime::now()) 40 | } 41 | 42 | /// Returns the amount of time elapsed since this instant was created. 43 | /// 44 | /// This corresponds to [`std::time::SystemTime::elapsed`]. 45 | #[inline] 46 | pub fn elapsed(&self, system_time: SystemTime) -> Result { 47 | system_time.std.elapsed() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/fs/access_unchecked.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::{open, AccessType, FollowSymlinks, OpenOptions}; 2 | use std::path::Path; 3 | use std::{fs, io}; 4 | 5 | /// *Unsandboxed* function similar to `access`, but which does not perform 6 | /// sandboxing. 7 | pub(crate) fn access_unchecked( 8 | start: &fs::File, 9 | path: &Path, 10 | type_: AccessType, 11 | follow: FollowSymlinks, 12 | ) -> io::Result<()> { 13 | let mut options = OpenOptions::new(); 14 | options.follow(follow); 15 | match type_ { 16 | AccessType::Exists => { 17 | options.read(true); 18 | } 19 | AccessType::Access(modes) => { 20 | if modes.readable { 21 | options.read(true); 22 | } 23 | if modes.writable { 24 | options.write(true); 25 | } 26 | if modes.executable { 27 | options.read(true); 28 | } 29 | } 30 | } 31 | open(start, path, &options).map(|_| ()) 32 | } 33 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/fs/copy.rs: -------------------------------------------------------------------------------- 1 | use super::get_path::get_path; 2 | use crate::fs::{open, OpenOptions}; 3 | use std::path::Path; 4 | use std::{fs, io}; 5 | 6 | pub(crate) fn copy_impl( 7 | from_start: &fs::File, 8 | from_path: &Path, 9 | to_start: &fs::File, 10 | to_path: &Path, 11 | ) -> io::Result { 12 | let from = open(from_start, from_path, OpenOptions::new().read(true))?; 13 | let to = open( 14 | to_start, 15 | to_path, 16 | OpenOptions::new().create(true).truncate(true).write(true), 17 | )?; 18 | fs::copy(get_path(&from)?, get_path(&to)?) 19 | } 20 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/fs/create_dir_unchecked.rs: -------------------------------------------------------------------------------- 1 | use super::get_path::concatenate; 2 | use crate::fs::DirOptions; 3 | use std::path::Path; 4 | use std::{fs, io}; 5 | 6 | /// *Unsandboxed* function similar to `create_dir`, but which does not perform 7 | /// sandboxing. 8 | /// 9 | /// Windows doesn't have any extra flags in `DirOptions`, so the `options` 10 | /// parameter is ignored. 11 | pub(crate) fn create_dir_unchecked( 12 | start: &fs::File, 13 | path: &Path, 14 | _options: &DirOptions, 15 | ) -> io::Result<()> { 16 | let out_path = concatenate(start, path)?; 17 | fs::create_dir(out_path) 18 | } 19 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/fs/dir_options_ext.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone)] 2 | pub(crate) struct DirOptionsExt {} 3 | 4 | impl DirOptionsExt { 5 | pub(crate) const fn new() -> Self { 6 | Self {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/fs/errors.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use windows_sys::Win32::Foundation; 3 | 4 | #[cold] 5 | pub(crate) fn no_such_file_or_directory() -> io::Error { 6 | io::Error::from_raw_os_error(Foundation::ERROR_FILE_NOT_FOUND as i32) 7 | } 8 | 9 | #[cold] 10 | pub(crate) fn is_directory() -> io::Error { 11 | io::Error::from_raw_os_error(Foundation::ERROR_DIRECTORY_NOT_SUPPORTED as i32) 12 | } 13 | 14 | #[cold] 15 | pub(crate) fn is_not_directory() -> io::Error { 16 | io::Error::from_raw_os_error(Foundation::ERROR_DIRECTORY as i32) 17 | } 18 | 19 | #[cold] 20 | pub(crate) fn too_many_symlinks() -> io::Error { 21 | io::Error::from_raw_os_error(Foundation::ERROR_TOO_MANY_LINKS as i32) 22 | } 23 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/fs/get_path.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::OsString; 2 | use std::os::windows::ffi::{OsStrExt, OsStringExt}; 3 | use std::path::{Path, PathBuf}; 4 | use std::{fs, io}; 5 | 6 | /// Calculates system path of `file`. 7 | /// 8 | /// This function will automatically strip the extended prefix from the 9 | /// resultant path to allow for joining this resultant path with relative 10 | /// components. 11 | pub(crate) fn get_path(file: &fs::File) -> io::Result { 12 | // get system path to the handle 13 | let path = winx::file::get_file_path(file)?; 14 | 15 | // strip extended prefix; otherwise we will error out on any relative 16 | // components with `out_path` 17 | let wide: Vec<_> = path.as_os_str().encode_wide().collect(); 18 | let wide_final = if wide.starts_with(&['\\' as u16, '\\' as _, '?' as _, '\\' as _]) { 19 | &wide[4..] 20 | } else { 21 | &wide 22 | }; 23 | Ok(PathBuf::from(OsString::from_wide(wide_final))) 24 | } 25 | 26 | /// Convenience function for calling `get_path` and concatenating the result 27 | /// with `path`. 28 | pub(super) fn concatenate(file: &fs::File, path: &Path) -> io::Result { 29 | let file_path = get_path(file)?; 30 | Ok(file_path.join(path)) 31 | } 32 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/fs/hard_link_unchecked.rs: -------------------------------------------------------------------------------- 1 | use super::get_path::concatenate; 2 | use std::path::Path; 3 | use std::{fs, io}; 4 | 5 | /// *Unsandboxed* function similar to `hard_link`, but which does not perform 6 | /// sandboxing. 7 | pub(crate) fn hard_link_unchecked( 8 | old_start: &fs::File, 9 | old_path: &Path, 10 | new_start: &fs::File, 11 | new_path: &Path, 12 | ) -> io::Result<()> { 13 | let old_full_path = concatenate(old_start, old_path)?; 14 | let new_full_path = concatenate(new_start, new_path)?; 15 | fs::hard_link(old_full_path, new_full_path) 16 | } 17 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/fs/is_file_read_write_impl.rs: -------------------------------------------------------------------------------- 1 | use io_lifetimes::AsHandle; 2 | use std::{fs, io}; 3 | 4 | pub(crate) fn is_file_read_write_impl(file: &fs::File) -> io::Result<(bool, bool)> { 5 | let handle = file.as_handle(); 6 | let access_mode = winx::file::query_access_information(handle)?; 7 | let read = access_mode.contains(winx::file::AccessMode::FILE_READ_DATA); 8 | let write = access_mode.contains(winx::file::AccessMode::FILE_WRITE_DATA) 9 | || access_mode.contains(winx::file::AccessMode::FILE_APPEND_DATA); 10 | Ok((read, write)) 11 | } 12 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/fs/is_same_file.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::ImplMetadataExt; 2 | #[cfg(windows_by_handle)] 3 | use crate::fs::Metadata; 4 | use std::{fs, io}; 5 | 6 | /// Determine if `a` and `b` refer to the same inode on the same device. 7 | pub(crate) fn is_same_file(a: &fs::File, b: &fs::File) -> io::Result { 8 | let a_metadata = ImplMetadataExt::from(a, &a.metadata()?)?; 9 | let b_metadata = ImplMetadataExt::from(b, &b.metadata()?)?; 10 | Ok(a_metadata.is_same_file(&b_metadata)) 11 | } 12 | 13 | /// Determine if `a` and `b` are metadata for the same inode on the same 14 | /// device. 15 | #[cfg(windows_by_handle)] 16 | #[allow(dead_code)] 17 | pub(crate) fn is_same_file_metadata(a: &Metadata, b: &Metadata) -> io::Result { 18 | use crate::fs::MetadataExt; 19 | Ok(a.volume_serial_number() == b.volume_serial_number() && a.file_index() == b.file_index()) 20 | } 21 | 22 | /// Determine if `a` and `b` definitely refer to different inodes. 23 | /// 24 | /// This is similar to `is_same_file`, but is conservative, and doesn't depend 25 | /// on nightly-only features. 26 | #[cfg(racy_asserts)] 27 | pub(crate) fn is_different_file(a: &fs::File, b: &fs::File) -> io::Result { 28 | #[cfg(windows_by_handle)] 29 | { 30 | is_same_file(a, b).map(|same| !same) 31 | } 32 | 33 | #[cfg(not(windows_by_handle))] 34 | { 35 | let a_metadata = Metadata::from_std(a.metadata()?); 36 | let b_metadata = Metadata::from_std(b.metadata()?); 37 | is_different_file_metadata(&a_metadata, &b_metadata) 38 | } 39 | } 40 | 41 | /// Determine if `a` and `b` are metadata for definitely different inodes. 42 | /// 43 | /// This is similar to `is_same_file_metadata`, but is conservative, and 44 | /// doesn't depend on nightly-only features. 45 | #[cfg(racy_asserts)] 46 | pub(crate) fn is_different_file_metadata(a: &Metadata, b: &Metadata) -> io::Result { 47 | #[cfg(windows_by_handle)] 48 | { 49 | is_same_file_metadata(a, b).map(|same| !same) 50 | } 51 | 52 | #[cfg(not(windows_by_handle))] 53 | { 54 | // Conservatively just compare creation times. 55 | Ok(a.created()? != b.created()?) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/fs/read_dir_inner.rs: -------------------------------------------------------------------------------- 1 | use super::get_path::concatenate; 2 | use crate::fs::{open_dir, DirEntryInner, FollowSymlinks}; 3 | use std::path::{Component, Path}; 4 | use std::{fmt, fs, io}; 5 | 6 | pub(crate) struct ReadDirInner { 7 | std: fs::ReadDir, 8 | } 9 | 10 | impl ReadDirInner { 11 | pub(crate) fn new(start: &fs::File, path: &Path, follow: FollowSymlinks) -> io::Result { 12 | assert_eq!( 13 | follow, 14 | FollowSymlinks::Yes, 15 | "`read_dir` without following symlinks is not implemented yet" 16 | ); 17 | let dir = open_dir(start, path)?; 18 | Self::new_unchecked(&dir, Component::CurDir.as_ref()) 19 | } 20 | 21 | pub(crate) fn read_base_dir(start: &fs::File) -> io::Result { 22 | Self::new_unchecked(&start, Component::CurDir.as_ref()) 23 | } 24 | 25 | pub(crate) fn new_unchecked(start: &fs::File, path: &Path) -> io::Result { 26 | let full_path = concatenate(start, path)?; 27 | Ok(Self { 28 | std: fs::read_dir(full_path)?, 29 | }) 30 | } 31 | 32 | pub(super) fn from_std(std: fs::ReadDir) -> Self { 33 | Self { std } 34 | } 35 | } 36 | 37 | impl Iterator for ReadDirInner { 38 | type Item = io::Result; 39 | 40 | fn next(&mut self) -> Option { 41 | self.std 42 | .next() 43 | .map(|result| result.map(DirEntryInner::from_std)) 44 | } 45 | } 46 | 47 | impl fmt::Debug for ReadDirInner { 48 | // Like libstd's version, but doesn't print the path. 49 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 50 | let mut b = f.debug_struct("ReadDir"); 51 | // `fs::ReadDir`'s `Debug` just prints the path, and since we're not 52 | // printing that, we don't have anything else to print. 53 | b.finish() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/fs/read_link_impl.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::{open, FollowSymlinks, OpenOptions, OpenOptionsExt}; 2 | use std::path::{Path, PathBuf}; 3 | use std::{fs, io}; 4 | use windows_sys::Win32::Storage::FileSystem::{ 5 | FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, 6 | }; 7 | 8 | /// *Unsandboxed* function similar to `read_link`, but which does not perform 9 | /// sandboxing. 10 | pub(crate) fn read_link_impl(start: &fs::File, path: &Path) -> io::Result { 11 | // Open the link with no access mode, instead of generic read. 12 | // By default FILE_LIST_DIRECTORY is denied for the junction "C:\Documents and 13 | // Settings", so this is needed for a common case. 14 | let mut opts = OpenOptions::new(); 15 | opts.access_mode(0); 16 | opts.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS); 17 | opts.follow(FollowSymlinks::No); 18 | let file = open(start, path, &opts)?; 19 | winx::file::read_link(&file) 20 | } 21 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/fs/read_link_unchecked.rs: -------------------------------------------------------------------------------- 1 | use super::get_path::concatenate; 2 | use std::path::{Path, PathBuf}; 3 | use std::{fs, io}; 4 | 5 | /// *Unsandboxed* function similar to `read_link`, but which does not perform 6 | /// sandboxing. 7 | pub(crate) fn read_link_unchecked( 8 | start: &fs::File, 9 | path: &Path, 10 | _reuse: PathBuf, 11 | ) -> io::Result { 12 | let full_path = concatenate(start, path)?; 13 | fs::read_link(full_path) 14 | } 15 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/fs/remove_dir_all_impl.rs: -------------------------------------------------------------------------------- 1 | use super::get_path::get_path; 2 | use crate::fs::{open_dir, open_dir_nofollow, remove_dir, stat, FollowSymlinks}; 3 | use std::path::Path; 4 | use std::{fs, io}; 5 | 6 | pub(crate) fn remove_dir_all_impl(start: &fs::File, path: &Path) -> io::Result<()> { 7 | // Open the directory, following symlinks, to make sure it is a directory. 8 | let file = open_dir(start, path)?; 9 | // Test whether the path is a symlink. 10 | let md = stat(start, path, FollowSymlinks::No)?; 11 | drop(file); 12 | if md.is_symlink() { 13 | // If so, just remove the link. 14 | remove_dir(start, path) 15 | } else { 16 | // Otherwise, remove the tree. 17 | let dir = open_dir_nofollow(start, path)?; 18 | remove_open_dir_all_impl(dir) 19 | } 20 | } 21 | 22 | pub(crate) fn remove_open_dir_all_impl(dir: fs::File) -> io::Result<()> { 23 | // Close the directory so that we can delete it. This is racy; see the 24 | // comments in `remove_open_dir_impl` for details. 25 | let path = get_path(&dir)?; 26 | drop(dir); 27 | fs::remove_dir_all(&path) 28 | } 29 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/fs/remove_dir_unchecked.rs: -------------------------------------------------------------------------------- 1 | use super::get_path::concatenate; 2 | use std::path::Path; 3 | use std::{fs, io}; 4 | 5 | /// *Unsandboxed* function similar to `remove_dir`, but which does not perform 6 | /// sandboxing. 7 | pub(crate) fn remove_dir_unchecked(start: &fs::File, path: &Path) -> io::Result<()> { 8 | let full_path = concatenate(start, path)?; 9 | fs::remove_dir(full_path) 10 | } 11 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/fs/remove_file_unchecked.rs: -------------------------------------------------------------------------------- 1 | use super::get_path::concatenate; 2 | use std::path::Path; 3 | use std::{fs, io}; 4 | 5 | /// *Unsandboxed* function similar to `remove_file`, but which does not perform 6 | /// sandboxing. 7 | pub(crate) fn remove_file_unchecked(start: &fs::File, path: &Path) -> io::Result<()> { 8 | let full_path = concatenate(start, path)?; 9 | fs::remove_file(full_path) 10 | } 11 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/fs/remove_open_dir_impl.rs: -------------------------------------------------------------------------------- 1 | use super::get_path::get_path; 2 | use std::{fs, io}; 3 | 4 | pub(crate) fn remove_open_dir_impl(dir: fs::File) -> io::Result<()> { 5 | let path = get_path(&dir)?; 6 | 7 | // Drop the directory before removing it, since we open directories without 8 | // `FILE_SHARE_DELETE`, and removing it requires accessing via its name 9 | // rather than its handle. 10 | // 11 | // There is a window here in which another process could remove or rename 12 | // a directory with this path after the handle is dropped, however it's 13 | // unlikely to happen by accident, and unlikely to cause major problems. 14 | // It may cause spurious failures, or failures with different error codes, 15 | // but this appears to be unavoidable. 16 | // 17 | // Even if we did have `FILE_SHARE_DELETE` and we kept the handle open 18 | // while doing the `remove_dir, `FILE_SHARE_DELETE` would grant other 19 | // processes the right to remove or rename the directory. So there 20 | // doesn't seem to be a race-free way of removing opened directories. 21 | drop(dir); 22 | 23 | fs::remove_dir(path) 24 | } 25 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/fs/rename_unchecked.rs: -------------------------------------------------------------------------------- 1 | use super::get_path::concatenate; 2 | use std::path::Path; 3 | use std::{fs, io}; 4 | 5 | /// *Unsandboxed* function similar to `rename`, but which does not perform 6 | /// sandboxing. 7 | pub(crate) fn rename_unchecked( 8 | old_start: &fs::File, 9 | old_path: &Path, 10 | new_start: &fs::File, 11 | new_path: &Path, 12 | ) -> io::Result<()> { 13 | let old_full_path = concatenate(old_start, old_path)?; 14 | let new_full_path = concatenate(new_start, new_path)?; 15 | fs::rename(old_full_path, new_full_path) 16 | } 17 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/fs/set_permissions_unchecked.rs: -------------------------------------------------------------------------------- 1 | use super::get_path::concatenate; 2 | use crate::fs::Permissions; 3 | use std::path::Path; 4 | use std::{fs, io}; 5 | 6 | /// *Unsandboxed* function similar to `set_permissions`, but which does not 7 | /// perform sandboxing. 8 | pub(crate) fn set_permissions_unchecked( 9 | start: &fs::File, 10 | path: &Path, 11 | perm: Permissions, 12 | ) -> io::Result<()> { 13 | // According to [Rust's documentation], `fs::set_permissions` uses 14 | // `SetFileAttributes`, and according to [Windows' documentation] 15 | // `SetFileAttributes` does not follow symbolic links. 16 | // 17 | // [Windows' documentation]: https://docs.microsoft.com/en-us/windows/win32/fileio/symbolic-link-effects-on-file-systems-functions#setfileattributes 18 | // [Rust's documentation]: https://doc.rust-lang.org/std/fs/fn.set_permissions.html#platform-specific-behavior 19 | let out_path = concatenate(start, path)?; 20 | fs::set_permissions(out_path, perm.into_std(start)?) 21 | } 22 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/fs/set_symlink_permissions_unchecked.rs: -------------------------------------------------------------------------------- 1 | use super::get_path::concatenate; 2 | use crate::fs::Permissions; 3 | use std::path::Path; 4 | use std::{fs, io}; 5 | 6 | /// This can just use `AT_SYMLINK_NOFOLLOW`. 7 | pub(crate) fn set_symlink_permissions_unchecked( 8 | start: &fs::File, 9 | path: &Path, 10 | perm: Permissions, 11 | ) -> io::Result<()> { 12 | // According to [Rust's documentation], `fs::set_permissions` uses 13 | // `SetFileAttributes`, and according to [Windows' documentation] 14 | // `SetFileAttributes` does not follow symbolic links. 15 | // 16 | // [Windows' documentation]: https://docs.microsoft.com/en-us/windows/win32/fileio/symbolic-link-effects-on-file-systems-functions#setfileattributes 17 | // [Rust's documentation]: https://doc.rust-lang.org/std/fs/fn.set_permissions.html#platform-specific-behavior 18 | let out_path = concatenate(start, path)?; 19 | fs::set_permissions(out_path, perm.into_std(start)?) 20 | } 21 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/fs/set_times_impl.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::{open, OpenOptions, OpenOptionsExt, SystemTimeSpec}; 2 | use std::path::Path; 3 | use std::{fs, io}; 4 | use windows_sys::Win32::Storage::FileSystem::{ 5 | FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, 6 | }; 7 | 8 | #[inline] 9 | pub(crate) fn set_times_impl( 10 | start: &fs::File, 11 | path: &Path, 12 | atime: Option, 13 | mtime: Option, 14 | ) -> io::Result<()> { 15 | set_times_inner(start, path, atime, mtime, 0) 16 | } 17 | 18 | #[inline] 19 | pub(crate) fn set_times_nofollow_impl( 20 | start: &fs::File, 21 | path: &Path, 22 | atime: Option, 23 | mtime: Option, 24 | ) -> io::Result<()> { 25 | set_times_inner(start, path, atime, mtime, FILE_FLAG_OPEN_REPARSE_POINT) 26 | } 27 | 28 | fn set_times_inner( 29 | start: &fs::File, 30 | path: &Path, 31 | atime: Option, 32 | mtime: Option, 33 | custom_flags: u32, 34 | ) -> io::Result<()> { 35 | let custom_flags = custom_flags | FILE_FLAG_BACKUP_SEMANTICS; 36 | 37 | // On Windows, `set_times` requires write permissions. 38 | let file = open( 39 | start, 40 | path, 41 | OpenOptions::new().write(true).custom_flags(custom_flags), 42 | )?; 43 | fs_set_times::SetTimes::set_times( 44 | &file, 45 | atime.map(SystemTimeSpec::into_std), 46 | mtime.map(SystemTimeSpec::into_std), 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/fs/stat_unchecked.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::OpenOptionsExt; 2 | use crate::fs::{open_unchecked, FollowSymlinks, Metadata, OpenOptions}; 3 | use std::path::Path; 4 | use std::{fs, io}; 5 | use windows_sys::Win32::Storage::FileSystem::{ 6 | FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, 7 | }; 8 | 9 | /// *Unsandboxed* function similar to `stat`, but which does not perform 10 | /// sandboxing. 11 | pub(crate) fn stat_unchecked( 12 | start: &fs::File, 13 | path: &Path, 14 | follow: FollowSymlinks, 15 | ) -> io::Result { 16 | // Attempt to open the file to get the metadata that way, as that gives 17 | // us all the info. 18 | let mut opts = OpenOptions::new(); 19 | 20 | // Explicitly request no access, because we're just querying metadata. 21 | opts.access_mode(0); 22 | 23 | match follow { 24 | FollowSymlinks::Yes => { 25 | opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS); 26 | opts.follow(FollowSymlinks::Yes); 27 | } 28 | FollowSymlinks::No => { 29 | opts.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS); 30 | opts.follow(FollowSymlinks::No); 31 | } 32 | } 33 | 34 | let file = open_unchecked(start, path, &opts)?; 35 | Metadata::from_file(&file) 36 | } 37 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/fs/symlink_unchecked.rs: -------------------------------------------------------------------------------- 1 | use super::get_path::concatenate; 2 | use std::path::Path; 3 | use std::{fs, io}; 4 | 5 | /// *Unsandboxed* function similar to `symlink_file`, but which does not 6 | /// perform sandboxing. 7 | pub(crate) fn symlink_file_unchecked( 8 | old_path: &Path, 9 | new_start: &fs::File, 10 | new_path: &Path, 11 | ) -> io::Result<()> { 12 | let new_full_path = concatenate(new_start, new_path)?; 13 | std::os::windows::fs::symlink_file(old_path, new_full_path) 14 | } 15 | 16 | /// *Unsandboxed* function similar to `symlink_dir`, but which does not perform 17 | /// sandboxing. 18 | pub(crate) fn symlink_dir_unchecked( 19 | old_path: &Path, 20 | new_start: &fs::File, 21 | new_path: &Path, 22 | ) -> io::Result<()> { 23 | let new_full_path = concatenate(new_start, new_path)?; 24 | std::os::windows::fs::symlink_dir(old_path, new_full_path) 25 | } 26 | -------------------------------------------------------------------------------- /cap-primitives/src/windows/mod.rs: -------------------------------------------------------------------------------- 1 | //! The `winx` module contains code specific to Windows, supported by the 2 | //! `winx` crate. 3 | 4 | pub(crate) mod fs; 5 | -------------------------------------------------------------------------------- /cap-rand/COPYRIGHT: -------------------------------------------------------------------------------- 1 | Short version for non-lawyers: 2 | 3 | `cap-rand` is triple-licensed under Apache 2.0 with the LLVM Exception, 4 | Apache 2.0, and MIT terms. 5 | 6 | 7 | Longer version: 8 | 9 | Copyrights in the `cap-rand` project are retained by their contributors. 10 | No copyright assignment is required to contribute to the `cap-rand` 11 | project. 12 | 13 | Some files include code derived from Rust's `libstd`; see the comments in 14 | the code for details. 15 | 16 | Except as otherwise noted (below and/or in individual files), `cap-rand` 17 | is licensed under: 18 | 19 | - the Apache License, Version 2.0, with the LLVM Exception 20 | or 21 | 22 | - the Apache License, Version 2.0 23 | or 24 | , 25 | - or the MIT license 26 | or 27 | , 28 | 29 | at your option. 30 | -------------------------------------------------------------------------------- /cap-rand/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cap-rand" 3 | version = "3.4.4" 4 | description = "Capability-based random number generators" 5 | authors = [ 6 | "Dan Gohman ", 7 | "Jakub Konka ", 8 | ] 9 | license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" 10 | keywords = ["random", "rng"] 11 | categories = ["algorithms"] 12 | repository = "https://github.com/bytecodealliance/cap-std" 13 | edition = "2021" 14 | 15 | [dependencies] 16 | ambient-authority = "0.0.2" 17 | rand = "0.8.1" 18 | 19 | [features] 20 | default = [] 21 | small_rng = ["rand/small_rng"] 22 | -------------------------------------------------------------------------------- /cap-rand/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /cap-rand/README.md: -------------------------------------------------------------------------------- 1 |
2 |

cap-rand

3 | 4 |

5 | Capability-based random number generators 6 |

7 | 8 |

9 | Github Actions CI Status 10 | crates.io page 11 | docs.rs docs 12 |

13 |
14 | 15 | The `cap-rand` crate provides a capability-based interface to random number 16 | generators via the [`rand`] crate. 17 | 18 | [`rand`]: https://crates.io/crates/rand 19 | -------------------------------------------------------------------------------- /cap-std/COPYRIGHT: -------------------------------------------------------------------------------- 1 | Short version for non-lawyers: 2 | 3 | `cap-std` is triple-licensed under Apache 2.0 with the LLVM Exception, 4 | Apache 2.0, and MIT terms. 5 | 6 | 7 | Longer version: 8 | 9 | Copyrights in the `cap-std` project are retained by their contributors. 10 | No copyright assignment is required to contribute to the `cap-std` 11 | project. 12 | 13 | Some files include code derived from Rust's `libstd`; see the comments in 14 | the code for details. 15 | 16 | Except as otherwise noted (below and/or in individual files), `cap-std` 17 | is licensed under: 18 | 19 | - the Apache License, Version 2.0, with the LLVM Exception 20 | or 21 | 22 | - the Apache License, Version 2.0 23 | or 24 | , 25 | - or the MIT license 26 | or 27 | , 28 | 29 | at your option. 30 | -------------------------------------------------------------------------------- /cap-std/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cap-std" 3 | version = "3.4.4" 4 | description = "Capability-based version of the Rust standard library" 5 | authors = [ 6 | "Dan Gohman ", 7 | "Jakub Konka ", 8 | ] 9 | license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" 10 | keywords = ["std", "api", "network", "file"] 11 | categories = ["filesystem", "network-programming"] 12 | repository = "https://github.com/bytecodealliance/cap-std" 13 | edition = "2021" 14 | 15 | [package.metadata.docs.rs] 16 | all-features = true 17 | rustdoc-args = ["--cfg=docsrs"] 18 | 19 | [dependencies] 20 | arf-strings = { version = "0.7.0", optional = true } 21 | cap-primitives = { path = "../cap-primitives", version = "^3.4.4" } 22 | io-extras = "0.18.3" 23 | io-lifetimes = { version = "2.0.0", default-features = false } 24 | camino = { version = "1.0.5", optional = true } 25 | 26 | [target.'cfg(not(windows))'.dependencies] 27 | rustix = { version = "1.0.0", features = ["fs"] } 28 | 29 | [features] 30 | default = [] 31 | fs_utf8 = ["camino"] 32 | arf_strings = ["fs_utf8", "arf-strings"] 33 | -------------------------------------------------------------------------------- /cap-std/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /cap-std/src/fs/mod.rs: -------------------------------------------------------------------------------- 1 | //! A capability-based filesystem API modeled after [`std::fs`]. 2 | //! 3 | //! This corresponds to [`std::fs`]. 4 | //! 5 | //! Instead of [`std::fs`'s free functions] and [`std::fs::File`]'s 6 | //! constructors which operate on bare paths, this crate has methods on [`Dir`] 7 | //! which operate on paths which must be relative to the directory. 8 | //! 9 | //! Where `std` says "the filesystem", this API says "a filesystem", as 10 | //! it doesn't assume that there's a single global filesystem namespace. 11 | //! 12 | //! Since all functions which expose raw file descriptors are `unsafe`, 13 | //! I/O handles in this API are unforgeable (unsafe code notwithstanding). 14 | //! This combined with a lack of absolute paths provides a natural 15 | //! capability-based interface. 16 | //! 17 | //! This crate uses the existing `std::path::Path` rather than having its own 18 | //! path type, however while `std::path::Path` is mostly just a pure datatype, 19 | //! it includes aliases for several `std::fs` functions. To preserve the 20 | //! capability-based interface, avoid using `std::path::Path`'s `canonicalize`, 21 | //! `read_link`, `read_dir`, `metadata`, and `symlink_metadata` functions. 22 | //! 23 | //! [`std::fs`'s free functions]: https://doc.rust-lang.org/std/fs/#functions 24 | 25 | mod dir; 26 | mod dir_entry; 27 | mod file; 28 | mod read_dir; 29 | 30 | pub use dir::Dir; 31 | pub use dir_entry::DirEntry; 32 | pub use file::File; 33 | pub use read_dir::ReadDir; 34 | 35 | // Re-export types from `cap_primitives`. 36 | pub use cap_primitives::fs::{DirBuilder, FileType, Metadata, OpenOptions, Permissions}; 37 | 38 | // Re-export conditional types from `cap_primitives`. 39 | #[cfg(any(unix, target_os = "vxworks", all(windows, windows_file_type_ext)))] 40 | pub use cap_primitives::fs::FileTypeExt; 41 | #[cfg(unix)] 42 | pub use cap_primitives::fs::{DirBuilderExt, PermissionsExt}; 43 | pub use cap_primitives::fs::{FileExt, MetadataExt, OpenOptionsExt}; 44 | -------------------------------------------------------------------------------- /cap-std/src/fs/read_dir.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::DirEntry; 2 | use std::{fmt, io}; 3 | 4 | /// Iterator over the entries in a directory. 5 | /// 6 | /// This corresponds to [`std::fs::ReadDir`]. 7 | /// 8 | /// There is no `from_std` method, as `std::fs::ReadDir` doesn't provide a way 9 | /// to construct a `ReadDir` without opening directories by ambient paths. 10 | pub struct ReadDir { 11 | pub(crate) inner: cap_primitives::fs::ReadDir, 12 | } 13 | 14 | impl Iterator for ReadDir { 15 | type Item = io::Result; 16 | 17 | #[inline] 18 | fn next(&mut self) -> Option { 19 | self.inner 20 | .next() 21 | .map(|inner| inner.map(|inner| DirEntry { inner })) 22 | } 23 | } 24 | 25 | impl fmt::Debug for ReadDir { 26 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 27 | self.inner.fmt(f) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cap-std/src/fs_utf8/read_dir.rs: -------------------------------------------------------------------------------- 1 | use crate::fs_utf8::DirEntry; 2 | use std::{fmt, io}; 3 | 4 | /// Iterator over the entries in a directory. 5 | /// 6 | /// This corresponds to [`std::fs::ReadDir`]. 7 | /// 8 | /// There is no `from_std` method, as `std::fs::ReadDir` doesn't provide a way 9 | /// to construct a `ReadDir` without opening directories by ambient paths. 10 | pub struct ReadDir { 11 | cap_std: crate::fs::ReadDir, 12 | } 13 | 14 | impl ReadDir { 15 | /// Constructs a new instance of `Self` from the given `cap_std::fs::File`. 16 | #[inline] 17 | pub fn from_cap_std(cap_std: crate::fs::ReadDir) -> Self { 18 | Self { cap_std } 19 | } 20 | } 21 | 22 | impl Iterator for ReadDir { 23 | type Item = io::Result; 24 | 25 | #[inline] 26 | fn next(&mut self) -> Option { 27 | self.cap_std 28 | .next() 29 | .map(|result| result.map(DirEntry::from_cap_std)) 30 | } 31 | } 32 | 33 | impl fmt::Debug for ReadDir { 34 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 35 | self.cap_std.fmt(f) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cap-std/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A capability-based API modeled after [`std`]. 2 | //! 3 | //! This corresponds to [`std`]. 4 | //! 5 | //! Capability-based APIs represent access to external resources as values 6 | //! which can be passed around between different parts of a program. 7 | //! 8 | //! Two notable features are the [`Dir`] and [`Pool`] types: 9 | //! - `Dir` represents an open directory in a filesystem. Instead of opening 10 | //! files by absolute paths or paths relative to the current working 11 | //! directory, files are opened via paths relative to a `Dir`. The concepts 12 | //! of a process-wide "current working directory" and a single global 13 | //! filesystem namespace are de-emphasized. 14 | //! - `Pool` represents a set of network addresses. Instead of allowing 15 | //! applications to request access to any address and then applying 16 | //! process-wide filtering rules, filtering rules are built into pools which 17 | //! may be passed through the program. 18 | //! 19 | //! On WASI, use of this library closely reflects the underlying system 20 | //! API, so it avoids compatibility layers. 21 | //! 22 | //! [`Dir`]: fs::Dir 23 | //! [`Pool`]: net::Pool 24 | 25 | #![deny(missing_docs)] 26 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] 27 | #![cfg_attr(target_os = "wasi", feature(wasi_ext))] 28 | #![cfg_attr(can_vector, feature(can_vector))] 29 | #![cfg_attr(write_all_vectored, feature(write_all_vectored))] 30 | #![doc( 31 | html_logo_url = "https://raw.githubusercontent.com/bytecodealliance/cap-std/main/media/cap-std.svg" 32 | )] 33 | #![doc( 34 | html_favicon_url = "https://raw.githubusercontent.com/bytecodealliance/cap-std/main/media/cap-std.ico" 35 | )] 36 | #![cfg_attr(io_lifetimes_use_std, feature(io_safety))] 37 | 38 | pub mod fs; 39 | #[cfg(feature = "fs_utf8")] 40 | pub mod fs_utf8; 41 | #[cfg(not(target_os = "wasi"))] // Disable `net` on WASI until it has networking support. 42 | pub mod net; 43 | pub mod os; 44 | pub mod time; 45 | // Re-export ambient_authority etc. so that users can use our version. 46 | #[doc(hidden)] 47 | pub use cap_primitives::ambient_authority_known_at_compile_time; 48 | pub use cap_primitives::{ambient_authority, AmbientAuthority}; 49 | // And these are also part of our public API 50 | pub use cap_primitives::ipnet; 51 | pub use io_lifetimes; 52 | -------------------------------------------------------------------------------- /cap-std/src/net/incoming.rs: -------------------------------------------------------------------------------- 1 | use crate::net::TcpStream; 2 | use std::{fmt, io, net}; 3 | 4 | /// An iterator that infinitely `accept`s connections on a [`TcpListener`]. 5 | /// 6 | /// This corresponds to [`std::net::Incoming`]. 7 | /// 8 | /// [`TcpListener`]: struct.TcpListener.html 9 | pub struct Incoming<'a> { 10 | std: net::Incoming<'a>, 11 | } 12 | 13 | impl<'a> Incoming<'a> { 14 | /// Constructs a new instance of `Self` from the given 15 | /// `std::net::Incoming`. 16 | /// 17 | /// This grants access the resources the `std::net::Incoming` instance 18 | /// already has access to. 19 | #[inline] 20 | pub fn from_std(std: net::Incoming<'a>) -> Self { 21 | Self { std } 22 | } 23 | } 24 | 25 | impl<'a> Iterator for Incoming<'a> { 26 | type Item = io::Result; 27 | 28 | #[inline] 29 | fn next(&mut self) -> Option { 30 | self.std.next().map(|result| { 31 | let tcp_stream = result?; 32 | Ok(TcpStream::from_std(tcp_stream)) 33 | }) 34 | } 35 | 36 | #[inline] 37 | fn size_hint(&self) -> (usize, Option) { 38 | self.std.size_hint() 39 | } 40 | } 41 | 42 | impl<'a> fmt::Debug for Incoming<'a> { 43 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 44 | self.std.fmt(f) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /cap-std/src/net/mod.rs: -------------------------------------------------------------------------------- 1 | //! A capability-based network API modeled after [`std::net`]. 2 | //! 3 | //! This corresponds to [`std::net`]. 4 | //! 5 | //! Instead of [`std::net`]'s constructor methods which take an address to 6 | //! connect to, this crates has methods on [`Pool`] which operate on addresses 7 | //! which must be present in the pool. 8 | //! 9 | //! [`Pool`]: struct.Pool.html 10 | 11 | mod incoming; 12 | mod pool; 13 | mod tcp_listener; 14 | mod tcp_stream; 15 | mod udp_socket; 16 | 17 | pub use incoming::*; 18 | pub use pool::*; 19 | pub use tcp_listener::*; 20 | pub use tcp_stream::*; 21 | pub use udp_socket::*; 22 | 23 | // Re-export things from `std::net` that we can use as-is. 24 | pub use std::net::{ 25 | AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, SocketAddrV4, SocketAddrV6, 26 | ToSocketAddrs, 27 | }; 28 | 29 | // TODO: re-export experimental Ipv6MulticastScope? 30 | -------------------------------------------------------------------------------- /cap-std/src/os/mod.rs: -------------------------------------------------------------------------------- 1 | //! OS-specific functionality. 2 | //! 3 | //! This corresponds to [`std::os`]. 4 | 5 | #[cfg(unix)] 6 | pub mod unix; 7 | -------------------------------------------------------------------------------- /cap-std/src/os/unix/mod.rs: -------------------------------------------------------------------------------- 1 | //! Platform-specific extensions to std for Unix platforms. 2 | //! 3 | //! This corresponds to [`std::os::unix`]. 4 | //! 5 | //! [`std::os::unix`]: https://doc.rust-lang.org/std/os/unix/ 6 | 7 | pub mod net; 8 | -------------------------------------------------------------------------------- /cap-std/src/os/unix/net/incoming.rs: -------------------------------------------------------------------------------- 1 | use crate::os::unix::net::UnixStream; 2 | use std::os::unix; 3 | use std::{fmt, io}; 4 | 5 | /// An iterator over incoming connections to a [`UnixListener`]. 6 | /// 7 | /// This corresponds to [`std::os::unix::net::Incoming`]. 8 | /// 9 | /// [`std::os::unix::net::Incoming`]: https://doc.rust-lang.org/std/os/unix/net/struct.Incoming.html 10 | /// [`UnixListener`]: struct.UnixListener.html 11 | pub struct Incoming<'a> { 12 | std: unix::net::Incoming<'a>, 13 | } 14 | 15 | impl<'a> Incoming<'a> { 16 | /// Constructs a new instance of `Self` from the given 17 | /// `std::os::unix::net::Incoming`. 18 | /// 19 | /// This grants access the resources the `std::os::unix::net::Incoming` 20 | /// instance already has access to. 21 | #[inline] 22 | pub fn from_std(std: unix::net::Incoming<'a>) -> Self { 23 | Self { std } 24 | } 25 | } 26 | 27 | impl<'a> Iterator for Incoming<'a> { 28 | type Item = io::Result; 29 | 30 | #[inline] 31 | fn next(&mut self) -> Option { 32 | self.std.next().map(|result| { 33 | let unix_stream = result?; 34 | Ok(UnixStream::from_std(unix_stream)) 35 | }) 36 | } 37 | 38 | #[inline] 39 | fn size_hint(&self) -> (usize, Option) { 40 | self.std.size_hint() 41 | } 42 | } 43 | 44 | impl<'a> fmt::Debug for Incoming<'a> { 45 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 46 | self.std.fmt(f) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /cap-std/src/os/unix/net/mod.rs: -------------------------------------------------------------------------------- 1 | //! Unix-specific networking functionality 2 | //! 3 | //! This corresponds to [`std::os::unix::net`]. 4 | //! 5 | //! This module is not yet implemented. And it's not easily implementable 6 | //! on many platforms. See [this POSIX discussion] which ultimately didn't 7 | //! succeed in adding support to POSIX. 8 | //! 9 | //! [`std::os::unix::net`]: https://doc.rust-lang.org/std/os/unix/net/ 10 | //! [this POSIX discussion]: https://www.austingroupbugs.net/view.php?id=980 11 | 12 | mod incoming; 13 | mod unix_datagram; 14 | mod unix_listener; 15 | mod unix_stream; 16 | 17 | pub use incoming::*; 18 | pub use unix_datagram::*; 19 | pub use unix_listener::*; 20 | pub use unix_stream::*; 21 | 22 | pub use std::os::unix::net::SocketAddr; 23 | -------------------------------------------------------------------------------- /cap-std/src/time/mod.rs: -------------------------------------------------------------------------------- 1 | //! A capability-based clock API modeled after [`std::time`]. 2 | //! 3 | //! This corresponds to [`std::time`]. 4 | //! 5 | //! Instead of [`std::time`]'s methods which return the current time, this 6 | //! crate has methods on [`SystemClock`] and [`MonotonicClock`]. 7 | 8 | pub use cap_primitives::time::{ 9 | Duration, Instant, MonotonicClock, SystemClock, SystemTime, SystemTimeError, 10 | }; 11 | -------------------------------------------------------------------------------- /cap-tempfile/COPYRIGHT: -------------------------------------------------------------------------------- 1 | Short version for non-lawyers: 2 | 3 | `cap-tempfile` is triple-licensed under Apache 2.0 with the LLVM Exception, 4 | Apache 2.0, and MIT terms. 5 | 6 | 7 | Longer version: 8 | 9 | Copyrights in the `cap-tempfile` project are retained by their contributors. 10 | No copyright assignment is required to contribute to the `cap-tempfile` 11 | project. 12 | 13 | Some files include code derived from Rust's `libstd`; see the comments in 14 | the code for details. 15 | 16 | Except as otherwise noted (below and/or in individual files), `cap-tempfile` 17 | is licensed under: 18 | 19 | - the Apache License, Version 2.0, with the LLVM Exception 20 | or 21 | 22 | - the Apache License, Version 2.0 23 | or 24 | , 25 | - or the MIT license 26 | or 27 | , 28 | 29 | at your option. 30 | -------------------------------------------------------------------------------- /cap-tempfile/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cap-tempfile" 3 | version = "3.4.4" 4 | description = "Capability-based temporary directories" 5 | authors = [ 6 | "Dan Gohman ", 7 | "Jakub Konka ", 8 | ] 9 | license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" 10 | keywords = ["filesystem", "tmpfile", "tempfile"] 11 | categories = ["filesystem"] 12 | repository = "https://github.com/bytecodealliance/cap-std" 13 | edition = "2021" 14 | 15 | [dependencies] 16 | cap-std = { path = "../cap-std", version = "^3.4.4" } 17 | uuid = { version = "1.0.0", features = ["v4"] } 18 | camino = { version = "1.0.5", optional = true } 19 | 20 | [target.'cfg(target_os = "emscripten")'.dependencies] 21 | rand = "0.8.1" 22 | 23 | [target.'cfg(not(windows))'.dependencies] 24 | rustix = { version = "1.0.0" } 25 | 26 | [target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies] 27 | rustix-linux-procfs = "0.1.1" 28 | 29 | [target.'cfg(windows)'.dev-dependencies.windows-sys] 30 | version = ">=0.52, <=0.59" 31 | features = [ 32 | "Win32_Foundation", 33 | ] 34 | 35 | [features] 36 | default = [] 37 | fs_utf8 = ["cap-std/fs_utf8", "camino"] 38 | arf_strings = ["cap-std/arf_strings"] 39 | -------------------------------------------------------------------------------- /cap-tempfile/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /cap-tempfile/README.md: -------------------------------------------------------------------------------- 1 |
2 |

cap-tempfile

3 | 4 |

5 | Capability-based temporary directories 6 |

7 | 8 |

9 | Github Actions CI Status 10 | crates.io page 11 | docs.rs docs 12 |

13 |
14 | 15 | The `cap-tempfile` crate provides utilities for creating temporary directories 16 | via the [`tempfile`] crate, but which provide [`Dir`]s instead of `Path`s. 17 | 18 | [`tempfile`]: https://crates.io/crates/tempfile 19 | [`Dir`]: https://docs.rs/cap-std/latest/cap_std/fs/struct.Dir.html 20 | -------------------------------------------------------------------------------- /cap-time-ext/COPYRIGHT: -------------------------------------------------------------------------------- 1 | Short version for non-lawyers: 2 | 3 | `cap-time-ext` is triple-licensed under Apache 2.0 with the LLVM Exception, 4 | Apache 2.0, and MIT terms. 5 | 6 | 7 | Longer version: 8 | 9 | Copyrights in the `cap-time-ext` project are retained by their contributors. 10 | No copyright assignment is required to contribute to the `cap-time-ext` 11 | project. 12 | 13 | Some files include code derived from Rust's `libstd`; see the comments in 14 | the code for details. 15 | 16 | Except as otherwise noted (below and/or in individual files), `cap-time-ext` 17 | is licensed under: 18 | 19 | - the Apache License, Version 2.0, with the LLVM Exception 20 | or 21 | 22 | - the Apache License, Version 2.0 23 | or 24 | , 25 | - or the MIT license 26 | or 27 | , 28 | 29 | at your option. 30 | -------------------------------------------------------------------------------- /cap-time-ext/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cap-time-ext" 3 | version = "3.4.4" 4 | description = "Extension traits for `SystemClock` and `MonotonicClock`" 5 | authors = [ 6 | "Dan Gohman ", 7 | "Jakub Konka ", 8 | ] 9 | license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" 10 | keywords = ["time"] 11 | categories = ["date-and-time"] 12 | repository = "https://github.com/bytecodealliance/cap-std" 13 | edition = "2021" 14 | 15 | [dependencies] 16 | ambient-authority = "0.0.2" 17 | cap-primitives = { path = "../cap-primitives", version = "^3.4.4" } 18 | cap-std = { path = "../cap-std", optional = true, version = "^3.4.4" } 19 | iana-time-zone = "0.1.57" 20 | 21 | [target.'cfg(not(windows))'.dependencies] 22 | rustix = { version = "1.0.0", features = ["time"] } 23 | 24 | [target.'cfg(windows)'.dependencies] 25 | once_cell = "1.5.2" 26 | winx = "0.36.0" 27 | -------------------------------------------------------------------------------- /cap-time-ext/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /cap-time-ext/README.md: -------------------------------------------------------------------------------- 1 |
2 |

cap-time-ext

3 | 4 |

5 | Extension traits for `SystemClock` and `MonotonicClock` 6 |

7 | 8 |

9 | Github Actions CI Status 10 | crates.io page 11 | docs.rs docs 12 |

13 |
14 | 15 | The `cap-time-ext` crate provides extension traits adding extra features 16 | to clock types such as [`SystemClock`] and [`MonotonicClock`]. 17 | 18 | [`SystemClock`]: https://docs.rs/cap-std/latest/cap_std/time/struct.SystemClock.html 19 | [`MonotonicClock`]: https://docs.rs/cap-std/latest/cap_std/time/struct.MonotonicClock.html 20 | -------------------------------------------------------------------------------- /cap-time-ext/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Extension traits for `SystemClock` and `MonotonicClock` 2 | 3 | #![deny(missing_docs)] 4 | #![forbid(unsafe_code)] 5 | #![doc( 6 | html_logo_url = "https://raw.githubusercontent.com/bytecodealliance/cap-std/main/media/cap-std.svg" 7 | )] 8 | #![doc( 9 | html_favicon_url = "https://raw.githubusercontent.com/bytecodealliance/cap-std/main/media/cap-std.ico" 10 | )] 11 | 12 | mod monotonic_clock; 13 | mod system_clock; 14 | mod timezone; 15 | 16 | pub use monotonic_clock::MonotonicClockExt; 17 | pub use system_clock::SystemClockExt; 18 | pub use timezone::{Timezone, TimezoneError}; 19 | -------------------------------------------------------------------------------- /cap-time-ext/src/monotonic_clock.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(windows))] 2 | use rustix::time::{clock_getres, ClockId}; 3 | use std::time; 4 | use std::time::Duration; 5 | #[cfg(windows)] 6 | use {once_cell::sync::Lazy, winx::time::perf_counter_frequency}; 7 | 8 | /// Extension trait for `cap_std::time::MonotonicClock`. 9 | pub trait MonotonicClockExt { 10 | /// A monotonic clock datapoint. 11 | type Instant; 12 | 13 | /// Similar to `MonotonicClock::now`, but takes an additional `precision` 14 | /// parameter allowing callers to inform the implementation when they 15 | /// don't need full precision. The implementation need not make any 16 | /// effort to provide a time with greater precision. 17 | fn now_with(&self, precision: Duration) -> Self::Instant; 18 | 19 | /// Return the resolution of the clock. 20 | fn resolution(&self) -> Duration; 21 | } 22 | 23 | #[cfg(not(windows))] 24 | impl MonotonicClockExt for cap_primitives::time::MonotonicClock { 25 | type Instant = cap_primitives::time::Instant; 26 | 27 | #[cfg(not(target_os = "wasi"))] 28 | #[inline] 29 | fn now_with(&self, _precision: Duration) -> Self::Instant { 30 | // On systems with no optimized form of `clock_gettime`, ignore the 31 | // precision argument. 32 | Self::Instant::from_std(time::Instant::now()) 33 | } 34 | 35 | fn resolution(&self) -> Duration { 36 | let spec = clock_getres(ClockId::Realtime); 37 | Duration::new( 38 | spec.tv_sec.try_into().unwrap(), 39 | spec.tv_nsec.try_into().unwrap(), 40 | ) 41 | } 42 | } 43 | 44 | #[cfg(windows)] 45 | impl MonotonicClockExt for cap_primitives::time::MonotonicClock { 46 | type Instant = cap_primitives::time::Instant; 47 | 48 | #[inline] 49 | fn now_with(&self, _precision: Duration) -> Self::Instant { 50 | // On systems with no optimized form of `clock_gettime`, ignore the 51 | // precision argument. 52 | Self::Instant::from_std(time::Instant::now()) 53 | } 54 | 55 | fn resolution(&self) -> Duration { 56 | Duration::new(0, (*PERF_COUNTER_RES).try_into().unwrap()) 57 | } 58 | } 59 | 60 | #[cfg(windows)] 61 | static PERF_COUNTER_RES: Lazy = 62 | Lazy::new(|| 1_000_000_000 / perf_counter_frequency().unwrap()); 63 | -------------------------------------------------------------------------------- /cap-time-ext/src/system_clock.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(windows))] 2 | use rustix::time::{clock_getres, ClockId}; 3 | use std::time::{self, Duration}; 4 | 5 | /// Extension trait for `cap_std::time::SystemClock`. 6 | pub trait SystemClockExt { 7 | /// A system clock datapoint. 8 | type SystemTime; 9 | 10 | /// Similar to `SystemClock::now`, but takes an additional `precision` 11 | /// parameter allowing callers to inform the implementation when they 12 | /// don't need full precision. The implementation need not make any 13 | /// effort to provide a time with greater precision. 14 | fn now_with(&self, precision: Duration) -> Self::SystemTime; 15 | 16 | /// Return the resolution of the clock. 17 | fn resolution(&self) -> Duration; 18 | } 19 | 20 | #[cfg(not(windows))] 21 | impl SystemClockExt for cap_primitives::time::SystemClock { 22 | type SystemTime = cap_primitives::time::SystemTime; 23 | 24 | #[cfg(not(target_os = "wasi"))] 25 | #[inline] 26 | fn now_with(&self, _precision: Duration) -> Self::SystemTime { 27 | // On systems with no optimized form of `clock_gettime`, ignore the 28 | // precision argument. 29 | Self::SystemTime::from_std(time::SystemTime::now()) 30 | } 31 | 32 | fn resolution(&self) -> Duration { 33 | let spec = clock_getres(ClockId::Realtime); 34 | Duration::new( 35 | spec.tv_sec.try_into().unwrap(), 36 | spec.tv_nsec.try_into().unwrap(), 37 | ) 38 | } 39 | } 40 | 41 | #[cfg(windows)] 42 | impl SystemClockExt for cap_primitives::time::SystemClock { 43 | type SystemTime = cap_primitives::time::SystemTime; 44 | 45 | #[inline] 46 | fn now_with(&self, _precision: Duration) -> Self::SystemTime { 47 | // On systems with no optimized form of `clock_gettime`, ignore the 48 | // precision argument. 49 | Self::SystemTime::from_std(time::SystemTime::now()) 50 | } 51 | 52 | fn resolution(&self) -> Duration { 53 | // According to [this blog post], the system timer resolution is 55ms 54 | // or 10ms. Use the more conservative of the two. 55 | // 56 | // [this blog post]: https://devblogs.microsoft.com/oldnewthing/20170921-00/?p=97057 57 | Duration::new(0, 55_000_000) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /cap-time-ext/src/timezone.rs: -------------------------------------------------------------------------------- 1 | use ambient_authority::AmbientAuthority; 2 | use iana_time_zone::get_timezone; 3 | 4 | /// A reference to a timezone resource. 5 | pub struct Timezone(()); 6 | 7 | /// An error type returned by `Timezone::timezone_name`. 8 | #[derive(Debug)] 9 | pub struct TimezoneError(String); 10 | 11 | impl std::error::Error for TimezoneError {} 12 | 13 | impl std::fmt::Display for TimezoneError { 14 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 15 | self.0.fmt(f) 16 | } 17 | } 18 | 19 | impl Timezone { 20 | /// Constructs a new instance of `Self`. 21 | /// 22 | /// # Ambient Authority 23 | /// 24 | /// This uses ambient authority to accesses clocks. 25 | #[inline] 26 | pub const fn new(ambient_authority: AmbientAuthority) -> Self { 27 | let _ = ambient_authority; 28 | Self(()) 29 | } 30 | 31 | /// Returns the combined date and time with timezone. 32 | /// 33 | /// Converts NaiveTime to DateTime 34 | #[inline] 35 | pub fn timezone_name(&self) -> Result { 36 | get_timezone().map_err(|e| TimezoneError(e.to_string())) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/kv-cli.rs: -------------------------------------------------------------------------------- 1 | //! A simple key-value store that stores data in a project data directory. 2 | //! 3 | //! Keys are filesystem paths, which isn't a great idea in general, because 4 | //! it bubbles up filesystem idiosyncrasies such as the case sensitivity 5 | //! scheme the filesystem uses, but it makes a simple illustration of the 6 | //! `cap-std` API. 7 | 8 | use cap_directories::{ambient_authority, ProjectDirs}; 9 | use std::env::args; 10 | use std::path::PathBuf; 11 | use std::str; 12 | 13 | fn main() -> anyhow::Result<()> { 14 | // Parse command-line arguments. 15 | let mut args = args(); 16 | args.next(); // skip the program name 17 | let key: PathBuf = args.next().ok_or_else(usage)?.into(); 18 | let value = args.next(); 19 | if args.next().is_some() { 20 | return Err(usage()); 21 | } 22 | 23 | // Obtain the `data_dir` for this program. 24 | let project_dirs = ProjectDirs::from( 25 | "com.example", 26 | "Example Organization", 27 | "Cap-std Key-Value CLI Example", 28 | ambient_authority(), 29 | ) 30 | .ok_or_else(no_project_dirs)?; 31 | let mut data_dir = project_dirs.data_dir()?; 32 | 33 | if let Some(value) = value { 34 | // `kv-cli key value` -- set the value of `key` to `value` 35 | 36 | // If the key contains separators, create the directory. 37 | let parent = key.parent(); 38 | let file_name = key.file_name().ok_or_else(need_filename)?; 39 | if let Some(parent) = parent { 40 | if !parent.as_os_str().is_empty() { 41 | data_dir.create_dir_all(parent)?; 42 | data_dir = data_dir.open_dir(parent)?; 43 | } 44 | } 45 | 46 | // Write the value. 47 | data_dir.write(file_name, value)?; 48 | } else { 49 | // `kv-cli key` -- get the value of `key` and print it. 50 | println!("{}", str::from_utf8(&data_dir.read(key)?)?); 51 | } 52 | 53 | Ok(()) 54 | } 55 | 56 | fn usage() -> anyhow::Error { 57 | anyhow::Error::msg("usage: kv-cli []") 58 | } 59 | 60 | fn need_filename() -> anyhow::Error { 61 | anyhow::Error::msg("kv-cli key must end with a filename component (and not `..`)") 62 | } 63 | 64 | fn no_project_dirs() -> anyhow::Error { 65 | anyhow::Error::msg("kv-cli requires a home directory") 66 | } 67 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | artifacts 2 | corpus 3 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cap-std-fuzz" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [package.metadata] 8 | cargo-fuzz = true 9 | 10 | [dependencies] 11 | libfuzzer-sys = "0.4.0" 12 | arbitrary = { version = "1.0.0", features = ["derive"] } 13 | tempfile = "3.1.0" 14 | cap-primitives = { path = "../cap-primitives", features = ["arbitrary"] } 15 | # Depend on io-lifetimes with default features, as the fuzzing framework 16 | # seems to add a dependency on `io_lifetimes::OwnedFd::drop` even when the 17 | # code itself doesn't have one. 18 | io-lifetimes = "2.0.0" 19 | 20 | [[bin]] 21 | name = "cap-primitives" 22 | path = "fuzz_targets/cap-primitives.rs" 23 | test = false 24 | doc = false 25 | 26 | # Work around https://github.com/rust-lang/cargo/issues/8338 27 | [workspace] 28 | -------------------------------------------------------------------------------- /fuzz/README.md: -------------------------------------------------------------------------------- 1 | # Fuzz targets for `cap-std` 2 | 3 | This crate defines various `libFuzzer` targets for `cap-std` which can be run 4 | via `cargo-fuzz` plugin. 5 | 6 | Currently, there is only a simple fuzzer for the `cap-primitives` crate which 7 | constructs random paths and attempts random filesystem operations on them. If 8 | `cap-primitives`' sandbox is working as intended, these operations either stay 9 | within a temporary directory or fail. Many of the operations in `cap-primitives` 10 | have backup checks in `cfg(racy_asserts)` builds, to diagnose sandbox escapes. 11 | 12 | Caution is recommended when running this fuzzer, since it is a filesystem 13 | fuzzer, and if it should find a way to escape the sandbox and avoid the backup 14 | checks, it could cause data loss. 15 | 16 | ## Example: fuzzing `cap-primitives` 17 | 18 | 1. Install `cargo-fuzz` plugin: 19 | 20 | ``` 21 | cargo install cargo-fuzz 22 | ``` 23 | 24 | 2. Install `nightly` toolchain: 25 | 26 | ``` 27 | rustup toolchain add nightly 28 | ``` 29 | 30 | 3. Fuzz away: 31 | 32 | ``` 33 | env 'RUSTFLAGS=--cfg racy_asserts' cargo +nightly fuzz run cap-primitives 34 | ``` 35 | -------------------------------------------------------------------------------- /media/cap-std.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytecodealliance/cap-std/54716a1d87e3141172303a28bdfd23e42e1037cf/media/cap-std.ico -------------------------------------------------------------------------------- /tests/dir-entry-ext.rs: -------------------------------------------------------------------------------- 1 | // This file contains tests for `cap_fs_ext::FileTypeExt`. 2 | 3 | #![cfg_attr(all(windows, windows_by_handle), feature(windows_by_handle))] 4 | 5 | #[macro_use] 6 | mod sys_common; 7 | 8 | use sys_common::io::tmpdir; 9 | 10 | #[test] 11 | fn test_dir_entry_ext() { 12 | let tmpdir = tmpdir(); 13 | check!(tmpdir.create("a")); 14 | 15 | // First try with the regular `metadata()`. All nones. 16 | #[cfg(all(windows, windows_by_handle))] 17 | for entry in check!(tmpdir.entries()) { 18 | use cap_std::fs::MetadataExt; 19 | let entry = check!(entry); 20 | assert!(check!(entry.metadata()).volume_serial_number().is_none()); 21 | assert!(check!(entry.metadata()).number_of_links().is_none()); 22 | assert!(check!(entry.metadata()).file_index().is_none()); 23 | } 24 | 25 | // Now try with `full_metadata()`. 26 | for entry in check!(tmpdir.entries()) { 27 | use cap_fs_ext::{DirEntryExt, MetadataExt}; 28 | let entry = check!(entry); 29 | check!(entry.full_metadata()).dev(); 30 | check!(entry.full_metadata()).nlink(); 31 | check!(entry.full_metadata()).ino(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/file-type-ext.rs: -------------------------------------------------------------------------------- 1 | // This file contains tests for `cap_fs_ext::FileTypeExt`. 2 | 3 | #[macro_use] 4 | mod sys_common; 5 | 6 | use cap_fs_ext::FileTypeExt; 7 | use sys_common::io::tmpdir; 8 | 9 | #[test] 10 | fn test_file_type_ext() { 11 | let tmpdir = tmpdir(); 12 | let a = check!(tmpdir.create("a")); 13 | 14 | let tmpdir_metadata = check!(tmpdir.dir_metadata()); 15 | let a_metadata = check!(a.metadata()); 16 | 17 | assert!(!tmpdir_metadata.file_type().is_char_device()); 18 | assert!(!a_metadata.file_type().is_char_device()); 19 | 20 | assert!(!tmpdir_metadata.file_type().is_block_device()); 21 | assert!(!a_metadata.file_type().is_block_device()); 22 | 23 | assert!(!tmpdir_metadata.file_type().is_fifo()); 24 | assert!(!a_metadata.file_type().is_fifo()); 25 | 26 | assert!(!tmpdir_metadata.file_type().is_socket()); 27 | assert!(!a_metadata.file_type().is_socket()); 28 | 29 | assert!(tmpdir_metadata.file_type().is_dir()); 30 | assert!(!a_metadata.file_type().is_dir()); 31 | 32 | assert!(!tmpdir_metadata.file_type().is_file()); 33 | assert!(a_metadata.file_type().is_file()); 34 | 35 | assert!(!tmpdir_metadata.file_type().is_symlink()); 36 | assert!(!a_metadata.file_type().is_symlink()); 37 | } 38 | -------------------------------------------------------------------------------- /tests/is-file-read-write.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod sys_common; 3 | 4 | use cap_fs_ext::{IsFileReadWrite, OpenOptions}; 5 | use sys_common::io::tmpdir; 6 | 7 | #[test] 8 | fn basic_is_file_read_write() { 9 | let tmpdir = tmpdir(); 10 | 11 | let file = check!(tmpdir.open_with( 12 | "file", 13 | OpenOptions::new() 14 | .create(true) 15 | .truncate(true) 16 | .write(true) 17 | .read(true) 18 | )); 19 | assert_eq!(check!(file.is_file_read_write()), (true, true)); 20 | 21 | let file = check!(tmpdir.open_with("file", OpenOptions::new().append(true).read(true))); 22 | assert_eq!(check!(file.is_file_read_write()), (true, true)); 23 | 24 | let file = check!(tmpdir.open_with( 25 | "file", 26 | OpenOptions::new() 27 | .create(true) 28 | .truncate(true) 29 | .write(true) 30 | .read(false) 31 | )); 32 | assert_eq!(check!(file.is_file_read_write()), (false, true)); 33 | 34 | let file = check!(tmpdir.open_with( 35 | "file", 36 | OpenOptions::new() 37 | .create(false) 38 | .truncate(false) 39 | .write(false) 40 | .read(true) 41 | )); 42 | assert_eq!(check!(file.is_file_read_write()), (true, false)); 43 | } 44 | -------------------------------------------------------------------------------- /tests/issue-22577.rs: -------------------------------------------------------------------------------- 1 | // This test module derived from Rust's src/test/ui/issues/issue-22577.rs 2 | // at revision 108e90ca78f052c0c1c49c42a22c85620be19712. 3 | 4 | // run-pass 5 | #![allow(dead_code)] 6 | // pretty-expanded FIXME #23616 7 | // ignore-cloudabi no std::fs 8 | 9 | use cap_std::{fs, net}; 10 | 11 | fn assert_both() {} 12 | fn assert_send() {} 13 | 14 | #[test] 15 | fn issue_22577() { 16 | assert_both::(); 17 | assert_both::(); 18 | assert_both::(); 19 | assert_both::(); 20 | assert_both::(); 21 | assert_both::(); 22 | 23 | assert_both::(); 24 | assert_both::(); 25 | assert_both::(); 26 | assert_both::(); 27 | assert_both::(); 28 | assert_both::(); 29 | assert_both::(); 30 | assert_both::(); 31 | } 32 | -------------------------------------------------------------------------------- /tests/net.rs: -------------------------------------------------------------------------------- 1 | // This file is derived from Rust's library/std/src/net/test.rs at 2 | // revision 377d1a984cd2a53327092b90aa1d8b7e22d1e347. 3 | 4 | #![allow(warnings)] // not used on emscripten 5 | 6 | use cap_std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}; 7 | use std::env; 8 | use std::sync::atomic::{AtomicUsize, Ordering}; 9 | 10 | static PORT: AtomicUsize = AtomicUsize::new(0); 11 | 12 | pub fn next_test_ip4() -> SocketAddr { 13 | let port = PORT.fetch_add(1, Ordering::SeqCst) as u16 + base_port(); 14 | SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), port)) 15 | } 16 | 17 | pub fn next_test_ip6() -> SocketAddr { 18 | let port = PORT.fetch_add(1, Ordering::SeqCst) as u16 + base_port(); 19 | SocketAddr::V6(SocketAddrV6::new( 20 | Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 21 | port, 22 | 0, 23 | 0, 24 | )) 25 | } 26 | 27 | pub fn sa4(a: Ipv4Addr, p: u16) -> SocketAddr { 28 | SocketAddr::V4(SocketAddrV4::new(a, p)) 29 | } 30 | 31 | pub fn sa6(a: Ipv6Addr, p: u16) -> SocketAddr { 32 | SocketAddr::V6(SocketAddrV6::new(a, p, 0, 0)) 33 | } 34 | 35 | pub fn tsa(a: A) -> Result, String> { 36 | match a.to_socket_addrs() { 37 | Ok(a) => Ok(a.collect()), 38 | Err(e) => Err(e.to_string()), 39 | } 40 | } 41 | 42 | // The bots run multiple builds at the same time, and these builds 43 | // all want to use ports. This function figures out which workspace 44 | // it is running in and assigns a port range based on it. 45 | fn base_port() -> u16 { 46 | let cwd = if cfg!(target_env = "sgx") { 47 | String::from("sgx") 48 | } else { 49 | env::current_dir() 50 | .unwrap() 51 | .into_os_string() 52 | .into_string() 53 | .unwrap() 54 | }; 55 | let dirs = [ 56 | "32-opt", 57 | "32-nopt", 58 | "musl-64-opt", 59 | "cross-opt", 60 | "64-opt", 61 | "64-nopt", 62 | "64-opt-vg", 63 | "64-debug-opt", 64 | "all-opt", 65 | "snap3", 66 | "dist", 67 | "sgx", 68 | ]; 69 | dirs.iter() 70 | .enumerate() 71 | .find(|&(_, dir)| cwd.contains(dir)) 72 | .map(|p| p.0) 73 | .unwrap_or(0) as u16 74 | * 1000 75 | + 19600 76 | } 77 | -------------------------------------------------------------------------------- /tests/open-ambient.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod sys_common; 3 | 4 | use cap_std::ambient_authority; 5 | use cap_std::fs::{Dir, File}; 6 | 7 | #[test] 8 | fn test_open_ambient() { 9 | let _ = File::open_ambient("Cargo.toml", ambient_authority()).unwrap(); 10 | } 11 | 12 | #[test] 13 | fn test_create_ambient() { 14 | let dir = tempfile::tempdir().unwrap(); 15 | let foo_path = dir.path().join("foo"); 16 | let _ = File::create_ambient(&foo_path, ambient_authority()).unwrap(); 17 | let _ = File::open_ambient(&foo_path, ambient_authority()).unwrap(); 18 | let _ = File::create_ambient(&foo_path, ambient_authority()).unwrap(); 19 | } 20 | 21 | #[test] 22 | fn test_create_dir_ambient() { 23 | let dir = tempfile::tempdir().unwrap(); 24 | let foo_path = dir.path().join("foo"); 25 | Dir::create_ambient_dir_all(&foo_path, ambient_authority()).unwrap(); 26 | let foo = Dir::open_ambient_dir(&foo_path, ambient_authority()).unwrap(); 27 | let base = foo.open_parent_dir(ambient_authority()).unwrap(); 28 | let _foo_again = base.open_dir("foo").unwrap(); 29 | } 30 | -------------------------------------------------------------------------------- /tests/rand.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn test_std_rng_from_entropy() { 3 | let rng = cap_rand::std_rng_from_entropy(cap_rand::ambient_authority()); 4 | assert_eq!(rng.clone(), rng); 5 | } 6 | -------------------------------------------------------------------------------- /tests/rename-directory.rs: -------------------------------------------------------------------------------- 1 | // This test module derived from Rust's 2 | // src/test/ui-fulldeps/rename-directory.rs at revision 3 | // 108e90ca78f052c0c1c49c42a22c85620be19712. 4 | 5 | // run-pass 6 | 7 | #![allow(unused_must_use)] 8 | #![allow(unused_imports)] 9 | // This test can't be a unit test in std, 10 | // because it needs `TempDir`, which is in extra 11 | 12 | // ignore-cross-compile 13 | 14 | mod sys_common; 15 | 16 | use cap_std::fs::{self, File}; 17 | use std::env; 18 | use std::ffi::CString; 19 | use std::path::{Path, PathBuf}; 20 | use sys_common::io::tmpdir; 21 | 22 | #[test] 23 | fn rename_directory() { 24 | let tmpdir = tmpdir(); 25 | let old_path = Path::new("foo/bar/baz"); 26 | tmpdir.create_dir_all(&old_path).unwrap(); 27 | let test_file = &old_path.join("temp.txt"); 28 | 29 | tmpdir.create(test_file).unwrap(); 30 | 31 | let new_path = Path::new("quux/blat"); 32 | tmpdir.create_dir_all(&new_path).unwrap(); 33 | tmpdir.rename(&old_path, &tmpdir, &new_path.join("newdir")); 34 | assert!(tmpdir.is_dir(new_path.join("newdir"))); 35 | assert!(tmpdir.exists(new_path.join("newdir/temp.txt"))); 36 | } 37 | -------------------------------------------------------------------------------- /tests/reopendir.rs: -------------------------------------------------------------------------------- 1 | //! Tests for various forms of reopening a directory handle. 2 | 3 | #[macro_use] 4 | mod sys_common; 5 | 6 | use sys_common::io::tmpdir; 7 | 8 | #[test] 9 | fn reopendir_a() { 10 | let tmpdir = tmpdir(); 11 | check!(tmpdir.create_dir_all("dir/inner")); 12 | 13 | let inner = check!(tmpdir.open_dir("dir/inner")); 14 | 15 | check!(inner.open_dir(".")); 16 | } 17 | 18 | #[test] 19 | fn reopendir_b() { 20 | let tmpdir = tmpdir(); 21 | check!(tmpdir.create_dir_all("dir/inner")); 22 | 23 | let inner = check!(tmpdir.open_dir("dir/inner")); 24 | 25 | check!(inner.open_dir("./")); 26 | } 27 | 28 | #[test] 29 | fn reopendir_c() { 30 | let tmpdir = tmpdir(); 31 | check!(tmpdir.create_dir_all("dir/inner")); 32 | 33 | let inner = check!(tmpdir.open_dir("dir/inner")); 34 | 35 | check!(inner.open_dir("./.")); 36 | } 37 | 38 | #[test] 39 | fn reopendir_d() { 40 | let tmpdir = tmpdir(); 41 | check!(tmpdir.create_dir_all("dir/inner")); 42 | 43 | let _inner = check!(tmpdir.open_dir("dir/inner")); 44 | 45 | check!(tmpdir.open_dir("dir/inner")); 46 | } 47 | 48 | #[test] 49 | fn reopendir_e() { 50 | let tmpdir = tmpdir(); 51 | check!(tmpdir.create_dir_all("dir/inner")); 52 | 53 | let _inner = check!(tmpdir.open_dir("dir/inner")); 54 | 55 | check!(tmpdir.open_dir("dir/inner/.")); 56 | } 57 | 58 | #[test] 59 | fn reopendir_f() { 60 | let tmpdir = tmpdir(); 61 | check!(tmpdir.create_dir_all("dir/inner")); 62 | 63 | let _inner = check!(tmpdir.open_dir("dir/inner")); 64 | 65 | check!(tmpdir.open_dir("dir/inner/")); 66 | } 67 | -------------------------------------------------------------------------------- /tests/root.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod sys_common; 3 | 4 | use cap_std::ambient_authority; 5 | use cap_std::fs::Dir; 6 | use std::fs; 7 | use std::path::Component; 8 | 9 | #[test] 10 | fn open_root() { 11 | let root = Dir::open_ambient_dir(Component::RootDir.as_os_str(), ambient_authority()) 12 | .expect("expect to be able to open the root directory"); 13 | error_contains!( 14 | root.read_dir(Component::ParentDir.as_os_str()), 15 | "a path led outside of the filesystem" 16 | ); 17 | } 18 | 19 | /// Attempt to remove the root directory, which should fail, and check that the 20 | /// error message is as expected. 21 | #[test] 22 | fn remove_root() { 23 | let _observed = { 24 | let root = Dir::open_ambient_dir(Component::RootDir.as_os_str(), ambient_authority()) 25 | .expect("expect to be able to open the root directory"); 26 | root.remove_open_dir().unwrap_err() 27 | }; 28 | let _expected = fs::remove_dir(Component::RootDir.as_os_str()).unwrap_err(); 29 | // TODO: Investigate why these asserts spuriously fail on Windows and QEMU. 30 | //assert_eq!(expected.to_string(), observed.to_string()); 31 | //assert_eq!(expected.kind(), observed.kind()); 32 | } 33 | -------------------------------------------------------------------------------- /tests/sys/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | #[cfg(unix)] 4 | mod unix; 5 | 6 | #[cfg(unix)] 7 | pub use self::unix::*; 8 | -------------------------------------------------------------------------------- /tests/sys/unix/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod weak; 2 | -------------------------------------------------------------------------------- /tests/sys_common/io.rs: -------------------------------------------------------------------------------- 1 | use cap_tempfile::tempdir; 2 | 3 | #[cfg(feature = "fs_utf8")] 4 | pub use cap_tempfile::utf8::TempDir as TempDirUtf8; 5 | pub use cap_tempfile::TempDir; 6 | 7 | #[allow(unused)] 8 | pub fn tmpdir() -> TempDir { 9 | use cap_tempfile::ambient_authority; 10 | 11 | // It's ok to call `ambient_authority()` here, rather than take an 12 | // `AmbientAuthority` argument, because this function is only used 13 | // by tests. 14 | tempdir(ambient_authority()).expect("expected to be able to create a temporary directory") 15 | } 16 | 17 | #[cfg(feature = "fs_utf8")] 18 | #[allow(unused)] 19 | pub fn tmpdir_utf8() -> TempDirUtf8 { 20 | use cap_tempfile::ambient_authority; 21 | use cap_tempfile::utf8::tempdir; 22 | 23 | // It's ok to call `ambient_authority()` here, rather than take an 24 | // `AmbientAuthority` argument, because this function is only used 25 | // by tests. 26 | tempdir(ambient_authority()).expect("expected to be able to create a temporary directory") 27 | } 28 | --------------------------------------------------------------------------------