cntr-fuse-0.4.2/.cargo_vcs_info.json0000644000000001360000000000100127450ustar { "git": { "sha1": "1783f799adddef9cf321fb9315e00e007bef4361" }, "path_in_vcs": "" }cntr-fuse-0.4.2/.cirrus.yml000064400000000000000000000011251046102023000136440ustar 00000000000000freebsd_instance: image_family: freebsd-12-1 task: name: FreeBSD 12.1 cargo_cache: folder: $CARGO_HOME/registry fingerprint_script: cat Cargo.lock || echo "" setup_script: - pkg install -y pkgconf fusefs-libs - fetch https://sh.rustup.rs -o rustup.sh - sh rustup.sh -y build_script: - . $HOME/.cargo/env - cargo build --all --all-targets doc_script: - . $HOME/.cargo/env - cargo doc --all --no-deps --all-features test_script: - . $HOME/.cargo/env - cargo test --all --all-targets before_cache_script: rm -rf $CARGO_HOME/registry/index cntr-fuse-0.4.2/.github/dependabot.yml000064400000000000000000000003171046102023000157260ustar 00000000000000version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" - package-ecosystem: "cargo" directory: "/" schedule: interval: "weekly" cntr-fuse-0.4.2/.github/workflows/ci.yml000064400000000000000000000022611046102023000162510ustar 00000000000000name: CI on: [push, pull_request] jobs: test: name: Test runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: - ubuntu-latest rust: - stable steps: - name: Install FUSE (Linux) if: startsWith(matrix.os, 'ubuntu') run: sudo apt-get update && sudo apt-get install -y --no-install-recommends libfuse-dev pkg-config - name: Install Rust toolchain uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} default: true profile: minimal - name: Display version information run: rustup --version; cargo --version; rustc --version - name: Check out repository uses: actions/checkout@v4 - name: Build everything uses: actions-rs/cargo@v1 with: command: build args: --all --all-targets - name: Build documentation uses: actions-rs/cargo@v1 with: command: doc args: --all --no-deps --all-features - name: Run all unit tests uses: actions-rs/cargo@v1 with: command: test args: --all --all-targets cntr-fuse-0.4.2/.gitignore000064400000000000000000000000241046102023000135210ustar 00000000000000/Cargo.lock /target cntr-fuse-0.4.2/CHANGELOG.md000064400000000000000000000047461046102023000133610ustar 00000000000000# Rust FUSE - Changelog ## 0.4.0 - UNRELEASED * Split into `fuse`, `fuse-abi` and `fuse-sys` crate * GitHub repository renamed to `fuse-rs` (previously `rust-fuse`) ## 0.3.1 - 2017-11-08 * Offsets to `read`, `write` and `readdir` methods are signed integers now (breaking change, sorry) * Link `libosxfuse` on macOS, `libfuse` on all other systems ## 0.3.0 - 2017-01-06 * Fix extended attribute handling (`getxattr` and `listxattr` methods changed and `ReplyXattr` was added) * `mount` now also returns a `Result` since it may fail if the session fails to run * Filenames are now passed as `&OsStr` in the filesystem interface * Removed publishing of documentation on GitHub pages. Docs are now available on https://docs.rs/fuse * Add `FileType::Socket` ## 0.2.8 - 2016-07-31 * Documentation of releases is build by CI now and made available at https://zargony.github.io/rust-fuse * Fix `unmount` on BSD systems * Simplified `libfuse` detection with `pkg-config` * `ReplyDirectory::sized` was removed since it was impossible to use it safely ## 0.2.7 - 2015-09-08 * Update to latest Rust stable - no longer needs nightly Rust * A filesystem implementation doesn't need to be `Send` anymore to be mounted synchronously * A filesystem implementation doesn't need to be 'static anymore to be mounted asynchronously * CI tests are covering nightly, beta and stable Rust under OSX and Linux now ## 0.2.6 - 2015-04-23 * Update to latest Rust nightly * Fix mounting of filesystems as non-root on Linux systems ## 0.2.5 - 2015-03-21 * Update to latest Rust nightly * `unmount` returns a `Result` now since unmounting may fail internally * Fix `unmount` on Linux systems * Remove deprecated file types from interface (got rid of `std::old_io`) * Introducing `FileType` ## 0.2.4 - 2015-02-22 * Update to latest Rust nightly * `spawn_mount` returns a `Result` now since starting a new thread may fail * Paths are now passed using `std::path::Path` (got rid of `std::old_path`) * FUSE options are now passed as a slice of `OsStr` rather than a slice of bytes ## 0.2.3 - 2015-01-17 * Update to latest Rust nightly ## 0.2.2 - 2015-01-14 * Update to latest Rust nightly * Ensure that `Reply` is `Send` to support asynchronous processing * Add CI testing under Linux ## 0.2.1 - 2015-01-07 * Update to latest Rust nightly * Use `build.rs` and `pkg-config` to discover `libfuse` / `libosxfuse` ## 0.2.0 - 2014-12-25 Initial release ## pre-0.2.0 - 2013-10-03 No versioning (based on make, cargo and crates.io didn't exist yet) cntr-fuse-0.4.2/Cargo.lock0000644000000231300000000000100107170ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] [[package]] name = "bitflags" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cntr-fuse" version = "0.4.2" dependencies = [ "cntr-fuse-abi", "cntr-fuse-sys", "env_logger", "libc", "log", ] [[package]] name = "cntr-fuse-abi" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea6ecd4a8a57b0262d82c7521f498ecc01f60888091b1eb3b3c70ccafd2a25d" [[package]] name = "cntr-fuse-sys" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfb0da5a62f5c9c384041af4d81ab52c19b0064663d0b5b6626393af20c224f9" dependencies = [ "pkg-config", ] [[package]] name = "env_logger" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" dependencies = [ "humantime", "is-terminal", "log", "regex", "termcolor", ] [[package]] name = "errno" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "hermit-abi" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "is-terminal" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", "rustix", "windows-sys 0.48.0", ] [[package]] name = "libc" version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "linux-raw-sys" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "log" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ "cfg-if", ] [[package]] name = "memchr" version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "pkg-config" version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" [[package]] name = "regex" version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rustix" version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "termcolor" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" dependencies = [ "winapi-util", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.0", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" dependencies = [ "windows_aarch64_gnullvm 0.52.0", "windows_aarch64_msvc 0.52.0", "windows_i686_gnu 0.52.0", "windows_i686_msvc 0.52.0", "windows_x86_64_gnu 0.52.0", "windows_x86_64_gnullvm 0.52.0", "windows_x86_64_msvc 0.52.0", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" cntr-fuse-0.4.2/Cargo.toml0000644000000027060000000000100107500ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "cntr-fuse" version = "0.4.2" authors = ["Jörg Thalheim "] description = "Rust library for filesystems in userspace (FUSE) (fork with functionality needed for the cntr project)" homepage = "https://github.com/Mic92/cntr-fuse" readme = "README.md" keywords = [ "fuse", "filesystem", "system", "bindings", ] categories = [ "api-bindings", "filesystem", ] license = "MIT" repository = "https://github.com/Mic92/cntr-fuse" [[example]] name = "hello" required-features = ["libfuse"] [[example]] name = "null" required-features = ["libfuse"] [dependencies.cntr-fuse-abi] version = "=0.4.1" [dependencies.cntr-fuse-sys] version = "=0.4.1" [dependencies.libc] version = "0.2.51" [dependencies.log] version = "0.4.6" [dev-dependencies.env_logger] version = "0.10.1" [features] default = ["libfuse"] libfuse = ["cntr-fuse-sys/libfuse"] [badges.cirrus-ci] repository = "zargony/fuse-rs" [badges.github] repository = "zargony/fuse-rs" cntr-fuse-0.4.2/Cargo.toml.orig000064400000000000000000000017741046102023000144350ustar 00000000000000[package] name = "cntr-fuse" edition = "2018" version = "0.4.2" authors = ["Jörg Thalheim "] description = "Rust library for filesystems in userspace (FUSE) (fork with functionality needed for the cntr project)" homepage = "https://github.com/Mic92/cntr-fuse" repository = "https://github.com/Mic92/cntr-fuse" readme = "README.md" keywords = ["fuse", "filesystem", "system", "bindings"] categories = ["api-bindings", "filesystem"] license = "MIT" [workspace] members = [".", "fuse-abi", "fuse-sys"] [badges] cirrus-ci = { repository = "zargony/fuse-rs" } github = { repository = "zargony/fuse-rs" } [dependencies] cntr-fuse-abi = { path = "./fuse-abi", version = "=0.4.1" } cntr-fuse-sys = { path = "./fuse-sys", version = "=0.4.1" } libc = "0.2.51" log = "0.4.6" [dev-dependencies] env_logger = "0.10.1" [features] default = ["libfuse"] libfuse = [ "cntr-fuse-sys/libfuse" ] [[example]] name = "hello" required-features = ["libfuse"] [[example]] name = "null" required-features = ["libfuse"] cntr-fuse-0.4.2/LICENSE.md000064400000000000000000000021621046102023000131420ustar 00000000000000The MIT License (MIT) ===================== Copyright © `2013` `Andreas Neuhaus` `https://zargony.com/` Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cntr-fuse-0.4.2/README.md000064400000000000000000000076201046102023000130210ustar 00000000000000# Rust FUSE - Filesystem in Userspace ![Crates.io](https://img.shields.io/crates/l/fuse) [![Crates.io](https://img.shields.io/crates/v/fuse)](https://crates.io/crates/fuse) ## About **fuse-rs** is a [Rust] library crate for easy implementation of [FUSE filesystems][libfuse] in userspace. fuse-rs does not just provide bindings, it is a rewrite of the original FUSE C library to fully take advantage of Rust's architecture. ## Documentation [Crate documentation][documentation] ## Details A working FUSE filesystem consists of three parts: 1. The **kernel driver** that registers as a filesystem and forwards operations into a communication channel to a userspace process that handles them. 1. The **userspace library** (libfuse) that helps the userspace process to establish and run communication with the kernel driver. 1. The **userspace implementation** that actually processes the filesystem operations. The kernel driver is provided by the FUSE project, the userspace implementation needs to be provided by the developer. fuse-rs provides a replacement for the libfuse userspace library between these two. This way, a developer can fully take advantage of the Rust type interface and runtime features when building a FUSE filesystem in Rust. Except for a single setup (mount) function call and a final teardown (unmount) function call to libfuse, everything runs in Rust. ## Dependencies FUSE must be installed to build or run programs that use fuse-rs (i.e. kernel driver and libraries. Some platforms may also require userland utils like `fusermount`). A default installation of FUSE is usually sufficient. To build fuse-rs or any program that depends on it, `pkg-config` needs to be installed as well. ### Linux [FUSE for Linux][libfuse] is available in most Linux distributions and usually called `fuse`. To install on a Debian based system: ```sh sudo apt-get install fuse ``` Install on CentOS: ```sh sudo yum install fuse ``` To build, FUSE libraries and headers are required. The package is usually called `libfuse-dev` or `fuse-devel`. Also `pkg-config` is required for locating libraries and headers. ```sh sudo apt-get install libfuse-dev pkg-config ``` ```sh sudo yum install fuse-devel pkgconfig ``` ### macOS Installer packages can be downloaded from the [FUSE for macOS homepage][FUSE for macOS]. To install using [Homebrew]: ```sh brew cask install osxfuse ``` To install `pkg-config` (required for building only): ```sh brew install pkg-config ``` ### FreeBSD Install packages `fusefs-libs` and `pkgconf`. ```sh pkg install fusefs-libs pkgconf ``` ## Usage Put this in your `Cargo.toml`: ```toml [dependencies] fuse = "0.4" ``` To create a new filesystem, implement the trait `fuse::Filesystem`. See the [documentation] for details or the `examples` directory for some basic examples. ## To Do There's still a lot of stuff to be done. Feel free to contribute. See the [list of issues][issues] on GitHub and search the source files for comments containing "`TODO`" or "`FIXME`" to see what's still missing. ## Compatibility Developed and tested on macOS. Tested under [Linux][libfuse], [macOS][FUSE for macOS] and [FreeBSD][FUSEFS] using stable, beta and nightly [Rust] versions (see [CI] for details). ## Contribution Fork, hack, submit pull request. Make sure to make it useful for the target audience, keep the project's philosophy and Rust coding standards in mind. For larger or essential changes, you may want to open an issue for discussion first. Also remember to update the [Changelog] if your changes are relevant to the users. [issues]: https://github.com/zargony/fuse-rs/issues [documentation]: https://docs.rs/fuse [CI]: https://github.com/zargony/fuse-rs/actions [Rust]: https://rust-lang.org [Homebrew]: https://brew.sh [Changelog]: https://keepachangelog.com/en/1.0.0/ [libfuse]: https://github.com/libfuse/libfuse/ [FUSE for macOS]: https://osxfuse.github.io [FUSEFS]: https://wiki.freebsd.org/FUSEFS cntr-fuse-0.4.2/default.nix000064400000000000000000000002311046102023000136750ustar 00000000000000with import {}; stdenv.mkDerivation { name = "env"; buildInputs = [ bashInteractive cargo rustc pkg-config fuse ]; } cntr-fuse-0.4.2/examples/hello.rs000064400000000000000000000053131046102023000150260ustar 00000000000000use std::env; use std::ffi::OsStr; use std::time::{Duration, UNIX_EPOCH}; use libc::ENOENT; use cntr_fuse::{FileType, FileAttr, Filesystem, Request, ReplyEntry, ReplyAttr, ReplyDirectory, ReplyRead}; const TTL: Duration = Duration::from_secs(1); // 1 second const HELLO_DIR_ATTR: FileAttr = FileAttr { ino: 1, size: 0, blocks: 0, atime: UNIX_EPOCH, // 1970-01-01 00:00:00 mtime: UNIX_EPOCH, ctime: UNIX_EPOCH, crtime: UNIX_EPOCH, kind: FileType::Directory, perm: 0o755, nlink: 2, uid: 501, gid: 20, rdev: 0, flags: 0, }; const HELLO_TXT_CONTENT: &str = "Hello World!\n"; const HELLO_TXT_ATTR: FileAttr = FileAttr { ino: 2, size: 13, blocks: 1, atime: UNIX_EPOCH, // 1970-01-01 00:00:00 mtime: UNIX_EPOCH, ctime: UNIX_EPOCH, crtime: UNIX_EPOCH, kind: FileType::RegularFile, perm: 0o644, nlink: 1, uid: 501, gid: 20, rdev: 0, flags: 0, }; struct HelloFS; impl Filesystem for HelloFS { fn lookup(&mut self, _req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) { if parent == 1 && name.to_str() == Some("hello.txt") { reply.entry(&TTL, &HELLO_TXT_ATTR, 0); } else { reply.error(ENOENT); } } fn getattr(&mut self, _req: &Request, ino: u64, reply: ReplyAttr) { match ino { 1 => reply.attr(&TTL, &HELLO_DIR_ATTR), 2 => reply.attr(&TTL, &HELLO_TXT_ATTR), _ => reply.error(ENOENT), } } fn read(&mut self, _req: &Request, ino: u64, _fh: u64, offset: i64, _size: u32, reply: ReplyRead) { if ino == 2 { reply.data(&HELLO_TXT_CONTENT.as_bytes()[offset as usize..]); } else { reply.error(ENOENT); } } fn readdir(&mut self, _req: &Request, ino: u64, _fh: u64, offset: i64, mut reply: ReplyDirectory) { if ino != 1 { reply.error(ENOENT); return; } let entries = vec![ (1, FileType::Directory, "."), (1, FileType::Directory, ".."), (2, FileType::RegularFile, "hello.txt"), ]; for (i, entry) in entries.into_iter().enumerate().skip(offset as usize) { // i + 1 means the index of the next entry reply.add(entry.0, (i + 1) as i64, entry.1, entry.2); } reply.ok(); } } fn main() { env_logger::init(); let mountpoint = env::args_os().nth(1).unwrap(); let options = ["-o", "ro", "-o", "fsname=hello"] .iter() .map(|o| o.as_ref()) .collect::>(); cntr_fuse::mount(HelloFS, mountpoint, &options).unwrap(); } cntr-fuse-0.4.2/examples/null.rs000064400000000000000000000003541046102023000146750ustar 00000000000000use std::env; use cntr_fuse::Filesystem; struct NullFS; impl Filesystem for NullFS {} fn main() { env_logger::init(); let mountpoint = env::args_os().nth(1).unwrap(); cntr_fuse::mount(NullFS, mountpoint, &[]).unwrap(); } cntr-fuse-0.4.2/src/channel.rs000064400000000000000000000153351046102023000143110ustar 00000000000000//! FUSE kernel driver communication //! //! Raw communication channel to the FUSE kernel driver. use std::io; #[cfg(feature = "libfuse")] use std::ffi::{CString, CStr, OsStr}; use std::path::{PathBuf, Path}; #[cfg(feature = "libfuse")] use cntr_fuse_sys::{fuse_args, fuse_mount_compat25}; #[cfg(feature = "libfuse")] use std::os::unix::ffi::OsStrExt; use libc::{self, c_void, size_t}; #[cfg(feature = "libfuse")] use libc::c_int; use std::os::unix::io::RawFd; use log::error; use crate::reply::ReplySender; /// Helper function to provide options as a fuse_args struct /// (which contains an argc count and an argv pointer) #[cfg(feature = "libfuse")] fn with_fuse_args T>(options: &[&OsStr], f: F) -> T { let mut args = vec![CString::new("fuse-rs").unwrap()]; args.extend(options.iter().map(|s| CString::new(s.as_bytes()).unwrap())); let argptrs: Vec<_> = args.iter().map(|s| s.as_ptr()).collect(); f(&fuse_args { argc: argptrs.len() as i32, argv: argptrs.as_ptr(), allocated: 0 }) } /// A raw communication channel to the FUSE kernel driver #[derive(Debug)] pub struct Channel { mountpoint: PathBuf, pub fd: RawFd } impl Channel { /// Create a new communication channel to the kernel driver by mounting the /// given path. The kernel driver will delegate filesystem operations of /// the given path to the channel. If the channel is dropped, the path is /// unmounted. #[cfg(feature = "libfuse")] pub fn new(mountpoint: &Path, options: &[&OsStr]) -> io::Result { let mountpoint = mountpoint.canonicalize()?; with_fuse_args(options, |args| { let mnt = CString::new(mountpoint.as_os_str().as_bytes())?; let fd = unsafe { fuse_mount_compat25(mnt.as_ptr(), args) }; if fd < 0 { Err(io::Error::last_os_error()) } else { Ok(Channel { mountpoint: mountpoint, fd: fd }) } }) } pub fn new_from_fd(fd: RawFd, mountpoint: &Path) -> io::Result { Ok(Channel { fd: fd, mountpoint: mountpoint.to_path_buf() }) } /// Return path of the mounted filesystem pub fn mountpoint(&self) -> &Path { &self.mountpoint } /// Receives data up to the capacity of the given buffer (can block). pub fn receive(&self, buffer: &mut Vec) -> io::Result<()> { let rc = unsafe { libc::read(self.fd, buffer.as_ptr() as *mut c_void, buffer.capacity() as size_t) }; if rc < 0 { Err(io::Error::last_os_error()) } else { unsafe { buffer.set_len(rc as usize); } Ok(()) } } /// Returns a sender object for this channel. The sender object can be /// used to send to the channel. Multiple sender objects can be used /// and they can safely be sent to other threads. pub fn sender(&self) -> ChannelSender { // Since write/writev syscalls are threadsafe, we can simply create // a sender by using the same fd and use it in other threads. Only // the channel closes the fd when dropped. If any sender is used after // dropping the channel, it'll return an EBADF error. ChannelSender { fd: self.fd } } } impl Drop for Channel { fn drop(&mut self) { // TODO: send ioctl FUSEDEVIOCSETDAEMONDEAD on macOS before closing the fd // Close the communication channel to the kernel driver // (closing it before unnmount prevents sync unmount deadlock) unsafe { libc::close(self.fd); } // Unmount this channel's mount point #[cfg(feature = "libfuse")] let _ = unmount(&self.mountpoint); } } #[derive(Clone, Copy, Debug)] pub struct ChannelSender { fd: RawFd, } impl ChannelSender { /// Send all data in the slice of slice of bytes in a single write (can block). pub fn send(&self, buffer: &[&[u8]]) -> io::Result<()> { let iovecs: Vec<_> = buffer.iter().map(|d| { libc::iovec { iov_base: d.as_ptr() as *mut c_void, iov_len: d.len() as size_t } }).collect(); let rc = unsafe { libc::writev(self.fd, iovecs.as_ptr(), iovecs.len() as RawFd) }; if rc < 0 { Err(io::Error::last_os_error()) } else { Ok(()) } } } impl ReplySender for ChannelSender { fn send(&self, data: &[&[u8]]) { if let Err(err) = ChannelSender::send(self, data) { error!("Failed to send FUSE reply: {}", err); } } } /// Unmount an arbitrary mount point #[cfg(feature = "libfuse")] pub fn unmount(mountpoint: &Path) -> io::Result<()> { // fuse_unmount_compat22 unfortunately doesn't return a status. Additionally, // it attempts to call realpath, which in turn calls into the filesystem. So // if the filesystem returns an error, the unmount does not take place, with // no indication of the error available to the caller. So we call unmount // directly, which is what osxfuse does anyway, since we already converted // to the real path when we first mounted. #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "bitrig", target_os = "netbsd"))] #[inline] fn libc_umount(mnt: &CStr) -> c_int { unsafe { libc::unmount(mnt.as_ptr(), 0) } } #[cfg(not(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "bitrig", target_os = "netbsd")))] #[inline] fn libc_umount(mnt: &CStr) -> c_int { use cntr_fuse_sys::fuse_unmount_compat22; use std::io::ErrorKind::PermissionDenied; let rc = unsafe { libc::umount(mnt.as_ptr()) }; if rc < 0 && io::Error::last_os_error().kind() == PermissionDenied { // Linux always returns EPERM for non-root users. We have to let the // library go through the setuid-root "fusermount -u" to unmount. unsafe { fuse_unmount_compat22(mnt.as_ptr()); } 0 } else { rc } } let mnt = CString::new(mountpoint.as_os_str().as_bytes())?; let rc = libc_umount(&mnt); if rc < 0 { Err(io::Error::last_os_error()) } else { Ok(()) } } #[cfg(feature = "libfuse")] #[cfg(test)] mod test { use super::with_fuse_args; use std::ffi::{CStr, OsStr}; #[test] fn fuse_args() { with_fuse_args(&[OsStr::new("foo"), OsStr::new("bar")], |args| { assert_eq!(args.argc, 3); assert_eq!(unsafe { CStr::from_ptr(*args.argv.offset(0)).to_bytes() }, b"fuse-rs"); assert_eq!(unsafe { CStr::from_ptr(*args.argv.offset(1)).to_bytes() }, b"foo"); assert_eq!(unsafe { CStr::from_ptr(*args.argv.offset(2)).to_bytes() }, b"bar"); }); } } cntr-fuse-0.4.2/src/lib.rs000064400000000000000000000445741046102023000134560ustar 00000000000000//! FUSE userspace library implementation //! //! This is an improved rewrite of the FUSE userspace library (lowlevel interface) to fully take //! advantage of Rust's architecture. The only thing we rely on in the real libfuse are mount //! and unmount calls which are needed to establish a fd to talk to the kernel driver. #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)] #[cfg(feature = "libfuse")] use std::io; use std::ffi::OsStr; use std::path::Path; use std::time::SystemTime; use libc::{c_int, ENOSYS}; pub use cntr_fuse_abi::FUSE_ROOT_ID; pub use cntr_fuse_abi::{fuse_forget_one, consts}; pub use reply::{Reply, ReplyEmpty, ReplyData, ReplyEntry, ReplyAttr, ReplyOpen}; pub use reply::{ReplyWrite, ReplyStatfs, ReplyCreate, ReplyLock, ReplyBmap, ReplyDirectory, ReplyDirectoryPlus}; pub use reply::{ReplyXattr, ReplyIoctl, ReplyLseek, ReplyRead}; #[cfg(target_os = "macos")] pub use reply::ReplyXTimes; pub use request::{Request, UtimeSpec}; pub use session::{Session, BackgroundSession}; mod channel; mod ll; mod reply; mod request; mod session; /// File types #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum FileType { /// Named pipe (S_IFIFO) NamedPipe, /// Character device (S_IFCHR) CharDevice, /// Block device (S_IFBLK) BlockDevice, /// Directory (S_IFDIR) Directory, /// Regular file (S_IFREG) RegularFile, /// Symbolic link (S_IFLNK) Symlink, /// Unix domain socket (S_IFSOCK) Socket, /// Unknown file type Unknown } /// File attributes #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct FileAttr { /// Inode number pub ino: u64, /// Size in bytes pub size: u64, /// Size in blocks pub blocks: u64, /// Time of last access pub atime: SystemTime, /// Time of last modification pub mtime: SystemTime, /// Time of last change pub ctime: SystemTime, /// Time of creation (macOS only) pub crtime: SystemTime, /// Kind of file (directory, file, pipe, etc) pub kind: FileType, /// Permissions pub perm: u16, /// Number of hard links pub nlink: u32, /// User id pub uid: u32, /// Group id pub gid: u32, /// Rdev pub rdev: u32, /// Flags (macOS only, see chflags(2)) pub flags: u32, } /// Filesystem trait. /// /// This trait must be implemented to provide a userspace filesystem via FUSE. /// These methods correspond to fuse_lowlevel_ops in libfuse. Reasonable default /// implementations are provided here to get a mountable filesystem that does /// nothing. pub trait Filesystem { /// Initialize filesystem. /// Called before any other filesystem method. fn init(&mut self, _req: &Request<'_>) -> Result<(), c_int> { Ok(()) } /// Clean up filesystem. /// Called on filesystem exit. fn destroy(&mut self, _req: &Request<'_>) {} /// Look up a directory entry by name and get its attributes. fn lookup(&mut self, _req: &Request<'_>, _parent: u64, _name: &OsStr, reply: ReplyEntry) { reply.error(ENOSYS); } /// Forget about an inode. /// The nlookup parameter indicates the number of lookups previously performed on /// this inode. If the filesystem implements inode lifetimes, it is recommended that /// inodes acquire a single reference on each lookup, and lose nlookup references on /// each forget. The filesystem may ignore forget calls, if the inodes don't need to /// have a limited lifetime. On unmount it is not guaranteed, that all referenced /// inodes will receive a forget message. fn forget(&mut self, _req: &Request<'_>, _ino: u64, _nlookup: u64) {} /// Like forget, but take multiple forget requests at once for performance. The default /// implementation will fallback to forget. fn forget_multi(&mut self, req: &Request<'_>, forget_data: &[fuse_forget_one]) { for inode in forget_data { self.forget(req, inode.nodeid, inode.nlookup); } } /// Get file attributes. fn getattr(&mut self, _req: &Request<'_>, _ino: u64, reply: ReplyAttr) { reply.error(ENOSYS); } /// Set file attributes. fn setattr(&mut self, _req: &Request<'_>, _ino: u64, _mode: Option, _uid: Option, _gid: Option, _size: Option, _atime: UtimeSpec, _mtime: UtimeSpec, _fh: Option, _crtime: Option, _chgtime: Option, _bkuptime: Option, _flags: Option, reply: ReplyAttr) { reply.error(ENOSYS); } /// Read symbolic link. fn readlink(&mut self, _req: &Request<'_>, _ino: u64, reply: ReplyData) { reply.error(ENOSYS); } /// Create file node. /// Create a regular file, character device, block device, fifo or socket node. fn mknod(&mut self, _req: &Request<'_>, _parent: u64, _name: &OsStr, _mode: u32, _umask: u32, _rdev: u32, reply: ReplyEntry) { reply.error(ENOSYS); } /// Create a directory. fn mkdir(&mut self, _req: &Request<'_>, _parent: u64, _name: &OsStr, _mode: u32, _umask: u32, reply: ReplyEntry) { reply.error(ENOSYS); } /// Remove a file. fn unlink(&mut self, _req: &Request<'_>, _parent: u64, _name: &OsStr, reply: ReplyEmpty) { reply.error(ENOSYS); } /// Remove a directory. fn rmdir(&mut self, _req: &Request<'_>, _parent: u64, _name: &OsStr, reply: ReplyEmpty) { reply.error(ENOSYS); } /// Create a symbolic link. fn symlink(&mut self, _req: &Request<'_>, _parent: u64, _name: &OsStr, _link: &Path, reply: ReplyEntry) { reply.error(ENOSYS); } /// Rename a file. fn rename(&mut self, _req: &Request<'_>, _parent: u64, _name: &OsStr, _newparent: u64, _newname: &OsStr, reply: ReplyEmpty) { reply.error(ENOSYS); } /// Rename a file. fn rename2(&mut self, _req: &Request<'_>, _parent: u64, _name: &OsStr, _newparent: u64, _newname: &OsStr, _flags: u32, reply: ReplyEmpty) { reply.error(ENOSYS); } /// Create a hard link. fn link(&mut self, _req: &Request<'_>, _ino: u64, _newparent: u64, _newname: &OsStr, reply: ReplyEntry) { reply.error(ENOSYS); } /// Open a file. /// Open flags (with the exception of O_CREAT, O_EXCL, O_NOCTTY and O_TRUNC) are /// available in flags. Filesystem may store an arbitrary file handle (pointer, index, /// etc) in fh, and use this in other all other file operations (read, write, flush, /// release, fsync). Filesystem may also implement stateless file I/O and not store /// anything in fh. There are also some flags (direct_io, keep_cache) which the /// filesystem may set, to change the way the file is opened. See fuse_file_info /// structure in for more details. fn open(&mut self, _req: &Request<'_>, _ino: u64, _flags: u32, reply: ReplyOpen) { reply.opened(0, 0); } /// Read data. /// Read should send exactly the number of bytes requested except on EOF or error, /// otherwise the rest of the data will be substituted with zeroes. An exception to /// this is when the file has been opened in 'direct_io' mode, in which case the /// return value of the read system call will reflect the return value of this /// operation. fh will contain the value set by the open method, or will be undefined /// if the open method didn't set any value. fn read(&mut self, _req: &Request<'_>, _ino: u64, _fh: u64, _offset: i64, _size: u32, reply: ReplyRead) { reply.error(ENOSYS); } /// Write data. /// Write should return exactly the number of bytes requested except on error. An /// exception to this is when the file has been opened in 'direct_io' mode, in /// which case the return value of the write system call will reflect the return /// value of this operation. fh will contain the value set by the open method, or /// will be undefined if the open method didn't set any value. fn write(&mut self, _req: &Request<'_>, _ino: u64, _fh: u64, _offset: i64, _data: &[u8], _flags: u32, reply: ReplyWrite) { reply.error(ENOSYS); } /// Flush method. /// This is called on each close() of the opened file. Since file descriptors can /// be duplicated (dup, dup2, fork), for one open call there may be many flush /// calls. Filesystems shouldn't assume that flush will always be called after some /// writes, or that if will be called at all. fh will contain the value set by the /// open method, or will be undefined if the open method didn't set any value. /// NOTE: the name of the method is misleading, since (unlike fsync) the filesystem /// is not forced to flush pending writes. One reason to flush data, is if the /// filesystem wants to return write errors. If the filesystem supports file locking /// operations (setlk, getlk) it should remove all locks belonging to 'lock_owner'. fn flush(&mut self, _req: &Request<'_>, _ino: u64, _fh: u64, _lock_owner: u64, reply: ReplyEmpty) { reply.error(ENOSYS); } /// Release an open file. /// Release is called when there are no more references to an open file: all file /// descriptors are closed and all memory mappings are unmapped. For every open /// call there will be exactly one release call. The filesystem may reply with an /// error, but error values are not returned to close() or munmap() which triggered /// the release. fh will contain the value set by the open method, or will be undefined /// if the open method didn't set any value. flags will contain the same flags as for /// open. fn release(&mut self, _req: &Request<'_>, _ino: u64, _fh: u64, _flags: u32, _lock_owner: u64, _flush: bool, reply: ReplyEmpty) { reply.ok(); } /// Synchronize file contents. /// If the datasync parameter is non-zero, then only the user data should be flushed, /// not the meta data. fn fsync(&mut self, _req: &Request<'_>, _ino: u64, _fh: u64, _datasync: bool, reply: ReplyEmpty) { reply.error(ENOSYS); } /// Open a directory. /// Filesystem may store an arbitrary file handle (pointer, index, etc) in fh, and /// use this in other all other directory stream operations (readdir, releasedir, /// fsyncdir). Filesystem may also implement stateless directory I/O and not store /// anything in fh, though that makes it impossible to implement standard conforming /// directory stream operations in case the contents of the directory can change /// between opendir and releasedir. fn opendir(&mut self, _req: &Request<'_>, _ino: u64, _flags: u32, reply: ReplyOpen) { reply.opened(0, 0); } /// Read directory. /// Send a buffer filled using buffer.fill(), with size not exceeding the /// requested size. Send an empty buffer on end of stream. fh will contain the /// value set by the opendir method, or will be undefined if the opendir method /// didn't set any value. fn readdir(&mut self, _req: &Request<'_>, _ino: u64, _fh: u64, _offset: i64, reply: ReplyDirectory) { reply.error(ENOSYS); } /// Read directory. /// Send a buffer filled using buffer.fill(), with size not exceeding the /// requested size. Send an empty buffer on end of stream. fh will contain the /// value set by the opendir method, or will be undefined if the opendir method /// didn't set any value. fn readdirplus(&mut self, _req: &Request<'_>, _ino: u64, _fh: u64, _offset: u64, reply: ReplyDirectoryPlus) { reply.error(ENOSYS); } /// Release an open directory. /// For every opendir call there will be exactly one releasedir call. fh will /// contain the value set by the opendir method, or will be undefined if the /// opendir method didn't set any value. fn releasedir(&mut self, _req: &Request<'_>, _ino: u64, _fh: u64, _flags: u32, reply: ReplyEmpty) { reply.ok(); } /// Synchronize directory contents. /// If the datasync parameter is set, then only the directory contents should /// be flushed, not the meta data. fh will contain the value set by the opendir /// method, or will be undefined if the opendir method didn't set any value. fn fsyncdir (&mut self, _req: &Request<'_>, _ino: u64, _fh: u64, _datasync: bool, reply: ReplyEmpty) { reply.error(ENOSYS); } /// Get file system statistics. fn statfs(&mut self, _req: &Request<'_>, _ino: u64, reply: ReplyStatfs) { reply.statfs(0, 0, 0, 0, 0, 512, 255, 0); } /// Set an extended attribute. fn setxattr(&mut self, _req: &Request<'_>, _ino: u64, _name: &OsStr, _value: &[u8], _flags: u32, _position: u32, reply: ReplyEmpty) { reply.error(ENOSYS); } /// Get an extended attribute. /// If `size` is 0, the size of the value should be sent with `reply.size()`. /// If `size` is not 0, and the value fits, send it with `reply.data()`, or /// `reply.error(ERANGE)` if it doesn't. fn getxattr(&mut self, _req: &Request<'_>, _ino: u64, _name: &OsStr, _size: u32, reply: ReplyXattr) { reply.error(ENOSYS); } /// List extended attribute names. /// If `size` is 0, the size of the value should be sent with `reply.size()`. /// If `size` is not 0, and the value fits, send it with `reply.data()`, or /// `reply.error(ERANGE)` if it doesn't. fn listxattr(&mut self, _req: &Request<'_>, _ino: u64, _size: u32, reply: ReplyXattr) { reply.error(ENOSYS); } /// Remove an extended attribute. fn removexattr(&mut self, _req: &Request<'_>, _ino: u64, _name: &OsStr, reply: ReplyEmpty) { reply.error(ENOSYS); } /// Check file access permissions. /// This will be called for the access() system call. If the 'default_permissions' /// mount option is given, this method is not called. This method is not called /// under Linux kernel versions 2.4.x fn access(&mut self, _req: &Request<'_>, _ino: u64, _mask: u32, reply: ReplyEmpty) { reply.error(ENOSYS); } /// Create and open a file. /// If the file does not exist, first create it with the specified mode, and then /// open it. Open flags (with the exception of O_NOCTTY) are available in flags. /// Filesystem may store an arbitrary file handle (pointer, index, etc) in fh, /// and use this in other all other file operations (read, write, flush, release, /// fsync). There are also some flags (direct_io, keep_cache) which the /// filesystem may set, to change the way the file is opened. See fuse_file_info /// structure in for more details. If this method is not /// implemented or under Linux kernel versions earlier than 2.6.15, the mknod() /// and open() methods will be called instead. fn create(&mut self, _req: &Request<'_>, _parent: u64, _name: &OsStr, _mode: u32, _umask: u32, _flags: u32, reply: ReplyCreate) { reply.error(ENOSYS); } /// Test for a POSIX file lock. fn getlk(&mut self, _req: &Request<'_>, _ino: u64, _fh: u64, _lock_owner: u64, _start: u64, _end: u64, _typ: u32, _pid: u32, reply: ReplyLock) { reply.error(ENOSYS); } /// Acquire, modify or release a POSIX file lock. /// For POSIX threads (NPTL) there's a 1-1 relation between pid and owner, but /// otherwise this is not always the case. For checking lock ownership, /// 'fi->owner' must be used. The l_pid field in 'struct flock' should only be /// used to fill in this field in getlk(). Note: if the locking methods are not /// implemented, the kernel will still allow file locking to work locally. /// Hence these are only interesting for network filesystems and similar. fn setlk(&mut self, _req: &Request<'_>, _ino: u64, _fh: u64, _lock_owner: u64, _start: u64, _end: u64, _typ: u32, _pid: u32, _sleep: bool, reply: ReplyEmpty) { reply.error(ENOSYS); } /// Map block index within file to block index within device. /// Note: This makes sense only for block device backed filesystems mounted /// with the 'blkdev' option fn bmap(&mut self, _req: &Request<'_>, _ino: u64, _blocksize: u32, _idx: u64, reply: ReplyBmap) { reply.error(ENOSYS); } /// control device fn ioctl(&mut self, _req: &Request<'_>, _ino: u64, _fh: u64, _flags: u32, _cmd: u32, _in_data: Option<&[u8]>, _out_size: u32, reply: ReplyIoctl) { reply.error(ENOSYS); } /// Preallocate or deallocate space to a file fn fallocate(&mut self, _req: &Request<'_>, _ino: u64, _fh: u64, _offset: u64, _length: u64, _mode: u32, reply: ReplyEmpty) { reply.error(ENOSYS); } /// macOS only: Rename the volume. Set fuse_init_out.flags during init to /// FUSE_VOL_RENAME to enable #[cfg(target_os = "macos")] fn setvolname(&mut self, _req: &Request<'_>, _name: &OsStr, reply: ReplyEmpty) { reply.error(ENOSYS); } /// macOS only (undocumented) #[cfg(target_os = "macos")] fn exchange(&mut self, _req: &Request<'_>, _parent: u64, _name: &OsStr, _newparent: u64, _newname: &OsStr, _options: u64, reply: ReplyEmpty) { reply.error(ENOSYS); } /// macOS only: Query extended times (bkuptime and crtime). Set fuse_init_out.flags /// during init to FUSE_XTIMES to enable #[cfg(target_os = "macos")] fn getxtimes(&mut self, _req: &Request<'_>, _ino: u64, reply: ReplyXTimes) { reply.error(ENOSYS); } /// Reposition read/write file offset fn lseek(&mut self, _req: &Request<'_>, _ino: u64, _fh: u64, _offset: i64, _whence: u32, reply: ReplyLseek) { reply.error(ENOSYS); } } /// Mount the given filesystem to the given mountpoint. This function will /// not return until the filesystem is unmounted. /// /// Note that you need to lead each option with a separate `"-o"` string. See /// `examples/hello.rs`. #[cfg(feature = "libfuse")] pub fn mount>(filesystem: FS, mountpoint: P, options: &[&OsStr]) -> io::Result<()>{ Session::new(filesystem, mountpoint.as_ref(), options, 0, 0).and_then(|mut se| se.run()) } /// Mount the given filesystem to the given mountpoint. This function spawns /// a background thread to handle filesystem operations while being mounted /// and therefore returns immediately. The returned handle should be stored /// to reference the mounted filesystem. If it's dropped, the filesystem will /// be unmounted. #[cfg(feature = "libfuse")] pub fn spawn_mount<'a, FS: 'static+Filesystem+Send+'a, P: AsRef>(filesystem: FS, mountpoint: P, options: &[&OsStr]) -> io::Result { Session::new(filesystem, mountpoint.as_ref(), options, 0, 0).and_then(|se| se.spawn()) } cntr-fuse-0.4.2/src/ll/argument.rs000064400000000000000000000105301046102023000151220ustar 00000000000000//! Argument decomposition for FUSE operation requests. //! //! Helper to decompose a slice of binary data (incoming FUSE request) into multiple data //! structures (request arguments). use std::ffi::OsStr; use std::mem; use std::os::unix::ffi::OsStrExt; /// An iterator that can be used to fetch typed arguments from a byte slice. pub struct ArgumentIterator<'a> { data: &'a [u8], } impl<'a> ArgumentIterator<'a> { /// Create a new argument iterator for the given byte slice. pub fn new(data: &'a [u8]) -> ArgumentIterator<'a> { ArgumentIterator { data } } /// Returns the size of the remaining data. pub fn len(&self) -> usize { self.data.len() } /// Fetch a slice of all remaining bytes. pub fn fetch_all(&mut self) -> &'a [u8] { let bytes = self.data; self.data = &[]; bytes } /// Fetch a slice of bytes of the given size. Returns `None` if there's not enough data left. pub fn fetch_bytes(&mut self, amt: usize) -> Option<&'a [u8]> { if amt > self.data.len() { return None; } let bytes = &self.data[..amt]; self.data = &self.data[amt..]; Some(bytes) } /// Fetch a typed argument. Returns `None` if there's not enough data left. This function is /// unsafe because there is no guarantee that the data actually contains the type T. pub unsafe fn fetch(&mut self) -> Option<&'a T> { let len = mem::size_of::(); let bytes = self.fetch_bytes(len)?; (bytes.as_ptr() as *const T).as_ref() } /// Fetch a (zero-terminated) string (can be non-utf8). Returns `None` if there's not enough /// data left or no zero-termination could be found. This function is unsafe because there is /// no guarantee that the data actually contains a string. pub unsafe fn fetch_str(&mut self) -> Option<&'a OsStr> { let len = self.data.iter().position(|&c| c == 0)?; let bytes = self.fetch_bytes(len)?; let _zero = self.fetch_bytes(1)?; Some(OsStr::from_bytes(&bytes)) } } #[cfg(test)] mod tests { use super::*; const TEST_DATA: [u8; 10] = [0x66, 0x6f, 0x6f, 0x00, 0x62, 0x61, 0x72, 0x00, 0x62, 0x61]; #[repr(C)] struct TestArgument { p1: u8, p2: u8, p3: u16 } #[test] fn all_data() { let mut it = ArgumentIterator::new(&TEST_DATA); unsafe { it.fetch_str().unwrap() }; let arg = it.fetch_all(); assert_eq!(arg, [0x62, 0x61, 0x72, 0x00, 0x62, 0x61]); } #[test] fn bytes_data() { let mut it = ArgumentIterator::new(&TEST_DATA); let arg = it.fetch_bytes(5).unwrap(); assert_eq!(arg, [0x66, 0x6f, 0x6f, 0x00, 0x62]); let arg = it.fetch_bytes(2).unwrap(); assert_eq!(arg, [0x61, 0x72]); assert_eq!(it.len(), 3); } #[test] fn generic_argument() { let mut it = ArgumentIterator::new(&TEST_DATA); let arg: &TestArgument = unsafe { it.fetch().unwrap() }; assert_eq!(arg.p1, 0x66); assert_eq!(arg.p2, 0x6f); assert_eq!(arg.p3, 0x006f); let arg: &TestArgument = unsafe { it.fetch().unwrap() }; assert_eq!(arg.p1, 0x62); assert_eq!(arg.p2, 0x61); assert_eq!(arg.p3, 0x0072); assert_eq!(it.len(), 2); } #[test] fn string_argument() { let mut it = ArgumentIterator::new(&TEST_DATA); let arg = unsafe { it.fetch_str().unwrap() }; assert_eq!(arg, "foo"); let arg = unsafe { it.fetch_str().unwrap() }; assert_eq!(arg, "bar"); assert_eq!(it.len(), 2); } #[test] fn mixed_arguments() { let mut it = ArgumentIterator::new(&TEST_DATA); let arg: &TestArgument = unsafe { it.fetch().unwrap() }; assert_eq!(arg.p1, 0x66); assert_eq!(arg.p2, 0x6f); assert_eq!(arg.p3, 0x006f); let arg = unsafe { it.fetch_str().unwrap() }; assert_eq!(arg, "bar"); let arg = it.fetch_all(); assert_eq!(arg, [0x62, 0x61]); } #[test] fn out_of_data() { let mut it = ArgumentIterator::new(&TEST_DATA); let _arg = it.fetch_bytes(8).unwrap(); let arg: Option<&TestArgument> = unsafe { it.fetch() }; assert!(arg.is_none()); assert_eq!(it.len(), 2); let arg = unsafe { it.fetch_str() }; assert!(arg.is_none()); assert_eq!(it.len(), 2); } } cntr-fuse-0.4.2/src/ll/mod.rs000064400000000000000000000001661046102023000140630ustar 00000000000000//! Low-level kernel communication. mod argument; mod request; pub use request::{Operation, Request, RequestError}; cntr-fuse-0.4.2/src/ll/request.rs000064400000000000000000000562451046102023000150050ustar 00000000000000//! Low-level filesystem operation request. //! //! A request represents information about a filesystem operation the kernel driver wants us to //! perform. use cntr_fuse_abi::*; use std::convert::TryFrom; use std::ffi::OsStr; use std::{error, fmt, mem, slice}; use super::argument::ArgumentIterator; /// Error that may occur while reading and parsing a request from the kernel driver. #[derive(Debug)] pub enum RequestError<'r> { /// Not enough data for parsing header (short read). ShortReadHeader(usize), /// Kernel requested an unknown operation. UnknownOperation(&'r fuse_in_header), /// Not enough data for arguments (short read). ShortRead(usize, usize), /// Insufficient argument data. InsufficientData, } impl fmt::Display for RequestError<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { RequestError::ShortReadHeader(len) => write!(f, "Short read of FUSE request header ({} < {})", len, mem::size_of::()), RequestError::ShortRead(len, total) => write!(f, "Short read of FUSE request ({} < {})", len, total), RequestError::UnknownOperation(error) => { write!(f, "Unknown FUSE opcode ({})", error.opcode) } RequestError::InsufficientData => write!(f, "Insufficient argument data"), } } } impl error::Error for RequestError<'_> {} /// Filesystem operation (and arguments) the kernel driver wants us to perform. The fields of each /// variant needs to match the actual arguments the kernel driver sends for the specific operation. #[derive(Debug)] pub enum Operation<'a> { Lookup { name: &'a OsStr, }, Forget { arg: &'a fuse_forget_in, }, GetAttr, SetAttr { arg: &'a fuse_setattr_in, }, ReadLink, SymLink { name: &'a OsStr, link: &'a OsStr, }, MkNod { arg: &'a fuse_mknod_in, name: &'a OsStr, }, MkDir { arg: &'a fuse_mkdir_in, name: &'a OsStr, }, Unlink { name: &'a OsStr, }, RmDir { name: &'a OsStr, }, Rename { arg: &'a fuse_rename_in, name: &'a OsStr, newname: &'a OsStr, }, Rename2 { arg: &'a fuse_rename2_in, name: &'a OsStr, newname: &'a OsStr, }, Link { arg: &'a fuse_link_in, name: &'a OsStr, }, Open { arg: &'a fuse_open_in, }, Read { arg: &'a fuse_read_in, }, Write { arg: &'a fuse_write_in, data: &'a [u8], }, StatFs, Release { arg: &'a fuse_release_in, }, FSync { arg: &'a fuse_fsync_in, }, SetXAttr { arg: &'a fuse_setxattr_in, name: &'a OsStr, value: &'a [u8], }, GetXAttr { arg: &'a fuse_getxattr_in, name: &'a OsStr, }, ListXAttr { arg: &'a fuse_getxattr_in, }, RemoveXAttr { name: &'a OsStr, }, Flush { arg: &'a fuse_flush_in, }, Init { arg: &'a fuse_init_in, }, OpenDir { arg: &'a fuse_open_in, }, ReadDir { arg: &'a fuse_read_in, }, ReadDirPlus { arg: &'a fuse_read_in, }, ReleaseDir { arg: &'a fuse_release_in, }, FSyncDir { arg: &'a fuse_fsync_in, }, GetLk { arg: &'a fuse_lk_in, }, SetLk { arg: &'a fuse_lk_in, }, SetLkW { arg: &'a fuse_lk_in, }, Access { arg: &'a fuse_access_in, }, Create { arg: &'a fuse_create_in, name: &'a OsStr, }, Interrupt { arg: &'a fuse_interrupt_in, }, BMap { arg: &'a fuse_bmap_in, }, Destroy, IoCtl { arg: &'a fuse_ioctl_in, in_data: Option<&'a [u8]>, }, Poll { arg: &'a fuse_poll_in, }, NotifyReply { arg: &'a fuse_notify_retrieve_in, }, BatchForget { arg: &'a fuse_batch_forget_in, nodes: &'a [fuse_forget_one], }, FAllocate { arg: &'a fuse_fallocate_in, }, #[cfg(target_os = "macos")] SetVolName { name: &'a OsStr, }, #[cfg(target_os = "macos")] GetXTimes, #[cfg(target_os = "macos")] Exchange { arg: &'a fuse_exchange_in, oldname: &'a OsStr, newname: &'a OsStr, }, CuseInit { arg: &'a cuse_init_in, }, Lseek { arg: &'a fuse_lseek_in, } } impl<'a> fmt::Display for Operation<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Operation::Lookup { name } => write!(f, "LOOKUP name {:?}", name), Operation::Forget { arg } => write!(f, "FORGET nlookup {}", arg.nlookup), Operation::GetAttr => write!(f, "GETATTR"), Operation::SetAttr { arg } => write!(f, "SETATTR valid {:#x}", arg.valid), Operation::ReadLink => write!(f, "READLINK"), Operation::SymLink { name, link } => write!(f, "SYMLINK name {:?}, link {:?}", name, link), Operation::MkNod { arg, name } => write!(f, "MKNOD name {:?}, mode {:#05o}, rdev {}", name, arg.mode, arg.rdev), Operation::MkDir { arg, name } => write!(f, "MKDIR name {:?}, mode {:#05o}", name, arg.mode), Operation::Unlink { name } => write!(f, "UNLINK name {:?}", name), Operation::RmDir { name } => write!(f, "RMDIR name {:?}", name), Operation::Rename { arg, name, newname } => write!(f, "RENAME name {:?}, newdir {:#018x}, newname {:?}", name, arg.newdir, newname), Operation::Rename2 { arg, name, newname } => write!(f, "RENAME name {:?}, newdir {:#018x}, newname {:?}, flags {:?}", name, arg.newdir, newname, arg.flags), Operation::Link { arg, name } => write!(f, "LINK name {:?}, oldnodeid {:#018x}", name, arg.oldnodeid), Operation::Open { arg } => write!(f, "OPEN flags {:#x}", arg.flags), Operation::Read { arg } => write!(f, "READ fh {}, offset {}, size {}", arg.fh, arg.offset, arg.size), Operation::Write { arg, .. } => write!(f, "WRITE fh {}, offset {}, size {}, write flags {:#x}", arg.fh, arg.offset, arg.size, arg.write_flags), Operation::StatFs => write!(f, "STATFS"), Operation::Release { arg } => write!(f, "RELEASE fh {}, flags {:#x}, release flags {:#x}, lock owner {}", arg.fh, arg.flags, arg.release_flags, arg.lock_owner), Operation::FSync { arg } => write!(f, "FSYNC fh {}, fsync flags {:#x}", arg.fh, arg.fsync_flags), Operation::SetXAttr { arg, name, .. } => write!(f, "SETXATTR name {:?}, size {}, flags {:#x}", name, arg.size, arg.flags), Operation::GetXAttr { arg, name } => write!(f, "GETXATTR name {:?}, size {}", name, arg.size), Operation::ListXAttr { arg } => write!(f, "LISTXATTR size {}", arg.size), Operation::RemoveXAttr { name } => write!(f, "REMOVEXATTR name {:?}", name), Operation::Flush { arg } => write!(f, "FLUSH fh {}, lock owner {}", arg.fh, arg.lock_owner), Operation::Init { arg } => write!(f, "INIT kernel ABI {}.{}, flags {:#x}, max readahead {}", arg.major, arg.minor, arg.flags, arg.max_readahead), Operation::OpenDir { arg } => write!(f, "OPENDIR flags {:#x}", arg.flags), Operation::ReadDir { arg } => write!(f, "READDIR fh {}, offset {}, size {}", arg.fh, arg.offset, arg.size), Operation::ReadDirPlus { arg } => write!(f, "READDIRPLUS fh {}, offset {}, size {}", arg.fh, arg.offset, arg.size), Operation::ReleaseDir { arg } => write!(f, "RELEASEDIR fh {}, flags {:#x}, release flags {:#x}, lock owner {}", arg.fh, arg.flags, arg.release_flags, arg.lock_owner), Operation::FSyncDir { arg } => write!(f, "FSYNCDIR fh {}, fsync flags {:#x}", arg.fh, arg.fsync_flags), Operation::GetLk { arg } => write!(f, "GETLK fh {}, lock owner {}", arg.fh, arg.owner), Operation::SetLk { arg } => write!(f, "SETLK fh {}, lock owner {}", arg.fh, arg.owner), Operation::SetLkW { arg } => write!(f, "SETLKW fh {}, lock owner {}", arg.fh, arg.owner), Operation::Access { arg } => write!(f, "ACCESS mask {:#05o}", arg.mask), Operation::Create { arg, name } => write!(f, "CREATE name {:?}, mode {:#05o}, flags {:#x}", name, arg.mode, arg.flags), Operation::Interrupt { arg } => write!(f, "INTERRUPT unique {}", arg.unique), Operation::BMap { arg } => write!(f, "BMAP blocksize {}, ids {}", arg.blocksize, arg.block), Operation::Destroy => write!(f, "DESTROY"), Operation::IoCtl { arg, in_data: _ } => write!(f, "IOCTL fh {}, flags {}, cmd {}, in_size {}, out_size {}", arg.fh, arg.flags, arg.cmd, arg.in_size, arg.out_size), Operation::Poll { arg } => write!(f, "POLL fh {}, kh {}, flags {}", arg.fh, arg.kh, arg.flags), Operation::NotifyReply { arg } => write!(f, "NOTIFYREPLY offset {}, size {}", arg.offset, arg.size), Operation::BatchForget { arg, nodes: _ } => write!(f, "BATCH_FORGET size {}", arg.count), Operation::FAllocate { arg } => write!(f, "FALLOCATE fh {}, offset {}, length {}, mode {}", arg.fh, arg.offset, arg.length, arg.mode), #[cfg(target_os = "macos")] Operation::SetVolName { name } => write!(f, "SETVOLNAME name {:?}", name), #[cfg(target_os = "macos")] Operation::GetXTimes => write!(f, "GETXTIMES"), #[cfg(target_os = "macos")] Operation::Exchange { arg, oldname, newname } => write!(f, "EXCHANGE olddir {:#018x}, oldname {:?}, newdir {:#018x}, newname {:?}, options {:#x}", arg.olddir, oldname, arg.newdir, newname, arg.options), Operation::CuseInit { arg } => write!(f, "CUSEINIT major {}, minor {}, flags {}", arg.major, arg.minor, arg.flags), Operation::Lseek { arg } => write!(f, "LSEEK fh {}, offset {}, whence {}", arg.fh, arg.offset, arg.whence), } } } impl<'a> Operation<'a> { fn parse(opcode: &fuse_opcode, data: &mut ArgumentIterator<'a>) -> Option { unsafe { Some(match opcode { fuse_opcode::FUSE_LOOKUP => Operation::Lookup { name: data.fetch_str()?, }, fuse_opcode::FUSE_FORGET => Operation::Forget { arg: data.fetch()? }, fuse_opcode::FUSE_GETATTR => Operation::GetAttr, fuse_opcode::FUSE_SETATTR => Operation::SetAttr { arg: data.fetch()? }, fuse_opcode::FUSE_READLINK => Operation::ReadLink, fuse_opcode::FUSE_SYMLINK => Operation::SymLink { name: data.fetch_str()?, link: data.fetch_str()?, }, fuse_opcode::FUSE_MKNOD => Operation::MkNod { arg: data.fetch()?, name: data.fetch_str()?, }, fuse_opcode::FUSE_MKDIR => Operation::MkDir { arg: data.fetch()?, name: data.fetch_str()?, }, fuse_opcode::FUSE_UNLINK => Operation::Unlink { name: data.fetch_str()?, }, fuse_opcode::FUSE_RMDIR => Operation::RmDir { name: data.fetch_str()?, }, fuse_opcode::FUSE_RENAME => Operation::Rename { arg: data.fetch()?, name: data.fetch_str()?, newname: data.fetch_str()?, }, fuse_opcode::FUSE_RENAME2 => Operation::Rename2 { arg: data.fetch()?, name: data.fetch_str()?, newname: data.fetch_str()?, }, fuse_opcode::FUSE_LINK => Operation::Link { arg: data.fetch()?, name: data.fetch_str()?, }, fuse_opcode::FUSE_OPEN => Operation::Open { arg: data.fetch()? }, fuse_opcode::FUSE_READ => Operation::Read { arg: data.fetch()? }, fuse_opcode::FUSE_WRITE => Operation::Write { arg: data.fetch()?, data: data.fetch_all(), }, fuse_opcode::FUSE_STATFS => Operation::StatFs, fuse_opcode::FUSE_RELEASE => Operation::Release { arg: data.fetch()? }, fuse_opcode::FUSE_FSYNC => Operation::FSync { arg: data.fetch()? }, fuse_opcode::FUSE_SETXATTR => Operation::SetXAttr { arg: data.fetch()?, name: data.fetch_str()?, value: data.fetch_all(), }, fuse_opcode::FUSE_GETXATTR => Operation::GetXAttr { arg: data.fetch()?, name: data.fetch_str()?, }, fuse_opcode::FUSE_LISTXATTR => Operation::ListXAttr { arg: data.fetch()? }, fuse_opcode::FUSE_REMOVEXATTR => Operation::RemoveXAttr { name: data.fetch_str()?, }, fuse_opcode::FUSE_FLUSH => Operation::Flush { arg: data.fetch()? }, fuse_opcode::FUSE_INIT => Operation::Init { arg: data.fetch()? }, fuse_opcode::FUSE_OPENDIR => Operation::OpenDir { arg: data.fetch()? }, fuse_opcode::FUSE_READDIR => Operation::ReadDir { arg: data.fetch()? }, fuse_opcode::FUSE_RELEASEDIR => Operation::ReleaseDir { arg: data.fetch()? }, fuse_opcode::FUSE_FSYNCDIR => Operation::FSyncDir { arg: data.fetch()? }, fuse_opcode::FUSE_GETLK => Operation::GetLk { arg: data.fetch()? }, fuse_opcode::FUSE_SETLK => Operation::SetLk { arg: data.fetch()? }, fuse_opcode::FUSE_SETLKW => Operation::SetLkW { arg: data.fetch()? }, fuse_opcode::FUSE_ACCESS => Operation::Access { arg: data.fetch()? }, fuse_opcode::FUSE_CREATE => Operation::Create { arg: data.fetch()?, name: data.fetch_str()?, }, fuse_opcode::FUSE_INTERRUPT => Operation::Interrupt { arg: data.fetch()? }, fuse_opcode::FUSE_BMAP => Operation::BMap { arg: data.fetch()? }, fuse_opcode::FUSE_DESTROY => Operation::Destroy, fuse_opcode::FUSE_IOCTL => { let arg: &fuse_ioctl_in = data.fetch()?; Operation::IoCtl { arg: arg, in_data: if arg.in_size > 0 { Some(data.fetch_all()) } else { None }, } }, fuse_opcode::FUSE_POLL => Operation::Poll { arg: data.fetch()? }, fuse_opcode::FUSE_NOTIFY_REPLY => Operation::NotifyReply { arg: data.fetch()? }, fuse_opcode::FUSE_BATCH_FORGET => { let arg: &fuse_batch_forget_in = data.fetch()?; let node_data: &[u8] = data.fetch_all(); assert!(node_data.len() / mem::size_of::() == arg.count as usize); let nodes : &[fuse_forget_one] = slice::from_raw_parts(node_data.as_ptr() as *const fuse_forget_one, arg.count as usize); Operation::BatchForget { arg: arg, nodes: nodes, } }, fuse_opcode::FUSE_FALLOCATE => Operation::FAllocate { arg: data.fetch()? }, fuse_opcode::FUSE_READDIRPLUS => Operation::ReadDirPlus { arg: data.fetch()? }, #[cfg(target_os = "macos")] fuse_opcode::FUSE_SETVOLNAME => Operation::SetVolName { name: data.fetch_str()?, }, #[cfg(target_os = "macos")] fuse_opcode::FUSE_GETXTIMES => Operation::GetXTimes, #[cfg(target_os = "macos")] fuse_opcode::FUSE_EXCHANGE => Operation::Exchange { arg: data.fetch()?, oldname: data.fetch_str()?, newname: data.fetch_str()?, }, fuse_opcode::CUSE_INIT => Operation::CuseInit { arg: data.fetch()? }, fuse_opcode::FUSE_LSEEK => Operation::Lseek { arg: data.fetch()? }, }) } } } /// Low-level request of a filesystem operation the kernel driver wants to perform. #[derive(Debug)] pub struct Request<'a> { header: &'a fuse_in_header, operation: Operation<'a>, } impl<'a> fmt::Display for Request<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "FUSE({:3}) ino {:#018x}: {}", self.header.unique, self.header.nodeid, self.operation) } } impl<'a> TryFrom<&'a [u8]> for Request<'a> { type Error = RequestError<'a>; fn try_from(data: &'a [u8]) -> Result { // Parse a raw packet as sent by the kernel driver into typed data. Every request always // begins with a `fuse_in_header` struct followed by arguments depending on the opcode. let data_len = data.len(); let mut data = ArgumentIterator::new(data); // Parse header let header: &fuse_in_header = unsafe { data.fetch() }.ok_or_else(|| RequestError::ShortReadHeader(data.len()))?; // Parse/check opcode let opcode = fuse_opcode::try_from(header.opcode) .map_err(|_: InvalidOpcodeError| RequestError::UnknownOperation(header))?; // Check data size if data_len < header.len as usize { return Err(RequestError::ShortRead(data_len, header.len as usize)); } // Parse/check operation arguments let operation = Operation::parse(&opcode, &mut data).ok_or_else(|| RequestError::InsufficientData)?; Ok(Self { header, operation }) } } impl<'a> Request<'a> { /// Returns the unique identifier of this request. /// /// The FUSE kernel driver assigns a unique id to every concurrent request. This allows to /// distinguish between multiple concurrent requests. The unique id of a request may be /// reused in later requests after it has completed. #[inline] pub fn unique(&self) -> u64 { self.header.unique } /// Returns the node id of the inode this request is targeted to. #[inline] pub fn nodeid(&self) -> u64 { self.header.nodeid } /// Returns the UID that the process that triggered this request runs under. #[inline] pub fn uid(&self) -> u32 { self.header.uid } /// Returns the GID that the process that triggered this request runs under. #[inline] pub fn gid(&self) -> u32 { self.header.gid } /// Returns the PID of the process that triggered this request. #[inline] pub fn pid(&self) -> u32 { self.header.pid } /// Returns the filesystem operation (and its arguments) of this request. #[inline] pub fn operation(&self) -> &Operation<'_> { &self.operation } } #[cfg(test)] mod tests { use super::*; #[cfg(target_endian = "big")] const INIT_REQUEST: [u8; 56] = [ 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x1a, // len, opcode 0xde, 0xad, 0xbe, 0xef, 0xba, 0xad, 0xd0, 0x0d, // unique 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // nodeid 0xc0, 0x01, 0xd0, 0x0d, 0xc0, 0x01, 0xca, 0xfe, // uid, gid 0xc0, 0xde, 0xba, 0x5e, 0x00, 0x00, 0x00, 0x00, // pid, padding 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, // major, minor 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // max_readahead, flags ]; #[cfg(target_endian = "little")] const INIT_REQUEST: [u8; 56] = [ 0x38, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, // len, opcode 0x0d, 0xf0, 0xad, 0xba, 0xef, 0xbe, 0xad, 0xde, // unique 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, // nodeid 0x0d, 0xd0, 0x01, 0xc0, 0xfe, 0xca, 0x01, 0xc0, // uid, gid 0x5e, 0xba, 0xde, 0xc0, 0x00, 0x00, 0x00, 0x00, // pid, padding 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // major, minor 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // max_readahead, flags ]; #[cfg(target_endian = "big")] const MKNOD_REQUEST: [u8; 64] = [ 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x08, // len, opcode 0xde, 0xad, 0xbe, 0xef, 0xba, 0xad, 0xd0, 0x0d, // unique 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, // nodeid 0xc0, 0x01, 0xd0, 0x0d, 0xc0, 0x01, 0xca, 0xfe, // uid, gid 0xc0, 0xde, 0xba, 0x5e, 0x00, 0x00, 0x00, 0x00, // pid, padding 0x00, 0x00, 0x01, 0xa4, 0x00, 0x00, 0x00, 0x00, // mode, rdev 0xa4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // umask, padding 0x66, 0x6f, 0x6f, 0x2e, 0x74, 0x78, 0x74, 0x00, // name ]; #[cfg(target_endian = "little")] const MKNOD_REQUEST: [u8; 64] = [ 0x38, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // len, opcode 0x0d, 0xf0, 0xad, 0xba, 0xef, 0xbe, 0xad, 0xde, // unique 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, // nodeid 0x0d, 0xd0, 0x01, 0xc0, 0xfe, 0xca, 0x01, 0xc0, // uid, gid 0x5e, 0xba, 0xde, 0xc0, 0x00, 0x00, 0x00, 0x00, // pid, padding 0xa4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mode, rdev 0xa4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // umask, padding 0x66, 0x6f, 0x6f, 0x2e, 0x74, 0x78, 0x74, 0x00, // name ]; #[test] fn short_read_header() { match Request::try_from(&INIT_REQUEST[..20]) { Err(RequestError::ShortReadHeader(20)) => (), _ => panic!("Unexpected request parsing result"), } } #[test] fn short_read() { match Request::try_from(&INIT_REQUEST[..48]) { Err(RequestError::ShortRead(48, 56)) => (), _ => panic!("Unexpected request parsing result"), } } #[test] fn init() { let req = Request::try_from(&INIT_REQUEST[..]).unwrap(); assert_eq!(req.header.len, 56); assert_eq!(req.header.opcode, 26); assert_eq!(req.unique(), 0xdead_beef_baad_f00d); assert_eq!(req.nodeid(), 0x1122_3344_5566_7788); assert_eq!(req.uid(), 0xc001_d00d); assert_eq!(req.gid(), 0xc001_cafe); assert_eq!(req.pid(), 0xc0de_ba5e); match req.operation() { Operation::Init { arg } => { assert_eq!(arg.major, 7); assert_eq!(arg.minor, 8); assert_eq!(arg.max_readahead, 4096); } _ => panic!("Unexpected request operation"), } } #[test] fn mknod() { let req = Request::try_from(&MKNOD_REQUEST[..]).unwrap(); assert_eq!(req.header.len, 56); assert_eq!(req.header.opcode, 8); assert_eq!(req.unique(), 0xdead_beef_baad_f00d); assert_eq!(req.nodeid(), 0x1122_3344_5566_7788); assert_eq!(req.uid(), 0xc001_d00d); assert_eq!(req.gid(), 0xc001_cafe); assert_eq!(req.pid(), 0xc0de_ba5e); match req.operation() { Operation::MkNod { arg, name } => { assert_eq!(arg.mode, 0o644); assert_eq!(*name, "foo.txt"); } _ => panic!("Unexpected request operation"), } } } cntr-fuse-0.4.2/src/reply.rs000064400000000000000000001176461046102023000140440ustar 00000000000000//! Filesystem operation reply //! //! A reply is passed to filesystem operation implementations and must be used to send back the //! result of an operation. The reply can optionally be sent to another thread to asynchronously //! work on an operation and provide the result later. Also it allows replying with a block of //! data without cloning the data. A reply *must always* be used (by calling either ok() or //! error() exactly once). use std::{mem, ptr, slice}; use std::convert::AsRef; use std::ffi::OsStr; use std::fmt; use std::marker::PhantomData; use std::os::unix::ffi::OsStrExt; use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH}; use cntr_fuse_abi::{fuse_attr, fuse_kstatfs, fuse_file_lock, fuse_entry_out, fuse_attr_out, fuse_lseek_out}; use cntr_fuse_abi::{fuse_open_out, fuse_write_out, fuse_statfs_out, fuse_lk_out, fuse_bmap_out, fuse_ioctl_out}; use cntr_fuse_abi::fuse_getxattr_out; #[cfg(target_os = "macos")] use cntr_fuse_abi::fuse_getxtimes_out; use cntr_fuse_abi::{fuse_out_header, fuse_dirent, fuse_direntplus}; use libc::{self, c_int, S_IFIFO, S_IFCHR, S_IFBLK, S_IFDIR, S_IFREG, S_IFLNK, S_IFSOCK, EIO}; use log::{warn, debug}; use crate::{FileType, FileAttr}; /// Generic reply callback to send data pub trait ReplySender: Send + 'static { /// Send data. fn send(&self, data: &[&[u8]]); } impl fmt::Debug for Box { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "Box") } } /// Generic reply trait pub trait Reply { /// Create a new reply for the given request fn new(unique: u64, sender: S) -> Self; } /// Serialize an arbitrary type to bytes (memory copy, useful for fuse_*_out types) fn as_bytes U>(data: &T, f: F) -> U { let len = mem::size_of::(); match len { 0 => f(&[]), len => { let p = data as *const T as *const u8; let bytes = unsafe { slice::from_raw_parts(p, len) }; f(&[bytes]) } } } fn time_from_system_time(system_time: &SystemTime) -> Result<(u64, u32), SystemTimeError> { let duration = system_time.duration_since(UNIX_EPOCH)?; Ok((duration.as_secs(), duration.subsec_nanos())) } // Some platforms like Linux x86_64 have mode_t = u32, and lint warns of a trivial_numeric_casts. // But others like macOS x86_64 have mode_t = u16, requiring a typecast. So, just silence lint. #[allow(trivial_numeric_casts)] /// Returns the mode for a given file kind and permission fn mode_from_kind_and_perm(kind: FileType, perm: u16) -> u32 { (match kind { FileType::NamedPipe => S_IFIFO, FileType::CharDevice => S_IFCHR, FileType::BlockDevice => S_IFBLK, FileType::Directory => S_IFDIR, FileType::RegularFile => S_IFREG, FileType::Symlink => S_IFLNK, FileType::Socket => S_IFSOCK, FileType::Unknown => 0, }) as u32 | perm as u32 } /// Returns a fuse_attr from FileAttr #[cfg(target_os = "macos")] fn fuse_attr_from_attr(attr: &FileAttr) -> fuse_attr { // FIXME: unwrap may panic, use unwrap_or((0, 0)) or return a result instead? let (atime_secs, atime_nanos) = time_from_system_time(&attr.atime).unwrap(); let (mtime_secs, mtime_nanos) = time_from_system_time(&attr.mtime).unwrap(); let (ctime_secs, ctime_nanos) = time_from_system_time(&attr.ctime).unwrap(); let (crtime_secs, crtime_nanos) = time_from_system_time(&attr.crtime).unwrap(); fuse_attr { ino: attr.ino, size: attr.size, blocks: attr.blocks, atime: atime_secs, mtime: mtime_secs, ctime: ctime_secs, crtime: crtime_secs, atimensec: atime_nanos, mtimensec: mtime_nanos, ctimensec: ctime_nanos, crtimensec: crtime_nanos, mode: mode_from_kind_and_perm(attr.kind, attr.perm), nlink: attr.nlink, uid: attr.uid, gid: attr.gid, rdev: attr.rdev, blksize: 0, padding: 0, flags: attr.flags, } } /// Returns a fuse_attr from FileAttr #[cfg(not(target_os = "macos"))] fn fuse_attr_from_attr(attr: &FileAttr) -> fuse_attr { // FIXME: unwrap may panic, use unwrap_or((0, 0)) or return a result instead? let (atime_secs, atime_nanos) = time_from_system_time(&attr.atime).unwrap(); let (mtime_secs, mtime_nanos) = time_from_system_time(&attr.mtime).unwrap(); let (ctime_secs, ctime_nanos) = time_from_system_time(&attr.ctime).unwrap(); fuse_attr { ino: attr.ino, size: attr.size, blocks: attr.blocks, atime: atime_secs, mtime: mtime_secs, ctime: ctime_secs, atimensec: atime_nanos, mtimensec: mtime_nanos, ctimensec: ctime_nanos, mode: mode_from_kind_and_perm(attr.kind, attr.perm), nlink: attr.nlink, uid: attr.uid, gid: attr.gid, rdev: attr.rdev, blksize: 0, padding: 0, } } /// /// Raw reply /// #[derive(Debug)] pub struct ReplyRaw { /// Unique id of the request to reply to unique: u64, /// Closure to call for sending the reply sender: Option>, /// Marker for being able to have T on this struct (which enforces /// reply types to send the correct type of data) marker: PhantomData, } impl Reply for ReplyRaw { fn new(unique: u64, sender: S) -> ReplyRaw { let sender = Box::new(sender); ReplyRaw { unique: unique, sender: Some(sender), marker: PhantomData } } } impl ReplyRaw { /// Reply to a request with the given error code and data. Must be called /// only once (the `ok` and `error` methods ensure this by consuming `self`) fn send(&mut self, err: c_int, bytes: &[&[u8]]) { assert!(self.sender.is_some()); let len = bytes.iter().fold(0, |l, b| l + b.len()); let header = fuse_out_header { len: (mem::size_of::() + len) as u32, error: -err, unique: self.unique, }; as_bytes(&header, |headerbytes| { let sender = self.sender.take().unwrap(); let mut sendbytes = headerbytes.to_vec(); sendbytes.extend(bytes); sender.send(&sendbytes); }); } /// Reply to a request with the given type pub fn ok(mut self, data: &E) { as_bytes(data, |bytes| { self.send(0, bytes); }) } /// Reply to a request with the given error code pub fn error(mut self, err: c_int) { self.send(err, &[]); } } impl Drop for ReplyRaw { fn drop(&mut self) { if self.sender.is_some() { warn!("Reply not sent for operation {}, replying with I/O error", self.unique); self.send(EIO, &[]); } } } /// /// Empty reply /// #[derive(Debug)] pub struct ReplyEmpty { reply: ReplyRaw<()>, } impl Reply for ReplyEmpty { fn new(unique: u64, sender: S) -> ReplyEmpty { ReplyEmpty { reply: Reply::new(unique, sender) } } } impl ReplyEmpty { /// Reply to a request with nothing pub fn ok(mut self) { self.reply.send(0, &[]); } /// Reply to a request with the given error code pub fn error(self, err: c_int) { self.reply.error(err); } } /// /// Read reply /// #[derive(Debug)] pub struct ReplyRead { reply: ReplyRaw<()>, unique: u64 } impl ReplyRead { /// Create a new reply for a read request pub fn new(unique: u64, sender: S) -> ReplyRead { ReplyRead { reply: Reply::new(unique, sender), unique: unique } } /// Reply to a request with the given data pub fn data(mut self, data: &[u8]) { self.reply.send(0, &[data]); } /// Reply to a request with the given error code pub fn error(self, err: c_int) { self.reply.error(err); } } /// /// Data reply /// #[derive(Debug)] pub struct ReplyData { reply: ReplyRaw<()>, } impl Reply for ReplyData { fn new(unique: u64, sender: S) -> ReplyData { ReplyData { reply: Reply::new(unique, sender) } } } impl ReplyData { /// Reply to a request with the given data pub fn data(mut self, data: &[u8]) { self.reply.send(0, &[data]); } /// Reply to a request with the given error code pub fn error(self, err: c_int) { self.reply.error(err); } } /// /// Entry reply /// #[derive(Debug)] pub struct ReplyEntry { reply: ReplyRaw, } impl Reply for ReplyEntry { fn new(unique: u64, sender: S) -> ReplyEntry { ReplyEntry { reply: Reply::new(unique, sender) } } } impl ReplyEntry { /// Reply to a request with the given entry pub fn entry(self, ttl: &Duration, attr: &FileAttr, generation: u64) { self.reply.ok(&fuse_entry_out { nodeid: attr.ino, generation: generation, entry_valid: ttl.as_secs(), attr_valid: ttl.as_secs(), entry_valid_nsec: ttl.subsec_nanos(), attr_valid_nsec: ttl.subsec_nanos(), attr: fuse_attr_from_attr(attr), }); } /// Reply to a request with the given error code pub fn error(self, err: c_int) { self.reply.error(err); } } /// /// Attribute Reply /// #[derive(Debug)] pub struct ReplyAttr { reply: ReplyRaw, } impl Reply for ReplyAttr { fn new(unique: u64, sender: S) -> ReplyAttr { ReplyAttr { reply: Reply::new(unique, sender) } } } impl ReplyAttr { /// Reply to a request with the given attribute pub fn attr(self, ttl: &Duration, attr: &FileAttr) { self.reply.ok(&fuse_attr_out { attr_valid: ttl.as_secs(), attr_valid_nsec: ttl.subsec_nanos(), dummy: 0, attr: fuse_attr_from_attr(attr), }); } /// Reply to a request with the given error code pub fn error(self, err: c_int) { self.reply.error(err); } } /// /// XTimes Reply /// #[cfg(target_os = "macos")] #[derive(Debug)] pub struct ReplyXTimes { reply: ReplyRaw, } #[cfg(target_os = "macos")] impl Reply for ReplyXTimes { fn new(unique: u64, sender: S) -> ReplyXTimes { ReplyXTimes { reply: Reply::new(unique, sender) } } } #[cfg(target_os = "macos")] impl ReplyXTimes { /// Reply to a request with the given xtimes pub fn xtimes(self, bkuptime: SystemTime, crtime: SystemTime) { // FIXME: unwrap may panic, use unwrap_or((0, 0)) or return a result instead? let (bkuptime_secs, bkuptime_nanos) = time_from_system_time(&bkuptime).unwrap(); let (crtime_secs, crtime_nanos) = time_from_system_time(&crtime).unwrap(); self.reply.ok(&fuse_getxtimes_out { bkuptime: bkuptime_secs, crtime: crtime_secs, bkuptimensec: bkuptime_nanos, crtimensec: crtime_nanos, }); } /// Reply to a request with the given error code pub fn error(self, err: c_int) { self.reply.error(err); } } /// /// Open Reply /// #[derive(Debug)] pub struct ReplyOpen { reply: ReplyRaw, } impl Reply for ReplyOpen { fn new(unique: u64, sender: S) -> ReplyOpen { ReplyOpen { reply: Reply::new(unique, sender) } } } impl ReplyOpen { /// Reply to a request with the given open result pub fn opened(self, fh: u64, flags: u32) { self.reply.ok(&fuse_open_out { fh: fh, open_flags: flags, padding: 0, }); } /// Reply to a request with the given error code pub fn error(self, err: c_int) { self.reply.error(err); } } /// /// Write Reply /// #[derive(Debug)] pub struct ReplyWrite { reply: ReplyRaw, } impl Reply for ReplyWrite { fn new(unique: u64, sender: S) -> ReplyWrite { ReplyWrite { reply: Reply::new(unique, sender) } } } impl ReplyWrite { /// Reply to a request with the given open result pub fn written(self, size: u32) { self.reply.ok(&fuse_write_out { size: size, padding: 0, }); } /// Reply to a request with the given error code pub fn error(self, err: c_int) { self.reply.error(err); } } /// /// Statfs Reply /// #[derive(Debug)] pub struct ReplyStatfs { reply: ReplyRaw, } impl Reply for ReplyStatfs { fn new(unique: u64, sender: S) -> ReplyStatfs { ReplyStatfs { reply: Reply::new(unique, sender) } } } impl ReplyStatfs { /// Reply to a request with the given open result pub fn statfs(self, blocks: u64, bfree: u64, bavail: u64, files: u64, ffree: u64, bsize: u32, namelen: u32, frsize: u32) { self.reply.ok(&fuse_statfs_out { st: fuse_kstatfs { blocks: blocks, bfree: bfree, bavail: bavail, files: files, ffree: ffree, bsize: bsize, namelen: namelen, frsize: frsize, padding: 0, spare: [0; 6], }, }); } /// Reply to a request with the given error code pub fn error(self, err: c_int) { self.reply.error(err); } } /// /// Create reply /// #[derive(Debug)] pub struct ReplyCreate { reply: ReplyRaw<(fuse_entry_out, fuse_open_out)>, } impl Reply for ReplyCreate { fn new(unique: u64, sender: S) -> ReplyCreate { ReplyCreate { reply: Reply::new(unique, sender) } } } impl ReplyCreate { /// Reply to a request with the given entry pub fn created(self, ttl: &Duration, attr: &FileAttr, generation: u64, fh: u64, flags: u32) { self.reply.ok(&(fuse_entry_out { nodeid: attr.ino, generation: generation, entry_valid: ttl.as_secs(), attr_valid: ttl.as_secs(), entry_valid_nsec: ttl.subsec_nanos(), attr_valid_nsec: ttl.subsec_nanos(), attr: fuse_attr_from_attr(attr), }, fuse_open_out { fh: fh, open_flags: flags, padding: 0, })); } /// Reply to a request with the given error code pub fn error(self, err: c_int) { self.reply.error(err); } } /// /// Lock Reply /// #[derive(Debug)] pub struct ReplyLock { reply: ReplyRaw, } impl Reply for ReplyLock { fn new(unique: u64, sender: S) -> ReplyLock { ReplyLock { reply: Reply::new(unique, sender) } } } impl ReplyLock { /// Reply to a request with the given open result pub fn locked(self, start: u64, end: u64, typ: u32, pid: u32) { self.reply.ok(&fuse_lk_out { lk: fuse_file_lock { start: start, end: end, typ: typ, pid: pid, }, }); } /// Reply to a request with the given error code pub fn error(self, err: c_int) { self.reply.error(err); } } /// /// Bmap Reply /// #[derive(Debug)] pub struct ReplyBmap { reply: ReplyRaw, } impl Reply for ReplyBmap { fn new(unique: u64, sender: S) -> ReplyBmap { ReplyBmap { reply: Reply::new(unique, sender) } } } impl ReplyBmap { /// Reply to a request with the given open result pub fn bmap(self, block: u64) { self.reply.ok(&fuse_bmap_out { block: block, }); } /// Reply to a request with the given error code pub fn error(self, err: c_int) { self.reply.error(err); } } /// /// Ioctl Reply /// #[derive(Debug)] pub struct ReplyIoctl { reply: ReplyRaw, } impl Reply for ReplyIoctl { fn new(unique: u64, sender: S) -> ReplyIoctl { ReplyIoctl { reply: Reply::new(unique, sender) } } } impl ReplyIoctl { /// Reply to a request with the given open result pub fn ioctl(mut self, result: i32, data: &[u8]) { let header = fuse_ioctl_out { result: result, // these fields are only needed for unrestricted ioctls flags: 0, in_iovs: 1, out_iovs: if data.len() > 0 { 1 } else { 0 }, }; let header_len = mem::size_of::(); let header_p = &header as *const fuse_ioctl_out as *const u8; let header_bytes = unsafe { slice::from_raw_parts(header_p, header_len) }; debug!("IOCTL() header_bytes {}, data {}", header_bytes.len(), data.len()); if data.len() > 0 { self.reply.send(0, &[header_bytes, data]); } else { self.reply.send(0, &[header_bytes]); } } /// Reply to a request with the given error code pub fn error(self, err: c_int) { self.reply.error(err); } } /// /// Directory reply /// #[derive(Debug)] pub struct ReplyDirectory { reply: ReplyRaw<()>, data: Vec, } impl ReplyDirectory { /// Creates a new ReplyDirectory with a specified buffer size. pub fn new(unique: u64, sender: S, size: usize) -> ReplyDirectory { ReplyDirectory { reply: Reply::new(unique, sender), data: Vec::with_capacity(size), } } /// Add an entry to the directory reply buffer. Returns true if the buffer is full. /// A transparent offset value can be provided for each entry. The kernel uses these /// value to request the next entries in further readdir calls pub fn add>(&mut self, ino: u64, offset: i64, kind: FileType, name: T) -> bool { let name = name.as_ref().as_bytes(); let entlen = mem::size_of::() + name.len(); let entsize = (entlen + mem::size_of::() - 1) & !(mem::size_of::() - 1); // 64bit align let padlen = entsize - entlen; if self.data.len() + entsize > self.data.capacity() { return true; } unsafe { let p = self.data.as_mut_ptr().offset(self.data.len() as isize); let pdirent: *mut fuse_dirent = mem::transmute(p); (*pdirent).ino = ino; (*pdirent).off = offset as u64; (*pdirent).namelen = name.len() as u32; (*pdirent).typ = mode_from_kind_and_perm(kind, 0) >> 12; let p = p.offset(mem::size_of_val(&*pdirent) as isize); ptr::copy_nonoverlapping(name.as_ptr(), p, name.len()); let p = p.offset(name.len() as isize); ptr::write_bytes(p, 0u8, padlen); let newlen = self.data.len() + entsize; self.data.set_len(newlen); } false } /// Reply to a request with the filled directory buffer pub fn ok(mut self) { self.reply.send(0, &[&self.data]); } /// Reply to a request with the given error code pub fn error(self, err: c_int) { self.reply.error(err); } } /// /// DirectoryPlus reply /// #[derive(Debug)] pub struct ReplyDirectoryPlus { reply: ReplyRaw<()>, data: Vec, } impl ReplyDirectoryPlus { /// Creates a new ReplyDirectory with a specified buffer size. pub fn new(unique: u64, sender: S, size: usize) -> ReplyDirectoryPlus { ReplyDirectoryPlus { reply: Reply::new(unique, sender), data: Vec::with_capacity(size), } } /// Add an entry to the directory reply buffer. Returns true if the buffer is full. /// A transparent offset value can be provided for each entry. The kernel uses these /// value to request the next entries in further readdir calls pub fn add>(&mut self, ino: u64, offset: i64, name: T, ttl: &Duration, attr: &FileAttr, generation: u64) -> bool { let name = name.as_ref().as_bytes(); let entlen = mem::size_of::() + name.len(); let entsize = (entlen + mem::size_of::() - 1) & !(mem::size_of::() - 1); // 64bit align let padlen = entsize - entlen; if self.data.len() + entsize > self.data.capacity() { return true; } unsafe { let p = self.data.as_mut_ptr().offset(self.data.len() as isize); let pdirentplus: *mut fuse_direntplus = mem::transmute(p); (*pdirentplus).entry_out = fuse_entry_out { nodeid: attr.ino, generation: generation, entry_valid: ttl.as_secs(), attr_valid: ttl.as_secs(), entry_valid_nsec: ttl.subsec_nanos(), attr_valid_nsec: ttl.subsec_nanos(), attr: fuse_attr_from_attr(attr), }; let pdirent: *mut fuse_dirent = &mut (*pdirentplus).dirent; (*pdirent).ino = ino; (*pdirent).off = offset as u64; (*pdirent).namelen = name.len() as u32; (*pdirent).typ = mode_from_kind_and_perm(attr.kind, 0) >> 12; let p = p.offset(mem::size_of_val(&*pdirent) as isize); ptr::copy_nonoverlapping(name.as_ptr(), p, name.len()); let p = p.offset(name.len() as isize); ptr::write_bytes(p, 0u8, padlen); let newlen = self.data.len() + entsize; self.data.set_len(newlen); } false } /// Reply to a request with the filled directory buffer pub fn ok(mut self) { self.reply.send(0, &[&self.data]); } /// Reply to a request with the given error code pub fn error(self, err: c_int) { self.reply.error(err); } } /// /// Xattr reply /// #[derive(Debug)] pub struct ReplyXattr { reply: ReplyRaw, } impl Reply for ReplyXattr { fn new(unique: u64, sender: S) -> ReplyXattr { ReplyXattr { reply: Reply::new(unique, sender) } } } impl ReplyXattr { /// Reply to a request with the size of the xattr. pub fn size(self, size: u32) { self.reply.ok(&fuse_getxattr_out { size: size, padding: 0, }); } /// Reply to a request with the data in the xattr. pub fn data(mut self, data: &[u8]) { self.reply.send(0, &[data]); } /// Reply to a request with the given error code. pub fn error(self, err: c_int) { self.reply.error(err); } } /// /// Lseek Reply /// #[derive(Debug)] pub struct ReplyLseek { reply: ReplyRaw, } impl Reply for ReplyLseek { fn new(unique: u64, sender: S) -> ReplyLseek { ReplyLseek { reply: Reply::new(unique, sender) } } } impl ReplyLseek { /// Reply to a request with seeked offset pub fn offset(self, offset: i64) { self.reply.ok(&fuse_lseek_out { offset: offset, }); } /// Reply to a request with the given error code pub fn error(self, err: c_int) { self.reply.error(err); } } #[cfg(test)] mod test { use std::thread; use std::sync::mpsc::{channel, Sender}; use std::time::{Duration, UNIX_EPOCH}; use super::as_bytes; use super::{Reply, ReplyRaw, ReplyEmpty, ReplyData, ReplyEntry, ReplyAttr, ReplyOpen}; use super::{ReplyWrite, ReplyStatfs, ReplyCreate, ReplyLock, ReplyBmap, ReplyDirectory}; use super::ReplyXattr; #[cfg(target_os = "macos")] use super::ReplyXTimes; use crate::{FileType, FileAttr}; #[allow(dead_code)] #[repr(C)] struct Data { a: u8, b: u8, c: u16 } #[test] fn serialize_empty() { let data = (); as_bytes(&data, |bytes| { assert!(bytes.is_empty()); }); } #[test] fn serialize_slice() { let data: [u8; 4] = [0x12, 0x34, 0x56, 0x78]; as_bytes(&data, |bytes| { assert_eq!(bytes, [[0x12, 0x34, 0x56, 0x78]]); }); } #[test] fn serialize_struct() { let data = Data { a: 0x12, b: 0x34, c: 0x5678 }; as_bytes(&data, |bytes| { assert_eq!(bytes, [[0x12, 0x34, 0x78, 0x56]]); }); } #[test] fn serialize_tuple() { let data = (Data { a: 0x12, b: 0x34, c: 0x5678 }, Data { a: 0x9a, b: 0xbc, c: 0xdef0 }); as_bytes(&data, |bytes| { assert_eq!(bytes, [[0x12, 0x34, 0x78, 0x56, 0x9a, 0xbc, 0xf0, 0xde]]); }); } struct AssertSender { expected: Vec>, } impl super::ReplySender for AssertSender { fn send(&self, data: &[&[u8]]) { assert_eq!(self.expected, data); } } #[test] fn reply_raw() { let data = Data { a: 0x12, b: 0x34, c: 0x5678 }; let sender = AssertSender { expected: vec![ vec![0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00], vec![0x12, 0x34, 0x78, 0x56], ] }; let reply: ReplyRaw = Reply::new(0xdeadbeef, sender); reply.ok(&data); } #[test] fn reply_error() { let sender = AssertSender { expected: vec![ vec![0x10, 0x00, 0x00, 0x00, 0xbe, 0xff, 0xff, 0xff, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00], ] }; let reply: ReplyRaw = Reply::new(0xdeadbeef, sender); reply.error(66); } #[test] fn reply_empty() { let sender = AssertSender { expected: vec![ vec![0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00], ] }; let reply: ReplyEmpty = Reply::new(0xdeadbeef, sender); reply.ok(); } #[test] fn reply_data() { let sender = AssertSender { expected: vec![ vec![0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00], vec![0xde, 0xad, 0xbe, 0xef], ] }; let reply: ReplyData = Reply::new(0xdeadbeef, sender); reply.data(&[0xde, 0xad, 0xbe, 0xef]); } #[test] fn reply_entry() { let sender = AssertSender { expected: if cfg!(target_os = "macos") { vec![ vec![0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00], vec![0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], ] } else { vec![ vec![0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00], vec![0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], ] } }; let reply: ReplyEntry = Reply::new(0xdeadbeef, sender); let time = UNIX_EPOCH + Duration::new(0x1234, 0x5678); let ttl = Duration::new(0x8765, 0x4321); let attr = FileAttr { ino: 0x11, size: 0x22, blocks: 0x33, atime: time, mtime: time, ctime: time, crtime: time, kind: FileType::RegularFile, perm: 0o644, nlink: 0x55, uid: 0x66, gid: 0x77, rdev: 0x88, flags: 0x99 }; reply.entry(&ttl, &attr, 0xaa); } #[test] fn reply_attr() { let sender = AssertSender { expected: if cfg!(target_os = "macos") { vec![ vec![0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00], vec![0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], ] } else { vec![ vec![0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00], vec![0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], ] } }; let reply: ReplyAttr = Reply::new(0xdeadbeef, sender); let time = UNIX_EPOCH + Duration::new(0x1234, 0x5678); let ttl = Duration::new(0x8765, 0x4321); let attr = FileAttr { ino: 0x11, size: 0x22, blocks: 0x33, atime: time, mtime: time, ctime: time, crtime: time, kind: FileType::RegularFile, perm: 0o644, nlink: 0x55, uid: 0x66, gid: 0x77, rdev: 0x88, flags: 0x99 }; reply.attr(&ttl, &attr); } #[test] #[cfg(target_os = "macos")] fn reply_xtimes() { let sender = AssertSender { expected: vec![ vec![0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00], vec![0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00], ] }; let reply: ReplyXTimes = Reply::new(0xdeadbeef, sender); let time = UNIX_EPOCH + Duration::new(0x1234, 0x5678); reply.xtimes(time, time); } #[test] fn reply_open() { let sender = AssertSender { expected: vec![ vec![0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00], vec![0x22, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], ] }; let reply: ReplyOpen = Reply::new(0xdeadbeef, sender); reply.opened(0x1122, 0x33); } #[test] fn reply_write() { let sender = AssertSender { expected: vec![ vec![0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00], vec![0x22, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], ] }; let reply: ReplyWrite = Reply::new(0xdeadbeef, sender); reply.written(0x1122); } #[test] fn reply_statfs() { let sender = AssertSender { expected: vec![ vec![0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00], vec![0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], ] }; let reply: ReplyStatfs = Reply::new(0xdeadbeef, sender); reply.statfs(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88); } #[test] fn reply_create() { let sender = AssertSender { expected: if cfg!(target_os = "macos") { vec![ vec![0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00], vec![0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], ] } else { vec![ vec![0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00], vec![0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x21, 0x43, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], ] } }; let reply: ReplyCreate = Reply::new(0xdeadbeef, sender); let time = UNIX_EPOCH + Duration::new(0x1234, 0x5678); let ttl = Duration::new(0x8765, 0x4321); let attr = FileAttr { ino: 0x11, size: 0x22, blocks: 0x33, atime: time, mtime: time, ctime: time, crtime: time, kind: FileType::RegularFile, perm: 0o644, nlink: 0x55, uid: 0x66, gid: 0x77, rdev: 0x88, flags: 0x99 }; reply.created(&ttl, &attr, 0xaa, 0xbb, 0xcc); } #[test] fn reply_lock() { let sender = AssertSender { expected: vec![ vec![0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00], vec![0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00], ] }; let reply: ReplyLock = Reply::new(0xdeadbeef, sender); reply.locked(0x11, 0x22, 0x33, 0x44); } #[test] fn reply_bmap() { let sender = AssertSender { expected: vec![ vec![0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00], vec![0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], ] }; let reply: ReplyBmap = Reply::new(0xdeadbeef, sender); reply.bmap(0x1234); } #[test] fn reply_directory() { let sender = AssertSender { expected: vec![ vec![0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde, 0x00, 0x00, 0x00, 0x00], vec![0xbb, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00 ,0x00, 0x00, 0xdd, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x72, 0x73], ] }; let mut reply = ReplyDirectory::new(0xdeadbeef, sender, 4096); reply.add(0xaabb, 1, FileType::Directory, "hello"); reply.add(0xccdd, 2, FileType::RegularFile, "world.rs"); reply.ok(); } impl super::ReplySender for Sender<()> { fn send(&self, _: &[&[u8]]) { Sender::send(self, ()).unwrap() } } #[test] fn reply_xattr_size() { let sender = AssertSender { expected: vec![ vec![0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEF, 0xBE, 0xAD, 0xDE, 0x00, 0x00, 0x00, 0x00], vec![0x78, 0x56, 0x34, 0x12, 0x00,0x00, 0x00, 0x00], ] }; let reply = ReplyXattr::new(0xdeadbeef, sender); reply.size(0x12345678); } #[test] fn reply_xattr_data() { let sender = AssertSender { expected: vec![ vec![0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEF, 0xBE, 0xAD, 0xDE, 0x00, 0x00, 0x00, 0x00], vec![0x11, 0x22, 0x33, 0x44], ] }; let reply = ReplyXattr::new(0xdeadbeef, sender); reply.data(&vec![0x11, 0x22, 0x33, 0x44]); } #[test] fn async_reply() { let (tx, rx) = channel::<()>(); let reply: ReplyEmpty = Reply::new(0xdeadbeef, tx); thread::spawn(move || { reply.ok(); }); rx.recv().unwrap(); } } cntr-fuse-0.4.2/src/request.rs000064400000000000000000000461231046102023000143700ustar 00000000000000//! Filesystem operation request //! //! A request represents information about a filesystem operation the kernel driver wants us to //! perform. //! //! TODO: This module is meant to go away soon in favor of `ll::Request`. use std::convert::TryFrom; use std::path::Path; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use libc::{EIO, ENOSYS, EPROTO}; use cntr_fuse_abi::*; use cntr_fuse_abi::consts::*; use log::{debug, error, warn}; use crate::channel::ChannelSender; use crate::ll; use crate::reply::{Reply, ReplyRaw, ReplyEmpty, ReplyDirectory, ReplyDirectoryPlus, ReplyRead}; use crate::session::{MAX_WRITE_SIZE, Session}; use crate::Filesystem; /// We generally support async reads #[cfg(not(target_os = "macos"))] const INIT_FLAGS: u32 = FUSE_ASYNC_READ | FUSE_PARALLEL_DIROPS | FUSE_ATOMIC_O_TRUNC | FUSE_POSIX_ACL | FUSE_WRITEBACK_CACHE | FUSE_DONT_MASK; /// On macOS, we additionally support case insensitiveness, volume renames and xtimes /// TODO: we should eventually let the filesystem implementation decide which flags to set #[cfg(target_os = "macos")] const INIT_FLAGS: u32 = FUSE_ASYNC_READ | FUSE_EXPORT_SUPPORT | FUSE_BIG_WRITES | FUSE_CASE_INSENSITIVE | FUSE_VOL_RENAME | FUSE_XTIMES; /// Request data structure #[derive(Debug)] pub struct Request<'a> { /// Channel sender for sending the reply ch: ChannelSender, /// Request raw data data: &'a [u8], /// Parsed request request: ll::Request<'a>, } /// A file timestamp. #[derive(Clone, Copy, Debug, Hash, PartialEq)] pub enum UtimeSpec { #[cfg(target_os = "linux")] /// File timestamp is set to the current time. Now, /// The corresponding file timestamp is left unchanged. Omit, /// File timestamp is set to value Time(SystemTime) } #[cfg(target_os = "linux")] fn atime_to_timespec(arg: &fuse_setattr_in) -> UtimeSpec { if arg.valid & FATTR_ATIME_NOW != 0 { UtimeSpec::Now } else if arg.valid & FATTR_ATIME != 0 { UtimeSpec::Time(UNIX_EPOCH + Duration::new(arg.atime, arg.atimensec)) } else { UtimeSpec::Omit } } #[cfg(target_os = "linux")] fn mtime_to_timespec(arg: &fuse_setattr_in) -> UtimeSpec { if arg.valid & FATTR_MTIME_NOW != 0 { UtimeSpec::Now } else if arg.valid & FATTR_MTIME != 0 { UtimeSpec::Time(UNIX_EPOCH + Duration::new(arg.mtime, arg.mtimensec)) } else { UtimeSpec::Omit } } #[cfg(not(target_os = "linux"))] fn atime_to_timespec(arg: &fuse_setattr_in) -> UtimeSpec { if arg.valid & FATTR_ATIME != 0 { UtimeSpec::Time(UNIX_EPOCH + Duration::new(arg.atime, arg.atimensec)) } else { UtimeSpec::Omit } } #[cfg(not(target_os = "linux"))] fn mtime_to_timespec(arg: &fuse_setattr_in) -> UtimeSpec { if arg.valid & FATTR_MTIME != 0 { UtimeSpec::Time(UNIX_EPOCH + Duration::new(arg.atime, arg.atimensec)) } else { UtimeSpec::Omit } } impl<'a> Request<'a> { /// Create a new request from the given data pub fn new(ch: ChannelSender, data: &'a [u8]) -> Result, ll::RequestError<'a>> { ll::Request::try_from(data).map(|request| Self { ch, data, request }) } /// Dispatch request to the given filesystem. /// This calls the appropriate filesystem operation method for the /// request and sends back the returned reply to the kernel pub fn dispatch(&self, se: &mut Session) { debug!("{}", self.request); match self.request.operation() { // Filesystem initialization ll::Operation::Init { arg } => { let reply: ReplyRaw = self.reply(); // We don't support ABI versions before 7.6 if arg.major < 7 || (arg.major == 7 && arg.minor < 6) { error!("Unsupported FUSE ABI version {}.{}. Sorry, your kernel is too old!", arg.major, arg.minor); reply.error(EPROTO); return; } // Remember ABI version supported by kernel se.proto_major = arg.major; se.proto_minor = arg.minor; // Call filesystem init method and give it a chance to return an error let res = se.filesystem.init(self); if let Err(err) = res { reply.error(err); return; } // Reply with our desired version and settings. If the kernel supports a // larger major version, it'll re-send a matching init message. If it // supports only lower major versions, we replied with an error above. se.initialized = true; if arg.minor < 23 { let init = fuse_init_out_22 { major: FUSE_KERNEL_VERSION, minor: FUSE_KERNEL_MINOR_VERSION, max_readahead: arg.max_readahead, // accept any readahead size flags: arg.flags & INIT_FLAGS, // use features given in INIT_FLAGS and reported as capable max_background: se.max_background, congestion_threshold: se.congestion_threshold, max_write: MAX_WRITE_SIZE as u32, // use a max write size that fits into the session's buffer }; debug!("INIT response: ABI {}.{}, flags {:#x}, max readahead {}, max write {}", init.major, init.minor, init.flags, init.max_readahead, init.max_write); reply.ok(&init); } else { let init = fuse_init_out { major: FUSE_KERNEL_VERSION, minor: FUSE_KERNEL_MINOR_VERSION, max_readahead: arg.max_readahead, // accept any readahead size flags: arg.flags & INIT_FLAGS, // use features given in INIT_FLAGS and reported as capable max_background: se.max_background, congestion_threshold: se.congestion_threshold, max_write: MAX_WRITE_SIZE as u32, // use a max write size that fits into the session's buffer time_gran: 1, reserved: [0; 9] }; debug!("INIT response: ABI {}.{}, flags {:#x}, max readahead {}, max write {}", init.major, init.minor, init.flags, init.max_readahead, init.max_write); reply.ok(&init); } } // Any operation is invalid before initialization _ if !se.initialized => { warn!("Ignoring FUSE operation before init: {}", self.request); self.reply::().error(EIO); } // Filesystem destroyed ll::Operation::Destroy => { se.filesystem.destroy(self); se.destroyed = true; self.reply::().ok(); } // Any operation is invalid after destroy _ if se.destroyed => { warn!("Ignoring FUSE operation after destroy: {}", self.request); self.reply::().error(EIO); } ll::Operation::Interrupt { .. } => { // TODO: handle FUSE_INTERRUPT self.reply::().error(ENOSYS); } ll::Operation::Lookup { name } => { se.filesystem.lookup(self, self.request.nodeid(), &name, self.reply()); } ll::Operation::Forget { arg } => { se.filesystem.forget(self, self.request.nodeid(), arg.nlookup); // no reply } ll::Operation::GetAttr => { se.filesystem.getattr(self, self.request.nodeid(), self.reply()); } ll::Operation::SetAttr { arg } => { let mode = match arg.valid & FATTR_MODE { 0 => None, _ => Some(arg.mode), }; let uid = match arg.valid & FATTR_UID { 0 => None, _ => Some(arg.uid), }; let gid = match arg.valid & FATTR_GID { 0 => None, _ => Some(arg.gid), }; let size = match arg.valid & FATTR_SIZE { 0 => None, _ => Some(arg.size), }; let atime = atime_to_timespec(arg); let mtime = mtime_to_timespec(arg); let fh = match arg.valid & FATTR_FH { 0 => None, _ => Some(arg.fh), }; #[cfg(target_os = "macos")] #[inline] fn get_macos_setattr(arg: &fuse_setattr_in) -> (Option, Option, Option, Option) { let crtime = match arg.valid & FATTR_CRTIME { 0 => None, _ => Some(UNIX_EPOCH + Duration::new(arg.crtime, arg.crtimensec)), }; let chgtime = match arg.valid & FATTR_CHGTIME { 0 => None, _ => Some(UNIX_EPOCH + Duration::new(arg.chgtime, arg.chgtimensec)), }; let bkuptime = match arg.valid & FATTR_BKUPTIME { 0 => None, _ => Some(UNIX_EPOCH + Duration::new(arg.bkuptime, arg.bkuptimensec)), }; let flags = match arg.valid & FATTR_FLAGS { 0 => None, _ => Some(arg.flags), }; (crtime, chgtime, bkuptime, flags) } #[cfg(not(target_os = "macos"))] #[inline] fn get_macos_setattr(_arg: &fuse_setattr_in) -> (Option, Option, Option, Option) { (None, None, None, None) } let (crtime, chgtime, bkuptime, flags) = get_macos_setattr(arg); se.filesystem.setattr(self, self.request.nodeid(), mode, uid, gid, size, atime, mtime, fh, crtime, chgtime, bkuptime, flags, self.reply()); } ll::Operation::ReadLink => { se.filesystem.readlink(self, self.request.nodeid(), self.reply()); } ll::Operation::MkNod { arg, name } => { se.filesystem.mknod(self, self.request.nodeid(), &name, arg.mode, arg.umask, arg.rdev, self.reply()); } ll::Operation::MkDir { arg, name } => { se.filesystem.mkdir(self, self.request.nodeid(), &name, arg.mode, arg.umask, self.reply()); } ll::Operation::Unlink { name } => { se.filesystem.unlink(self, self.request.nodeid(), &name, self.reply()); } ll::Operation::RmDir { name } => { se.filesystem.rmdir(self, self.request.nodeid(), &name, self.reply()); } ll::Operation::SymLink { name, link } => { se.filesystem.symlink(self, self.request.nodeid(), &name, &Path::new(link), self.reply()); } ll::Operation::Rename { arg, name, newname } => { se.filesystem.rename(self, self.request.nodeid(), &name, arg.newdir, &newname, self.reply()); } ll::Operation::Link { arg, name } => { se.filesystem.link(self, arg.oldnodeid, self.request.nodeid(), &name, self.reply()); } ll::Operation::Open { arg } => { se.filesystem.open(self, self.request.nodeid(), arg.flags, self.reply()); } ll::Operation::Read { arg } => { let reply = ReplyRead::new(self.request.unique(), self.ch); se.filesystem.read(self, self.request.nodeid(), arg.fh, arg.offset as i64, arg.size, reply); } ll::Operation::Write { arg, data } => { assert!(data.len() == arg.size as usize); se.filesystem.write(self, self.request.nodeid(), arg.fh, arg.offset as i64, data, arg.write_flags, self.reply()); } ll::Operation::Flush { arg } => { se.filesystem.flush(self, self.request.nodeid(), arg.fh, arg.lock_owner, self.reply()); } ll::Operation::Release { arg } => { let flush = match arg.release_flags & FUSE_RELEASE_FLUSH { 0 => false, _ => true, }; se.filesystem.release(self, self.request.nodeid(), arg.fh, arg.flags, arg.lock_owner, flush, self.reply()); } ll::Operation::FSync { arg } => { let datasync = match arg.fsync_flags & 1 { 0 => false, _ => true, }; se.filesystem.fsync(self, self.request.nodeid(), arg.fh, datasync, self.reply()); } ll::Operation::OpenDir { arg } => { se.filesystem.opendir(self, self.request.nodeid(), arg.flags, self.reply()); } ll::Operation::ReadDir { arg } => { se.filesystem.readdir(self, self.request.nodeid(), arg.fh, arg.offset as i64, ReplyDirectory::new(self.request.unique(), self.ch, arg.size as usize)); } ll::Operation::ReadDirPlus { arg } => { se.filesystem.readdirplus(self, self.request.nodeid(), arg.fh, arg.offset, ReplyDirectoryPlus::new(self.request.unique(), self.ch, arg.size as usize)); } ll::Operation::Rename2 { arg, name, newname } => { se.filesystem.rename2(self, self.request.nodeid(), &name, arg.newdir, &newname, arg.flags, self.reply()); } ll::Operation::Lseek { arg } => { se.filesystem.lseek(self, self.request.nodeid(), arg.fh, arg.offset, arg.whence, self.reply()); } ll::Operation::ReleaseDir { arg } => { se.filesystem.releasedir(self, self.request.nodeid(), arg.fh, arg.flags, self.reply()); } ll::Operation::FSyncDir { arg } => { let datasync = match arg.fsync_flags & 1 { 0 => false, _ => true, }; se.filesystem.fsyncdir(self, self.request.nodeid(), arg.fh, datasync, self.reply()); } ll::Operation::StatFs => { se.filesystem.statfs(self, self.request.nodeid(), self.reply()); } ll::Operation::SetXAttr { arg, name, value } => { assert!(value.len() == arg.size as usize); #[cfg(target_os = "macos")] #[inline] fn get_position (arg: &fuse_setxattr_in) -> u32 { arg.position } #[cfg(not(target_os = "macos"))] #[inline] fn get_position (_arg: &fuse_setxattr_in) -> u32 { 0 } se.filesystem.setxattr(self, self.request.nodeid(), name, value, arg.flags, get_position(arg), self.reply()); } ll::Operation::GetXAttr { arg, name } => { se.filesystem.getxattr(self, self.request.nodeid(), name, arg.size, self.reply()); } ll::Operation::ListXAttr { arg } => { se.filesystem.listxattr(self, self.request.nodeid(), arg.size, self.reply()); } ll::Operation::RemoveXAttr { name } => { se.filesystem.removexattr(self, self.request.nodeid(), name, self.reply()); } ll::Operation::Access { arg } => { se.filesystem.access(self, self.request.nodeid(), arg.mask, self.reply()); } ll::Operation::Create { arg, name } => { se.filesystem.create(self, self.request.nodeid(), &name, arg.mode, arg.umask, arg.flags, self.reply()); } ll::Operation::IoCtl { arg, in_data } => { if (arg.flags & FUSE_IOCTL_UNRESTRICTED) > 0 { self.reply::().error(ENOSYS); } else { se.filesystem.ioctl(self, self.request.nodeid(), arg.fh, arg.flags, arg.cmd, *in_data, arg.out_size, self.reply()); } }, ll::Operation::Poll { arg: _ } => { self.reply::().error(ENOSYS); } ll::Operation::NotifyReply { arg: _ } => { self.reply::().error(ENOSYS); } ll::Operation::BatchForget { arg: _, nodes } => { se.filesystem.forget_multi(self, nodes); // no reply }, ll::Operation::FAllocate { arg } => { se.filesystem.fallocate(self, self.request.nodeid(), arg.fh, arg.offset, arg.length, arg.mode, self.reply()); } ll::Operation::GetLk { arg } => { se.filesystem.getlk(self, self.request.nodeid(), arg.fh, arg.owner, arg.lk.start, arg.lk.end, arg.lk.typ, arg.lk.pid, self.reply()); } ll::Operation::SetLk { arg } => { se.filesystem.setlk(self, self.request.nodeid(), arg.fh, arg.owner, arg.lk.start, arg.lk.end, arg.lk.typ, arg.lk.pid, false, self.reply()); } ll::Operation::SetLkW { arg } => { se.filesystem.setlk(self, self.request.nodeid(), arg.fh, arg.owner, arg.lk.start, arg.lk.end, arg.lk.typ, arg.lk.pid, true, self.reply()); } ll::Operation::BMap { arg } => { se.filesystem.bmap(self, self.request.nodeid(), arg.blocksize, arg.block, self.reply()); } #[cfg(target_os = "macos")] ll::Operation::SetVolName { name } => { se.filesystem.setvolname(self, name, self.reply()); } #[cfg(target_os = "macos")] ll::Operation::GetXTimes => { se.filesystem.getxtimes(self, self.request.nodeid(), self.reply()); } #[cfg(target_os = "macos")] ll::Operation::Exchange { arg, oldname, newname } => { se.filesystem.exchange(self, arg.olddir, &oldname, arg.newdir, &newname, arg.options, self.reply()); } ll::Operation::CuseInit { arg: _ } => { self.reply::().error(ENOSYS); } } } /// Create a reply object for this request that can be passed to the filesystem /// implementation and makes sure that a request is replied exactly once fn reply(&self) -> T { Reply::new(self.request.unique(), self.ch) } /// Returns the unique identifier of this request #[inline] #[allow(dead_code)] pub fn unique(&self) -> u64 { self.request.unique() } /// Returns the uid of this request #[inline] #[allow(dead_code)] pub fn uid(&self) -> u32 { self.request.uid() } /// Returns the gid of this request #[inline] #[allow(dead_code)] pub fn gid(&self) -> u32 { self.request.gid() } /// Returns the pid of this request #[inline] #[allow(dead_code)] pub fn pid(&self) -> u32 { self.request.pid() } } cntr-fuse-0.4.2/src/session.rs000064400000000000000000000167461046102023000143730ustar 00000000000000//! Filesystem session //! //! A session runs a filesystem implementation while it is being mounted to a specific mount //! point. A session begins by mounting the filesystem and ends by unmounting it. While the //! filesystem is mounted, the session loop receives, dispatches and replies to kernel requests //! for filesystem operations under its mount point. use std::io; use std::fmt; #[cfg(feature = "libfuse")] use std::ffi::OsStr; use std::os::unix::io::RawFd; use std::path::{PathBuf, Path}; use std::thread::{self, JoinHandle}; use log::info; #[cfg(feature = "libfuse")] use log::error; use libc::{EAGAIN, EINTR, ENODEV, ENOENT, ENOSYS}; #[cfg(feature = "libfuse")] use crate::channel; use crate::channel::Channel; use crate::ll::RequestError; use crate::request::Request; use crate::Filesystem; use crate::Reply; use crate::ReplyEmpty; /// The max size of write requests from the kernel. The absolute minimum is 4k, /// FUSE recommends at least 128k, max 16M. The FUSE default is 16M on macOS /// and 128k on other systems. pub const MAX_WRITE_SIZE: usize = 128 * 1024; const PAGE_SIZE: usize = 4096; /// Size of the buffer for reading a request from the kernel. Since the kernel may send /// up to MAX_WRITE_SIZE bytes in a write request, we use that value plus some extra space. const BUFFER_SIZE: usize = MAX_WRITE_SIZE + PAGE_SIZE; /// The session data structure #[derive(Debug)] pub struct Session { /// Filesystem operation implementations pub filesystem: FS, /// Communication channel to the kernel driver ch: Channel, /// FUSE protocol major version pub proto_major: u32, /// FUSE protocol minor version pub proto_minor: u32, /// True if the filesystem is initialized (init operation done) pub initialized: bool, /// True if the filesystem was destroyed (destroy operation done) pub destroyed: bool, /// Number of queued requests in the kernel pub max_background: u16, /// Threshold when waiting fuse users are put into sleep state instead of busy loop pub congestion_threshold: u16 } impl Session { /// Create a new session by mounting the given filesystem to the given mountpoint #[cfg(feature = "libfuse")] pub fn new(filesystem: FS, mountpoint: &Path, options: &[&OsStr], max_background: u16, congestion_threshold: u16) -> io::Result> { info!("Mounting {}", mountpoint.display()); Channel::new(mountpoint, options).map(|ch| { Session { filesystem: filesystem, ch: ch, proto_major: 0, proto_minor: 0, initialized: false, destroyed: false, max_background: max_background, congestion_threshold: congestion_threshold } }) } /// Create a new session by using a file descriptor "/dev/fuse" pub fn new_from_fd(filesystem: FS, fd: RawFd, mountpoint: &Path, max_background: u16, congestion_threshold: u16) -> io::Result> { Ok(Session { filesystem: filesystem, ch: Channel::new_from_fd(fd, mountpoint)?, proto_major: 0, proto_minor: 0, // This hacky in general, but ok for CntrFS, // we need this in CntrFs to support multi-threading. initialized: true, destroyed: false, max_background: max_background, congestion_threshold: congestion_threshold }) } /// Return path of the mounted filesystem pub fn mountpoint(&self) -> &Path { &self.ch.mountpoint() } /// Run the session loop that receives kernel requests and dispatches them to method /// calls into the filesystem. This read-dispatch-loop is non-concurrent to prevent /// having multiple buffers (which take up much memory), but the filesystem methods /// may run concurrent by spawning threads. pub fn run(&mut self) -> io::Result<()> { // Buffer for receiving requests from the kernel. Only one is allocated and // it is reused immediately after dispatching to conserve memory and allocations. let mut buffer: Vec = Vec::with_capacity(BUFFER_SIZE); loop { // Read the next request from the given channel to kernel driver // The kernel driver makes sure that we get exactly one request per read match self.ch.receive(&mut buffer) { Ok(()) => match Request::new(self.ch.sender(), &buffer) { // Dispatch request Ok(req) => req.dispatch(self), Err(RequestError::UnknownOperation(header)) => { let reply = ReplyEmpty::new(header.unique, self.ch.sender()); reply.error(ENOSYS); continue; } // Quit loop on illegal request Err(_) => break, }, Err(err) => match err.raw_os_error() { // Operation interrupted. Accordingly to FUSE, this is safe to retry Some(ENOENT) => continue, // Interrupted system call, retry Some(EINTR) => continue, // Explicitly try again Some(EAGAIN) => continue, // Filesystem was unmounted, quit the loop Some(ENODEV) => break, // Unhandled error _ => return Err(err), } } } Ok(()) } } impl<'a, FS: 'static + Filesystem + Send + 'a> Session { /// Run the session loop in a background thread pub fn spawn(self) -> io::Result { BackgroundSession::new(self) } } impl Drop for Session { fn drop(&mut self) { info!("Unmounted {}", self.mountpoint().display()); } } /// The background session data structure pub struct BackgroundSession { /// Path of the mounted filesystem pub mountpoint: PathBuf, /// Thread guard of the background session pub guard: JoinHandle>, } impl<'a> BackgroundSession { /// Create a new background session for the given session by running its /// session loop in a background thread. If the returned handle is dropped, /// the filesystem is unmounted and the given session ends. pub fn new(se: Session) -> io::Result { let mountpoint = se.mountpoint().to_path_buf(); let child = thread::spawn(move || { let mut se = se; se.run() }); Ok(BackgroundSession { mountpoint: mountpoint, guard: child }) } } impl<'a> Drop for BackgroundSession { fn drop(&mut self) { info!("Unmounting {}", self.mountpoint.display()); // Unmounting the filesystem will eventually end the session loop, // drop the session and hence end the background thread. #[cfg(feature = "libfuse")] match channel::unmount(&self.mountpoint) { Ok(()) => (), Err(err) => error!("Failed to unmount {}: {}", self.mountpoint.display(), err), } } } // replace with #[derive(Debug)] if Debug ever gets implemented for // thread_scoped::JoinGuard impl<'a> fmt::Debug for BackgroundSession { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { write!(f, "BackgroundSession {{ mountpoint: {:?}, guard: JoinGuard<()> }}", self.mountpoint) } }