├── .cargo └── config ├── .gitignore ├── filer ├── src │ ├── str_build.rs │ ├── lib.rs │ ├── branch.rs │ ├── types.rs │ ├── thread_ctx.rs │ ├── fs.rs │ ├── plugin_store.rs │ ├── error.rs │ ├── backend.rs │ └── node.rs └── Cargo.toml ├── daemon.conf ├── client.conf ├── memflow.service ├── filer-fuse ├── Cargo.toml └── src │ └── lib.rs ├── Makefile ├── filer-tokio ├── Cargo.toml └── src │ └── lib.rs ├── Cargo.toml ├── cloudflow ├── Cargo.toml └── src │ ├── util.rs │ ├── lib.rs │ ├── connector.rs │ ├── module.rs │ ├── process.rs │ └── os.rs ├── cloudflow-minidump ├── Cargo.toml └── src │ └── lib.rs ├── cloudflow-node ├── Cargo.toml └── src │ └── main.rs ├── LICENSE ├── install.sh ├── CONTRIBUTE.md ├── .github └── workflows │ └── build.yml └── README.md /.cargo/config: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | *.swp 4 | .vscode 5 | flow-bench/target 6 | -------------------------------------------------------------------------------- /filer/src/str_build.rs: -------------------------------------------------------------------------------- 1 | use crate::error::*; 2 | 3 | pub trait StrBuild: Sized { 4 | fn build(input: &str, ctx: &C) -> Result; 5 | } 6 | -------------------------------------------------------------------------------- /daemon.conf: -------------------------------------------------------------------------------- 1 | { 2 | "verbosity": "info", 3 | "pid_file": "/var/run/memflow.pid", 4 | "log_file": "/var/log/memflow.log", 5 | "socket_addr": "127.0.0.1:8000" 6 | } 7 | -------------------------------------------------------------------------------- /client.conf: -------------------------------------------------------------------------------- 1 | { 2 | "verbosity": "info", 3 | "pid_file": "/var/run/memflow.pid", 4 | "log_file": "/var/log/memflow.log", 5 | "socket_addr": "http://127.0.0.1:8000" 6 | } 7 | -------------------------------------------------------------------------------- /memflow.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=memflow daemon service 3 | 4 | [Service] 5 | Type=simple 6 | ExecStart=/usr/bin/memflowd --config /etc/memflow/daemon.conf 7 | 8 | [Install] 9 | WantedBy=default.target 10 | -------------------------------------------------------------------------------- /filer-fuse/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "filer-fuse" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | filer = { version = "0.1", path = "../filer" } 8 | time = "0.1" 9 | libc = "0.2" 10 | log = "0.4" 11 | 12 | [target.'cfg(not(windows))'.dependencies] 13 | fuse_mt = "0.5" 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all release debug test install 2 | 3 | all: 4 | make test 5 | make release 6 | 7 | release: 8 | cargo build --release --all-features 9 | 10 | debug: 11 | cargo build --all-features 12 | 13 | clean: 14 | cargo clean 15 | 16 | test: 17 | cargo test --all-features 18 | 19 | install: 20 | ./install.sh 21 | -------------------------------------------------------------------------------- /filer-tokio/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "filer-tokio" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | tokio = { version = "1", features = ["full"] } 10 | filer = { version = "0.1", path = "../filer" } 11 | cglue = "=0.2.14" 12 | async-trait = "0.1" 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [profile.bench] 2 | debug = true 3 | 4 | [profile.release] 5 | #lto = true 6 | debug = true 7 | 8 | [workspace] 9 | resolver = "1" 10 | members = [ 11 | "cloudflow", 12 | "cloudflow-node", 13 | "filer", 14 | "filer-fuse", 15 | "filer-tokio", 16 | "cloudflow-minidump" 17 | ] 18 | default-members = [ 19 | "cloudflow", 20 | "cloudflow-node", 21 | "filer", 22 | "filer-fuse", 23 | "filer-tokio", 24 | "cloudflow-minidump" 25 | ] -------------------------------------------------------------------------------- /filer/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod backend; 2 | pub mod branch; 3 | pub mod error; 4 | pub mod fs; 5 | pub mod node; 6 | pub mod plugin_store; 7 | pub mod str_build; 8 | pub mod thread_ctx; 9 | pub mod types; 10 | 11 | pub mod prelude { 12 | pub mod v1 { 13 | pub use crate::{ 14 | backend::*, branch::*, error::*, fs::*, node::*, plugin_store::*, str_build::StrBuild, 15 | thread_ctx::*, types::*, 16 | }; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /cloudflow/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cloudflow" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | memflow = "0.2" 10 | abi_stable = "0.10" 11 | cglue = { version = "=0.2.14", default-features = false, features = ["layout_checks"] } 12 | filer = { version = "0.1", path = "../filer" } 13 | once_cell = "1.9" 14 | num = "0.4" 15 | dashmap = "5" 16 | -------------------------------------------------------------------------------- /filer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "filer" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | abi_stable = { version = "0.10" } 10 | cglue = { version = "=0.2.14", default-features = false, features = ["layout_checks"] } 11 | dashmap = "5" 12 | sharded-slab = "0.1" 13 | crossbeam-deque = "0.8" 14 | once_cell = "1.9" 15 | 16 | [features] 17 | default = ["std"] 18 | std = [] 19 | -------------------------------------------------------------------------------- /cloudflow-minidump/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cloudflow-minidump" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | minidump-writer = { git = "https://github.com/h33p/minidump-writer" } 10 | memflow = "0.2" 11 | abi_stable = "0.10" 12 | cglue = { version = "=0.2.14", default-features = false, features = ["layout_checks"] } 13 | filer = { version = "0.1", path = "../filer" } 14 | cloudflow = { version = "0.1", path = "../cloudflow" } 15 | -------------------------------------------------------------------------------- /cloudflow-node/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cloudflow-node" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [[bin]] 7 | name = "cloudflow" 8 | path = "src/main.rs" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | cloudflow = { path = "../cloudflow" } 14 | anyhow = "1" 15 | memflow = "0.2" 16 | filer = { version = "0.1", path = "../filer" } 17 | filer-fuse = { version = "0.1", path = "../filer-fuse" } 18 | cloudflow-minidump = { version = "0.1", path = "../cloudflow-minidump" } 19 | simplelog = "^0.12.1" 20 | log = "0.4" 21 | clap = { version = "4.4", features = ["cargo"] } 22 | sudo = "0.6" 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ko1N 4 | Copyright (c) 2020 Aurimas Blažulionis <0x60@pm.me> 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cargo build --release --all-features 4 | 5 | # figure out connector name 6 | if [[ "$OSTYPE" == "darwin"* ]]; then 7 | FILENAME=target/release/libmemflow_daemon_connector.dylib 8 | else 9 | FILENAME=target/release/libmemflow_daemon_connector.so 10 | fi 11 | 12 | if [ ! -z "$1" ] && [ $1 = "--system" ]; then 13 | # install daemon + cli 14 | if [[ "$OSTYPE" != "darwin"* ]]; then 15 | echo "installing/updating daemon service and cli tool" 16 | 17 | sudo systemctl stop memflow.service 18 | 19 | sudo cp target/release/memflow-cli /usr/bin/memflow 20 | sudo cp target/release/memflow-daemon /usr/bin/memflowd 21 | 22 | sudo mkdir -p /etc/memflow/ 23 | sudo cp daemon.conf /etc/memflow/daemon.conf 24 | 25 | sudo cp client.conf /etc/memflow/client.conf 26 | 27 | sudo cp memflow.service /etc/systemd/system/ 28 | sudo systemctl enable memflow.service 29 | sudo systemctl start memflow.service 30 | fi 31 | 32 | # install connector to system dir 33 | echo "installing connector system-wide in /usr/lib/memflow" 34 | if [[ ! -d /usr/lib/memflow ]]; then 35 | sudo mkdir /usr/lib/memflow 36 | fi 37 | sudo cp $FILENAME /usr/lib/memflow 38 | fi 39 | 40 | # install connector in user dir 41 | echo "installing connector for user in ~/.local/lib/memflow" 42 | if [[ ! -d ~/.local/lib/memflow ]]; then 43 | mkdir -p ~/.local/lib/memflow 44 | fi 45 | cp $FILENAME ~/.local/lib/memflow 46 | -------------------------------------------------------------------------------- /CONTRIBUTE.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | There is a feature missing? A bug you have noticed? Some inconsistencies? **Contributions are welcome, and are encouraged!** 4 | 5 | ## Guidelines 6 | 7 | We welcome your contributions, and we love to keep our code standards high. So, there are a few key guidelines that you should follow for smooth sailing: 8 | 9 | - All our code is formatted using rustfmt. Please, run `cargo fmt --all` before committing your changes. 10 | - Make sure all of the tests pass with `cargo test`, as this would prevent us from merging your changes. 11 | - Make sure that clippy does not complain with `cargo clippy --all-targets --all-features --workspace -- -D warnings -D clippy::all` 12 | 13 | ## Review 14 | 15 | Once you submit a pull request, one of the maintainers will have a look at it, and give you some feedback. If everything looks alright, we will be almost ready to merge it in! If not, the maintainer will point you to the right direction where things may need changing in the code. 16 | 17 | ## Merging 18 | 19 | Once the code is ready, the last step is merging. There are only 2 important things that you need to confirm: 20 | 21 | - That the code is yours 22 | - And that you agree with the project's license terms to be applied to the entire pull request. 23 | 24 | By default, we will go by the assumption that those 2 points are true, but it would still be nice that you confirmed those. And sometimes, we may ask you to do so, just to be sure. 25 | 26 | Ultimately, unless you state otherwise, the merged code will be licensed under the current license of the project. 27 | 28 | Anyways, thanks for giving this a read, and happy hacking! 29 | -------------------------------------------------------------------------------- /cloudflow/src/util.rs: -------------------------------------------------------------------------------- 1 | pub use cglue::slice::CSliceMut; 2 | 3 | use filer::prelude::v1::*; 4 | use memflow::prelude::v1::*; 5 | 6 | /// Splits the connector/os arguments into parts. 7 | /// 8 | /// The parts provided are: 9 | /// 10 | /// 1. Parent OS/Connector to chain with. 11 | /// 2. Name of the plugin library. 12 | /// 3. Arguments for the plugin. 13 | /// 14 | pub fn split_args(input: &str) -> (Option<&str>, &str, &str) { 15 | let input = input.trim(); 16 | 17 | let (chain_with, input) = if input.starts_with("-c ") { 18 | let input = input.strip_prefix("-c").unwrap().trim(); 19 | 20 | input 21 | .split_once(" ") 22 | .map(|(a, b)| (Some(a), b)) 23 | .unwrap_or((Some(input), "")) 24 | } else { 25 | (None, input) 26 | }; 27 | 28 | let (name, args) = input.split_once(":").unwrap_or((input, "")); 29 | (chain_with, name, args) 30 | } 31 | 32 | pub fn memdata_map, CTup2>) -> O, O>( 33 | VecOps { inp, out, out_fail }: VecOps>, 34 | func: F, 35 | ) -> O { 36 | let inp = inp.map(|CTup2(a, b)| CTup3(a.into(), a.into(), b)); 37 | let mut out = 38 | out.map(|c| |CTup2(a, b): CTup2| c.call(CTup2(a.to_umem() as Size, b))); 39 | let mut out = out.as_mut().map(<_>::into); 40 | let mut out_fail = out_fail.map(|c| { 41 | |CTup2(a, b): CTup2| { 42 | c.call( 43 | ( 44 | CTup2(a.to_umem() as Size, b), 45 | filer::error::Error( 46 | filer::error::ErrorOrigin::Other, 47 | filer::error::ErrorKind::Unknown, 48 | ), 49 | ) 50 | .into(), 51 | ) 52 | } 53 | }); 54 | let mut out_fail = out_fail.as_mut().map(<_>::into); 55 | MemOps::with_raw(inp, out.as_mut(), out_fail.as_mut(), func) 56 | } 57 | -------------------------------------------------------------------------------- /cloudflow/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate filer; 3 | 4 | pub use cglue::slice::CSliceMut; 5 | use cglue::trait_group::c_void; 6 | use connector::ThreadedConnectorArc; 7 | use filer::prelude::v1::*; 8 | use memflow::prelude::v1::*; 9 | use os::OsRoot; 10 | use std::sync::Arc; 11 | 12 | pub mod connector; 13 | pub mod module; 14 | pub mod os; 15 | pub mod process; 16 | pub mod util; 17 | 18 | const BUILTIN_PLUGINS: &[extern "C" fn(&Node, CArc)] = &[ 19 | os::on_node, 20 | process::on_node, 21 | module::on_node, 22 | connector::on_node, 23 | ]; 24 | 25 | pub fn create_node() -> CArcSome { 26 | let backend = NodeBackend::default(); 27 | 28 | MemflowBackend::to_node(&backend); 29 | 30 | let node = Node::new(backend); 31 | 32 | for plugin in BUILTIN_PLUGINS { 33 | plugin(&node, Default::default()); 34 | } 35 | 36 | node.into() 37 | } 38 | 39 | pub struct MemflowBackend { 40 | connector: Arc>>, 41 | os: Arc>>, 42 | inventory: Inventory, 43 | } 44 | 45 | impl Default for MemflowBackend { 46 | fn default() -> Self { 47 | Self { 48 | connector: LocalBackend::default().with_new().into(), 49 | os: LocalBackend::default().with_new().into(), 50 | inventory: Inventory::scan(), 51 | } 52 | } 53 | } 54 | 55 | impl MemflowBackend { 56 | fn new_arc() -> CArcSome { 57 | let ret = Arc::from(Self::default()); 58 | 59 | // SAFETY: we are not reading the underlying object from anywhere else. 60 | unsafe { 61 | unsafe fn ptr_mut(ptr: *const T) -> *mut T { 62 | ptr as *mut T 63 | } 64 | 65 | (*ptr_mut(&*ret.connector)).set_context(ret.clone()); 66 | (*ptr_mut(&*ret.os)).set_context(ret.clone()); 67 | } 68 | 69 | ret.into() 70 | } 71 | 72 | fn add_to_node(&self, backend: &NodeBackend) { 73 | backend.add_backend("connector", self.connector.clone()); 74 | backend.add_backend("os", self.os.clone()); 75 | } 76 | 77 | pub fn to_node(backend: &NodeBackend) { 78 | Self::new_arc().add_to_node(backend) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: install fuse 16 | run: sudo apt-get install fuse libfuse-dev pkg-config 17 | 18 | - name: Build 19 | run: cargo build --workspace --verbose 20 | 21 | - name: Build examples 22 | run: cargo build --workspace --examples --verbose 23 | 24 | test: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v2 28 | 29 | - name: install fuse 30 | run: sudo apt-get install fuse libfuse-dev pkg-config 31 | 32 | - name: Run all tests 33 | run: cargo test --workspace --verbose 34 | 35 | lint: 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v2 39 | 40 | - name: install fuse 41 | run: sudo apt-get install fuse libfuse-dev pkg-config 42 | 43 | - run: rustup component add clippy 44 | 45 | - name: Check formatting 46 | run: cargo fmt -- --check 47 | 48 | - uses: actions-rs/clippy-check@v1 49 | with: 50 | token: ${{ secrets.GITHUB_TOKEN }} 51 | args: --all-targets 52 | 53 | build-coverage: 54 | runs-on: ubuntu-latest 55 | steps: 56 | - uses: actions/checkout@v2 57 | 58 | - name: install fuse 59 | run: sudo apt-get install fuse libfuse-dev pkg-config 60 | 61 | - name: Set up Rust nightly 62 | uses: actions-rs/toolchain@v1 63 | with: 64 | profile: minimal 65 | toolchain: nightly 66 | override: true 67 | 68 | - run: rustup component add rustfmt 69 | 70 | - run: cargo install grcov 71 | 72 | - name: Run tests with coverage 73 | run: | 74 | export CARGO_INCREMENTAL=0 75 | export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" 76 | export RUSTDOCFLAGS="-Cpanic=abort" 77 | cargo build --workspace --exclude memflow-derive 78 | cargo test --workspace --exclude memflow-derive 79 | grcov ./target/debug/ -s . -t lcov --llvm --branch --ignore-not-existing -o ./target/debug/coverage 80 | bash <(curl -s https://codecov.io/bash) -f ./target/debug/coverage -t ${{ secrets.CODECOV_TOKEN }}; 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cloudflow 2 | 3 | ![Build and test](https://github.com/memflow/memflow-cli/workflows/Build%20and%20test/badge.svg?branch=master) 4 | [![codecov](https://codecov.io/gh/memflow/memflow-cli/branch/master/graph/badge.svg?token=XT7R158N6W)](https://codecov.io/gh/memflow/memflow-cli) 5 | 6 | Make memflow scale. 7 | 8 | ## Pluggable framework and UI for memflow 9 | 10 | This project aims to be an extensible framework for memflow applications. Adding new features should require as least boilerplate as possible, and accessing them should be as trivial as possible. 11 | 12 | ### Features 13 | 14 | This project is currently in its infancy, but it already has the following features: 15 | 16 | * FUSE interface. 17 | 18 | * Full connector/os chaining. 19 | 20 | * Process information. 21 | 22 | * Standalone minidump generator. 23 | 24 | ### How to install 25 | 26 | Building from source: 27 | 28 | ``` 29 | cargo install cloudflow-node --git https://github.com/memflow/cloudflow 30 | ``` 31 | 32 | ### How to use 33 | 34 | Run an elevated instance with FUSE (you can add `v` multiple time to increase verbosity e.g. `-vvv`): 35 | 36 | ``` 37 | cloudflow -ef 38 | ``` 39 | 40 | You should be able to see the following messages: 41 | 42 | ``` 43 | Mounting FUSE filesystem on /cloudflow 44 | Initialized! 45 | ``` 46 | 47 | Create a new connector instance to connect to the first QEMU VM instance which can be found on your system: 48 | 49 | ``` 50 | echo "my_qemu_vm qemu" >> /cloudflow/connector/new 51 | ``` 52 | 53 | To connect to a specific VM with the name 'my-qemu-vm' you can pass an argument to the connector: 54 | 55 | ``` 56 | echo "my_qemu_vm qemu:my-qemu-vm" >> /cloudflow/connector/new 57 | ``` 58 | 59 | Create a new OS instance on top of QEMU: 60 | 61 | ``` 62 | echo "win -c my_qemu_vm win32" >> /cloudflow/os/new 63 | ``` 64 | 65 | Optionally you can specify the architecture for the new OS instance: 66 | 67 | ``` 68 | echo "win -c my_qemu_vm win32::arch=x64" >> /cloudflow/os/new 69 | ``` 70 | 71 | The input format can contain args and extra args. Both are parsed by the new connector/OS instance. The available options therefore depend on the type of the new instance. For all of the operations above the input format is as follows: 72 | 73 | ``` 74 | [-c chain_on] [:args[:extra args]] 75 | ``` 76 | 77 | Get kernel minidump: 78 | 79 | ``` 80 | cat /cloudflow/os/win/processes/by-name/System/mini.dmp > System.dmp 81 | ``` 82 | 83 | ## Contributing 84 | 85 | Please check [CONTRIBUTE.md](CONTRIBUTE.md) 86 | -------------------------------------------------------------------------------- /filer/src/branch.rs: -------------------------------------------------------------------------------- 1 | use crate::error::*; 2 | use crate::fs::*; 3 | use crate::plugin_store::*; 4 | 5 | use abi_stable::StableAbi; 6 | use cglue::arc::CArc; 7 | use cglue::trait_group::c_void; 8 | use cglue::trait_obj; 9 | 10 | pub use cglue::slice::CSliceMut; 11 | 12 | use std::collections::HashMap; 13 | 14 | pub fn split_path(path: &str) -> (&str, Option<&str>) { 15 | path.split_once('/') 16 | .map(|(a, b)| (a, Some(b))) 17 | .unwrap_or((path, None)) 18 | } 19 | 20 | pub fn map_entry( 21 | branch: &T, 22 | entry: &Mapping, 23 | remote: Option<&str>, 24 | plugins: &CPluginStore, 25 | ) -> Result { 26 | match (remote, entry) { 27 | (Some(path), Mapping::Branch(map, ctx)) => map(branch, ctx) 28 | .as_ref() 29 | .ok_or::(ErrorKind::NotFound)? 30 | .get_entry(path, plugins), 31 | (None, Mapping::Branch(map, ctx)) => Ok(DirEntry::Branch( 32 | Option::from(map(branch, ctx)).ok_or(ErrorKind::NotFound)?, 33 | )), 34 | (None, Mapping::Leaf(map, ctx)) => Ok(DirEntry::Leaf( 35 | Option::from(map(branch, ctx)).ok_or(ErrorKind::NotFound)?, 36 | )), 37 | _ => Err(ErrorKind::NotFound.into()), 38 | } 39 | } 40 | 41 | pub fn forward_entry( 42 | branch: impl Branch + 'static, 43 | ctx: CArc, 44 | path: Option<&str>, 45 | plugins: &CPluginStore, 46 | ) -> Result { 47 | if let Some(path) = path { 48 | branch.get_entry(path, plugins) 49 | } else { 50 | Ok(DirEntry::Branch(trait_obj!((branch, ctx) as Branch))) 51 | } 52 | } 53 | 54 | pub fn get_entry( 55 | branch: &T, 56 | path: &str, 57 | plugins: &CPluginStore, 58 | ) -> Result { 59 | let (local, remote) = split_path(path); 60 | 61 | let entry = plugins 62 | .lookup_entry::(local) 63 | .ok_or(ErrorKind::NotFound)?; 64 | 65 | map_entry(branch, &entry, remote, plugins) 66 | } 67 | 68 | pub fn list( 69 | branch: &T, 70 | plugins: &CPluginStore, 71 | ) -> Result> { 72 | let mut ret = vec![]; 73 | 74 | plugins.entry_list::( 75 | (&mut |(name, entry): (&str, &Mapping)| { 76 | ret.push( 77 | map_entry(branch, entry, None, plugins).map(|entry| (name.to_string(), entry)), 78 | ); 79 | true 80 | }) 81 | .into(), 82 | ); 83 | 84 | Ok(ret.into_iter().filter_map(Result::ok).collect()) 85 | } 86 | -------------------------------------------------------------------------------- /cloudflow-minidump/src/lib.rs: -------------------------------------------------------------------------------- 1 | use cglue::trait_group::c_void; 2 | use cloudflow::process::LazyProcessArc; 3 | use filer::prelude::v1::{ErrorKind, *}; 4 | use memflow::prelude::v1::*; 5 | 6 | use minidump_writer::{ 7 | minidump::Minidump, 8 | streams::{ 9 | Memory64ListStream, MemoryDescriptor, MinidumpModule, ModuleListStream, SystemInfoStream, 10 | }, 11 | }; 12 | 13 | pub extern "C" fn on_node(node: &Node, ctx: CArc) { 14 | node.plugins 15 | .register_mapping("mini.dmp", Mapping::Leaf(map_into_minidump, ctx)); 16 | } 17 | 18 | extern "C" fn map_into_minidump( 19 | proc: &LazyProcessArc, 20 | ctx: &CArc, 21 | ) -> COption> { 22 | let file = FnFile::new(proc.clone(), |proc| { 23 | let proc = proc.proc().ok_or(ErrorKind::Uninitialized)?; 24 | let mut proc = proc.get(); 25 | 26 | let maps = proc.mapped_mem_vec(-1); 27 | let mut modules = proc.module_list().map_err(|_| ErrorKind::Uninitialized)?; 28 | modules.sort_by_key(|m| m.base.to_umem()); 29 | 30 | let mut ret = vec![]; 31 | let mut cursor = std::io::Cursor::new(&mut ret); 32 | let mut minidump = Minidump::default(); 33 | 34 | let mut module_list = ModuleListStream::default(); 35 | 36 | for i in modules { 37 | module_list.add_module(MinidumpModule { 38 | base_of_image: i.base.to_umem() as u64, 39 | size_of_image: i.size as _, 40 | checksum: 0, 41 | time_date_stamp: 0, 42 | name: i.name.to_string(), 43 | }); 44 | } 45 | 46 | minidump 47 | .directory 48 | .push(Box::new(SystemInfoStream::with_arch_and_version( 49 | 9, 10, 0, 19041, //major, minor, build, 50 | ))); 51 | minidump.directory.push(Box::new(module_list)); 52 | 53 | let mut memory_list = Memory64ListStream::default(); 54 | 55 | for CTup3(addr, size, _) in maps { 56 | let mut buf = vec![0; size as usize]; 57 | proc.read_raw_into(addr, &mut buf) 58 | .data_part() 59 | .map_err(|_| ErrorKind::UnableToReadFile)?; 60 | memory_list.list.push(MemoryDescriptor { 61 | start_of_memory: addr.to_umem() as _, 62 | buf, 63 | }); 64 | } 65 | 66 | minidump.directory.push(Box::new(memory_list)); 67 | minidump 68 | .write_all(&mut cursor) 69 | .map_err(|_| ErrorKind::UnableToWriteFile)?; 70 | 71 | Ok(ret) 72 | }); 73 | 74 | COption::Some(trait_obj!((file, ctx.clone()) as Leaf)) 75 | } 76 | -------------------------------------------------------------------------------- /cloudflow-node/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::*; 3 | use cloudflow::*; 4 | 5 | use log::*; 6 | 7 | fn main() -> Result<()> { 8 | let args = parse_args(); 9 | let (mount_path, fuse_uid, fuse_gid, elevate, level) = extract_args(&args)?; 10 | 11 | if elevate { 12 | sudo::escalate_if_needed().expect("failed to elevate privileges"); 13 | info!("Elevated privileges!"); 14 | } 15 | 16 | simplelog::TermLogger::init( 17 | level.to_level_filter(), 18 | simplelog::Config::default(), 19 | simplelog::TerminalMode::Stdout, 20 | simplelog::ColorChoice::Auto, 21 | ) 22 | .unwrap(); 23 | 24 | let node = create_node(); 25 | 26 | // Add custom plugin 27 | cloudflow_minidump::on_node(&node, Default::default()); 28 | 29 | if let Some(mount_path) = mount_path { 30 | println!("Mounting FUSE filesystem on {}", mount_path); 31 | std::fs::create_dir_all(mount_path)?; 32 | filer_fuse::mount( 33 | node, 34 | mount_path, 35 | sudo::check() == sudo::RunningAs::Root, 36 | fuse_uid.unwrap_or(0), 37 | fuse_gid.unwrap_or(0), 38 | )?; 39 | } 40 | 41 | println!("Initialized!"); 42 | 43 | loop {} 44 | } 45 | 46 | fn parse_args() -> ArgMatches { 47 | Command::new("cloudflow") 48 | .version(crate_version!()) 49 | .author(crate_authors!()) 50 | .arg(Arg::new("verbose").short('v').action(ArgAction::Count)) 51 | .arg( 52 | Arg::new("fuse") 53 | .long("fuse") 54 | .short('f') 55 | .action(ArgAction::SetTrue) 56 | .required(false), 57 | ) 58 | .arg( 59 | Arg::new("fuse-mount") 60 | .long("fuse-mount") 61 | .short('F') 62 | .action(ArgAction::Set) 63 | .required(false), 64 | ) 65 | .arg( 66 | Arg::new("fuse-uid") 67 | .long("fuse-uid") 68 | .short('u') 69 | .action(ArgAction::Set) 70 | .required(false), 71 | ) 72 | .arg( 73 | Arg::new("fuse-gid") 74 | .long("fuse-gid") 75 | .short('g') 76 | .action(ArgAction::Set) 77 | .required(false), 78 | ) 79 | .arg( 80 | Arg::new("elevate") 81 | .long("elevate") 82 | .short('e') 83 | .action(ArgAction::SetTrue) 84 | .required(false), 85 | ) 86 | .get_matches() 87 | } 88 | 89 | fn extract_args( 90 | matches: &ArgMatches, 91 | ) -> Result<(Option<&str>, Option, Option, bool, log::Level)> { 92 | // set log level 93 | let level = match matches.get_count("verbose") { 94 | 0 => Level::Error, 95 | 1 => Level::Warn, 96 | 2 => Level::Info, 97 | 3 => Level::Debug, 98 | 4 => Level::Trace, 99 | _ => Level::Trace, 100 | }; 101 | 102 | let fuse_mount = if matches.get_flag("fuse") { 103 | matches 104 | .get_one::("fuse-mount") 105 | .map(String::as_str) 106 | .or(Some("/cloudflow")) 107 | } else { 108 | None 109 | }; 110 | 111 | let fuse_uid = matches 112 | .get_one::("fuse-uid") 113 | .map(|s| s.parse()) 114 | .transpose()?; 115 | let fuse_gid = matches 116 | .get_one::("fuse-gid") 117 | .map(|s| s.parse()) 118 | .transpose()?; 119 | 120 | let elevate = matches.get_flag("elevate"); 121 | 122 | Ok((fuse_mount, fuse_uid, fuse_gid, elevate, level)) 123 | } 124 | -------------------------------------------------------------------------------- /cloudflow/src/connector.rs: -------------------------------------------------------------------------------- 1 | use crate::util::*; 2 | use crate::MemflowBackend; 3 | use abi_stable::StableAbi; 4 | pub use cglue::slice::CSliceMut; 5 | use cglue::trait_group::c_void; 6 | use filer::branch; 7 | use filer::prelude::v1::{Error, ErrorKind, ErrorOrigin, Result, *}; 8 | use memflow::prelude::v1::*; 9 | 10 | use std::sync::Arc; 11 | 12 | pub extern "C" fn on_node(node: &Node, ctx: CArc) { 13 | node.plugins.register_mapping( 14 | "mem", 15 | Mapping::Leaf(self_as_leaf::, ctx), 16 | ); 17 | } 18 | 19 | thread_types!( 20 | ConnectorInstanceArcBox<'static>, 21 | ThreadedConnector, 22 | ThreadedConnectorArc 23 | ); 24 | 25 | impl Branch for ThreadedConnectorArc { 26 | fn get_entry(&self, path: &str, plugins: &CPluginStore) -> Result { 27 | branch::get_entry(self, path, plugins) 28 | } 29 | 30 | fn list( 31 | &self, 32 | plugins: &CPluginStore, 33 | out: &mut OpaqueCallback, 34 | ) -> Result<()> { 35 | branch::list(self, plugins)? 36 | .into_iter() 37 | .map(|(name, entry)| BranchListEntry::new(name.into(), entry)) 38 | .feed_into_mut(out); 39 | Ok(()) 40 | } 41 | } 42 | 43 | impl Leaf for ThreadedConnectorArc { 44 | fn open(&self) -> Result> { 45 | Ok(FileOpsObj::new( 46 | (**self).clone(), 47 | Some(ThreadedConnector::read), 48 | Some(ThreadedConnector::write), 49 | Some(ThreadedConnector::rpc), 50 | )) 51 | } 52 | 53 | fn metadata(&self) -> Result { 54 | Ok(NodeMetadata { 55 | is_branch: false, 56 | has_read: true, 57 | has_write: true, 58 | has_rpc: true, 59 | size: self.get_orig().metadata().max_address.to_umem() as Size, 60 | ..Default::default() 61 | }) 62 | } 63 | } 64 | 65 | impl StrBuild>> for ThreadedConnectorArc { 66 | fn build(input: &str, ctx: &CArc>) -> Result { 67 | let (chain_with, name, args) = split_args(input); 68 | 69 | let ctx = ctx.as_ref().ok_or(ErrorKind::NotFound)?; 70 | 71 | let chain_with = if let Some(cw) = chain_with { 72 | Some( 73 | ctx.os 74 | .get(cw) 75 | .ok_or(ErrorKind::NotFound)? 76 | .get_orig() 77 | .clone(), 78 | ) 79 | } else { 80 | None 81 | }; 82 | 83 | ctx.inventory 84 | .create_connector( 85 | name, 86 | chain_with, 87 | Some(&str::parse(args).map_err(|_| ErrorKind::InvalidArgument)?), 88 | ) 89 | .map(|c| ThreadedConnector::from(c).self_arc_up()) 90 | .map_err(|_| ErrorKind::Uninitialized.into()) 91 | } 92 | } 93 | 94 | impl ThreadedConnector { 95 | extern "C" fn read(&self, data: VecOps) -> i32 { 96 | int_res_wrap! { 97 | memdata_map(data, |data| { 98 | self.get() 99 | .phys_view() 100 | .zero_fill_gaps() 101 | .read_raw_iter( 102 | data, 103 | ) 104 | .map_err(|_| Error(ErrorOrigin::Read, ErrorKind::Unknown)) 105 | }) 106 | } 107 | } 108 | 109 | extern "C" fn write(&self, data: VecOps) -> i32 { 110 | int_res_wrap! { 111 | memdata_map(data, |data| { 112 | self.get() 113 | .phys_view() 114 | .write_raw_iter( 115 | data, 116 | ) 117 | .map_err(|_| Error(ErrorOrigin::Write, ErrorKind::Unknown)) 118 | }) 119 | } 120 | } 121 | 122 | extern "C" fn rpc(&self, _input: CSliceRef, _output: CSliceMut) -> i32 { 123 | Result::Ok(()).into_int_result() 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /filer/src/types.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use abi_stable::StableAbi; 3 | use cglue::prelude::v1::*; 4 | use cglue::trait_group::c_void; 5 | use core::num::NonZeroI32; 6 | use sharded_slab::{Entry, Slab}; 7 | 8 | pub type Size = u64; 9 | 10 | pub type RWData<'a> = CTup2>; 11 | pub type ROData<'a> = CTup2>; 12 | 13 | #[repr(C)] 14 | #[derive(StableAbi)] 15 | pub struct FailData(T, NonZeroI32); 16 | 17 | impl From<(T, Error)> for FailData { 18 | fn from((d, e): (T, Error)) -> Self { 19 | Self(d, e.into_int_err()) 20 | } 21 | } 22 | 23 | impl From> for (T, Error) { 24 | fn from(FailData(d, e): FailData) -> Self { 25 | (d, Error::from_int_err(e)) 26 | } 27 | } 28 | 29 | pub type RWFailData<'a> = FailData>; 30 | pub type ROFailData<'a> = FailData>; 31 | 32 | /*pub type RWCallback<'a, 'b> = OpaqueCallback<'a, RWData<'b>>; 33 | pub type ROCallback<'a, 'b> = OpaqueCallback<'a, ROData<'b>>; 34 | pub type RWFailCallback<'a, 'b> = OpaqueCallback<'a, RWFailData<'b>>; 35 | pub type ROFailCallback<'a, 'b> = OpaqueCallback<'a, ROFailData<'b>>;*/ 36 | 37 | #[repr(C)] 38 | #[derive(StableAbi)] 39 | pub struct VecOps<'a, T: 'a> { 40 | pub inp: CIterator<'a, T>, 41 | pub out: Option<&'a mut OpaqueCallback<'a, T>>, 42 | pub out_fail: Option<&'a mut OpaqueCallback<'a, FailData>>, 43 | } 44 | 45 | impl<'a, T, I: Into>> From for VecOps<'a, T> { 46 | fn from(inp: I) -> Self { 47 | Self { 48 | inp: inp.into(), 49 | out: None, 50 | out_fail: None, 51 | } 52 | } 53 | } 54 | 55 | pub fn opt_call(cb: Option<&mut OpaqueCallback>, data: T) -> bool { 56 | cb.map(|cb| cb.call(data)).unwrap_or(true) 57 | } 58 | 59 | pub trait ArcType: Sized + 'static { 60 | type ArcSelf: Into>; 61 | 62 | fn arc_up(self) -> CArcSome { 63 | self.into() 64 | } 65 | 66 | fn self_arc_up(self) -> Self::ArcSelf 67 | where 68 | CArcSome: Into, 69 | { 70 | CArcSome::from(self).into() 71 | } 72 | 73 | fn into_arc(arc: Self::ArcSelf) -> CArcSome { 74 | arc.into() 75 | } 76 | 77 | fn from_arc(arc: CArcSome) -> Self::ArcSelf 78 | where 79 | CArcSome: Into, 80 | { 81 | arc.into() 82 | } 83 | } 84 | 85 | #[derive(StableAbi, Default, Clone, Copy, Debug)] 86 | #[repr(C)] 87 | pub struct NodeMetadata { 88 | pub is_branch: bool, 89 | pub has_read: bool, 90 | pub has_write: bool, 91 | pub has_rpc: bool, 92 | pub size: Size, 93 | } 94 | 95 | impl NodeMetadata { 96 | pub fn branch() -> Self { 97 | Self { 98 | is_branch: true, 99 | ..Default::default() 100 | } 101 | } 102 | } 103 | 104 | pub trait SetContext { 105 | fn set_context(&mut self, ctx: &CArc); 106 | } 107 | 108 | use core::sync::atomic::{AtomicUsize, Ordering}; 109 | 110 | pub struct RcSlabEntry<'a, T> { 111 | entry: Entry<'a, (AtomicUsize, T)>, 112 | } 113 | 114 | impl<'a, T> From> for RcSlabEntry<'a, T> { 115 | fn from(entry: Entry<'a, (AtomicUsize, T)>) -> Self { 116 | Self { entry } 117 | } 118 | } 119 | 120 | impl core::ops::Deref for RcSlabEntry<'_, T> { 121 | type Target = T; 122 | 123 | fn deref(&self) -> &Self::Target { 124 | &self.entry.1 125 | } 126 | } 127 | 128 | pub struct RcSlab { 129 | slab: Slab<(AtomicUsize, T)>, 130 | } 131 | 132 | impl Default for RcSlab { 133 | fn default() -> Self { 134 | Self { 135 | slab: Default::default(), 136 | } 137 | } 138 | } 139 | 140 | impl RcSlab { 141 | pub fn insert(&self, val: T) -> Option { 142 | self.slab.insert((1.into(), val)) 143 | } 144 | 145 | pub fn get(&self, idx: usize) -> Option> { 146 | self.slab.get(idx).map(<_>::into) 147 | } 148 | 149 | pub fn dec_rc(&self, idx: usize) -> Option>> { 150 | self.slab.get(idx).map(|entry| { 151 | if entry.0.fetch_sub(1, Ordering::Relaxed) == 1 { 152 | self.slab.remove(idx); 153 | Some(entry.into()) 154 | } else { 155 | None 156 | } 157 | }) 158 | } 159 | 160 | pub fn inc_rc(&self, idx: usize) -> Option<()> { 161 | self.slab.get(idx).and_then(|entry| { 162 | let mut prev = entry.0.load(Ordering::Relaxed); 163 | while prev != 0 { 164 | match entry.0.compare_exchange_weak( 165 | prev, 166 | prev + 1, 167 | Ordering::Relaxed, 168 | Ordering::Relaxed, 169 | ) { 170 | Ok(p) => { 171 | prev = p; 172 | break; 173 | } 174 | Err(p) => prev = p, 175 | } 176 | } 177 | 178 | if prev == 0 { 179 | None 180 | } else { 181 | Some(()) 182 | } 183 | }) 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /filer/src/thread_ctx.rs: -------------------------------------------------------------------------------- 1 | use abi_stable::StableAbi; 2 | pub use cglue::arc::CArcSome; 3 | use cglue::prelude::v1::*; 4 | pub use cglue::slice::CSliceMut; 5 | use cglue::trait_group::c_void; 6 | use core::mem::MaybeUninit; 7 | 8 | /// Defines new arc wrapper types. 9 | /// 10 | /// This will define `$new` type which will contain `$type`, as well as `$new_arc` type 11 | /// which is effectively `CArcSome<$new>`. 12 | /// 13 | /// Typically, read/write/rpc operations would be implemented on `$new`, while `Branch`/`Leaf` 14 | /// traits would be implemented on `$new_arc`. This is done in such a way so that CGlue can nicely 15 | /// opaquify objects while not hiding reference counts from filesystem. 16 | #[macro_export] 17 | macro_rules! arc_types { 18 | ($type:ty, $new:ident, $new_arc: ident) => { 19 | #[derive(StableAbi)] 20 | #[repr(transparent)] 21 | pub struct $new($type); 22 | 23 | #[derive(Clone, StableAbi)] 24 | #[repr(transparent)] 25 | pub struct $new_arc($crate::thread_ctx::CArcSome<$new>); 26 | 27 | impl $crate::types::ArcType for $new { 28 | type ArcSelf = $new_arc; 29 | } 30 | 31 | impl From<$type> for $new { 32 | fn from(input: $type) -> Self { 33 | Self(input) 34 | } 35 | } 36 | 37 | impl From<$type> for $new_arc { 38 | fn from(input: $type) -> Self { 39 | Self($new::from(input).into()) 40 | } 41 | } 42 | 43 | impl core::ops::Deref for $new { 44 | type Target = $type; 45 | 46 | fn deref(&self) -> &Self::Target { 47 | &self.0 48 | } 49 | } 50 | 51 | impl core::ops::Deref for $new_arc { 52 | type Target = $crate::thread_ctx::CArcSome<$new>; 53 | 54 | fn deref(&self) -> &Self::Target { 55 | &self.0 56 | } 57 | } 58 | 59 | impl From<$new_arc> for $crate::thread_ctx::CArcSome<$new> { 60 | fn from($new_arc(arc): $new_arc) -> Self { 61 | arc 62 | } 63 | } 64 | 65 | impl From<$crate::thread_ctx::CArcSome<$new>> for $new_arc { 66 | fn from(arc: $crate::thread_ctx::CArcSome<$new>) -> Self { 67 | $new_arc(arc) 68 | } 69 | } 70 | }; 71 | } 72 | 73 | /// Defines new threaded wrapper types. 74 | /// 75 | /// This will define `$new` type which will contain `ThreadCtx<$type>`, as well as `$new_arc` type 76 | /// which is effectively `CArcSome<$new>`. 77 | /// 78 | /// This is effetively an alias for `arc_types!(ThreadCtx<$type>, $new, $new_arc)` with a few extra 79 | /// helpers. 80 | #[macro_export] 81 | macro_rules! thread_types { 82 | ($type:ty, $new:ident, $new_arc: ident) => { 83 | $crate::thread_types!($type, $new, $new_arc, 32); 84 | }; 85 | ($type:ty, $new:ident, $new_arc: ident, $threads:expr) => { 86 | $crate::arc_types!($crate::thread_ctx::ThreadCtx<$type>, $new, $new_arc); 87 | 88 | impl From<$type> for $new { 89 | fn from(input: $type) -> Self { 90 | Self($crate::thread_ctx::ThreadCtx::new(input, $threads)) 91 | } 92 | } 93 | 94 | impl From<$type> for $new_arc { 95 | fn from(input: $type) -> Self { 96 | Self($new::from(input).into()) 97 | } 98 | } 99 | }; 100 | } 101 | 102 | #[derive(StableAbi)] 103 | #[repr(C)] 104 | pub struct ThreadCtx { 105 | orig: T, 106 | stack: CBox<'static, c_void>, 107 | stack_push: for<'a> extern "C" fn(&c_void, COption), 108 | stack_pop: for<'a> extern "C" fn(&c_void, &mut MaybeUninit>) -> bool, 109 | } 110 | 111 | pub struct ThreadCtxHandle<'a, T: 'static> { 112 | value: MaybeUninit, 113 | ctx: &'a ThreadCtx, 114 | } 115 | 116 | impl Drop for ThreadCtxHandle<'_, T> { 117 | fn drop(&mut self) { 118 | self.ctx.push(Some(unsafe { self.value.as_ptr().read() })) 119 | } 120 | } 121 | 122 | impl core::ops::Deref for ThreadCtxHandle<'_, T> { 123 | type Target = T; 124 | 125 | fn deref(&self) -> &Self::Target { 126 | unsafe { self.value.as_ptr().as_ref().unwrap() } 127 | } 128 | } 129 | 130 | impl core::ops::DerefMut for ThreadCtxHandle<'_, T> { 131 | fn deref_mut(&mut self) -> &mut Self::Target { 132 | unsafe { self.value.as_mut_ptr().as_mut().unwrap() } 133 | } 134 | } 135 | 136 | impl ThreadCtx { 137 | pub fn new(orig: T, size: usize) -> Self { 138 | // SAFETY: All types in the opaque functions match!!! It is safe, but needs care!!! 139 | 140 | let stack = crossbeam_deque::Worker::>::new_lifo(); 141 | 142 | for _ in 0..size { 143 | stack.push(COption::None); 144 | } 145 | 146 | let stack = CBox::from(stack).into_opaque(); 147 | 148 | extern "C" fn stack_pop(stack: &c_void, out: &mut MaybeUninit>) -> bool { 149 | match unsafe { 150 | (*(stack as *const _ as *const crossbeam_deque::Worker>)).pop() 151 | } { 152 | Some(t) => { 153 | out.write(t); 154 | true 155 | } 156 | None => false, 157 | } 158 | } 159 | 160 | extern "C" fn stack_push(stack: &c_void, val: COption) { 161 | unsafe { 162 | (*(stack as *const _ as *const crossbeam_deque::Worker>)).push(val) 163 | }; 164 | } 165 | 166 | Self { 167 | orig, 168 | stack, 169 | stack_pop: stack_pop::, 170 | stack_push: stack_push::, 171 | } 172 | } 173 | 174 | fn push(&self, val: Option) { 175 | (self.stack_push)(&*self.stack, val.into()) 176 | } 177 | 178 | fn pop(&self) -> Option> { 179 | let mut out = MaybeUninit::uninit(); 180 | if (self.stack_pop)(&*self.stack, &mut out) { 181 | Some(unsafe { out.assume_init() }.into()) 182 | } else { 183 | None 184 | } 185 | } 186 | 187 | pub fn get_orig(&self) -> &T { 188 | &self.orig 189 | } 190 | } 191 | 192 | impl ThreadCtx { 193 | pub fn get(&self) -> ThreadCtxHandle { 194 | let v = loop { 195 | match self.pop() { 196 | Some(Some(v)) => break v, 197 | Some(None) => break self.orig.clone(), 198 | None => {} 199 | } 200 | }; 201 | 202 | ThreadCtxHandle { 203 | value: MaybeUninit::new(v), 204 | ctx: self, 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /filer/src/fs.rs: -------------------------------------------------------------------------------- 1 | //! Internal FS representation. Does not cross backends. 2 | 3 | use crate::error::*; 4 | use crate::plugin_store::*; 5 | use crate::types::NodeMetadata; 6 | 7 | use crate::types::*; 8 | use abi_stable::StableAbi; 9 | use cglue::prelude::v1::*; 10 | use cglue::result::from_int_result_empty; 11 | pub use cglue::slice::CSliceMut; 12 | use cglue::trait_group::c_void; 13 | 14 | /// Safely wrap fallible functions to return a integer result value. 15 | /// 16 | /// This is effectively needed when defining `extern "C"` functions that for file ops. 17 | #[macro_export] 18 | macro_rules! int_res_wrap { 19 | ($($expr:tt)*) => { 20 | let __wrapped_fn = || -> $crate::error::Result<()> { $($expr)* }; 21 | __wrapped_fn().into_int_result() 22 | }; 23 | } 24 | 25 | #[repr(C)] 26 | #[derive(StableAbi)] 27 | pub enum DirEntry { 28 | Branch(BranchArcBox<'static>), 29 | Leaf(LeafArcBox<'static>), 30 | } 31 | 32 | #[repr(C)] 33 | #[derive(StableAbi)] 34 | pub struct BranchListEntry { 35 | pub name: ReprCString, 36 | pub obj: DirEntry, 37 | } 38 | 39 | impl BranchListEntry { 40 | pub fn new(name: ReprCString, obj: DirEntry) -> Self { 41 | Self { name, obj } 42 | } 43 | } 44 | 45 | #[cglue_trait] 46 | #[int_result] 47 | pub trait Branch { 48 | fn get_entry(&self, path: &str, plugins: &CPluginStore) -> Result; 49 | fn list(&self, plugins: &CPluginStore, out: &mut OpaqueCallback) 50 | -> Result<()>; 51 | 52 | fn list_recurse( 53 | &self, 54 | path: &str, 55 | plugins: &CPluginStore, 56 | out: &mut OpaqueCallback, 57 | ) -> Result<()> { 58 | if path.is_empty() { 59 | self.list(plugins, out) 60 | } else { 61 | match self.get_entry(path, plugins) { 62 | Ok(DirEntry::Branch(branch)) => branch.list(plugins, out), 63 | Ok(_) => Err(Error(ErrorOrigin::Branch, ErrorKind::InvalidPath)), 64 | Err(e) => Err(e), 65 | } 66 | } 67 | } 68 | } 69 | 70 | #[cglue_trait] 71 | #[int_result] 72 | pub trait Leaf { 73 | fn open(&self) -> Result>; 74 | fn metadata(&self) -> Result; 75 | } 76 | 77 | #[repr(C)] 78 | #[derive(Clone, StableAbi)] 79 | pub struct FileOpsObj { 80 | obj: CArcSome, 81 | read: Option extern "C" fn(&T, data: VecOps) -> i32>, 82 | write: Option extern "C" fn(&T, data: VecOps) -> i32>, 83 | rpc: Option< 84 | for<'a> extern "C" fn(&'a T, input: CSliceRef<'a, u8>, output: CSliceMut<'a, u8>) -> i32, 85 | >, 86 | } 87 | 88 | impl FileOpsObj { 89 | pub fn new( 90 | obj: CArcSome, 91 | read: Option extern "C" fn(&T, data: VecOps) -> i32>, 92 | write: Option extern "C" fn(&T, data: VecOps) -> i32>, 93 | rpc: Option< 94 | for<'a> extern "C" fn( 95 | &'a T, 96 | input: CSliceRef<'a, u8>, 97 | output: CSliceMut<'a, u8>, 98 | ) -> i32, 99 | >, 100 | ) -> FileOpsObj { 101 | Self { 102 | obj, 103 | read, 104 | write, 105 | rpc, 106 | } 107 | .into_opaque() 108 | } 109 | 110 | pub fn read(&self, data: VecOps) -> Result<()> { 111 | from_int_result_empty((self 112 | .read 113 | .ok_or(Error(ErrorOrigin::Read, ErrorKind::NotImplemented))?)( 114 | &self.obj, data 115 | )) 116 | } 117 | 118 | pub fn write(&self, data: VecOps) -> Result<()> { 119 | from_int_result_empty((self 120 | .write 121 | .ok_or(Error(ErrorOrigin::Write, ErrorKind::NotImplemented))?)( 122 | &self.obj, data, 123 | )) 124 | } 125 | 126 | pub fn rpc(&self, input: &[u8], output: &mut [u8]) -> Result<()> { 127 | from_int_result_empty((self 128 | .rpc 129 | .ok_or(Error(ErrorOrigin::Rpc, ErrorKind::NotImplemented))?)( 130 | &self.obj, 131 | input.into(), 132 | output.into(), 133 | )) 134 | } 135 | } 136 | 137 | unsafe impl cglue::trait_group::Opaquable for FileOpsObj { 138 | type OpaqueTarget = FileOpsObj; 139 | } 140 | 141 | #[derive(Clone)] 142 | pub struct FnFile { 143 | ctx: C, 144 | data: once_cell::sync::OnceCell, 145 | func: fn(&C) -> Result, 146 | } 147 | 148 | impl + Clone + 'static> Leaf for FnFile { 149 | fn open(&self) -> Result> { 150 | Ok(FileOpsObj::new( 151 | self.clone().into(), 152 | Some(Self::read), 153 | None, 154 | None, 155 | )) 156 | } 157 | 158 | fn metadata(&self) -> Result { 159 | Ok(NodeMetadata { 160 | is_branch: false, 161 | has_read: true, 162 | ..Default::default() 163 | }) 164 | } 165 | } 166 | 167 | impl> FnFile { 168 | pub fn new(ctx: C, func: fn(&C) -> Result) -> Self { 169 | Self { 170 | ctx, 171 | data: Default::default(), 172 | func, 173 | } 174 | } 175 | 176 | extern "C" fn read<'a>(&self, mut data: VecOps>) -> i32 { 177 | int_res_wrap! { 178 | let file = self.data.get_or_try_init(|| (self.func)(&self.ctx))?; 179 | let file: &[u8] = file.as_ref(); 180 | 181 | for CTup2(off, to) in data.inp { 182 | let maps = &file[std::cmp::min(off as usize, file.len())..]; 183 | let min_len = std::cmp::min(maps.len(), to.len()); 184 | let to: &'a mut [u8] = to.into(); 185 | let (to, to_reject) = to.split_at_mut(min_len); 186 | to.copy_from_slice(&maps[..min_len]); 187 | 188 | let mut cont = false; 189 | 190 | if !to.is_empty() { 191 | cont = opt_call(data.out.as_deref_mut(), CTup2(off, to.into())); 192 | } 193 | if !to_reject.is_empty() { 194 | cont = opt_call( 195 | data.out_fail.as_deref_mut(), 196 | ( 197 | CTup2(off + min_len as u64, to_reject.into()), 198 | Error(ErrorOrigin::Read, ErrorKind::OutOfBounds), 199 | ) 200 | .into(), 201 | ) || cont; 202 | } 203 | 204 | if !cont { 205 | return Err(Error(ErrorOrigin::Read, ErrorKind::Unknown)); 206 | } 207 | } 208 | 209 | Ok(()) 210 | } 211 | } 212 | } 213 | 214 | pub extern "C" fn self_as_leaf( 215 | obj: &T, 216 | ctx: &CArc, 217 | ) -> COption> { 218 | COption::Some(trait_obj!((obj.clone(), ctx.clone()) as Leaf)) 219 | } 220 | -------------------------------------------------------------------------------- /filer/src/plugin_store.rs: -------------------------------------------------------------------------------- 1 | use super::fs::*; 2 | 3 | use abi_stable::{ 4 | abi_stability::check_layout_compatibility, std_types::UTypeId, type_layout::TypeLayout, 5 | StableAbi, 6 | }; 7 | use cglue::prelude::v1::*; 8 | use cglue::trait_group::c_void; 9 | 10 | use dashmap::mapref::entry; 11 | use dashmap::DashMap; 12 | use sharded_slab::{Entry, Slab}; 13 | 14 | pub type MappingFunction = extern "C" fn(&T, &CArc) -> COption; 15 | 16 | #[derive(StableAbi)] 17 | // TODO: Why does the func type not work?? 18 | #[sabi(unsafe_opaque_fields)] 19 | #[repr(C)] 20 | pub enum Mapping { 21 | Branch(MappingFunction>, CArc), 22 | Leaf(MappingFunction>, CArc), 23 | } 24 | 25 | unsafe impl Opaquable for Mapping { 26 | type OpaqueTarget = Mapping; 27 | } 28 | 29 | impl Clone for Mapping { 30 | fn clone(&self) -> Self { 31 | match self { 32 | Mapping::Branch(a, b) => Mapping::Branch(*a, b.clone()), 33 | Mapping::Leaf(a, b) => Mapping::Leaf(*a, b.clone()), 34 | } 35 | } 36 | } 37 | 38 | type OpaqueMapping = Mapping; 39 | 40 | #[derive(Default)] 41 | pub struct PluginStore { 42 | // plugins: Vec> 43 | /// A never shrinking list of lists of mapping functions meant for specific types of plugins. 44 | /// 45 | /// Calling the mapping functions manually is inherently unsafe, because the types are meant to 46 | /// be opaque, and unchecked downcasting is being performed. 47 | entry_list: Slab>, 48 | /// Type layouts identified by slab index. 49 | layouts: DashMap, 50 | /// Map a specific type to opaque entries in the list. 51 | type_map: DashMap, 52 | } 53 | 54 | #[derive(StableAbi)] 55 | #[repr(C)] 56 | pub struct CPluginStore { 57 | store: CBox<'static, c_void>, 58 | lookup_entry: for<'a> unsafe extern "C" fn( 59 | &'a c_void, 60 | UTypeId, 61 | &'static TypeLayout, 62 | CSliceRef<'a, u8>, 63 | ) -> COption, 64 | entry_list: for<'a> unsafe extern "C" fn( 65 | &'a c_void, 66 | UTypeId, 67 | &'static TypeLayout, 68 | &mut OpaqueCallback<*const c_void>, 69 | ), 70 | register_mapping: for<'a> unsafe extern "C" fn( 71 | &'a c_void, 72 | UTypeId, 73 | &'static TypeLayout, 74 | CSliceRef<'a, u8>, 75 | OpaqueMapping, 76 | ) -> bool, 77 | } 78 | 79 | impl Default for CPluginStore { 80 | fn default() -> Self { 81 | PluginStore::default().into() 82 | } 83 | } 84 | 85 | impl From for CPluginStore { 86 | fn from(store: PluginStore) -> Self { 87 | unsafe extern "C" fn lookup_entry( 88 | store: &c_void, 89 | id: UTypeId, 90 | layout: &'static TypeLayout, 91 | name: CSliceRef, 92 | ) -> COption { 93 | let store = store as *const _ as *const PluginStore; 94 | let entries = (*store).entries_raw(id, layout); 95 | entries.get(name.into_str()).map(|e| (*e).clone()).into() 96 | } 97 | 98 | unsafe extern "C" fn entry_list( 99 | store: &c_void, 100 | id: UTypeId, 101 | layout: &'static TypeLayout, 102 | out: &mut OpaqueCallback<*const c_void>, 103 | ) { 104 | let store: &PluginStore = &*(store as *const _ as *const PluginStore); 105 | let entries = (*store).entries_raw(id, layout); 106 | entries 107 | .iter() 108 | .take_while(|e| { 109 | out.call( 110 | &CTup2(CSliceRef::from(e.key().as_str()), (*e.value()).clone()) as *const _ 111 | as *const c_void, 112 | ) 113 | }) 114 | .for_each(|_| {}); 115 | } 116 | 117 | unsafe extern "C" fn register_mapping( 118 | store: &c_void, 119 | id: UTypeId, 120 | layout: &'static TypeLayout, 121 | name: CSliceRef, 122 | mapping: OpaqueMapping, 123 | ) -> bool { 124 | let store = store as *const _ as *const PluginStore; 125 | (*store).register_mapping_raw(id, layout, name.into_str(), mapping) 126 | } 127 | 128 | Self { 129 | store: CBox::from(store).into_opaque(), 130 | lookup_entry, 131 | entry_list, 132 | register_mapping, 133 | } 134 | } 135 | } 136 | 137 | impl CPluginStore { 138 | pub fn register_mapping(&self, name: &str, mapping: Mapping) -> bool { 139 | unsafe { 140 | (self.register_mapping)( 141 | &*self.store, 142 | T::ABI_CONSTS.type_id.get(), 143 | T::LAYOUT, 144 | name.into(), 145 | mapping.into_opaque(), 146 | ) 147 | } 148 | } 149 | 150 | pub fn lookup_entry(&self, name: &str) -> Option> { 151 | let mapping: Option = unsafe { 152 | (self.lookup_entry)( 153 | &*self.store, 154 | T::ABI_CONSTS.type_id.get(), 155 | T::LAYOUT, 156 | name.into(), 157 | ) 158 | } 159 | .into(); 160 | unsafe { core::mem::transmute(mapping) } 161 | } 162 | 163 | pub fn entry_list<'a, T: StableAbi>( 164 | &'a self, 165 | mut callback: OpaqueCallback<(&'a str, &'a Mapping)>, 166 | ) { 167 | let cb = &mut move |data: *const c_void| { 168 | let CTup2(a, b): &CTup2, OpaqueMapping> = 169 | unsafe { &*(data as *const c_void as *const _) }; 170 | callback.call((unsafe { a.into_str() }, unsafe { 171 | &*(b as *const OpaqueMapping as *const Mapping) 172 | })) 173 | }; 174 | 175 | unsafe { 176 | (self.entry_list)( 177 | &*self.store, 178 | T::ABI_CONSTS.type_id.get(), 179 | T::LAYOUT, 180 | &mut cb.into(), 181 | ) 182 | }; 183 | } 184 | } 185 | 186 | impl PluginStore { 187 | pub unsafe fn entries_raw( 188 | &self, 189 | id: UTypeId, 190 | layout: &'static TypeLayout, 191 | ) -> Entry> { 192 | let idx = *self.type_map.entry(id).or_insert_with(|| { 193 | let (idx, inserted) = self 194 | .layouts 195 | .iter() 196 | .find(|p| check_layout_compatibility(layout, p.value()).is_ok()) 197 | .map(|p| (*p.key(), true)) 198 | .or_else(|| { 199 | self.entry_list 200 | .insert(Default::default()) 201 | .map(|i| (i, false)) 202 | }) 203 | .expect("Slab is full!"); 204 | 205 | if !inserted { 206 | self.layouts.insert(idx, layout); 207 | } 208 | 209 | idx 210 | }); 211 | 212 | self.entry_list.get(idx).unwrap() 213 | } 214 | 215 | pub fn entries(&self) -> Entry>> { 216 | let id = T::ABI_CONSTS.type_id.get(); 217 | unsafe { std::mem::transmute(self.entries_raw(id, T::LAYOUT)) } 218 | } 219 | 220 | pub unsafe fn register_mapping_raw( 221 | &self, 222 | id: UTypeId, 223 | layout: &'static TypeLayout, 224 | name: &str, 225 | mapping: OpaqueMapping, 226 | ) -> bool { 227 | let entries = self.entries_raw(id, layout); 228 | 229 | let entry = entries.entry(name.to_string()); 230 | 231 | if matches!(entry, entry::Entry::Vacant(_)) { 232 | entry.or_insert(mapping); 233 | true 234 | } else { 235 | false 236 | } 237 | } 238 | 239 | pub fn register_mapping(&self, name: &str, mapping: Mapping) -> bool { 240 | unsafe { 241 | self.register_mapping_raw( 242 | T::ABI_CONSTS.type_id.get(), 243 | T::LAYOUT, 244 | name, 245 | mapping.into_opaque(), 246 | ) 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /filer/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::num::NonZeroI32; 2 | use std::prelude::v1::*; 3 | use std::{fmt, result, str}; 4 | 5 | use cglue::result::IntError; 6 | 7 | #[cfg(feature = "std")] 8 | use std::error; 9 | 10 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 11 | pub struct Error(pub ErrorOrigin, pub ErrorKind); 12 | 13 | impl Error { 14 | /// Returns a static string representing the type of error. 15 | pub fn as_str(&self) -> &'static str { 16 | self.1.to_str() 17 | } 18 | 19 | /// Returns a static string representing the type of error. 20 | pub fn into_str(self) -> &'static str { 21 | self.as_str() 22 | } 23 | } 24 | 25 | impl IntError for Error { 26 | fn into_int_err(self) -> NonZeroI32 { 27 | let origin = ((self.0 as i32 + 1) & 0xFFFi32) << 4; 28 | let kind = ((self.1 as i32 + 1) & 0xFFFi32) << 16; 29 | NonZeroI32::new(-(1 + origin + kind)).unwrap() 30 | } 31 | 32 | fn from_int_err(err: NonZeroI32) -> Self { 33 | let origin = ((-err.get() - 1) >> 4i32) & 0xFFFi32; 34 | let kind = ((-err.get() - 1) >> 16i32) & 0xFFFi32; 35 | 36 | let error_origin = if origin > 0 && origin <= ErrorOrigin::Other as i32 + 1 { 37 | unsafe { std::mem::transmute(origin as u16 - 1) } 38 | } else { 39 | ErrorOrigin::Other 40 | }; 41 | 42 | let error_kind = if kind > 0 && kind <= ErrorKind::Unknown as i32 + 1 { 43 | unsafe { std::mem::transmute(kind as u16 - 1) } 44 | } else { 45 | ErrorKind::Unknown 46 | }; 47 | 48 | Self(error_origin, error_kind) 49 | } 50 | } 51 | 52 | impl fmt::Display for Error { 53 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 54 | write!(f, "{}: {}", self.0.to_str(), self.1.to_str()) 55 | } 56 | } 57 | 58 | #[cfg(feature = "std")] 59 | impl error::Error for Error { 60 | fn description(&self) -> &str { 61 | self.as_str() 62 | } 63 | } 64 | 65 | impl From for Error { 66 | fn from(origin: ErrorOrigin) -> Self { 67 | Error(origin, ErrorKind::Unknown) 68 | } 69 | } 70 | 71 | impl From for Error { 72 | fn from(kind: ErrorKind) -> Self { 73 | Error(ErrorOrigin::Other, kind) 74 | } 75 | } 76 | 77 | impl From for Error { 78 | fn from(_err: std::io::Error) -> Self { 79 | Error(ErrorOrigin::Io, ErrorKind::Unknown) 80 | } 81 | } 82 | 83 | #[repr(u16)] 84 | #[non_exhaustive] 85 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 86 | pub enum ErrorOrigin { 87 | Backend, 88 | Node, 89 | Leaf, 90 | Branch, 91 | Read, 92 | Write, 93 | Rpc, 94 | Io, 95 | 96 | Other, 97 | } 98 | 99 | impl ErrorOrigin { 100 | /// Returns a static string representing the type of error. 101 | pub fn to_str(self) -> &'static str { 102 | match self { 103 | ErrorOrigin::Backend => "backend", 104 | ErrorOrigin::Node => "node", 105 | ErrorOrigin::Leaf => "leaf", 106 | ErrorOrigin::Branch => "branch", 107 | ErrorOrigin::Read => "read", 108 | ErrorOrigin::Write => "write", 109 | ErrorOrigin::Rpc => "rpc", 110 | ErrorOrigin::Io => "io", 111 | ErrorOrigin::Other => "other", 112 | } 113 | } 114 | } 115 | 116 | #[repr(u16)] 117 | #[non_exhaustive] 118 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 119 | pub enum ErrorKind { 120 | Uninitialized, 121 | NotSupported, 122 | NotImplemented, 123 | Configuration, 124 | Offset, 125 | 126 | InvalidArgument, 127 | 128 | NotFound, 129 | OutOfBounds, 130 | 131 | InvalidPath, 132 | ReadOnly, 133 | UnableToReadDir, 134 | UnableToReadDirEntry, 135 | UnableToReadFile, 136 | UnableToCreateDirectory, 137 | UnableToWriteFile, 138 | UnableToSeekFile, 139 | 140 | VersionMismatch, 141 | AlreadyExists, 142 | PluginNotFound, 143 | InvalidAbi, 144 | 145 | Unknown, 146 | } 147 | 148 | impl ErrorKind { 149 | /// Returns a static string representing the type of error. 150 | pub fn to_str(self) -> &'static str { 151 | match self { 152 | ErrorKind::Uninitialized => "unitialized", 153 | ErrorKind::NotSupported => "not supported", 154 | ErrorKind::NotImplemented => "not implemented", 155 | ErrorKind::Configuration => "configuration error", 156 | ErrorKind::Offset => "offset error", 157 | 158 | ErrorKind::InvalidArgument => "invalid argument passed", 159 | 160 | ErrorKind::NotFound => "not found", 161 | ErrorKind::OutOfBounds => "out of bounds", 162 | 163 | ErrorKind::InvalidPath => "invalid path", 164 | ErrorKind::ReadOnly => "trying to write to a read only resource", 165 | ErrorKind::UnableToReadDir => "unable to read directory", 166 | ErrorKind::UnableToReadDirEntry => "unable to read directory entry", 167 | ErrorKind::UnableToReadFile => "unable to read file", 168 | ErrorKind::UnableToCreateDirectory => "unable to create directory", 169 | ErrorKind::UnableToWriteFile => "unable to write file", 170 | ErrorKind::UnableToSeekFile => "unable to seek file", 171 | 172 | ErrorKind::VersionMismatch => "version mismatch", 173 | ErrorKind::AlreadyExists => "already exists", 174 | ErrorKind::PluginNotFound => "plugin not found", 175 | ErrorKind::InvalidAbi => "invalid plugin ABI", 176 | 177 | ErrorKind::Unknown => "unknown error", 178 | } 179 | } 180 | } 181 | 182 | /// Specialized `Result` type for memflow results. 183 | pub type Result = result::Result; 184 | 185 | #[cfg(test)] 186 | mod tests { 187 | use super::*; 188 | use cglue::result::{ 189 | from_int_result, from_int_result_empty, into_int_out_result, into_int_result, IntError, 190 | }; 191 | use std::mem::MaybeUninit; 192 | use std::num::NonZeroI32; 193 | 194 | #[test] 195 | pub fn error_from_i32_invalid() { 196 | let mut err = Error::from_int_err(NonZeroI32::new(std::i32::MIN + 1).unwrap()); 197 | assert_eq!(err.0, ErrorOrigin::Other); 198 | assert_eq!(err.1, ErrorKind::Unknown); 199 | 200 | err = Error::from_int_err(NonZeroI32::new(-1).unwrap()); 201 | assert_eq!(err.0, ErrorOrigin::Other); 202 | assert_eq!(err.1, ErrorKind::Unknown); 203 | 204 | err = Error::from_int_err(NonZeroI32::new(-2).unwrap()); 205 | assert_eq!(err.0, ErrorOrigin::Other); 206 | assert_eq!(err.1, ErrorKind::Unknown); 207 | 208 | err = Error::from_int_err(NonZeroI32::new(-3).unwrap()); 209 | assert_eq!(err.0, ErrorOrigin::Other); 210 | assert_eq!(err.1, ErrorKind::Unknown); 211 | } 212 | 213 | #[test] 214 | pub fn error_to_from_i32() { 215 | let err = Error::from_int_err( 216 | Error(ErrorOrigin::Other, ErrorKind::InvalidArgument).into_int_err(), 217 | ); 218 | assert_eq!(err.0, ErrorOrigin::Other); 219 | assert_eq!(err.1, ErrorKind::InvalidArgument); 220 | } 221 | 222 | #[test] 223 | pub fn result_ok_void_ffi() { 224 | let r: Result<()> = Ok(()); 225 | let result: Result<()> = from_int_result_empty(into_int_result(r)); 226 | assert!(result.is_ok()); 227 | } 228 | 229 | #[test] 230 | pub fn result_ok_value_ffi() { 231 | let r: Result = Ok(1234i32); 232 | let mut out = MaybeUninit::::uninit(); 233 | let result: Result = unsafe { from_int_result(into_int_out_result(r, &mut out), out) }; 234 | assert!(result.is_ok()); 235 | assert_eq!(result.unwrap(), 1234i32); 236 | } 237 | 238 | #[test] 239 | pub fn result_error_void_ffi() { 240 | let r: Result = Err(Error(ErrorOrigin::Other, ErrorKind::InvalidArgument)); 241 | let result: Result<()> = from_int_result_empty(into_int_result(r)); 242 | assert!(result.is_err()); 243 | assert_eq!(result.err().unwrap().0, ErrorOrigin::Other); 244 | assert_eq!(result.err().unwrap().1, ErrorKind::InvalidArgument); 245 | } 246 | 247 | #[test] 248 | pub fn result_error_value_ffi() { 249 | let r: Result = Err(Error(ErrorOrigin::Other, ErrorKind::InvalidArgument)); 250 | let mut out = MaybeUninit::::uninit(); 251 | let result: Result = unsafe { from_int_result(into_int_out_result(r, &mut out), out) }; 252 | assert!(result.is_err()); 253 | assert_eq!(result.err().unwrap().0, ErrorOrigin::Other); 254 | assert_eq!(result.err().unwrap().1, ErrorKind::InvalidArgument); 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /cloudflow/src/module.rs: -------------------------------------------------------------------------------- 1 | use crate::process::ThreadedProcessArc; 2 | use crate::util::*; 3 | use abi_stable::StableAbi; 4 | pub use cglue::slice::CSliceMut; 5 | use cglue::trait_group::c_void; 6 | use filer::branch; 7 | use filer::prelude::v1::{Error, ErrorKind, ErrorOrigin, Result, *}; 8 | use memflow::prelude::v1::*; 9 | 10 | pub extern "C" fn on_node(node: &Node, ctx: CArc) { 11 | node.plugins 12 | .register_mapping("mem", Mapping::Leaf(self_as_leaf::, ctx.clone())); 13 | 14 | node.plugins 15 | .register_mapping("info", Mapping::Leaf(map_into_info, ctx.clone())); 16 | } 17 | 18 | arc_types!(ModuleBase, Module, ModuleArc); 19 | 20 | impl Branch for ModuleArc { 21 | fn get_entry(&self, path: &str, plugins: &CPluginStore) -> Result { 22 | branch::get_entry(self, path, plugins) 23 | } 24 | 25 | fn list( 26 | &self, 27 | plugins: &CPluginStore, 28 | out: &mut OpaqueCallback, 29 | ) -> Result<()> { 30 | branch::list(self, plugins)? 31 | .into_iter() 32 | .map(|(name, entry)| BranchListEntry::new(name.into(), entry)) 33 | .feed_into_mut(out); 34 | Ok(()) 35 | } 36 | } 37 | 38 | impl Leaf for ModuleArc { 39 | fn open(&self) -> Result> { 40 | Ok(FileOpsObj::new( 41 | ModuleBase::clone(self).into(), 42 | Some(ModuleBase::read), 43 | Some(ModuleBase::write), 44 | Some(ModuleBase::rpc), 45 | )) 46 | } 47 | 48 | fn metadata(&self) -> Result { 49 | Ok(NodeMetadata { 50 | is_branch: false, 51 | has_read: true, 52 | has_write: true, 53 | has_rpc: true, 54 | size: self.0.module_info.size, 55 | ..Default::default() 56 | }) 57 | } 58 | } 59 | 60 | #[repr(C)] 61 | #[derive(StableAbi, Clone)] 62 | pub struct ModuleBase { 63 | process: ThreadedProcessArc, 64 | module_info: ModuleInfo, 65 | } 66 | use std::cell::RefCell; 67 | impl ModuleBase { 68 | pub fn new(process: ThreadedProcessArc, module_info: ModuleInfo) -> Self { 69 | Self { 70 | process, 71 | module_info, 72 | } 73 | } 74 | 75 | extern "C" fn read(&self, data: VecOps) -> i32 { 76 | // TODO: size constraint 77 | int_res_wrap! { 78 | memdata_map(data, |data| { 79 | let base = self.module_info.base; 80 | let size = self.module_info.size; 81 | 82 | // wrap the data.inp iterator by first splitting it up at the modules max size 83 | // and then remapping the desired addr and meta_addr by the module base 84 | let inp = data.inp.flat_map(|CTup3(addr, meta_addr, data)| { 85 | let split = size - addr.to_umem(); 86 | let (left, right) = data.split_at(split); 87 | 88 | let new_addr = base + addr.to_umem(); 89 | let new_meta_addr = base + meta_addr.to_umem(); 90 | let left = left.map(|out| CTup3(new_addr, new_meta_addr, out)); 91 | let right = right.map(|out| CTup3(new_addr + split, new_meta_addr + split, out)); 92 | 93 | left.into_iter().chain(right.into_iter()) 94 | }); 95 | 96 | // TODO: handle non existent data.out / data.out_fail callbacks 97 | 98 | // wrap the data.out and data.out_fail callbacks: 99 | // first we check if the desired address falls inside or outside the module and call the appropiate callback. 100 | // second we subtract the module base that was added in the above function again. 101 | let out_cell = RefCell::new(data.out.unwrap()); 102 | let out_fail_cell = RefCell::new(data.out_fail.unwrap()); 103 | 104 | let out = &mut |CTup2(addr, out)| { 105 | if addr < base + size { 106 | let mut out_cb = out_cell.borrow_mut(); 107 | out_cb.call(CTup2(addr - base.to_umem(), out)) 108 | } else { 109 | let mut out_fail_cb = out_fail_cell.borrow_mut(); 110 | out_fail_cb.call(CTup2(addr - base.to_umem(), out)) 111 | } 112 | }; 113 | let out = &mut out.into(); 114 | let out = Some(out); 115 | 116 | let out_fail = &mut |CTup2(addr, out)| { 117 | if addr < base + size { 118 | let mut out_cb = out_cell.borrow_mut(); 119 | out_cb.call(CTup2(addr - base.to_umem(), out)) 120 | } else { 121 | let mut out_fail_cb = out_fail_cell.borrow_mut(); 122 | out_fail_cb.call(CTup2(addr - base.to_umem(), out)) 123 | } 124 | }; 125 | let out_fail = &mut out_fail.into(); 126 | let out_fail = Some(out_fail); 127 | 128 | // create a new MemOps object with the wrapped values 129 | MemOps::with_raw(inp, out, out_fail, |data| { 130 | self.process.get() 131 | .read_raw_iter(data) 132 | .map_err(|_| Error(ErrorOrigin::Read, ErrorKind::Unknown)) 133 | }) 134 | }) 135 | } 136 | } 137 | 138 | extern "C" fn write(&self, data: VecOps) -> i32 { 139 | // TODO: size constraint 140 | int_res_wrap! { 141 | memdata_map(data, |data| { 142 | let base = self.module_info.base; 143 | let size = self.module_info.size; 144 | 145 | // wrap the data.inp iterator by first splitting it up at the modules max size 146 | // and then remapping the desired addr and meta_addr by the module base 147 | let inp = data.inp.flat_map(|CTup3(addr, meta_addr, data)| { 148 | let split = size - addr.to_umem(); 149 | let (left, right) = data.split_at(split); 150 | 151 | let new_addr = base + addr.to_umem(); 152 | let new_meta_addr = base + meta_addr.to_umem(); 153 | let left = left.map(|out| CTup3(new_addr, new_meta_addr, out)); 154 | let right = right.map(|out| CTup3(new_addr + split, new_meta_addr + split, out)); 155 | 156 | left.into_iter().chain(right.into_iter()) 157 | }); 158 | 159 | // TODO: handle non existent data.out / data.out_fail callbacks 160 | 161 | // wrap the data.out and data.out_fail callbacks: 162 | // first we check if the desired address falls inside or outside the module and call the appropiate callback. 163 | // second we subtract the module base that was added in the above function again. 164 | let out_cell = RefCell::new(data.out.unwrap()); 165 | let out_fail_cell = RefCell::new(data.out_fail.unwrap()); 166 | 167 | let out = &mut |CTup2(addr, out)| { 168 | if addr < base + size { 169 | let mut out_cb = out_cell.borrow_mut(); 170 | out_cb.call(CTup2(addr - base.to_umem(), out)) 171 | } else { 172 | let mut out_fail_cb = out_fail_cell.borrow_mut(); 173 | out_fail_cb.call(CTup2(addr - base.to_umem(), out)) 174 | } 175 | }; 176 | let out = &mut out.into(); 177 | let out = Some(out); 178 | 179 | let out_fail = &mut |CTup2(addr, out)| { 180 | if addr < base + size { 181 | let mut out_cb = out_cell.borrow_mut(); 182 | out_cb.call(CTup2(addr - base.to_umem(), out)) 183 | } else { 184 | let mut out_fail_cb = out_fail_cell.borrow_mut(); 185 | out_fail_cb.call(CTup2(addr - base.to_umem(), out)) 186 | } 187 | }; 188 | let out_fail = &mut out_fail.into(); 189 | let out_fail = Some(out_fail); 190 | 191 | // create a new MemOps object with the wrapped values 192 | MemOps::with_raw(inp, out, out_fail, |data| { 193 | self.process.get() 194 | .write_raw_iter(data) 195 | .map_err(|_| Error(ErrorOrigin::Read, ErrorKind::Unknown)) 196 | }) 197 | }) 198 | } 199 | } 200 | 201 | extern "C" fn rpc(&self, _input: CSliceRef, _output: CSliceMut) -> i32 { 202 | Result::Ok(()).into_int_result() 203 | } 204 | } 205 | 206 | extern "C" fn map_into_info( 207 | module: &ModuleArc, 208 | ctx: &CArc, 209 | ) -> COption> { 210 | let file = FnFile::new(module.module_info.clone(), |module_info| { 211 | Ok(format!("{:#?}", module_info)) 212 | }); 213 | COption::Some(trait_obj!((file, ctx.clone()) as Leaf)) 214 | } 215 | -------------------------------------------------------------------------------- /filer/src/backend.rs: -------------------------------------------------------------------------------- 1 | use crate::error::*; 2 | use crate::fs::*; 3 | use crate::node::*; 4 | use crate::plugin_store::*; 5 | use crate::str_build::*; 6 | use crate::types::*; 7 | use abi_stable::StableAbi; 8 | use cglue::prelude::v1::*; 9 | 10 | pub use cglue::slice::CSliceMut; 11 | use cglue::trait_group::c_void; 12 | 13 | use dashmap::{mapref::one::Ref, DashMap}; 14 | 15 | #[derive(StableAbi)] 16 | #[repr(C)] 17 | pub struct ListEntry { 18 | pub name: ReprCString, 19 | pub is_branch: bool, 20 | } 21 | 22 | impl ListEntry { 23 | pub fn new(name: ReprCString, is_branch: bool) -> Self { 24 | Self { name, is_branch } 25 | } 26 | } 27 | 28 | fn map_exists(entries: &DashMap, name: &str) -> bool { 29 | ["new", "rm"].contains(&name) || entries.contains_key(name) 30 | } 31 | 32 | fn map_insert(entries: &DashMap, name: &str, entry: T) -> bool { 33 | if name.contains('/') || map_exists(entries, name) { 34 | false 35 | } else { 36 | entries.insert(name.into(), entry).is_none() 37 | } 38 | } 39 | 40 | fn map_checked_insert(entries: &DashMap, name: &str, entry: T) -> Result<()> { 41 | if !map_insert(entries, name, entry) { 42 | Err(Error(ErrorOrigin::Backend, ErrorKind::AlreadyExists)) 43 | } else { 44 | Ok(()) 45 | } 46 | } 47 | 48 | struct NewHandler( 49 | CArcSome>, 50 | CArc, 51 | fn(&str, &CArc) -> Result, 52 | ); 53 | 54 | impl NewHandler { 55 | extern "C" fn write(&self, mut data: VecOps) -> i32 { 56 | for d in data.inp { 57 | if let Err(e) = std::str::from_utf8(&d.1) 58 | .map_err(|_| Error(ErrorOrigin::Backend, ErrorKind::InvalidArgument)) 59 | .map(|a| a.split_once(' ').unwrap_or((a, ""))) 60 | .and_then(|(n, a)| { 61 | if !map_exists(&*self.0, n) { 62 | Ok((n, a)) 63 | } else { 64 | Err(Error(ErrorOrigin::Backend, ErrorKind::AlreadyExists)) 65 | } 66 | }) 67 | .and_then(|(n, a)| (self.2)(a, &self.1).map(|o| (n, o))) 68 | .and_then(|(n, o)| map_checked_insert(&*self.0, n, o)) 69 | { 70 | let _ = opt_call(data.out_fail.as_deref_mut(), (d, e).into()); 71 | } 72 | } 73 | 0 74 | } 75 | } 76 | 77 | struct RmHandler(CArcSome>); 78 | 79 | impl RmHandler { 80 | extern "C" fn write(&self, mut data: VecOps) -> i32 { 81 | for d in data.inp { 82 | if let Err(e) = std::str::from_utf8(&d.1) 83 | .map_err(|_| Error(ErrorOrigin::Backend, ErrorKind::InvalidArgument)) 84 | .map(|n| self.0.remove(n.trim())) 85 | { 86 | let _ = opt_call(data.out_fail.as_deref_mut(), (d, e).into()); 87 | } 88 | } 89 | 0 90 | } 91 | } 92 | 93 | pub struct LocalBackend { 94 | entries: CArcSome>, 95 | context: CArc, 96 | handle_objs: RcSlab>, 97 | build_fn: Option) -> Result>, 98 | new_handle: Result, 99 | rm_handle: Result, 100 | } 101 | 102 | impl Default for LocalBackend { 103 | fn default() -> Self { 104 | let entries: DashMap = Default::default(); 105 | let entries: CArcSome> = entries.into(); 106 | 107 | let handle_objs = Default::default(); 108 | 109 | let mut ret = Self { 110 | entries, 111 | handle_objs, 112 | context: CArc::default(), 113 | new_handle: Err(Error(ErrorOrigin::Backend, ErrorKind::NotSupported)), 114 | rm_handle: Err(ErrorKind::Unknown.into()), 115 | build_fn: None, 116 | }; 117 | ret.rebuild_rm(); 118 | ret 119 | } 120 | } 121 | 122 | impl>, C> LocalBackend { 123 | pub fn with_new(mut self) -> Self { 124 | self.build_fn = Some(T::build); 125 | self.rebuild_new(); 126 | self 127 | } 128 | } 129 | //impl FileOps for LocalBackend { 130 | //} 131 | 132 | impl LocalBackend { 133 | pub fn get(&self, name: &str) -> Option> { 134 | self.entries.get(name) 135 | } 136 | 137 | pub fn set_context(&mut self, context: C) { 138 | self.context = context.into(); 139 | self.rebuild_rm(); 140 | self.rebuild_new(); 141 | } 142 | 143 | pub fn rebuild_rm(&mut self) { 144 | if let Ok(rm_handle) = self.rm_handle { 145 | self.handle_objs.dec_rc(rm_handle); 146 | } 147 | let rm_obj = RmHandler(self.entries.clone()); 148 | let rm_obj = FileOpsObj::new(rm_obj.into(), None, Some(RmHandler::write), None); 149 | let rm_handle = self 150 | .handle_objs 151 | .insert(rm_obj) 152 | .ok_or(Error(ErrorOrigin::Backend, ErrorKind::Unknown)); 153 | self.rm_handle = rm_handle; 154 | } 155 | 156 | pub fn rebuild_new(&mut self) { 157 | if let Ok(new_handle) = self.new_handle { 158 | self.handle_objs.dec_rc(new_handle); 159 | } 160 | 161 | if let Some(build_fn) = self.build_fn { 162 | let new_obj = NewHandler(self.entries.clone(), self.context.clone(), build_fn); 163 | let new_obj = FileOpsObj::new(new_obj.into(), None, Some(NewHandler::write), None); 164 | let new_handle = self 165 | .handle_objs 166 | .insert(new_obj) 167 | .ok_or(Error(ErrorOrigin::Backend, ErrorKind::Unknown)); 168 | self.new_handle = new_handle; 169 | } 170 | } 171 | 172 | pub fn with_context_arc(self, context: CArc) -> LocalBackend { 173 | let Self { 174 | entries, 175 | handle_objs, 176 | new_handle, 177 | rm_handle, 178 | .. 179 | } = self; 180 | 181 | LocalBackend { 182 | entries, 183 | handle_objs, 184 | context, 185 | new_handle, 186 | rm_handle, 187 | build_fn: None, 188 | } 189 | } 190 | 191 | pub fn with_context(self, ctx: NC) -> LocalBackend { 192 | self.with_context_arc::(ctx.into()) 193 | } 194 | 195 | pub fn insert(&self, name: &str, entry: T) -> bool { 196 | map_insert(&*self.entries, name, entry) 197 | } 198 | 199 | pub fn checked_insert(&self, name: &str, entry: T) -> Result<()> { 200 | map_checked_insert(&*self.entries, name, entry) 201 | } 202 | 203 | fn push_obj(&self, obj: FileOpsObj) -> usize { 204 | self.handle_objs.insert(obj).unwrap() 205 | } 206 | } 207 | 208 | impl Backend for LocalBackend { 209 | fn read(&self, _stack: BackendStack, handle: usize, data: VecOps) -> Result<()> { 210 | match self.handle_objs.get(handle) { 211 | Some(f) => f.read(data), 212 | _ => Err(Error(ErrorOrigin::Backend, ErrorKind::NotFound)), 213 | } 214 | } 215 | 216 | fn write(&self, _stack: BackendStack, handle: usize, data: VecOps) -> Result<()> { 217 | match self.handle_objs.get(handle) { 218 | Some(f) => f.write(data), 219 | _ => Err(Error(ErrorOrigin::Backend, ErrorKind::NotFound)), 220 | } 221 | } 222 | 223 | fn rpc( 224 | &self, 225 | _stack: BackendStack, 226 | handle: usize, 227 | input: &[u8], 228 | output: &mut [u8], 229 | ) -> Result<()> { 230 | match self.handle_objs.get(handle) { 231 | Some(f) => f.rpc(input, output), 232 | _ => Err(Error(ErrorOrigin::Backend, ErrorKind::NotFound)), 233 | } 234 | } 235 | 236 | fn close(&self, _stack: BackendStack, handle: usize) -> Result<()> { 237 | if Ok(handle) != self.new_handle && Ok(handle) != self.rm_handle { 238 | match self.handle_objs.dec_rc(handle) { 239 | Some(_) => Ok(()), 240 | None => Err(Error(ErrorOrigin::Backend, ErrorKind::NotFound)), 241 | } 242 | } else { 243 | Ok(()) 244 | } 245 | } 246 | 247 | fn open(&self, _stack: BackendStack, path: &str, plugins: &CPluginStore) -> Result { 248 | let (branch, path) = path.split_once('/').unwrap_or((path, "")); 249 | 250 | if path.is_empty() && branch == "new" { 251 | self.new_handle 252 | } else if path.is_empty() && branch == "rm" { 253 | self.rm_handle 254 | } else { 255 | match self.entries.get(branch).map(|b| b.get_entry(path, plugins)) { 256 | Some(Ok(DirEntry::Leaf(leaf))) => leaf.open().map(|o| self.push_obj(o)), 257 | Some(Ok(_)) => Err(Error(ErrorOrigin::Backend, ErrorKind::InvalidArgument)), 258 | Some(Err(e)) => Err(e), 259 | _ => Err(Error(ErrorOrigin::Backend, ErrorKind::NotFound)), 260 | } 261 | } 262 | } 263 | 264 | /// Get metadata of given path. 265 | fn metadata( 266 | &self, 267 | _stack: BackendStack, 268 | path: &str, 269 | plugins: &CPluginStore, 270 | ) -> Result { 271 | let (branch, path) = path.split_once('/').unwrap_or((path, "")); 272 | 273 | if path.is_empty() && branch == "new" { 274 | self.new_handle.map(|_| NodeMetadata::default()) 275 | } else if path.is_empty() && branch == "rm" { 276 | self.rm_handle.map(|_| NodeMetadata::default()) 277 | } else { 278 | match self.entries.get(branch) { 279 | Some(b) => { 280 | if path.is_empty() { 281 | Ok(NodeMetadata::branch()) 282 | } else { 283 | match b.get_entry(path, plugins) { 284 | Ok(DirEntry::Leaf(leaf)) => leaf.metadata(), 285 | Ok(DirEntry::Branch(_)) => Ok(NodeMetadata::branch()), 286 | Err(e) => Err(e), 287 | } 288 | } 289 | } 290 | _ => Err(Error(ErrorOrigin::Backend, ErrorKind::NotFound)), 291 | } 292 | } 293 | } 294 | 295 | fn list( 296 | &self, 297 | _stack: BackendStack, 298 | path: &str, 299 | plugins: &CPluginStore, 300 | out: &mut OpaqueCallback, 301 | ) -> Result<()> { 302 | if path.is_empty() { 303 | self.entries 304 | .iter() 305 | .map(|r| r.key().clone()) 306 | .map(|n| ListEntry::new(n.into(), true)) 307 | .feed_into_mut(out); 308 | 309 | let _ = out.call(ListEntry::new("rm".into(), false)); 310 | 311 | if self.new_handle.is_ok() { 312 | let _ = out.call(ListEntry::new("new".into(), false)); 313 | } 314 | 315 | Ok(()) 316 | } else { 317 | let (branch, path) = path.split_once('/').unwrap_or((path, "")); 318 | match self.entries.get(branch) { 319 | Some(branch) => { 320 | let cb = &mut |entry: BranchListEntry| { 321 | out.call(ListEntry::new( 322 | entry.name, 323 | matches!(entry.obj, DirEntry::Branch(_)), 324 | )) 325 | }; 326 | 327 | branch.list_recurse(path, plugins, &mut cb.into()) 328 | } 329 | _ => Err(Error(ErrorOrigin::Backend, ErrorKind::NotFound)), 330 | } 331 | } 332 | } 333 | } 334 | 335 | #[repr(C)] 336 | #[derive(StableAbi)] 337 | pub enum BackendStack<'a> { 338 | Node(&'a CArcSome), 339 | Backend(&'a BackendStack<'a>, BackendRef<'a>), 340 | } 341 | 342 | impl<'a> From<&'a CArcSome> for BackendStack<'a> { 343 | fn from(node: &'a CArcSome) -> Self { 344 | BackendStack::Node(node) 345 | } 346 | } 347 | 348 | impl<'a, 'b: 'a, T: Backend> From<(&'a BackendStack<'b>, &'a T)> for BackendStack<'a> 349 | where 350 | &'a T: Into>, 351 | { 352 | fn from((stack, backend): (&'a BackendStack<'b>, &'a T)) -> Self { 353 | // SAFETY: We are shortening the lifetime 'b to 'a. This lifetime is only used to bind 354 | // references, thus it is okay. 355 | let stack = unsafe { core::mem::transmute(stack) }; 356 | BackendStack::Backend(stack, trait_obj!(backend as Backend)) 357 | } 358 | } 359 | 360 | #[cglue_trait] 361 | #[int_result] 362 | pub trait Backend { 363 | /// Perform read operation on the given handle 364 | fn read(&self, stack: BackendStack, handle: usize, data: VecOps) -> Result<()>; 365 | /// Perform write operation on the given handle. 366 | fn write(&self, stack: BackendStack, handle: usize, data: VecOps) -> Result<()>; 367 | /// Perform remote procedure call on the given handle. 368 | fn rpc( 369 | &self, 370 | stack: BackendStack, 371 | handle: usize, 372 | input: &[u8], 373 | output: &mut [u8], 374 | ) -> Result<()>; 375 | /// Close an opened handle. 376 | fn close(&self, stack: BackendStack, handle: usize) -> Result<()>; 377 | /// Open a leaf at the given path. The result is a handle. 378 | fn open(&self, stack: BackendStack, path: &str, plugins: &CPluginStore) -> Result; 379 | /// Get metadata of given path. 380 | fn metadata( 381 | &self, 382 | stack: BackendStack, 383 | path: &str, 384 | plugins: &CPluginStore, 385 | ) -> Result; 386 | /// List entries in the given path. It is a (name, is_branch) pair. 387 | fn list( 388 | &self, 389 | stack: BackendStack, 390 | path: &str, 391 | plugins: &CPluginStore, 392 | out: &mut OpaqueCallback, 393 | ) -> Result<()>; 394 | } 395 | -------------------------------------------------------------------------------- /cloudflow/src/process.rs: -------------------------------------------------------------------------------- 1 | use crate::module::{ModuleArc, ModuleBase}; 2 | use crate::os::OsBase; 3 | use crate::util::*; 4 | use abi_stable::StableAbi; 5 | pub use cglue::slice::CSliceMut; 6 | use cglue::trait_group::c_void; 7 | use dashmap::DashMap; 8 | use filer::branch; 9 | use filer::prelude::v1::{Error, ErrorKind, ErrorOrigin, Result, *}; 10 | use memflow::prelude::v1::*; 11 | 12 | use once_cell::sync::OnceCell; 13 | 14 | pub extern "C" fn on_node(node: &Node, ctx: CArc) { 15 | node.plugins.register_mapping( 16 | "mem", 17 | Mapping::Leaf(self_as_leaf::, ctx.clone()), 18 | ); 19 | 20 | node.plugins 21 | .register_mapping("info", Mapping::Leaf(map_into_info, ctx.clone())); 22 | 23 | node.plugins 24 | .register_mapping("maps", Mapping::Leaf(map_into_maps, ctx.clone())); 25 | 26 | node.plugins 27 | .register_mapping("phys_maps", Mapping::Leaf(map_into_phys_maps, ctx.clone())); 28 | 29 | node.plugins 30 | .register_mapping("modules", Mapping::Branch(ModuleList::map_into, ctx)); 31 | } 32 | 33 | thread_types!( 34 | IntoProcessInstanceArcBox<'static>, 35 | ThreadedProcess, 36 | ThreadedProcessArc 37 | ); 38 | 39 | arc_types!(LazyProcessBase, LazyProcess, LazyProcessArc); 40 | 41 | impl Branch for LazyProcessArc { 42 | fn get_entry(&self, path: &str, plugins: &CPluginStore) -> Result { 43 | branch::get_entry(self, path, plugins) 44 | } 45 | 46 | fn list( 47 | &self, 48 | plugins: &CPluginStore, 49 | out: &mut OpaqueCallback, 50 | ) -> Result<()> { 51 | branch::list(self, plugins)? 52 | .into_iter() 53 | .map(|(name, entry)| BranchListEntry::new(name.into(), entry)) 54 | .feed_into_mut(out); 55 | Ok(()) 56 | } 57 | } 58 | 59 | impl Leaf for LazyProcessArc { 60 | fn open(&self) -> Result> { 61 | Ok(FileOpsObj::new( 62 | self.proc().ok_or(ErrorKind::Uninitialized)?.clone().into(), 63 | Some(ThreadedProcess::read), 64 | Some(ThreadedProcess::write), 65 | Some(ThreadedProcess::rpc), 66 | )) 67 | } 68 | 69 | fn metadata(&self) -> Result { 70 | Ok(NodeMetadata { 71 | is_branch: false, 72 | has_read: true, 73 | has_write: true, 74 | has_rpc: true, 75 | // TODO: Proc vs Sys arch? 76 | size: (1 as Size) << self.proc_info.sys_arch.into_obj().address_space_bits(), 77 | ..Default::default() 78 | }) 79 | } 80 | } 81 | 82 | impl ThreadedProcess { 83 | pub(crate) extern "C" fn read(&self, data: VecOps) -> i32 { 84 | int_res_wrap! { 85 | memdata_map(data, |data| { 86 | self.get() 87 | .read_raw_iter(data) 88 | .map_err(|_| Error(ErrorOrigin::Read, ErrorKind::Unknown)) 89 | }) 90 | } 91 | } 92 | 93 | pub(crate) extern "C" fn write(&self, data: VecOps) -> i32 { 94 | int_res_wrap! { 95 | memdata_map(data, |data| { 96 | self.get() 97 | .write_raw_iter(data) 98 | .map_err(|_| Error(ErrorOrigin::Write, ErrorKind::Unknown)) 99 | }) 100 | } 101 | } 102 | 103 | pub(crate) extern "C" fn rpc(&self, _input: CSliceRef, _output: CSliceMut) -> i32 { 104 | Result::Ok(()).into_int_result() 105 | } 106 | } 107 | 108 | #[repr(C)] 109 | #[derive(StableAbi, Clone)] 110 | pub struct LazyProcessBase { 111 | os: OsBase, 112 | proc_info: ProcessInfo, 113 | proc_box: CArcSome, 114 | get_proc: unsafe extern "C" fn(&LazyProcessBase) -> Option<&ThreadedProcessArc>, 115 | } 116 | 117 | impl LazyProcessBase { 118 | unsafe extern "C" fn get_proc(&self) -> Option<&ThreadedProcessArc> { 119 | let proc_box = &*self.proc_box as *const c_void as *const OnceCell; 120 | 121 | (*proc_box) 122 | .get_or_try_init(|| { 123 | self.os 124 | .get_orig() 125 | .clone() 126 | .into_process_by_info(self.proc_info.clone()) 127 | .map(ThreadedProcessArc::from) 128 | }) 129 | .ok() 130 | } 131 | 132 | pub fn proc(&self) -> Option<&ThreadedProcessArc> { 133 | unsafe { (self.get_proc)(self) } 134 | } 135 | 136 | pub fn new(os: OsBase, proc_info: ProcessInfo) -> Self { 137 | Self { 138 | os, 139 | proc_info, 140 | proc_box: CArcSome::from(OnceCell::::new()).into_opaque(), 141 | get_proc: Self::get_proc, 142 | } 143 | } 144 | } 145 | 146 | fn format_perms(page_type: PageType) -> String { 147 | format!( 148 | "r{}{}", 149 | if page_type.contains(PageType::WRITEABLE) { 150 | 'w' 151 | } else { 152 | '-' 153 | }, 154 | if !page_type.contains(PageType::NOEXEC) { 155 | 'x' 156 | } else { 157 | '-' 158 | } 159 | ) 160 | } 161 | 162 | extern "C" fn map_into_info( 163 | proc: &LazyProcessArc, 164 | ctx: &CArc, 165 | ) -> COption> { 166 | let file = FnFile::new(proc.clone(), |proc| { 167 | let proc = proc.proc().ok_or(ErrorKind::Uninitialized)?; 168 | let info = proc.get_orig().info(); 169 | Ok(format!("{:#?}", info)) 170 | }); 171 | COption::Some(trait_obj!((file, ctx.clone()) as Leaf)) 172 | } 173 | 174 | extern "C" fn map_into_maps( 175 | proc: &LazyProcessArc, 176 | ctx: &CArc, 177 | ) -> COption> { 178 | let file = FnFile::new(proc.clone(), |proc| { 179 | let proc = proc.proc().ok_or(ErrorKind::Uninitialized)?; 180 | let mut proc = proc.get(); 181 | 182 | let maps = proc.mapped_mem_vec(-1); 183 | let mut modules = proc.module_list().map_err(|_| ErrorKind::Uninitialized)?; 184 | modules.sort_by_key(|m| m.base.to_umem()); 185 | 186 | let out = maps 187 | .into_iter() 188 | .map(|CTup3(vaddr, size, page_type)| { 189 | let module = modules 190 | .iter() 191 | .find(|m| m.base <= vaddr && m.base + m.size > vaddr); 192 | 193 | let perms = format_perms(page_type); 194 | 195 | format!( 196 | "{:x}-{:x} {} {}\n", 197 | vaddr, 198 | vaddr + size, 199 | perms, 200 | module.map(|m| m.name.as_ref()).unwrap_or_default() 201 | ) 202 | }) 203 | .collect::(); 204 | 205 | Ok(out) 206 | }); 207 | COption::Some(trait_obj!((file, ctx.clone()) as Leaf)) 208 | } 209 | 210 | extern "C" fn map_into_phys_maps( 211 | proc: &LazyProcessArc, 212 | ctx: &CArc, 213 | ) -> COption> { 214 | if proc 215 | .proc() 216 | .and_then(|proc| as_ref!(proc.get_orig() impl VirtualTranslate)) 217 | .is_some() 218 | { 219 | let file = FnFile::new(proc.clone(), |proc| { 220 | let proc = proc.proc().ok_or(ErrorKind::Uninitialized)?; 221 | let mut proc = proc.get(); 222 | let proc = as_mut!(proc impl VirtualTranslate).ok_or(ErrorKind::NotSupported)?; 223 | 224 | let maps = proc.virt_translation_map_vec(); 225 | let mut modules = proc.module_list().map_err(|_| ErrorKind::Unknown)?; 226 | modules.sort_by_key(|m| m.base.to_umem()); 227 | 228 | let out = maps 229 | .into_iter() 230 | .map(|tr| { 231 | let module = modules 232 | .iter() 233 | .find(|m| m.base <= tr.in_virtual && m.base + m.size > tr.in_virtual); 234 | 235 | let perms = format_perms(tr.out_physical.page_type()); 236 | 237 | format!( 238 | "{:x}-{:x} {} {:9x} {}\n", 239 | tr.in_virtual, 240 | tr.in_virtual + tr.size, 241 | perms, 242 | tr.out_physical.address, 243 | module.map(|m| m.name.as_ref()).unwrap_or_default() 244 | ) 245 | }) 246 | .collect::(); 247 | 248 | Ok(out) 249 | }); 250 | COption::Some(trait_obj!((file, ctx.clone()) as Leaf)) 251 | } else { 252 | COption::None 253 | } 254 | } 255 | 256 | #[repr(C)] 257 | #[derive(Clone, StableAbi)] 258 | pub struct LazyProcessRoot { 259 | process: LazyProcessBase, 260 | mlist: CArcSome, 261 | } 262 | 263 | impl core::ops::Deref for LazyProcessRoot { 264 | type Target = LazyProcessBase; 265 | 266 | fn deref(&self) -> &Self::Target { 267 | &self.process 268 | } 269 | } 270 | 271 | impl From for LazyProcessRoot { 272 | fn from(process: LazyProcessBase) -> Self { 273 | Self { 274 | mlist: CArcSome::from(ModuleList::from(process.clone())).into_opaque(), 275 | process, 276 | } 277 | } 278 | } 279 | 280 | impl LazyProcessRoot { 281 | unsafe fn mlist(&self) -> &CArcSome { 282 | (&self.mlist as *const CArcSome as *const CArcSome) 283 | .as_ref() 284 | .unwrap() 285 | } 286 | } 287 | 288 | impl Branch for LazyProcessRoot { 289 | fn get_entry(&self, path: &str, plugins: &CPluginStore) -> Result { 290 | branch::get_entry(self, path, plugins) 291 | } 292 | 293 | fn list( 294 | &self, 295 | plugins: &CPluginStore, 296 | out: &mut OpaqueCallback, 297 | ) -> Result<()> { 298 | branch::list(self, plugins)? 299 | .into_iter() 300 | .map(|(name, entry)| BranchListEntry::new(name.into(), entry)) 301 | .feed_into_mut(out); 302 | Ok(()) 303 | } 304 | } 305 | 306 | impl Leaf for LazyProcessRoot { 307 | fn open(&self) -> Result> { 308 | Ok(FileOpsObj::new( 309 | self.proc().ok_or(ErrorKind::Uninitialized)?.clone().into(), 310 | Some(ThreadedProcess::read), 311 | Some(ThreadedProcess::write), 312 | Some(ThreadedProcess::rpc), 313 | )) 314 | } 315 | 316 | fn metadata(&self) -> Result { 317 | Ok(NodeMetadata { 318 | is_branch: false, 319 | has_read: true, 320 | has_write: true, 321 | has_rpc: true, 322 | size: (1 as Size) 323 | << self 324 | .os 325 | .get_orig() 326 | .info() 327 | .arch 328 | .into_obj() 329 | .address_space_bits(), 330 | ..Default::default() 331 | }) 332 | } 333 | } 334 | 335 | #[derive(Clone)] 336 | struct ModuleList { 337 | process: LazyProcessBase, 338 | by_sys_arch: ModuleArchList, 339 | by_proc_arch: Option, 340 | } 341 | 342 | impl From for ModuleList { 343 | fn from(process: LazyProcessBase) -> Self { 344 | let sys_arch = process.os.get().info().arch; 345 | let by_sys_arch = (process.clone(), sys_arch).into(); 346 | 347 | let by_proc_arch = if let Some(proc) = process.proc() { 348 | let proc = proc.get(); 349 | let info = proc.info(); 350 | if info.proc_arch != info.sys_arch { 351 | Some((process.clone(), info.proc_arch).into()) 352 | } else { 353 | None 354 | } 355 | } else { 356 | None 357 | }; 358 | 359 | Self { 360 | process, 361 | by_sys_arch, 362 | by_proc_arch, 363 | } 364 | } 365 | } 366 | 367 | impl ModuleList { 368 | extern "C" fn map_into( 369 | process: &LazyProcessArc, 370 | ctx: &CArc, 371 | ) -> COption> { 372 | // TODO: improve this workaround 373 | let process: LazyProcessRoot = LazyProcessBase::clone(process).into(); 374 | COption::Some(trait_obj!( 375 | (unsafe { &**process.mlist() }.clone(), ctx.clone()) as Branch 376 | )) 377 | } 378 | } 379 | 380 | impl Branch for ModuleList { 381 | fn get_entry(&self, path: &str, plugins: &CPluginStore) -> Result { 382 | let (entry, path) = branch::split_path(path); 383 | 384 | if entry == self.by_sys_arch.arch.to_string() { 385 | return branch::forward_entry( 386 | self.by_sys_arch.clone(), 387 | self.process.os.ctx.clone(), 388 | path, 389 | plugins, 390 | ); 391 | } 392 | 393 | if let Some(by_proc_arch) = &self.by_proc_arch { 394 | if entry == by_proc_arch.arch.to_string() { 395 | return branch::forward_entry( 396 | by_proc_arch.clone(), 397 | self.process.os.ctx.clone(), 398 | path, 399 | plugins, 400 | ); 401 | } 402 | } 403 | 404 | Err(Error(ErrorOrigin::Branch, ErrorKind::NotFound)) 405 | } 406 | 407 | fn list( 408 | &self, 409 | _plugins: &CPluginStore, 410 | out: &mut OpaqueCallback, 411 | ) -> Result<()> { 412 | // display system architecture subfolder 413 | let _ = out.call(BranchListEntry::new( 414 | self.by_sys_arch.arch.to_string().into(), 415 | DirEntry::Branch(trait_obj!( 416 | (self.by_sys_arch.clone(), self.process.os.ctx.clone()) as Branch 417 | )), 418 | )); 419 | 420 | // display process architecture subfolder only if it differs from system architecture 421 | if let Some(by_proc_arch) = &self.by_proc_arch { 422 | let _ = out.call(BranchListEntry::new( 423 | by_proc_arch.arch.to_string().into(), 424 | DirEntry::Branch(trait_obj!( 425 | (by_proc_arch.clone(), self.process.os.ctx.clone()) as Branch 426 | )), 427 | )); 428 | } 429 | 430 | Ok(()) 431 | } 432 | } 433 | 434 | #[derive(Clone)] 435 | struct ModuleArchList { 436 | process: LazyProcessBase, 437 | arch: ArchitectureIdent, 438 | name_cache: CArcSome>, 439 | } 440 | 441 | impl From<(LazyProcessBase, ArchitectureIdent)> for ModuleArchList { 442 | fn from((process, arch): (LazyProcessBase, ArchitectureIdent)) -> Self { 443 | Self { 444 | process, 445 | arch, 446 | name_cache: DashMap::default().into(), 447 | } 448 | } 449 | } 450 | 451 | impl ModuleArchList { 452 | pub fn find_module_by_name(&self, name: &str) -> Option { 453 | let info = self.name_cache.get(name); 454 | if let Some(info) = info { 455 | Some(info.clone()) 456 | } else { 457 | let proc = self.process.proc()?; 458 | let info = proc.get().module_by_name(name).ok()?; 459 | self.name_cache.insert(name.to_string(), info.clone()); 460 | Some(info) 461 | } 462 | } 463 | } 464 | 465 | impl Branch for ModuleArchList { 466 | fn get_entry(&self, path: &str, plugins: &CPluginStore) -> Result { 467 | let (name, path) = branch::split_path(path); 468 | 469 | let info = self.find_module_by_name(name).ok_or(ErrorKind::NotFound)?; 470 | 471 | let proc = self.process.proc().ok_or(ErrorKind::Unknown)?; 472 | let module = ModuleArc::from(ModuleBase::new(proc.clone(), info.clone())); 473 | 474 | if let Some(path) = path { 475 | module.get_entry(path, plugins) 476 | } else { 477 | Ok(DirEntry::Branch(trait_obj!( 478 | (module, self.process.os.ctx.clone()) as Branch 479 | ))) 480 | } 481 | } 482 | 483 | fn list( 484 | &self, 485 | _plugins: &CPluginStore, 486 | out: &mut OpaqueCallback, 487 | ) -> Result<()> { 488 | self.name_cache.clear(); 489 | 490 | let proc = self.process.proc().ok_or(ErrorKind::Unknown)?; 491 | proc.get() 492 | .module_list_callback( 493 | Some(&self.arch), 494 | (&mut |info: ModuleInfo| { 495 | let name = info.name.to_string(); 496 | if self.name_cache.insert(name.clone(), info.clone()).is_none() { 497 | let module = ModuleArc::from(ModuleBase::new(proc.clone(), info)); 498 | let entry = 499 | DirEntry::Branch(trait_obj!( 500 | (module, self.process.os.ctx.clone()) as Branch 501 | )); 502 | out.call(BranchListEntry::new(format!("{}", name).into(), entry)) 503 | } else { 504 | true 505 | } 506 | }) 507 | .into(), 508 | ) 509 | .map_err(|_| ErrorKind::Unknown)?; 510 | 511 | Ok(()) 512 | } 513 | } 514 | -------------------------------------------------------------------------------- /filer/src/node.rs: -------------------------------------------------------------------------------- 1 | use crate::backend::*; 2 | use crate::error::*; 3 | use crate::fs::*; 4 | use crate::plugin_store::*; 5 | use crate::types::*; 6 | use abi_stable::StableAbi; 7 | use cglue::prelude::v1::*; 8 | 9 | pub use cglue::slice::CSliceMut; 10 | use cglue::trait_group::c_void; 11 | 12 | use dashmap::DashMap; 13 | use sharded_slab::Slab; 14 | 15 | #[derive(StableAbi)] 16 | #[repr(C)] 17 | pub enum HandleMap { 18 | Forward(usize, usize), 19 | Object(FileOpsObj), 20 | } 21 | 22 | #[repr(C)] 23 | #[derive(StableAbi)] 24 | pub struct Node { 25 | pub backend: BackendBox<'static>, 26 | pub plugins: CPluginStore, 27 | } 28 | 29 | impl Node { 30 | pub fn new> + 'static>(backend: T) -> Self { 31 | let backend = trait_obj!(backend as Backend); 32 | Self { 33 | backend, 34 | plugins: Default::default(), 35 | } 36 | } 37 | } 38 | 39 | impl Frontend for CArcSome { 40 | /// Perform read operation on the given handle 41 | fn read<'a>(&self, handle: usize, data: VecOps) -> Result<()> { 42 | self.backend.read(self.into(), handle, data) 43 | } 44 | /// Perform write operation on the given handle. 45 | fn write(&self, handle: usize, data: VecOps) -> Result<()> { 46 | self.backend.write(self.into(), handle, data) 47 | } 48 | /// Perform remote procedure call on the given handle. 49 | fn rpc(&self, handle: usize, input: &[u8], output: &mut [u8]) -> Result<()> { 50 | self.backend.rpc(self.into(), handle, input, output) 51 | } 52 | /// Close an already open handle. 53 | fn close(&self, handle: usize) -> Result<()> { 54 | self.backend.close(self.into(), handle) 55 | } 56 | /// Open a leaf at the given path. The result is a handle. 57 | fn open(&self, path: &str) -> Result { 58 | self.backend.open(self.into(), path, &self.plugins) 59 | } 60 | /// Get metadata of given path. 61 | fn metadata(&self, path: &str) -> Result { 62 | self.backend.metadata(self.into(), path, &self.plugins) 63 | } 64 | /// List entries in the given path. It is a (name, is_branch) pair. 65 | fn list(&self, path: &str, out: &mut OpaqueCallback) -> Result<()> { 66 | self.backend.list(self.into(), path, &self.plugins, out) 67 | } 68 | } 69 | 70 | #[derive(Default)] 71 | pub struct NodeBackend { 72 | backends: Slab>, 73 | /// Maps backend name to backend ID in the vec 74 | backend_map: DashMap, 75 | /// Maps handle to (backend, handle) pair. 76 | handles: RcSlab, 77 | } 78 | 79 | impl NodeBackend { 80 | pub fn add_backend(&self, name: &str, backend: impl Backend + 'static) { 81 | let name = name.to_string(); 82 | 83 | self.backend_map.entry(name).or_insert_with(|| { 84 | self.backends 85 | .insert(trait_obj!(backend as Backend)) 86 | .expect("Slab is full!") 87 | }); 88 | } 89 | } 90 | 91 | impl Backend for std::sync::Arc { 92 | fn read(&self, stack: BackendStack, handle: usize, data: VecOps) -> Result<()> { 93 | (**self).read(stack, handle, data) 94 | } 95 | 96 | fn write(&self, stack: BackendStack, handle: usize, data: VecOps) -> Result<()> { 97 | (**self).write(stack, handle, data) 98 | } 99 | 100 | fn rpc( 101 | &self, 102 | stack: BackendStack, 103 | handle: usize, 104 | input: &[u8], 105 | output: &mut [u8], 106 | ) -> Result<()> { 107 | (**self).rpc(stack, handle, input, output) 108 | } 109 | 110 | fn close(&self, stack: BackendStack, handle: usize) -> Result<()> { 111 | (**self).close(stack, handle) 112 | } 113 | 114 | fn open(&self, stack: BackendStack, path: &str, plugins: &CPluginStore) -> Result { 115 | (**self).open(stack, path, plugins) 116 | } 117 | 118 | fn metadata( 119 | &self, 120 | stack: BackendStack, 121 | path: &str, 122 | plugins: &CPluginStore, 123 | ) -> Result { 124 | (**self).metadata(stack, path, plugins) 125 | } 126 | 127 | fn list( 128 | &self, 129 | stack: BackendStack, 130 | path: &str, 131 | plugins: &CPluginStore, 132 | out: &mut OpaqueCallback, 133 | ) -> Result<()> { 134 | (**self).list(stack, path, plugins, out) 135 | } 136 | } 137 | 138 | impl Backend for CArcSome { 139 | fn read(&self, stack: BackendStack, handle: usize, data: VecOps) -> Result<()> { 140 | (**self).read(stack, handle, data) 141 | } 142 | 143 | fn write(&self, stack: BackendStack, handle: usize, data: VecOps) -> Result<()> { 144 | (**self).write(stack, handle, data) 145 | } 146 | 147 | fn rpc( 148 | &self, 149 | stack: BackendStack, 150 | handle: usize, 151 | input: &[u8], 152 | output: &mut [u8], 153 | ) -> Result<()> { 154 | (**self).rpc(stack, handle, input, output) 155 | } 156 | 157 | fn close(&self, stack: BackendStack, handle: usize) -> Result<()> { 158 | (**self).close(stack, handle) 159 | } 160 | 161 | fn open(&self, stack: BackendStack, path: &str, plugins: &CPluginStore) -> Result { 162 | (**self).open(stack, path, plugins) 163 | } 164 | 165 | fn metadata( 166 | &self, 167 | stack: BackendStack, 168 | path: &str, 169 | plugins: &CPluginStore, 170 | ) -> Result { 171 | (**self).metadata(stack, path, plugins) 172 | } 173 | 174 | fn list( 175 | &self, 176 | stack: BackendStack, 177 | path: &str, 178 | plugins: &CPluginStore, 179 | out: &mut OpaqueCallback, 180 | ) -> Result<()> { 181 | (**self).list(stack, path, plugins, out) 182 | } 183 | } 184 | 185 | impl Backend for NodeBackend { 186 | fn read(&self, stack: BackendStack, handle: usize, data: VecOps) -> Result<()> { 187 | match self.handles.get(handle).as_deref() { 188 | Some(&HandleMap::Forward(backend, handle)) => { 189 | if let Some(backend) = self.backends.get(backend) { 190 | backend.read((&stack, self).into(), handle, data) 191 | } else { 192 | Err(Error(ErrorOrigin::Node, ErrorKind::InvalidPath)) 193 | } 194 | } 195 | Some(HandleMap::Object(obj)) => obj.read(data), 196 | _ => Err(Error(ErrorOrigin::Node, ErrorKind::NotFound)), 197 | } 198 | } 199 | 200 | fn write(&self, stack: BackendStack, handle: usize, data: VecOps) -> Result<()> { 201 | match self.handles.get(handle).as_deref() { 202 | Some(&HandleMap::Forward(backend, handle)) => { 203 | if let Some(backend) = self.backends.get(backend) { 204 | backend.write((&stack, self).into(), handle, data) 205 | } else { 206 | Err(Error(ErrorOrigin::Node, ErrorKind::InvalidPath)) 207 | } 208 | } 209 | Some(HandleMap::Object(obj)) => obj.write(data), 210 | _ => Err(Error(ErrorOrigin::Node, ErrorKind::NotFound)), 211 | } 212 | } 213 | 214 | fn rpc( 215 | &self, 216 | stack: BackendStack, 217 | handle: usize, 218 | input: &[u8], 219 | output: &mut [u8], 220 | ) -> Result<()> { 221 | match self.handles.get(handle).as_deref() { 222 | Some(&HandleMap::Forward(backend, handle)) => { 223 | if let Some(backend) = self.backends.get(backend) { 224 | backend.rpc((&stack, self).into(), handle, input, output) 225 | } else { 226 | Err(Error(ErrorOrigin::Node, ErrorKind::InvalidPath)) 227 | } 228 | } 229 | Some(HandleMap::Object(obj)) => obj.rpc(input, output), 230 | _ => Err(Error(ErrorOrigin::Node, ErrorKind::NotFound)), 231 | } 232 | } 233 | 234 | fn close(&self, stack: BackendStack, handle: usize) -> Result<()> { 235 | if let Some(r) = self.handles.dec_rc(handle) { 236 | match r.as_deref() { 237 | Some(&HandleMap::Forward(backend, handle)) => { 238 | if let Some(backend) = self.backends.get(backend) { 239 | backend.close((&stack, self).into(), handle) 240 | } else { 241 | Err(Error(ErrorOrigin::Node, ErrorKind::NotFound)) 242 | } 243 | } 244 | _ => Ok(()), 245 | } 246 | } else { 247 | Err(Error(ErrorOrigin::Node, ErrorKind::NotFound)) 248 | } 249 | } 250 | 251 | fn open(&self, stack: BackendStack, path: &str, plugins: &CPluginStore) -> Result { 252 | if let Some((backend, path)) = path.split_once('/') { 253 | if let Some((bid, backend)) = self 254 | .backend_map 255 | .get(backend) 256 | .and_then(|idx| self.backends.get(*idx).map(|b| (*idx, b))) 257 | { 258 | let ret = backend.open((&stack, self).into(), path, plugins)?; 259 | let ret = self.handles.insert(HandleMap::Forward(bid, ret)).unwrap(); 260 | return Ok(ret); 261 | } 262 | } 263 | 264 | Err(Error(ErrorOrigin::Node, ErrorKind::NotFound)) 265 | } 266 | 267 | fn metadata( 268 | &self, 269 | stack: BackendStack, 270 | path: &str, 271 | plugins: &CPluginStore, 272 | ) -> Result { 273 | if let Some((backend, path)) = path.split_once('/') { 274 | if let Some((_, backend)) = self 275 | .backend_map 276 | .get(backend) 277 | .and_then(|idx| self.backends.get(*idx).map(|b| (*idx, b))) 278 | { 279 | backend.metadata((&stack, self).into(), path, plugins) 280 | } else { 281 | Err(Error(ErrorOrigin::Node, ErrorKind::NotFound)) 282 | } 283 | } else if path.is_empty() || self.backend_map.get(path).is_some() { 284 | Ok(NodeMetadata::branch()) 285 | } else { 286 | Err(Error(ErrorOrigin::Node, ErrorKind::NotFound)) 287 | } 288 | } 289 | 290 | fn list( 291 | &self, 292 | stack: BackendStack, 293 | path: &str, 294 | plugins: &CPluginStore, 295 | out: &mut OpaqueCallback, 296 | ) -> Result<()> { 297 | let path = path.trim_start_matches('/'); 298 | let (backend, path) = path.split_once('/').unwrap_or((path, "")); 299 | 300 | if !backend.is_empty() { 301 | if let Some(backend) = self 302 | .backend_map 303 | .get(backend) 304 | .and_then(|idx| self.backends.get(*idx)) 305 | { 306 | return backend.list((&stack, self).into(), path, plugins, out); 307 | } 308 | } else { 309 | self.backend_map 310 | .iter() 311 | .map(|r| r.key().clone()) 312 | .map(|k| ListEntry::new(k.into(), true)) 313 | .feed_into_mut(out); 314 | return Ok(()); 315 | } 316 | 317 | Err(Error(ErrorOrigin::Node, ErrorKind::NotFound)) 318 | } 319 | } 320 | 321 | #[cglue_trait] 322 | #[int_result] 323 | pub trait Frontend { 324 | /// Perform read operation on the given handle 325 | fn read(&self, handle: usize, data: VecOps) -> Result<()>; 326 | /// Perform write operation on the given handle. 327 | fn write(&self, handle: usize, data: VecOps) -> Result<()>; 328 | /// Perform remote procedure call on the given handle. 329 | fn rpc(&self, handle: usize, input: &[u8], output: &mut [u8]) -> Result<()>; 330 | /// Close an already open handle. 331 | fn close(&self, handle: usize) -> Result<()>; 332 | /// Open a leaf at the given path. The result is a handle. 333 | fn open(&self, path: &str) -> Result; 334 | /// Get metadata of given path. 335 | fn metadata(&self, path: &str) -> Result; 336 | /// List entries in the given path. It is a (name, is_branch) pair. 337 | fn list(&self, path: &str, out: &mut OpaqueCallback) -> Result<()>; 338 | 339 | #[skip_func] 340 | fn open_handle<'a>(&'a self, path: &str) -> Result> 341 | where 342 | Self: Sized, 343 | { 344 | Ok((self, self.open(path)?).into()) 345 | } 346 | 347 | #[skip_func] 348 | fn open_cursor<'a>(&'a self, path: &str) -> Result> 349 | where 350 | Self: Sized, 351 | { 352 | Ok((self, self.open(path)?).into()) 353 | } 354 | } 355 | 356 | pub struct ObjHandle<'a, T: Frontend>(&'a T, usize, bool); 357 | 358 | impl<'a, T: Frontend> From<(&'a T, usize)> for ObjHandle<'a, T> { 359 | fn from((a, b): (&'a T, usize)) -> Self { 360 | Self(a, b, true) 361 | } 362 | } 363 | 364 | impl<'a, T: Frontend> Drop for ObjHandle<'a, T> { 365 | fn drop(&mut self) { 366 | if self.2 { 367 | self.0.close(self.1).ok(); 368 | } 369 | } 370 | } 371 | 372 | impl<'a, T: Frontend> ObjHandle<'a, T> { 373 | /// Perform read operation on the given handle 374 | pub fn read(&self, data: VecOps) -> Result<()> { 375 | self.0.read(self.1, data) 376 | } 377 | /// Perform write operation on the given handle. 378 | pub fn write(&self, data: VecOps) -> Result<()> { 379 | self.0.write(self.1, data) 380 | } 381 | /// Perform remote procedure call on the given handle. 382 | pub fn rpc(&self, input: &[u8], output: &mut [u8]) -> Result<()> { 383 | self.0.rpc(self.1, input, output) 384 | } 385 | } 386 | 387 | pub struct ObjCursor<'a, T: Frontend>(ObjHandle<'a, T>, (Size, usize)); 388 | 389 | impl<'a, T: Frontend> From<(&'a T, usize)> for ObjCursor<'a, T> { 390 | fn from((a, b): (&'a T, usize)) -> Self { 391 | Self(ObjHandle(a, b, true), (0, 0)) 392 | } 393 | } 394 | 395 | impl<'a, T: Frontend> From<(&'a T, usize, bool)> for ObjCursor<'a, T> { 396 | fn from((a, b, c): (&'a T, usize, bool)) -> Self { 397 | Self(ObjHandle(a, b, c), (0, 0)) 398 | } 399 | } 400 | 401 | impl<'a, T: Frontend> From<(&'a T, usize, Size)> for ObjCursor<'a, T> { 402 | fn from((a, b, c): (&'a T, usize, Size)) -> Self { 403 | Self(ObjHandle(a, b, true), (c, 0)) 404 | } 405 | } 406 | 407 | fn single_io>( 408 | func: impl Fn(VecOps>) -> Result<()>, 409 | off: u64, 410 | buf: T, 411 | ) -> Result { 412 | let mut last_max = None; 413 | let mut last_min = None; 414 | 415 | let out = &mut |d: CTup2| { 416 | let off = d.0 + d.1.len() as u64; 417 | last_max = last_max.map(|r| core::cmp::max(off, r)).or(Some(off)); 418 | true 419 | }; 420 | let out = &mut out.into(); 421 | let out = Some(out); 422 | 423 | let out_fail = &mut |d: FailData>| { 424 | let (d, _) = d.into(); 425 | let off = d.0; 426 | last_min = last_min.map(|r| core::cmp::min(off, r)).or(Some(off)); 427 | true 428 | }; 429 | let out_fail = &mut out_fail.into(); 430 | let out_fail = Some(out_fail); 431 | 432 | let buf_len = buf.len(); 433 | 434 | let inp = &mut core::iter::once(CTup2(off, buf)); 435 | 436 | let data = VecOps { 437 | inp: inp.into(), 438 | out, 439 | out_fail, 440 | }; 441 | 442 | func(data) 443 | // If there was no error, and we did not receive any successful IO, 444 | // maybe there is simply no feedback for successful IO? This is currently 445 | // the case with memflow, thus this is sort of a hack until the API is there 446 | // TODO: remove when memflow has matching callback interface. 447 | .map(|_| last_max.unwrap_or(off + buf_len as u64)) 448 | .map(|maxio| core::cmp::min(last_min.unwrap_or(maxio), maxio)) 449 | .map(|lr| (lr - off) as usize) 450 | } 451 | 452 | impl<'a, T: Frontend> std::io::Read for ObjCursor<'a, T> { 453 | fn read(&mut self, buf: &mut [u8]) -> std::io::Result { 454 | let read = single_io(|data| self.0.read(data), self.1 .0, buf.into()) 455 | .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.as_str()))?; 456 | 457 | self.1 .0 += read as Size; 458 | 459 | if read == 0 && self.1 .1 == 0 { 460 | return Err(std::io::Error::new( 461 | std::io::ErrorKind::UnexpectedEof, 462 | "EoF", 463 | )); 464 | } 465 | 466 | self.1 .1 = read; 467 | 468 | Ok(read) 469 | } 470 | } 471 | 472 | impl<'a, T: Frontend> std::io::Write for ObjCursor<'a, T> { 473 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 474 | let written = single_io(|data| self.0.write(data), self.1 .0, buf.into()) 475 | .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.as_str()))?; 476 | 477 | self.1 .0 += written as Size; 478 | 479 | if written == 0 && self.1 .1 == 0 { 480 | return Err(std::io::Error::new( 481 | std::io::ErrorKind::UnexpectedEof, 482 | "EoF", 483 | )); 484 | } 485 | 486 | self.1 .1 = written; 487 | 488 | Ok(written) 489 | } 490 | 491 | fn flush(&mut self) -> std::io::Result<()> { 492 | Ok(()) 493 | } 494 | } 495 | 496 | use std::io::SeekFrom; 497 | 498 | impl<'a, T: Frontend> std::io::Seek for ObjCursor<'a, T> { 499 | fn seek(&mut self, s: SeekFrom) -> std::io::Result { 500 | match s { 501 | SeekFrom::Start(v) => { 502 | self.1 .0 = v as u64; 503 | Ok(v) 504 | } 505 | SeekFrom::Current(v) => { 506 | if v >= 0 { 507 | self.1 .0 += v as Size; 508 | } else { 509 | self.1 .0 -= (-v) as Size; 510 | } 511 | Ok(self.1 .0 as u64) 512 | } 513 | SeekFrom::End(_) => Err(std::io::Error::new( 514 | std::io::ErrorKind::Other, 515 | "not supprted", 516 | )), 517 | } 518 | } 519 | } 520 | -------------------------------------------------------------------------------- /filer-tokio/src/lib.rs: -------------------------------------------------------------------------------- 1 | use cglue::arc::CArcSome; 2 | use cglue::callback::OpaqueCallback; 3 | use cglue::result::IntError; 4 | use cglue::slice::CSliceMut; 5 | use cglue::tuple::CTup2; 6 | use core::mem::size_of; 7 | use core::num::NonZeroI32; 8 | use filer::prelude::v1::*; 9 | 10 | use std::collections::BTreeMap; 11 | use std::io; 12 | use tokio::io::{AsyncRead, AsyncWrite, BufReader}; 13 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 14 | use tokio::net::{tcp, TcpListener, TcpStream}; 15 | use tokio::runtime::{Handle, Runtime}; 16 | use tokio::sync::Mutex; 17 | use tokio::task::JoinHandle; 18 | 19 | #[derive(Default)] 20 | struct FragmentBuffer<'a> { 21 | bufs: Vec>, 22 | fragments: BTreeMap>, 23 | _phantom: core::marker::PhantomData<&'a mut [u8]>, 24 | } 25 | 26 | unsafe impl Send for FragmentBuffer<'_> {} 27 | 28 | impl<'a> FragmentBuffer<'a> { 29 | pub unsafe fn put_back(&mut self, fragment: *mut [u8]) { 30 | let len = (*fragment).len(); 31 | let raw = (*fragment).as_mut_ptr(); 32 | self.fragments.entry(len).or_default().push(raw); 33 | } 34 | 35 | pub fn get(&mut self, size: usize) -> &'a mut [u8] { 36 | let fragment = if let Some((&sz, fragments)) = self.fragments.range_mut(size..).next() { 37 | let frag = fragments.pop().unwrap(); 38 | if fragments.is_empty() { 39 | self.fragments.remove(&sz); 40 | } 41 | if sz > size { 42 | unsafe { 43 | self.put_back(core::slice::from_raw_parts_mut(frag.add(size), sz - size)); 44 | } 45 | } 46 | frag 47 | } else { 48 | let mut b = vec![0; size].into_boxed_slice(); 49 | let raw = b.as_mut_ptr(); 50 | self.bufs.push(b); 51 | raw 52 | }; 53 | unsafe { core::slice::from_raw_parts_mut(fragment, size) } 54 | } 55 | } 56 | 57 | #[derive(Default)] 58 | struct SegmentTree<'a> { 59 | segments: BTreeMap>, 60 | _phantom: core::marker::PhantomData<&'a mut [u8]>, 61 | } 62 | 63 | unsafe impl Send for SegmentTree<'_> {} 64 | 65 | impl<'a> SegmentTree<'a> { 66 | pub fn get(&mut self, start: Size, len: usize) -> Option<&'a mut [u8]> { 67 | let mut iter = self.segments.range_mut(..=start); 68 | 69 | while let Some((&seg_start, segs)) = iter.next_back() { 70 | for i in 0..segs.len() { 71 | let seg_end = seg_start + segs[i].1 as Size; 72 | let end = start + len as Size; 73 | 74 | if seg_end > start { 75 | let (seg, _seg_sz) = segs.swap_remove(i); 76 | 77 | // Add anything on the left 78 | if seg_start < start { 79 | self.add_seg(seg_start, unsafe { 80 | core::slice::from_raw_parts_mut(seg, (start - seg_start) as usize) 81 | }); 82 | } else { 83 | // Otherwise cleanup, if this was the last segment we took 84 | if segs.is_empty() { 85 | self.segments.remove(&seg_start); 86 | } 87 | } 88 | 89 | let seg = unsafe { seg.add((start - seg_start) as usize) }; 90 | 91 | // Add anything over the end 92 | if seg_end > end { 93 | self.add_seg(end, unsafe { 94 | core::slice::from_raw_parts_mut(seg.add(len), (seg_end - end) as usize) 95 | }); 96 | } 97 | 98 | return Some(unsafe { 99 | core::slice::from_raw_parts_mut( 100 | seg, 101 | (core::cmp::min(seg_end, end) - start) as usize, 102 | ) 103 | }); 104 | } 105 | } 106 | } 107 | 108 | None 109 | } 110 | 111 | pub fn add_seg(&mut self, start: Size, seg: &'a mut [u8]) { 112 | self.segments 113 | .entry(start) 114 | .or_default() 115 | .push((seg.as_mut_ptr(), seg.len())) 116 | } 117 | } 118 | 119 | #[repr(u8)] 120 | pub enum FrontendFuncs { 121 | Read = 0, 122 | Write, 123 | Rpc, 124 | Close, 125 | Open, 126 | Metadata, 127 | List, 128 | Highest, // fn read(&self, handle: usize, data: VecOps) -> Result<()>; 129 | // /// Perform write operation on the given handle. 130 | // fn write(&self, handle: usize, data: VecOps) -> Result<()>; 131 | // /// Perform remote procedure call on the given handle. 132 | // fn rpc(&self, handle: usize, input: &[u8], output: &mut [u8]) -> Result<()>; 133 | // /// Close an already open handle. 134 | // fn close(&self, handle: usize) -> Result<()>; 135 | // /// Open a leaf at the given path. The result is a handle. 136 | // fn open(&self, path: &str) -> Result; 137 | // /// Get metadata of given path. 138 | // fn metadata(&self, path: &str) -> Result; 139 | // /// List entries in the given path. It is a (name, is_branch) pair. 140 | // fn list(&self, path: &str, out: &mut OpaqueCallback) -> Result<()>; 141 | } 142 | 143 | pub trait SplitStream { 144 | type OwnedReadHalf: AsyncRead + Send + Unpin; 145 | type OwnedWriteHalf: AsyncWrite + Send + Unpin; 146 | 147 | fn into_split(self) -> (Self::OwnedReadHalf, Self::OwnedWriteHalf); 148 | } 149 | 150 | impl SplitStream for TcpStream { 151 | type OwnedReadHalf = tcp::OwnedReadHalf; 152 | type OwnedWriteHalf = tcp::OwnedWriteHalf; 153 | 154 | fn into_split(self) -> (Self::OwnedReadHalf, Self::OwnedWriteHalf) { 155 | TcpStream::into_split(self) 156 | } 157 | } 158 | 159 | #[async_trait::async_trait] 160 | pub trait Listener { 161 | type Stream: SplitStream + Unpin + Send + 'static; 162 | type SocketAddr; 163 | 164 | async fn accept(&self) -> io::Result<(Self::Stream, Self::SocketAddr)>; 165 | } 166 | 167 | #[async_trait::async_trait] 168 | impl Listener for TcpListener { 169 | type Stream = TcpStream; 170 | type SocketAddr = std::net::SocketAddr; 171 | 172 | async fn accept(&self) -> io::Result<(Self::Stream, Self::SocketAddr)> { 173 | TcpListener::accept(self).await 174 | } 175 | } 176 | 177 | pub struct FilerClient { 178 | stream: Mutex, 179 | runtime: Runtime, 180 | } 181 | 182 | impl Frontend for FilerClient { 183 | /// Perform read operation on the given handle 184 | fn read(&self, _handle: usize, mut data: VecOps) -> Result<()> { 185 | self.runtime.block_on(async { 186 | let mut stream = self.stream.lock().await; 187 | let mut bufs = SegmentTree::default(); 188 | 189 | stream.write_all(&[FrontendFuncs::Read as u8]).await?; 190 | 191 | for CTup2(addr, buf) in data.inp { 192 | stream.write_all(&(addr as Size).to_le_bytes()).await?; 193 | stream.write_all(&(buf.len() as Size).to_le_bytes()).await?; 194 | stream.write_all(&buf).await?; 195 | bufs.add_seg(addr, buf.into()); 196 | } 197 | 198 | stream.write_all(&(0 as Size).to_le_bytes()).await?; 199 | stream.write_all(&(0 as Size).to_le_bytes()).await?; 200 | 201 | loop { 202 | let mut idx = [0u8]; 203 | stream.read_exact(&mut idx).await?; 204 | match idx[0] { 205 | 0 => { 206 | let mut err = [0u8; size_of::()]; 207 | stream.read_exact(&mut err).await?; 208 | return NonZeroI32::new(i32::from_le_bytes(err)) 209 | .map(|v| Err(Error::from_int_err(v))) 210 | .unwrap_or(Ok(())); 211 | } 212 | 1 => { 213 | let mut addr = [0u8; size_of::()]; 214 | let mut buf_len = [0u8; size_of::()]; 215 | stream.read_exact(&mut addr).await?; 216 | stream.read_exact(&mut buf_len).await?; 217 | let mut addr = Size::from_le_bytes(addr); 218 | let mut buf_len = Size::from_le_bytes(buf_len) as usize; 219 | while buf_len > 0 { 220 | let buf = bufs.get(addr, buf_len).unwrap(); 221 | let blen = buf.len(); 222 | stream.read_exact(buf).await?; 223 | opt_call(data.out.as_deref_mut(), CTup2(addr, buf.into())); 224 | addr += blen as Size; 225 | buf_len -= blen; 226 | } 227 | } 228 | 2 => { 229 | let mut addr = [0u8; size_of::()]; 230 | let mut buf_len = [0u8; size_of::()]; 231 | let mut err = [0u8; size_of::()]; 232 | stream.read_exact(&mut addr).await?; 233 | stream.read_exact(&mut buf_len).await?; 234 | stream.read_exact(&mut err).await?; 235 | let mut addr = Size::from_le_bytes(addr); 236 | let mut buf_len = Size::from_le_bytes(buf_len) as usize; 237 | let _err = i32::from_le_bytes(err) as usize; 238 | while buf_len > 0 { 239 | let buf = bufs.get(addr, buf_len).unwrap(); 240 | let blen = buf.len(); 241 | opt_call(data.out.as_deref_mut(), CTup2(addr, buf.into())); 242 | addr += blen as Size; 243 | buf_len -= blen; 244 | } 245 | } 246 | _ => {} 247 | } 248 | } 249 | }) 250 | } 251 | /// Perform write operation on the given handle. 252 | fn write(&self, _handle: usize, _data: VecOps) -> Result<()> { 253 | todo!() 254 | } 255 | /// Perform remote procedure call on the given handle. 256 | fn rpc(&self, _handle: usize, _input: &[u8], _output: &mut [u8]) -> Result<()> { 257 | todo!() 258 | } 259 | /// Close an already open handle. 260 | fn close(&self, _handle: usize) -> Result<()> { 261 | todo!() 262 | } 263 | /// Open a leaf at the given path. The result is a handle. 264 | fn open(&self, _path: &str) -> Result { 265 | todo!() 266 | } 267 | /// Get metadata of given path. 268 | fn metadata(&self, _path: &str) -> Result { 269 | todo!() 270 | } 271 | /// List entries in the given path. It is a (name, is_branch) pair. 272 | fn list(&self, _path: &str, _out: &mut OpaqueCallback) -> Result<()> { 273 | todo!() 274 | } 275 | } 276 | 277 | pub struct FilerServer { 278 | listener: T, 279 | clients: Vec<(JoinHandle>, T::SocketAddr)>, 280 | node: CArcSome, 281 | } 282 | 283 | impl FilerServer { 284 | pub async fn run(mut self) -> io::Result<()> { 285 | loop { 286 | let (socket, addr) = self.listener.accept().await?; 287 | 288 | let node = self.node.clone(); 289 | 290 | let handle = tokio::spawn(async move { 291 | let (reader, mut writer) = socket.into_split(); 292 | let mut reader = BufReader::new(reader); 293 | 294 | // In a loop, read data from the socket and write the data back. 295 | loop { 296 | let cmd = &mut [0u8]; 297 | reader.read_exact(cmd).await?; 298 | let cmd = cmd[0]; 299 | 300 | if cmd < FrontendFuncs::Highest as u8 { 301 | use FrontendFuncs::*; 302 | match unsafe { core::mem::transmute::<_, FrontendFuncs>(cmd) } { 303 | Read => { 304 | let bufs = FragmentBuffer::default(); 305 | let bufs = Mutex::new(bufs); 306 | let writer = Mutex::new(&mut writer); 307 | 308 | let mut fh = [0u8; size_of::()]; 309 | reader.read_exact(&mut fh).await?; 310 | let fh = Size::from_le_bytes(fh) as usize; 311 | 312 | let handle = Handle::current(); 313 | 314 | let iter = &mut core::iter::from_fn(|| { 315 | let mut addr = [0u8; size_of::()]; 316 | 317 | let buf = handle 318 | .block_on(async { 319 | let mut size = [0u8; size_of::()]; 320 | reader.read_exact(&mut addr).await?; 321 | let sz = reader 322 | .read_exact(&mut size) 323 | .await 324 | .map(|_| Size::from_le_bytes(size)) 325 | .and_then(|sz| { 326 | if sz > 0 { 327 | Ok(sz) 328 | } else { 329 | Err(io::Error::from(io::ErrorKind::Other)) 330 | } 331 | })?; 332 | 333 | io::Result::Ok(bufs.lock().await.get(sz as usize)) 334 | }) 335 | .ok()?; 336 | 337 | let addr = Size::from_le_bytes(addr); 338 | 339 | Some(CTup2(addr, CSliceMut::from(buf))) 340 | }); 341 | 342 | let out = &mut |CTup2(addr, buf): RWData| { 343 | let _ = handle.block_on(async { 344 | let mut writer = writer.lock().await; 345 | writer.write_all(&[1]).await?; 346 | writer.write_all(&Size::to_le_bytes(addr)).await?; 347 | writer 348 | .write_all(&Size::to_le_bytes(buf.len() as _)) 349 | .await?; 350 | writer.write_all(&buf).await?; 351 | unsafe { 352 | bufs.lock().await.put_back(<&mut [u8]>::from(buf)) 353 | }; 354 | io::Result::Ok(()) 355 | }); 356 | true 357 | }; 358 | 359 | let out_fail = &mut |fdata: RWFailData| { 360 | let (CTup2(addr, buf), e) = fdata.into(); 361 | let _ = handle.block_on(async { 362 | let mut writer = writer.lock().await; 363 | writer.write_all(&[2]).await?; 364 | writer.write_all(&Size::to_le_bytes(addr)).await?; 365 | writer 366 | .write_all(&Size::to_le_bytes(buf.len() as _)) 367 | .await?; 368 | writer 369 | .write_all(&e.into_int_err().get().to_le_bytes()) 370 | .await?; 371 | unsafe { 372 | bufs.lock().await.put_back(<&mut [u8]>::from(buf)) 373 | }; 374 | io::Result::Ok(()) 375 | }); 376 | true 377 | }; 378 | 379 | let mut out = out.into(); 380 | let mut out_fail = out_fail.into(); 381 | 382 | let ops = VecOps { 383 | inp: iter.into(), 384 | out: Some(&mut out), 385 | out_fail: Some(&mut out_fail), 386 | }; 387 | 388 | let err = tokio::task::block_in_place(|| node.read(fh, ops)); 389 | 390 | let writer = writer.into_inner(); 391 | writer.write_all(&[0]).await?; 392 | let err = match err { 393 | Ok(_) => 0, 394 | Err(e) => e.into_int_err().get(), 395 | }; 396 | writer.write_all(&err.to_le_bytes()).await?; 397 | } 398 | Write => {} 399 | _ => {} 400 | } 401 | } 402 | 403 | /*let n = match reader.read(&mut buf).await { 404 | // socket closed 405 | Ok(n) if n == 0 => return, 406 | Ok(n) => n, 407 | Err(e) => { 408 | eprintln!("failed to read from socket; err = {:?}", e); 409 | return; 410 | } 411 | }; 412 | 413 | // Write the data back 414 | if let Err(e) = writer.write_all(&buf[0..n]).await { 415 | eprintln!("failed to write to socket; err = {:?}", e); 416 | return; 417 | }*/ 418 | } 419 | }); 420 | 421 | self.clients.push((handle, addr)); 422 | } 423 | } 424 | } 425 | -------------------------------------------------------------------------------- /cloudflow/src/os.rs: -------------------------------------------------------------------------------- 1 | use crate::process::{LazyProcessArc, LazyProcessBase}; 2 | use crate::util::*; 3 | use crate::MemflowBackend; 4 | use abi_stable::StableAbi; 5 | pub use cglue::slice::CSliceMut; 6 | use cglue::trait_group::c_void; 7 | use dashmap::DashMap; 8 | use filer::branch; 9 | use filer::prelude::v1::{Error, ErrorKind, ErrorOrigin, Result, *}; 10 | use memflow::prelude::v1::*; 11 | use num::Num; 12 | 13 | use std::sync::Arc; 14 | 15 | pub extern "C" fn on_node(node: &Node, ctx: CArc) { 16 | node.plugins 17 | .register_mapping("os", Mapping::Leaf(self_as_leaf::, ctx.clone())); 18 | 19 | node.plugins 20 | .register_mapping("processes", Mapping::Branch(ProcessList::map_into, ctx)); 21 | } 22 | 23 | thread_types!(OsInstanceArcBox<'static>, ThreadedOs, ThreadedOsArc); 24 | 25 | #[repr(C)] 26 | #[derive(Clone, StableAbi)] 27 | pub struct OsBase { 28 | os: ThreadedOsArc, 29 | pub(crate) ctx: CArc, 30 | } 31 | 32 | impl core::ops::Deref for OsBase { 33 | type Target = ThreadedOsArc; 34 | 35 | fn deref(&self) -> &Self::Target { 36 | &self.os 37 | } 38 | } 39 | 40 | #[repr(C)] 41 | #[derive(Clone, StableAbi)] 42 | pub struct OsRoot { 43 | os: OsBase, 44 | plist: CArcSome, 45 | } 46 | 47 | impl core::ops::Deref for OsRoot { 48 | type Target = OsBase; 49 | 50 | fn deref(&self) -> &Self::Target { 51 | &self.os 52 | } 53 | } 54 | 55 | impl From for OsRoot { 56 | fn from(os: OsBase) -> Self { 57 | Self { 58 | plist: CArcSome::from(ProcessList::from(os.clone())).into_opaque(), 59 | os, 60 | } 61 | } 62 | } 63 | 64 | impl OsRoot { 65 | unsafe fn plist(&self) -> &CArcSome { 66 | (&self.plist as *const CArcSome as *const CArcSome) 67 | .as_ref() 68 | .unwrap() 69 | } 70 | } 71 | 72 | impl Branch for OsRoot { 73 | fn get_entry(&self, path: &str, plugins: &CPluginStore) -> Result { 74 | branch::get_entry(self, path, plugins) 75 | } 76 | 77 | fn list( 78 | &self, 79 | plugins: &CPluginStore, 80 | out: &mut OpaqueCallback, 81 | ) -> Result<()> { 82 | branch::list(self, plugins)? 83 | .into_iter() 84 | .map(|(name, entry)| BranchListEntry::new(name.into(), entry)) 85 | .feed_into_mut(out); 86 | Ok(()) 87 | } 88 | } 89 | 90 | impl Leaf for OsRoot { 91 | fn open(&self) -> Result> { 92 | Ok(FileOpsObj::new( 93 | (**self.os).clone(), 94 | Some(ThreadedOs::read), 95 | Some(ThreadedOs::write), 96 | Some(ThreadedOs::rpc), 97 | )) 98 | } 99 | 100 | fn metadata(&self) -> Result { 101 | Ok(NodeMetadata { 102 | is_branch: false, 103 | has_read: true, 104 | has_write: true, 105 | has_rpc: true, 106 | size: (1 as Size) 107 | << self 108 | .os 109 | .get_orig() 110 | .info() 111 | .arch 112 | .into_obj() 113 | .address_space_bits(), 114 | ..Default::default() 115 | }) 116 | } 117 | } 118 | 119 | impl StrBuild>> for OsRoot { 120 | fn build(input: &str, ctx: &CArc>) -> Result { 121 | let (chain_with, name, args) = split_args(input); 122 | 123 | let ctx = ctx.as_ref().ok_or(ErrorKind::NotFound)?; 124 | 125 | let chain_with = if let Some(cw) = chain_with { 126 | Some( 127 | ctx.connector 128 | .get(cw) 129 | .ok_or(ErrorKind::NotFound)? 130 | .get_orig() 131 | .clone(), 132 | ) 133 | } else { 134 | None 135 | }; 136 | 137 | ctx.inventory 138 | .create_os( 139 | name, 140 | chain_with, 141 | Some(&str::parse(args).map_err(|_| ErrorKind::InvalidArgument)?), 142 | ) 143 | .map(|c| ThreadedOs::from(c).self_arc_up()) 144 | // TODO: set ctx 145 | .map(|c| OsBase { 146 | os: c.into(), 147 | ctx: Default::default(), 148 | }) 149 | .map(Self::from) 150 | .map_err(|_| ErrorKind::Uninitialized.into()) 151 | } 152 | } 153 | 154 | impl ThreadedOs { 155 | extern "C" fn read(&self, data: VecOps) -> i32 { 156 | int_res_wrap! { 157 | memdata_map(data, |data| { 158 | as_mut!(self.get() impl MemoryView) 159 | .ok_or(Error(ErrorOrigin::Read, ErrorKind::NotImplemented))? 160 | .read_raw_iter(data) 161 | .map_err(|_| Error(ErrorOrigin::Read, ErrorKind::Unknown)) 162 | }) 163 | } 164 | } 165 | 166 | extern "C" fn write(&self, data: VecOps) -> i32 { 167 | int_res_wrap! { 168 | memdata_map(data, |data| { 169 | as_mut!(self.get() impl MemoryView) 170 | .ok_or(Error(ErrorOrigin::Write, ErrorKind::NotImplemented))? 171 | .write_raw_iter(data) 172 | .map_err(|_| Error(ErrorOrigin::Write, ErrorKind::Unknown)) 173 | }) 174 | } 175 | } 176 | 177 | extern "C" fn rpc(&self, _input: CSliceRef, _output: CSliceMut) -> i32 { 178 | Result::Ok(()).into_int_result() 179 | } 180 | } 181 | 182 | #[derive(Clone)] 183 | struct ProcessList { 184 | os: OsBase, 185 | by_pid: PidProcessList, 186 | by_name: NameProcessList, 187 | by_pid_name: PidNameProcessList, 188 | } 189 | 190 | impl From for ProcessList { 191 | fn from(os: OsBase) -> Self { 192 | Self { 193 | by_pid: os.clone().into(), 194 | by_name: os.clone().into(), 195 | by_pid_name: os.clone().into(), 196 | os, 197 | } 198 | } 199 | } 200 | 201 | impl ProcessList { 202 | extern "C" fn map_into(os: &OsRoot, ctx: &CArc) -> COption> { 203 | COption::Some(trait_obj!( 204 | (unsafe { &**os.plist() }.clone(), ctx.clone()) as Branch 205 | )) 206 | } 207 | } 208 | 209 | impl Branch for ProcessList { 210 | fn get_entry(&self, path: &str, plugins: &CPluginStore) -> Result { 211 | let (entry, path) = branch::split_path(path); 212 | 213 | match entry { 214 | "by-pid" => { 215 | branch::forward_entry(self.by_pid.clone(), self.os.ctx.clone(), path, plugins) 216 | } 217 | "by-name" => { 218 | branch::forward_entry(self.by_name.clone(), self.os.ctx.clone(), path, plugins) 219 | } 220 | "by-pid-name" => { 221 | branch::forward_entry(self.by_pid_name.clone(), self.os.ctx.clone(), path, plugins) 222 | } 223 | addr => { 224 | let addr: umem = 225 | Num::from_str_radix(addr, 16).map_err(|_| ErrorKind::InvalidPath)?; 226 | 227 | let info = self 228 | .os 229 | .get() 230 | .process_info_by_address(addr.into()) 231 | .map_err(|_| ErrorKind::NotFound)?; 232 | 233 | let proc = LazyProcessArc::from(LazyProcessBase::new(self.os.clone(), info)); 234 | 235 | branch::forward_entry(proc, self.os.ctx.clone(), path, plugins) 236 | } 237 | } 238 | } 239 | 240 | fn list( 241 | &self, 242 | _plugins: &CPluginStore, 243 | out: &mut OpaqueCallback, 244 | ) -> Result<()> { 245 | let _ = out.call(BranchListEntry::new( 246 | "by-pid".into(), 247 | DirEntry::Branch(trait_obj!( 248 | (self.by_pid.clone(), self.os.ctx.clone()) as Branch 249 | )), 250 | )); 251 | let _ = out.call(BranchListEntry::new( 252 | "by-name".into(), 253 | DirEntry::Branch(trait_obj!( 254 | (self.by_name.clone(), self.os.ctx.clone()) as Branch 255 | )), 256 | )); 257 | let _ = out.call(BranchListEntry::new( 258 | "by-pid-name".into(), 259 | DirEntry::Branch(trait_obj!( 260 | (self.by_pid_name.clone(), self.os.ctx.clone()) as Branch 261 | )), 262 | )); 263 | 264 | self.os 265 | .get() 266 | .process_info_list_callback( 267 | (&mut |info: ProcessInfo| { 268 | let addr = info.address.to_umem(); 269 | let proc = LazyProcessArc::from(LazyProcessBase::new(self.os.clone(), info)); 270 | let entry = DirEntry::Branch(trait_obj!((proc, self.os.ctx.clone()) as Branch)); 271 | out.call(BranchListEntry::new(format!("{:x}", addr).into(), entry)) 272 | }) 273 | .into(), 274 | ) 275 | .map_err(|_| ErrorKind::Unknown)?; 276 | 277 | Ok(()) 278 | } 279 | } 280 | 281 | #[derive(Clone)] 282 | struct PidProcessList { 283 | os: OsBase, 284 | pid_cache: CArcSome>, 285 | } 286 | 287 | impl From for PidProcessList { 288 | fn from(os: OsBase) -> Self { 289 | Self { 290 | os, 291 | pid_cache: DashMap::default().into(), 292 | } 293 | } 294 | } 295 | 296 | impl PidProcessList { 297 | fn get_info(&self, pid: Pid) -> Result { 298 | let info = if let Some(addr) = self.pid_cache.get(&pid) { 299 | let info = self 300 | .os 301 | .get() 302 | .process_info_by_address(*addr) 303 | .map_err(|_| ErrorKind::NotFound)?; 304 | 305 | if info.pid == pid { 306 | Some(info) 307 | } else { 308 | None 309 | } 310 | } else { 311 | None 312 | }; 313 | 314 | info.map(|i| Ok(i)).unwrap_or_else(|| { 315 | self.os 316 | .get() 317 | .process_info_by_pid(pid) 318 | .map_err(|_| ErrorKind::NotFound.into()) 319 | .map(|info| { 320 | self.pid_cache.insert(pid, info.address); 321 | info 322 | }) 323 | }) 324 | } 325 | } 326 | 327 | impl Branch for PidProcessList { 328 | fn get_entry(&self, path: &str, plugins: &CPluginStore) -> Result { 329 | let (pid, path) = branch::split_path(path); 330 | 331 | let pid: Pid = str::parse(pid).map_err(|_| ErrorKind::InvalidPath)?; 332 | 333 | let info = self.get_info(pid)?; 334 | 335 | let proc = LazyProcessArc::from(LazyProcessBase::new(self.os.clone(), info)); 336 | 337 | if let Some(path) = path { 338 | proc.get_entry(path, plugins) 339 | } else { 340 | Ok(DirEntry::Branch(trait_obj!( 341 | (proc, self.os.ctx.clone()) as Branch 342 | ))) 343 | } 344 | } 345 | 346 | fn list(&self, _: &CPluginStore, out: &mut OpaqueCallback) -> Result<()> { 347 | self.pid_cache.clear(); 348 | self.os 349 | .get() 350 | .process_info_list_callback( 351 | (&mut |info: ProcessInfo| { 352 | let pid = info.pid; 353 | if self.pid_cache.insert(pid, info.address).is_none() { 354 | let proc = 355 | LazyProcessArc::from(LazyProcessBase::new(self.os.clone(), info)); 356 | let entry = 357 | DirEntry::Branch(trait_obj!((proc, self.os.ctx.clone()) as Branch)); 358 | out.call(BranchListEntry::new(format!("{}", pid).into(), entry)) 359 | } else { 360 | true 361 | } 362 | }) 363 | .into(), 364 | ) 365 | .map_err(|_| ErrorKind::Unknown)?; 366 | 367 | Ok(()) 368 | } 369 | } 370 | 371 | #[derive(Clone)] 372 | struct NameProcessList { 373 | os: OsBase, 374 | name_cache: CArcSome>, 375 | } 376 | 377 | impl From for NameProcessList { 378 | fn from(os: OsBase) -> Self { 379 | Self { 380 | os, 381 | name_cache: DashMap::default().into(), 382 | } 383 | } 384 | } 385 | 386 | impl NameProcessList { 387 | fn get_info(&self, name: &str) -> Result { 388 | let info = if let Some(addr) = self.name_cache.get(name) { 389 | let info = self 390 | .os 391 | .get() 392 | .process_info_by_address(*addr) 393 | .map_err(|_| ErrorKind::NotFound)?; 394 | 395 | if &*info.name == name { 396 | Some(info) 397 | } else { 398 | None 399 | } 400 | } else { 401 | None 402 | }; 403 | 404 | info.map(|i| Ok(i)).unwrap_or_else(|| { 405 | self.os 406 | .get() 407 | .process_info_by_name(name) 408 | .map_err(|_| ErrorKind::NotFound.into()) 409 | .map(|info| { 410 | self.name_cache.insert(name.into(), info.address); 411 | info 412 | }) 413 | }) 414 | } 415 | } 416 | 417 | impl Branch for NameProcessList { 418 | fn get_entry(&self, path: &str, plugins: &CPluginStore) -> Result { 419 | let (name, path) = branch::split_path(path); 420 | 421 | let info = self.get_info(name)?; 422 | 423 | let proc = LazyProcessArc::from(LazyProcessBase::new(self.os.clone(), info)); 424 | 425 | if let Some(path) = path { 426 | proc.get_entry(path, plugins) 427 | } else { 428 | Ok(DirEntry::Branch(trait_obj!( 429 | (proc, self.os.ctx.clone()) as Branch 430 | ))) 431 | } 432 | } 433 | 434 | fn list(&self, _: &CPluginStore, out: &mut OpaqueCallback) -> Result<()> { 435 | self.name_cache.clear(); 436 | self.os 437 | .get() 438 | .process_info_list_callback( 439 | (&mut |info: ProcessInfo| { 440 | let name = info.name.to_string(); 441 | if self.name_cache.insert(name.clone(), info.address).is_none() { 442 | let proc = 443 | LazyProcessArc::from(LazyProcessBase::new(self.os.clone(), info)); 444 | let entry = 445 | DirEntry::Branch(trait_obj!((proc, self.os.ctx.clone()) as Branch)); 446 | out.call(BranchListEntry::new(format!("{}", name).into(), entry)) 447 | } else { 448 | true 449 | } 450 | }) 451 | .into(), 452 | ) 453 | .map_err(|_| ErrorKind::Unknown)?; 454 | 455 | Ok(()) 456 | } 457 | } 458 | 459 | #[derive(Clone)] 460 | struct PidNameProcessList { 461 | os: OsBase, 462 | name_cache: CArcSome>, 463 | } 464 | 465 | impl From for PidNameProcessList { 466 | fn from(os: OsBase) -> Self { 467 | Self { 468 | os, 469 | name_cache: DashMap::default().into(), 470 | } 471 | } 472 | } 473 | 474 | impl PidNameProcessList { 475 | fn get_info(&self, name: &str) -> Result { 476 | let (name, pid) = name.rsplit_once(" ").ok_or(ErrorKind::InvalidArgument)?; 477 | let pid = pid 478 | .strip_prefix("(") 479 | .and_then(|p| p.strip_suffix(")")) 480 | .ok_or(ErrorKind::InvalidArgument)?; 481 | let pid = str::parse(pid).map_err(|_| ErrorKind::InvalidArgument)?; 482 | 483 | let info = if let Some(addr) = self.name_cache.get(name) { 484 | let info = self 485 | .os 486 | .get() 487 | .process_info_by_address(*addr) 488 | .map_err(|_| ErrorKind::NotFound)?; 489 | 490 | if &*info.name == name && info.pid == pid { 491 | Some(info) 492 | } else { 493 | None 494 | } 495 | } else { 496 | None 497 | }; 498 | 499 | info.map(|i| Ok(i)).unwrap_or_else(|| { 500 | self.os 501 | .get() 502 | .process_info_by_pid(pid) 503 | .map_err(|_| ErrorKind::NotFound.into()) 504 | .and_then(|i| { 505 | let name2: &str = &*i.name; 506 | if (name2.len() <= name.len() && name.starts_with(name2)) 507 | || name2.starts_with(name) 508 | { 509 | Ok(i) 510 | } else { 511 | Err(ErrorKind::NotFound.into()) 512 | } 513 | }) 514 | .map(|info| { 515 | self.name_cache.insert(name.into(), info.address); 516 | info 517 | }) 518 | }) 519 | } 520 | } 521 | 522 | impl Branch for PidNameProcessList { 523 | fn get_entry(&self, path: &str, plugins: &CPluginStore) -> Result { 524 | let (name, path) = branch::split_path(path); 525 | 526 | let info = self.get_info(name)?; 527 | 528 | let proc = LazyProcessArc::from(LazyProcessBase::new(self.os.clone(), info)); 529 | 530 | if let Some(path) = path { 531 | proc.get_entry(path, plugins) 532 | } else { 533 | Ok(DirEntry::Branch(trait_obj!( 534 | (proc, self.os.ctx.clone()) as Branch 535 | ))) 536 | } 537 | } 538 | 539 | fn list(&self, _: &CPluginStore, out: &mut OpaqueCallback) -> Result<()> { 540 | self.name_cache.clear(); 541 | self.os 542 | .get() 543 | .process_info_list_callback( 544 | (&mut |info: ProcessInfo| { 545 | let name = format!("{} ({})", info.name, info.pid); 546 | if self.name_cache.insert(name.clone(), info.address).is_none() { 547 | let proc = 548 | LazyProcessArc::from(LazyProcessBase::new(self.os.clone(), info)); 549 | let entry = 550 | DirEntry::Branch(trait_obj!((proc, self.os.ctx.clone()) as Branch)); 551 | out.call(BranchListEntry::new(name.into(), entry)) 552 | } else { 553 | true 554 | } 555 | }) 556 | .into(), 557 | ) 558 | .map_err(|_| ErrorKind::Unknown)?; 559 | 560 | Ok(()) 561 | } 562 | } 563 | -------------------------------------------------------------------------------- /filer-fuse/src/lib.rs: -------------------------------------------------------------------------------- 1 | use filer::prelude::v1::*; 2 | 3 | use fuse_mt::*; 4 | use log::*; 5 | use std::ffi::{OsStr, OsString}; 6 | use std::io::{Read, Seek, SeekFrom, Write}; 7 | use std::path::Path; 8 | 9 | use time::*; 10 | 11 | pub struct FilerFs { 12 | node: CArcSome, 13 | mount_point: String, 14 | uid: u32, 15 | gid: u32, 16 | readonly: bool, 17 | } 18 | 19 | const TTL: Timespec = Timespec { sec: 1, nsec: 0 }; 20 | const FOPEN_DIRECT_IO: u32 = 1 << 0; 21 | 22 | fn path_to_str(path: &Path) -> String { 23 | let ret = path 24 | .iter() 25 | .skip(1) 26 | .map(|s| s.to_string_lossy()) 27 | .collect::>() 28 | .join("/"); 29 | 30 | ret.strip_prefix('/').unwrap_or(&ret).to_string() 31 | } 32 | 33 | impl FilesystemMT for FilerFs { 34 | /// Called on mount, before any other function. 35 | fn init(&self, _req: RequestInfo) -> ResultEmpty { 36 | Ok(()) 37 | } 38 | 39 | /// Called on filesystem unmount. 40 | fn destroy(&self, _req: RequestInfo) { 41 | // Nothing. 42 | } 43 | 44 | /// Get the attributes of a filesystem entry. 45 | /// 46 | /// * `fh`: a file handle if this is called on an open file. 47 | fn getattr(&self, _req: RequestInfo, path: &Path, _fh: Option) -> ResultEntry { 48 | let ospath = path_to_str(path); 49 | match self.node.metadata(&ospath) { 50 | // TODO: handle readonly flags, directories 51 | // let masked_flags = 52 | // flags & !libc::O_WRONLY as u32 & !libc::O_RDWR as u32; 53 | Ok(NodeMetadata { 54 | is_branch, 55 | has_read, 56 | has_write, 57 | size, 58 | .. 59 | }) => { 60 | let perm = if is_branch { 61 | if self.readonly { 62 | 0o555 63 | } else { 64 | 0o755 65 | } 66 | } else { 67 | let has_write = has_write && !self.readonly; 68 | 0o200 * has_write as u16 + 0o444 * has_read as u16 69 | }; 70 | 71 | let now = time::get_time(); 72 | 73 | Ok(( 74 | TTL, 75 | FileAttr { 76 | size, 77 | blocks: 1, // TODO: 78 | atime: now, 79 | mtime: now, 80 | ctime: now, 81 | crtime: Timespec::new(0, 0), 82 | kind: if is_branch { 83 | FileType::Directory 84 | } else { 85 | FileType::RegularFile 86 | }, 87 | perm, 88 | nlink: 0, // TODO: ? 89 | uid: self.uid, 90 | gid: self.gid, 91 | rdev: 0, // TODO: 92 | flags: 0, 93 | }, 94 | )) 95 | } 96 | Err(_) => Err(libc::ENOENT), 97 | } 98 | } 99 | 100 | // The following operations in the FUSE C API are all one kernel call: setattr 101 | // We split them out to match the C API's behavior. 102 | 103 | /// Change the mode of a filesystem entry. 104 | /// 105 | /// * `fh`: a file handle if this is called on an open file. 106 | /// * `mode`: the mode to change the file to. 107 | fn chmod(&self, _req: RequestInfo, path: &Path, _fh: Option, _mode: u32) -> ResultEmpty { 108 | debug!("chmod {:?}", path); 109 | Err(libc::ENOSYS) 110 | } 111 | 112 | /// Change the owner UID and/or group GID of a filesystem entry. 113 | /// 114 | /// * `fh`: a file handle if this is called on an open file. 115 | /// * `uid`: user ID to change the file's owner to. If `None`, leave the UID unchanged. 116 | /// * `gid`: group ID to change the file's group to. If `None`, leave the GID unchanged. 117 | fn chown( 118 | &self, 119 | _req: RequestInfo, 120 | path: &Path, 121 | _fh: Option, 122 | _uid: Option, 123 | _gid: Option, 124 | ) -> ResultEmpty { 125 | debug!("chown {:?}", path); 126 | Err(libc::ENOSYS) 127 | } 128 | 129 | /// Set the length of a file. 130 | /// 131 | /// * `fh`: a file handle if this is called on an open file. 132 | /// * `size`: size in bytes to set as the file's length. 133 | fn truncate( 134 | &self, 135 | _req: RequestInfo, 136 | path: &Path, 137 | _fh: Option, 138 | _size: u64, 139 | ) -> ResultEmpty { 140 | debug!("truncate {:?}", path); 141 | Err(libc::ENOSYS) 142 | } 143 | 144 | /// Set timestamps of a filesystem entry. 145 | /// 146 | /// * `fh`: a file handle if this is called on an open file. 147 | /// * `atime`: the time of last access. 148 | /// * `mtime`: the time of last modification. 149 | fn utimens( 150 | &self, 151 | _req: RequestInfo, 152 | path: &Path, 153 | _fh: Option, 154 | _atime: Option, 155 | _mtime: Option, 156 | ) -> ResultEmpty { 157 | debug!("utimens {:?}", path); 158 | Err(libc::ENOSYS) 159 | } 160 | 161 | /// Set timestamps of a filesystem entry (with extra options only used on MacOS). 162 | #[allow(clippy::too_many_arguments)] 163 | fn utimens_macos( 164 | &self, 165 | _req: RequestInfo, 166 | path: &Path, 167 | _fh: Option, 168 | _crtime: Option, 169 | _chgtime: Option, 170 | _bkuptime: Option, 171 | _flags: Option, 172 | ) -> ResultEmpty { 173 | debug!("utimens_macos {:?}", path); 174 | Err(libc::ENOSYS) 175 | } 176 | 177 | // END OF SETATTR FUNCTIONS 178 | 179 | /// Read a symbolic link. 180 | fn readlink(&self, _req: RequestInfo, path: &Path) -> ResultData { 181 | debug!("readlink {:?}", path); 182 | Err(libc::ENOSYS) 183 | } 184 | 185 | /// Create a special file. 186 | /// 187 | /// * `parent`: path to the directory to make the entry under. 188 | /// * `name`: name of the entry. 189 | /// * `mode`: mode for the new entry. 190 | /// * `rdev`: if mode has the bits `S_IFCHR` or `S_IFBLK` set, this is the major and minor numbers for the device file. Otherwise it should be ignored. 191 | fn mknod( 192 | &self, 193 | _req: RequestInfo, 194 | parent: &Path, 195 | name: &OsStr, 196 | _mode: u32, 197 | _rdev: u32, 198 | ) -> ResultEntry { 199 | debug!("mknod {:?} {:?}", parent, name); 200 | Err(libc::ENOSYS) 201 | } 202 | 203 | /// Create a directory. 204 | /// 205 | /// * `parent`: path to the directory to make the directory under. 206 | /// * `name`: name of the directory. 207 | /// * `mode`: permissions for the new directory. 208 | fn mkdir(&self, _req: RequestInfo, parent: &Path, name: &OsStr, _mode: u32) -> ResultEntry { 209 | debug!("mkdir {:?} {:?}", parent, name); 210 | Err(libc::ENOSYS) 211 | } 212 | 213 | /// Remove a file. 214 | /// 215 | /// * `parent`: path to the directory containing the file to delete. 216 | /// * `name`: name of the file to delete. 217 | fn unlink(&self, _req: RequestInfo, parent: &Path, name: &OsStr) -> ResultEmpty { 218 | debug!("unlink {:?} {:?}", parent, name); 219 | Err(libc::ENOSYS) 220 | } 221 | 222 | /// Remove a directory. 223 | /// 224 | /// * `parent`: path to the directory containing the directory to delete. 225 | /// * `name`: name of the directory to delete. 226 | fn rmdir(&self, _req: RequestInfo, parent: &Path, name: &OsStr) -> ResultEmpty { 227 | debug!("rmdir {:?} {:?}", parent, name); 228 | Err(libc::ENOSYS) 229 | } 230 | 231 | /// Create a symbolic link. 232 | /// 233 | /// * `parent`: path to the directory to make the link in. 234 | /// * `name`: name of the symbolic link. 235 | /// * `target`: path (may be relative or absolute) to the target of the link. 236 | fn symlink( 237 | &self, 238 | _req: RequestInfo, 239 | parent: &Path, 240 | name: &OsStr, 241 | target: &Path, 242 | ) -> ResultEntry { 243 | debug!("symlink {:?} {:?} {:?}", parent, name, target); 244 | Err(libc::ENOSYS) 245 | } 246 | 247 | /// Rename a filesystem entry. 248 | /// 249 | /// * `parent`: path to the directory containing the existing entry. 250 | /// * `name`: name of the existing entry. 251 | /// * `newparent`: path to the directory it should be renamed into (may be the same as `parent`). 252 | /// * `newname`: name of the new entry. 253 | fn rename( 254 | &self, 255 | _req: RequestInfo, 256 | parent: &Path, 257 | name: &OsStr, 258 | newparent: &Path, 259 | newname: &OsStr, 260 | ) -> ResultEmpty { 261 | debug!( 262 | "rename {:?} {:?} {:?} {:?}", 263 | parent, name, newparent, newname 264 | ); 265 | Err(libc::ENOSYS) 266 | } 267 | 268 | /// Create a hard link. 269 | /// 270 | /// * `path`: path to an existing file. 271 | /// * `newparent`: path to the directory for the new link. 272 | /// * `newname`: name for the new link. 273 | fn link( 274 | &self, 275 | _req: RequestInfo, 276 | path: &Path, 277 | newparent: &Path, 278 | newname: &OsStr, 279 | ) -> ResultEntry { 280 | debug!("link {:?} {:?} {:?}", path, newparent, newname); 281 | Err(libc::ENOSYS) 282 | } 283 | 284 | /// Open a file. 285 | /// 286 | /// * `path`: path to the file. 287 | /// * `flags`: one of `O_RDONLY`, `O_WRONLY`, or `O_RDWR`, plus maybe additional flags. 288 | /// 289 | /// Return a tuple of (file handle, flags). The file handle will be passed to any subsequent 290 | /// calls that operate on the file, and can be any value you choose, though it should allow 291 | /// your filesystem to identify the file opened even without any path debug. 292 | fn open(&self, _req: RequestInfo, path: &Path, flags: u32) -> ResultOpen { 293 | let ospath = path_to_str(path); 294 | match self.node.open(&ospath) { 295 | // TODO: handle readonly flags, directories 296 | // let masked_flags = 297 | // flags & !libc::O_WRONLY as u32 & !libc::O_RDWR as u32; 298 | Ok(handle) => Ok((handle as u64, flags | FOPEN_DIRECT_IO)), 299 | Err(_) => Err(libc::ENOENT), 300 | } 301 | } 302 | 303 | /// Read from a file. 304 | /// 305 | /// Note that it is not an error for this call to request to read past the end of the file, and 306 | /// you should only return data up to the end of the file (i.e. the number of bytes returned 307 | /// will be fewer than requested; possibly even zero). Do not extend the file in this case. 308 | /// 309 | /// * `path`: path to the file. 310 | /// * `fh`: file handle returned from the `open` call. 311 | /// * `offset`: offset into the file to start reading. 312 | /// * `size`: number of bytes to read. 313 | /// * `callback`: a callback that must be invoked to return the result of the operation: either 314 | /// the result data as a slice, or an error code. 315 | /// 316 | /// Return the return value from the `callback` function. 317 | fn read( 318 | &self, 319 | _req: RequestInfo, 320 | _path: &Path, 321 | fh: u64, 322 | offset: u64, 323 | size: u32, 324 | callback: impl FnOnce(ResultSlice<'_>) -> CallbackResult, 325 | ) -> CallbackResult { 326 | // TODO: validate chunk size? 327 | let mut buf = vec![0; size as usize]; 328 | let mut cursor = ObjCursor::from((&self.node, fh as usize, false)); 329 | // TODO: proper error checking 330 | match cursor.seek(SeekFrom::Start(offset)) { 331 | Ok(off) if offset == off => match cursor.read(&mut buf) { 332 | Ok(read) => callback(Ok(&buf[..read])), 333 | Err(ref e) if e.kind() == std::io::ErrorKind::UnexpectedEof => { 334 | // return empty slice 335 | buf.truncate(0); 336 | callback(Ok(&buf)) 337 | } 338 | Err(_) => callback(Err(libc::EIO)), 339 | }, 340 | _ => callback(Err(libc::EIO)), 341 | } 342 | } 343 | 344 | /// Write to a file. 345 | /// 346 | /// * `path`: path to the file. 347 | /// * `fh`: file handle returned from the `open` call. 348 | /// * `offset`: offset into the file to start writing. 349 | /// * `data`: the data to write 350 | /// * `flags`: 351 | /// 352 | /// Return the number of bytes written. 353 | fn write( 354 | &self, 355 | _req: RequestInfo, 356 | _path: &Path, 357 | fh: u64, 358 | offset: u64, 359 | data: Vec, 360 | _flags: u32, 361 | ) -> ResultWrite { 362 | if self.readonly { 363 | return Err(libc::EIO); 364 | } 365 | 366 | let mut cursor = ObjCursor::from((&self.node, fh as usize, false)); 367 | // TODO: proper error checking 368 | // TODO: rw perms checking? 369 | match cursor.seek(SeekFrom::Start(offset)) { 370 | Ok(off) if offset == off => match cursor.write(&data) { 371 | Ok(written) => Ok(written as u32), 372 | Err(e) => { 373 | error!("{e}"); 374 | Err(libc::EIO) 375 | } 376 | }, 377 | _ => Err(libc::EIO), 378 | } 379 | } 380 | 381 | /// Called each time a program calls `close` on an open file. 382 | /// 383 | /// Note that because file descriptors can be duplicated (by `dup`, `dup2`, `fork`) this may be 384 | /// called multiple times for a given file handle. The main use of this function is if the 385 | /// filesystem would like to return an error to the `close` call. Note that most programs 386 | /// ignore the return value of `close`, though. 387 | /// 388 | /// * `path`: path to the file. 389 | /// * `fh`: file handle returned from the `open` call. 390 | /// * `lock_owner`: if the filesystem supports locking (`setlk`, `getlk`), remove all locks 391 | /// belonging to this lock owner. 392 | fn flush(&self, _req: RequestInfo, path: &Path, _fh: u64, _lock_owner: u64) -> ResultEmpty { 393 | debug!("flush {:?}", path); 394 | Err(libc::ENOSYS) 395 | } 396 | 397 | /// Called when an open file is closed. 398 | /// 399 | /// There will be one of these for each `open` call. After `release`, no more calls will be 400 | /// made with the given file handle. 401 | /// 402 | /// * `path`: path to the file. 403 | /// * `fh`: file handle returned from the `open` call. 404 | /// * `flags`: the flags passed when the file was opened. 405 | /// * `lock_owner`: if the filesystem supports locking (`setlk`, `getlk`), remove all locks 406 | /// belonging to this lock owner. 407 | /// * `flush`: whether pending data must be flushed or not. 408 | fn release( 409 | &self, 410 | _req: RequestInfo, 411 | _path: &Path, 412 | fh: u64, 413 | _flags: u32, 414 | _lock_owner: u64, 415 | _flush: bool, 416 | ) -> ResultEmpty { 417 | self.node.close(fh as _).map_err(|_| libc::EIO) 418 | } 419 | 420 | /// Write out any pending changes of a file. 421 | /// 422 | /// When this returns, data should be written to persistent storage. 423 | /// 424 | /// * `path`: path to the file. 425 | /// * `fh`: file handle returned from the `open` call. 426 | /// * `datasync`: if `false`, also write metadata, otherwise just write file data. 427 | fn fsync(&self, _req: RequestInfo, path: &Path, _fh: u64, _datasync: bool) -> ResultEmpty { 428 | debug!("fsync {:?}", path); 429 | Err(libc::ENOSYS) 430 | } 431 | 432 | /// Open a directory. 433 | /// 434 | /// Analogous to the `opend` call. 435 | /// 436 | /// * `path`: path to the directory. 437 | /// * `flags`: file access flags. Will contain `O_DIRECTORY` at least. 438 | /// 439 | /// Return a tuple of (file handle, flags). The file handle will be passed to any subsequent 440 | /// calls that operate on the directory, and can be any value you choose, though it should 441 | /// allow your filesystem to identify the directory opened even without any path debug. 442 | fn opendir(&self, _req: RequestInfo, _path: &Path, flags: u32) -> ResultOpen { 443 | Ok((1, flags)) 444 | } 445 | 446 | /// Get the entries of a directory. 447 | /// 448 | /// * `path`: path to the directory. 449 | /// * `fh`: file handle returned from the `opendir` call. 450 | /// 451 | /// Return all the entries of the directory. 452 | fn readdir(&self, _req: RequestInfo, path: &Path, _fh: u64) -> ResultReaddir { 453 | let ospath = path_to_str(path); 454 | let mut result = Vec::new(); 455 | let cb = &mut |entry: ListEntry| { 456 | result.push(DirectoryEntry { 457 | name: OsString::from(&*entry.name), 458 | kind: if entry.is_branch { 459 | FileType::Directory 460 | } else { 461 | FileType::RegularFile 462 | }, 463 | }); 464 | true 465 | }; 466 | self.node 467 | .list(&ospath, &mut cb.into()) 468 | .map_err(|_| libc::ENOENT)?; 469 | Ok(result) 470 | } 471 | 472 | /// Close an open directory. 473 | /// 474 | /// This will be called exactly once for each `opendir` call. 475 | /// 476 | /// * `path`: path to the directory. 477 | /// * `fh`: file handle returned from the `opendir` call. 478 | /// * `flags`: the file access flags passed to the `opendir` call. 479 | fn releasedir(&self, _req: RequestInfo, _path: &Path, _fh: u64, _flags: u32) -> ResultEmpty { 480 | //debug!("releasedir {:?}", path); 481 | //Err(libc::ENOSYS) 482 | Ok(()) 483 | } 484 | 485 | /// Write out any pending changes to a directory. 486 | /// 487 | /// Analogous to the `fsync` call. 488 | fn fsyncdir(&self, _req: RequestInfo, path: &Path, _fh: u64, _datasync: bool) -> ResultEmpty { 489 | debug!("fsyncdir {:?}", path); 490 | Err(libc::ENOSYS) 491 | } 492 | 493 | /// Get filesystem statistics. 494 | /// 495 | /// * `path`: path to some folder in the filesystem. 496 | /// 497 | /// See the `Statfs` struct for more details. 498 | fn statfs(&self, _req: RequestInfo, path: &Path) -> ResultStatfs { 499 | debug!("statfs {:?}", path); 500 | Err(libc::ENOSYS) 501 | } 502 | 503 | /// Set a file extended attribute. 504 | /// 505 | /// * `path`: path to the file. 506 | /// * `name`: attribute name. 507 | /// * `value`: the data to set the value to. 508 | /// * `flags`: can be either `XATTR_CREATE` or `XATTR_REPLACE`. 509 | /// * `position`: offset into the attribute value to write data. 510 | fn setxattr( 511 | &self, 512 | _req: RequestInfo, 513 | path: &Path, 514 | name: &OsStr, 515 | _value: &[u8], 516 | _flags: u32, 517 | _position: u32, 518 | ) -> ResultEmpty { 519 | debug!("setxattr {:?} {:?}", path, name); 520 | Err(libc::ENOSYS) 521 | } 522 | 523 | /// Get a file extended attribute. 524 | /// 525 | /// * `path`: path to the file 526 | /// * `name`: attribute name. 527 | /// * `size`: the maximum number of bytes to read. 528 | /// 529 | /// If `size` is 0, return `Xattr::Size(n)` where `n` is the size of the attribute data. 530 | /// Otherwise, return `Xattr::Data(data)` with the requested data. 531 | fn getxattr(&self, _req: RequestInfo, path: &Path, name: &OsStr, _size: u32) -> ResultXattr { 532 | debug!("getxattr {:?} {:?}", path, name); 533 | Err(libc::ENOSYS) 534 | } 535 | 536 | /// List extended attributes for a file. 537 | /// 538 | /// * `path`: path to the file. 539 | /// * `size`: maximum number of bytes to return. 540 | /// 541 | /// If `size` is 0, return `Xattr::Size(n)` where `n` is the size required for the list of 542 | /// attribute names. 543 | /// Otherwise, return `Xattr::Data(data)` where `data` is all the null-terminated attribute 544 | /// names. 545 | fn listxattr(&self, _req: RequestInfo, path: &Path, _size: u32) -> ResultXattr { 546 | debug!("listxattr {:?}", path); 547 | Err(libc::ENOSYS) 548 | } 549 | 550 | /// Remove an extended attribute for a file. 551 | /// 552 | /// * `path`: path to the file. 553 | /// * `name`: name of the attribute to remove. 554 | fn removexattr(&self, _req: RequestInfo, path: &Path, name: &OsStr) -> ResultEmpty { 555 | debug!("removexattr {:?} {:?}", path, name); 556 | Err(libc::ENOSYS) 557 | } 558 | 559 | /// Check for access to a file. 560 | /// 561 | /// * `path`: path to the file. 562 | /// * `mask`: mode bits to check for access to. 563 | /// 564 | /// Return `Ok(())` if all requested permissions are allowed, otherwise return `Err(EACCES)` 565 | /// or other error code as appropriate (e.g. `ENOENT` if the file doesn't exist). 566 | fn access(&self, _req: RequestInfo, path: &Path, _mask: u32) -> ResultEmpty { 567 | debug!("access {:?}", path); 568 | 569 | // TODO: build path structure and lazily evaluate it 570 | 571 | Err(libc::ENOSYS) 572 | } 573 | 574 | /// Create and open a new file. 575 | /// 576 | /// * `parent`: path to the directory to create the file in. 577 | /// * `name`: name of the file to be created. 578 | /// * `mode`: the mode to set on the new file. 579 | /// * `flags`: flags like would be passed to `open`. 580 | /// 581 | /// Return a `CreatedEntry` (which contains the new file's attributes as well as a file handle 582 | /// -- see documentation on `open` for more debug on that). 583 | fn create( 584 | &self, 585 | _req: RequestInfo, 586 | parent: &Path, 587 | name: &OsStr, 588 | _mode: u32, 589 | _flags: u32, 590 | ) -> ResultCreate { 591 | debug!("create {:?} {:?}", parent, name); 592 | Err(libc::ENOSYS) 593 | } 594 | 595 | // getlk 596 | 597 | // setlk 598 | 599 | // bmap 600 | 601 | /// macOS only: Rename the volume. 602 | /// 603 | /// * `name`: new name for the volume 604 | #[cfg(target_os = "macos")] 605 | fn setvolname(&self, _req: RequestInfo, name: &OsStr) -> ResultEmpty { 606 | debug!("create {:?}", name); 607 | Err(libc::ENOSYS) 608 | } 609 | 610 | // exchange (macOS only, undocumented) 611 | 612 | /// macOS only: Query extended times (bkuptime and crtime). 613 | /// 614 | /// * `path`: path to the file to get the times for. 615 | /// 616 | /// Return an `XTimes` struct with the times, or other error code as appropriate. 617 | #[cfg(target_os = "macos")] 618 | fn getxtimes(&self, _req: RequestInfo, path: &Path) -> ResultXTimes { 619 | debug!("getxtimes {:?}", path); 620 | Err(libc::ENOSYS) 621 | } 622 | } 623 | 624 | /// Drops the filesystem and removes it from the global state. 625 | impl Drop for FilerFs { 626 | fn drop(&mut self) { 627 | // TODO: drop all opened handles 628 | } 629 | } 630 | 631 | pub fn mount( 632 | node: CArcSome, 633 | mount_point: &str, 634 | allow_other: bool, 635 | uid: u32, 636 | gid: u32, 637 | ) -> Result<()> { 638 | if mount_point.is_empty() { 639 | return Err(ErrorKind::InvalidPath.into()); 640 | } 641 | 642 | let mount_point = mount_point.to_string(); 643 | 644 | info!("filesystem mounted at {}", mount_point); 645 | info!("please use 'umount' or 'fusermount -u' to unmount the filesystem"); 646 | 647 | let mut opts = "auto_unmount".to_string(); 648 | 649 | if allow_other { 650 | opts += ",allow_other"; 651 | } 652 | 653 | if uid > 0 && gid > 0 { 654 | opts += &format!(",uid={uid},gid={gid}"); 655 | } 656 | 657 | std::thread::spawn(move || { 658 | let opts = ["-o", &opts]; 659 | let mntopts = opts.iter().map(|o| o.as_ref()).collect::>(); 660 | 661 | // the filesystem will add itself into the global scope 662 | let vmfs = FilerFs { 663 | node, 664 | mount_point: mount_point.clone(), 665 | uid, 666 | gid, 667 | readonly: false, 668 | }; 669 | 670 | // blocks until the fs is umounted 671 | fuse_mt::mount(fuse_mt::FuseMT::new(vmfs, 8), &mount_point, &mntopts).unwrap(); 672 | }); 673 | 674 | Ok(()) 675 | } 676 | --------------------------------------------------------------------------------