├── .cirrus.yml ├── .gitignore ├── .rustfmt.toml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── Cargo.toml └── src │ ├── helloworld │ └── main.rs │ ├── memfs │ └── main.rs │ ├── path_memfs │ └── main.rs │ └── poll │ └── main.rs └── src ├── errno.rs ├── helper.rs ├── lib.rs ├── mount_options.rs ├── notify.rs ├── path ├── inode_generator.rs ├── inode_path_bridge.rs ├── mod.rs ├── path_filesystem.rs ├── reply.rs └── session.rs └── raw ├── abi.rs ├── connection ├── async_io.rs ├── mod.rs └── tokio.rs ├── filesystem.rs ├── flags.rs ├── mod.rs ├── reply.rs ├── request.rs └── session.rs /.cirrus.yml: -------------------------------------------------------------------------------- 1 | build: &BUILD 2 | cargo_cache: 3 | folder: $HOME/.cargo/registry 4 | fingerprint_script: cat Cargo.lock || echo "" 5 | build_script: 6 | - . $HOME/.cargo/env || true 7 | - rustup component add clippy 8 | - cargo check --all-targets --features=tokio-runtime,file-lock,unprivileged 9 | - cargo check --all-targets --features=async-io-runtime,file-lock,unprivileged 10 | # - RUSTDOCFLAGS="--cfg docsrs" cargo doc --features=file-lock,unprivileged,tokio-runtime # disable until doc_cfg and doc_auto_cfg are stable 11 | - cargo doc --features=file-lock,unprivileged,tokio-runtime 12 | - cargo clippy --all-targets --features=tokio-runtime,file-lock,unprivileged 13 | - cargo clippy --all-targets --features=async-io-runtime,file-lock,unprivileged 14 | before_cache_script: rm -rf $HOME/.cargo/registry/index 15 | 16 | 17 | task: 18 | name: FreeBSD 19 | freebsd_instance: 20 | image: freebsd-13-3-release-amd64 21 | setup_script: 22 | - fetch https://sh.rustup.rs -o rustup.sh 23 | - sh rustup.sh -y --profile=minimal 24 | - . $HOME/.cargo/env 25 | << : *BUILD 26 | 27 | task: 28 | name: MacOS 29 | macos_instance: 30 | image: ghcr.io/cirruslabs/macos-sonoma-base:latest 31 | setup_script: 32 | - curl https://sh.rustup.rs -o rustup.sh 33 | - sh rustup.sh -y --profile=minimal 34 | - . $HOME/.cargo/env 35 | << : *BUILD 36 | 37 | task: 38 | name: Linux 39 | container: 40 | image: rust:latest 41 | << : *BUILD 42 | 43 | minver_task: 44 | depends_on: 45 | - FreeBSD 46 | - Linux 47 | - MacOS 48 | freebsd_instance: 49 | image: freebsd-13-2-release-amd64 50 | setup_script: 51 | - fetch https://sh.rustup.rs -o rustup.sh 52 | - sh rustup.sh -y --default-toolchain nightly --profile=minimal 53 | - . $HOME/.cargo/env 54 | test_script: 55 | - . $HOME/.cargo/env || true 56 | - cargo update -Zdirect-minimal-versions 57 | - cargo check --all-targets --features=tokio-runtime,file-lock,unprivileged 58 | - cargo check --all-targets --features=async-io-runtime,file-lock,unprivileged 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 3 | .idea -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | group_imports = "StdExternalCrate" 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fuse3" 3 | version = "0.8.1" 4 | authors = ["Sherlock Holo "] 5 | edition = "2021" 6 | readme = "README.md" 7 | keywords = ["fuse", "filesystem", "system", "bindings"] 8 | categories = ["api-bindings", "filesystem"] 9 | license = "MIT" 10 | repository = "https://github.com/Sherlock-Holo/fuse3" 11 | description = "FUSE user-space library async version implementation." 12 | rust-version = "1.77" 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [workspace] 17 | members = [".", "examples"] 18 | 19 | [features] 20 | tokio-runtime = ["dep:tokio"] 21 | async-io-runtime = ["dep:async-fs", "dep:async-global-executor", "dep:async-lock", "dep:async-io", "dep:async-process", "futures-util/io"] 22 | file-lock = [] 23 | unprivileged = ["nix/socket", "dep:which"] 24 | 25 | [dependencies] 26 | async-fs = { version = "2.1.1", optional = true } 27 | async-global-executor = { version = "2.4.1", optional = true } 28 | async-lock = { version = "3.3.0", optional = true } 29 | async-notify = "0.3" 30 | async-io = { version = "2.3.1", optional = true } 31 | async-process = { version = "2.1.0", optional = true } 32 | bincode = "1.3.3" 33 | bytes = "1.5" 34 | futures-channel = { version = "0.3.30", features = ["sink"] } 35 | futures-util = { version = "0.3.30", features = ["sink"] } 36 | libc = "0.2.158" 37 | nix = { version = "0.29.0", default-features = false, features = ["fs", "mount", "user"] } 38 | serde = { version = "1.0.196", features = ["derive"] } 39 | slab = "0.4.9" 40 | tracing = "0.1.40" 41 | trait-make = "0.1" 42 | which = { version = "6", optional = true } 43 | 44 | [dependencies.tokio] 45 | version = "1.36" 46 | features = ["fs", "rt", "sync", "net", "macros", "process", "time"] 47 | optional = true 48 | 49 | [package.metadata.docs.rs] 50 | rustdoc-args = ["--cfg", "docsrs"] 51 | features = ["file-lock", "unprivileged", "tokio-runtime"] 52 | targets = [ 53 | "i686-unknown-freebsd", 54 | "i686-unknown-linux-gnu", 55 | "x86_64-unknown-freebsd", 56 | "x86_64-unknown-linux-gnu", 57 | ] 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sherlock Holo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fuse3 2 | an async version fuse library for rust 3 | 4 | [![Cargo](https://img.shields.io/crates/v/fuse3.svg)]( 5 | https://crates.io/crates/fuse3) 6 | [![Documentation](https://docs.rs/fuse3/badge.svg)]( 7 | https://docs.rs/fuse3) 8 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)]( 9 | https://github.com/Sherlock-Holo/fuse3) 10 | 11 | ## feature 12 | 13 | - support unprivileged mode by using `fusermount3` 14 | - support `readdirplus` to improve read dir performance 15 | - support posix file lock 16 | - support handles the `O_TRUNC` open flag 17 | - support async direct IO 18 | - support enable `no_open` and `no_open_dir` option 19 | 20 | ## still not support 21 | 22 | - `ioctl` implement 23 | - fuseblk mode 24 | 25 | ## unstable 26 | 27 | - `poll` 28 | - `notify_reply` 29 | 30 | ## Supported Rust Versions 31 | 32 | The minimum supported version is 1.75. 33 | 34 | ## License 35 | 36 | MIT 37 | -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples" 3 | version = "0.1.0" 4 | authors = ["Sherlock Holo "] 5 | edition = "2021" 6 | publish = false 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [[bin]] 11 | name = "helloworld" 12 | path = "src/helloworld/main.rs" 13 | 14 | [[bin]] 15 | name = "memfs" 16 | path = "src/memfs/main.rs" 17 | 18 | [[bin]] 19 | name = "poll" 20 | path = "src/poll/main.rs" 21 | 22 | [[bin]] 23 | name = "path_memfs" 24 | path = "src/path_memfs/main.rs" 25 | 26 | [dependencies] 27 | fuse3 = { path = "../", features = ["tokio-runtime", "unprivileged"] } 28 | libc = "0.2.158" 29 | tokio = { version = "1.36", features = ["macros", "rt", "time", "signal"] } 30 | futures-util = "0.3.30" 31 | mio = { version = "0.8.11", features = ["os-poll"] } 32 | tempfile = "3.10" 33 | bytes = "1.5" 34 | tracing = "0.1.40" 35 | tracing-subscriber = "0.3" 36 | -------------------------------------------------------------------------------- /examples/src/helloworld/main.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::ffi::{OsStr, OsString}; 3 | use std::iter::Skip; 4 | use std::num::NonZeroU32; 5 | use std::time::{Duration, SystemTime}; 6 | use std::vec::IntoIter; 7 | 8 | use bytes::Bytes; 9 | use fuse3::raw::prelude::*; 10 | use fuse3::{MountOptions, Result}; 11 | use futures_util::stream; 12 | use futures_util::stream::Iter; 13 | use tracing::Level; 14 | 15 | const CONTENT: &str = "hello world\n"; 16 | 17 | const PARENT_INODE: u64 = 1; 18 | const FILE_INODE: u64 = 2; 19 | const FILE_NAME: &str = "hello-world.txt"; 20 | const PARENT_MODE: u16 = 0o755; 21 | const FILE_MODE: u16 = 0o644; 22 | const TTL: Duration = Duration::from_secs(1); 23 | const STATFS: ReplyStatFs = ReplyStatFs { 24 | blocks: 1, 25 | bfree: 0, 26 | bavail: 0, 27 | files: 1, 28 | ffree: 0, 29 | bsize: 4096, 30 | namelen: u32::MAX, 31 | frsize: 0, 32 | }; 33 | 34 | struct HelloWorld; 35 | 36 | impl Filesystem for HelloWorld { 37 | async fn init(&self, _req: Request) -> Result { 38 | Ok(ReplyInit { 39 | max_write: NonZeroU32::new(16 * 1024).unwrap(), 40 | }) 41 | } 42 | 43 | async fn destroy(&self, _req: Request) {} 44 | 45 | async fn lookup(&self, _req: Request, parent: u64, name: &OsStr) -> Result { 46 | if parent != PARENT_INODE { 47 | return Err(libc::ENOENT.into()); 48 | } 49 | 50 | if name != OsStr::new(FILE_NAME) { 51 | return Err(libc::ENOENT.into()); 52 | } 53 | 54 | Ok(ReplyEntry { 55 | ttl: TTL, 56 | attr: FileAttr { 57 | ino: FILE_INODE, 58 | size: CONTENT.len() as u64, 59 | blocks: 0, 60 | atime: SystemTime::now().into(), 61 | mtime: SystemTime::now().into(), 62 | ctime: SystemTime::now().into(), 63 | kind: FileType::RegularFile, 64 | perm: FILE_MODE, 65 | nlink: 0, 66 | uid: 0, 67 | gid: 0, 68 | rdev: 0, 69 | blksize: 0, 70 | }, 71 | generation: 0, 72 | }) 73 | } 74 | 75 | async fn getattr( 76 | &self, 77 | _req: Request, 78 | inode: u64, 79 | _fh: Option, 80 | _flags: u32, 81 | ) -> Result { 82 | if inode == PARENT_INODE { 83 | Ok(ReplyAttr { 84 | ttl: TTL, 85 | attr: FileAttr { 86 | ino: PARENT_INODE, 87 | size: 0, 88 | blocks: 0, 89 | atime: SystemTime::now().into(), 90 | mtime: SystemTime::now().into(), 91 | ctime: SystemTime::now().into(), 92 | kind: FileType::Directory, 93 | perm: PARENT_MODE, 94 | nlink: 0, 95 | uid: 0, 96 | gid: 0, 97 | rdev: 0, 98 | blksize: 0, 99 | }, 100 | }) 101 | } else if inode == FILE_INODE { 102 | Ok(ReplyAttr { 103 | ttl: TTL, 104 | attr: FileAttr { 105 | ino: FILE_INODE, 106 | size: CONTENT.len() as _, 107 | blocks: 0, 108 | atime: SystemTime::now().into(), 109 | mtime: SystemTime::now().into(), 110 | ctime: SystemTime::now().into(), 111 | kind: FileType::RegularFile, 112 | perm: FILE_MODE, 113 | nlink: 0, 114 | uid: 0, 115 | gid: 0, 116 | rdev: 0, 117 | blksize: 0, 118 | }, 119 | }) 120 | } else { 121 | Err(libc::ENOENT.into()) 122 | } 123 | } 124 | 125 | async fn open(&self, _req: Request, inode: u64, flags: u32) -> Result { 126 | if inode != PARENT_INODE && inode != FILE_INODE { 127 | return Err(libc::ENOENT.into()); 128 | } 129 | 130 | Ok(ReplyOpen { fh: 0, flags }) 131 | } 132 | 133 | async fn read( 134 | &self, 135 | _req: Request, 136 | inode: u64, 137 | _fh: u64, 138 | offset: u64, 139 | size: u32, 140 | ) -> Result { 141 | if inode != FILE_INODE { 142 | return Err(libc::ENOENT.into()); 143 | } 144 | 145 | if offset as usize >= CONTENT.len() { 146 | Ok(ReplyData { data: Bytes::new() }) 147 | } else { 148 | let mut data = &CONTENT.as_bytes()[offset as usize..]; 149 | 150 | if data.len() > size as usize { 151 | data = &data[..size as usize]; 152 | } 153 | 154 | Ok(ReplyData { 155 | data: Bytes::copy_from_slice(data), 156 | }) 157 | } 158 | } 159 | 160 | type DirEntryStream<'a> 161 | = Iter>>> 162 | where 163 | Self: 'a; 164 | 165 | async fn readdir( 166 | &self, 167 | _req: Request, 168 | inode: u64, 169 | _fh: u64, 170 | offset: i64, 171 | ) -> Result>> { 172 | if inode == FILE_INODE { 173 | return Err(libc::ENOTDIR.into()); 174 | } 175 | 176 | if inode != PARENT_INODE { 177 | return Err(libc::ENOENT.into()); 178 | } 179 | 180 | let entries = vec![ 181 | Ok(DirectoryEntry { 182 | inode: PARENT_INODE, 183 | kind: FileType::Directory, 184 | name: OsString::from("."), 185 | offset: 1, 186 | }), 187 | Ok(DirectoryEntry { 188 | inode: PARENT_INODE, 189 | kind: FileType::Directory, 190 | name: OsString::from(".."), 191 | offset: 2, 192 | }), 193 | Ok(DirectoryEntry { 194 | inode: FILE_INODE, 195 | kind: FileType::RegularFile, 196 | name: OsString::from(FILE_NAME), 197 | offset: 3, 198 | }), 199 | ]; 200 | 201 | Ok(ReplyDirectory { 202 | entries: stream::iter(entries.into_iter().skip(offset as usize)), 203 | }) 204 | } 205 | 206 | async fn access(&self, _req: Request, inode: u64, _mask: u32) -> Result<()> { 207 | if inode != PARENT_INODE && inode != FILE_INODE { 208 | return Err(libc::ENOENT.into()); 209 | } 210 | 211 | Ok(()) 212 | } 213 | 214 | type DirEntryPlusStream<'a> 215 | = Iter>>> 216 | where 217 | Self: 'a; 218 | 219 | async fn readdirplus( 220 | &self, 221 | _req: Request, 222 | parent: u64, 223 | _fh: u64, 224 | offset: u64, 225 | _lock_owner: u64, 226 | ) -> Result>> { 227 | if parent == FILE_INODE { 228 | return Err(libc::ENOTDIR.into()); 229 | } 230 | 231 | if parent != PARENT_INODE { 232 | return Err(libc::ENOENT.into()); 233 | } 234 | 235 | let entries = vec![ 236 | Ok(DirectoryEntryPlus { 237 | inode: PARENT_INODE, 238 | generation: 0, 239 | kind: FileType::Directory, 240 | name: OsString::from("."), 241 | offset: 1, 242 | attr: FileAttr { 243 | ino: PARENT_INODE, 244 | size: 0, 245 | blocks: 0, 246 | atime: SystemTime::now().into(), 247 | mtime: SystemTime::now().into(), 248 | ctime: SystemTime::now().into(), 249 | kind: FileType::Directory, 250 | perm: PARENT_MODE, 251 | nlink: 0, 252 | uid: 0, 253 | gid: 0, 254 | rdev: 0, 255 | blksize: 0, 256 | }, 257 | entry_ttl: TTL, 258 | attr_ttl: TTL, 259 | }), 260 | Ok(DirectoryEntryPlus { 261 | inode: PARENT_INODE, 262 | generation: 0, 263 | kind: FileType::Directory, 264 | name: OsString::from(".."), 265 | offset: 2, 266 | attr: FileAttr { 267 | ino: PARENT_INODE, 268 | size: 0, 269 | blocks: 0, 270 | atime: SystemTime::now().into(), 271 | mtime: SystemTime::now().into(), 272 | ctime: SystemTime::now().into(), 273 | kind: FileType::Directory, 274 | perm: PARENT_MODE, 275 | nlink: 0, 276 | uid: 0, 277 | gid: 0, 278 | rdev: 0, 279 | blksize: 0, 280 | }, 281 | entry_ttl: TTL, 282 | attr_ttl: TTL, 283 | }), 284 | Ok(DirectoryEntryPlus { 285 | inode: FILE_INODE, 286 | generation: 0, 287 | kind: FileType::Directory, 288 | name: OsString::from(FILE_NAME), 289 | offset: 3, 290 | attr: FileAttr { 291 | ino: FILE_INODE, 292 | size: CONTENT.len() as _, 293 | blocks: 0, 294 | atime: SystemTime::now().into(), 295 | mtime: SystemTime::now().into(), 296 | ctime: SystemTime::now().into(), 297 | kind: FileType::RegularFile, 298 | perm: FILE_MODE, 299 | nlink: 0, 300 | uid: 0, 301 | gid: 0, 302 | rdev: 0, 303 | blksize: 0, 304 | }, 305 | entry_ttl: TTL, 306 | attr_ttl: TTL, 307 | }), 308 | ]; 309 | 310 | Ok(ReplyDirectoryPlus { 311 | entries: stream::iter(entries.into_iter().skip(offset as usize)), 312 | }) 313 | } 314 | 315 | async fn statfs(&self, _req: Request, _inode: u64) -> Result { 316 | Ok(STATFS) 317 | } 318 | } 319 | 320 | #[tokio::main(flavor = "current_thread")] 321 | async fn main() { 322 | log_init(); 323 | 324 | let args = env::args_os().skip(1).take(1).collect::>(); 325 | 326 | let mount_path = args.first(); 327 | 328 | let uid = unsafe { libc::getuid() }; 329 | let gid = unsafe { libc::getgid() }; 330 | 331 | let mut mount_options = MountOptions::default(); 332 | mount_options.uid(uid).gid(gid).read_only(true); 333 | 334 | let mount_path = mount_path.expect("no mount point specified"); 335 | Session::new(mount_options) 336 | .mount_with_unprivileged(HelloWorld {}, mount_path) 337 | .await 338 | .unwrap() 339 | .await 340 | .unwrap() 341 | } 342 | 343 | fn log_init() { 344 | let subscriber = tracing_subscriber::fmt() 345 | .with_max_level(Level::DEBUG) 346 | .finish(); 347 | tracing::subscriber::set_global_default(subscriber).unwrap(); 348 | } 349 | -------------------------------------------------------------------------------- /examples/src/memfs/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | use std::env; 3 | use std::ffi::{OsStr, OsString}; 4 | use std::io::{self, Cursor, Read}; 5 | use std::num::NonZeroU32; 6 | use std::sync::atomic::{AtomicU64, Ordering}; 7 | use std::sync::Arc; 8 | use std::time::{Duration, SystemTime}; 9 | use std::vec::IntoIter; 10 | 11 | use bytes::{Buf, BytesMut}; 12 | use fuse3::raw::prelude::*; 13 | use fuse3::{Errno, Inode, MountOptions, Result}; 14 | use futures_util::stream; 15 | use futures_util::stream::{Empty, Iter}; 16 | use futures_util::StreamExt; 17 | use libc::mode_t; 18 | use tokio::signal; 19 | use tokio::sync::RwLock; 20 | use tracing::metadata::LevelFilter; 21 | use tracing::{debug, info, subscriber}; 22 | use tracing_subscriber::layer::SubscriberExt; 23 | use tracing_subscriber::{fmt, Registry}; 24 | 25 | const TTL: Duration = Duration::from_secs(1); 26 | 27 | #[derive(Debug, Clone)] 28 | enum Entry { 29 | Dir(Arc>), 30 | File(Arc>), 31 | } 32 | 33 | impl Entry { 34 | async fn attr(&self) -> FileAttr { 35 | const BLOCK_SIZE: f64 = 4096f64; 36 | 37 | match self { 38 | Entry::Dir(dir) => { 39 | let nlink = Arc::strong_count(dir) - 1; 40 | let dir = dir.read().await; 41 | 42 | FileAttr { 43 | ino: dir.inode, 44 | size: 4096, 45 | blocks: 1, 46 | atime: SystemTime::UNIX_EPOCH.into(), 47 | mtime: SystemTime::UNIX_EPOCH.into(), 48 | ctime: SystemTime::UNIX_EPOCH.into(), 49 | kind: FileType::Directory, 50 | perm: fuse3::perm_from_mode_and_kind(FileType::Directory, dir.mode), 51 | nlink: nlink as _, 52 | uid: 0, 53 | gid: 0, 54 | rdev: 0, 55 | blksize: BLOCK_SIZE as _, 56 | } 57 | } 58 | 59 | Entry::File(file) => { 60 | let nlink = Arc::strong_count(file) - 1; 61 | let file = file.read().await; 62 | 63 | FileAttr { 64 | ino: file.inode, 65 | size: file.content.len() as _, 66 | blocks: (file.content.len() as f64 / BLOCK_SIZE).ceil() as _, 67 | atime: SystemTime::UNIX_EPOCH.into(), 68 | mtime: SystemTime::UNIX_EPOCH.into(), 69 | ctime: SystemTime::UNIX_EPOCH.into(), 70 | kind: FileType::RegularFile, 71 | perm: fuse3::perm_from_mode_and_kind(FileType::RegularFile, file.mode), 72 | nlink: nlink as _, 73 | uid: 0, 74 | gid: 0, 75 | rdev: 0, 76 | blksize: BLOCK_SIZE as _, 77 | } 78 | } 79 | } 80 | } 81 | 82 | async fn set_attr(&self, set_attr: SetAttr) -> FileAttr { 83 | match self { 84 | Entry::Dir(dir) => { 85 | let mut dir = dir.write().await; 86 | 87 | if let Some(mode) = set_attr.mode { 88 | dir.mode = mode; 89 | } 90 | } 91 | 92 | Entry::File(file) => { 93 | let mut file = file.write().await; 94 | 95 | if let Some(size) = set_attr.size { 96 | file.content.truncate(size as _); 97 | } 98 | 99 | if let Some(mode) = set_attr.mode { 100 | file.mode = mode; 101 | } 102 | } 103 | } 104 | 105 | self.attr().await 106 | } 107 | 108 | fn is_dir(&self) -> bool { 109 | matches!(self, Entry::Dir(_)) 110 | } 111 | 112 | async fn inode(&self) -> u64 { 113 | match self { 114 | Entry::Dir(dir) => { 115 | let dir = dir.read().await; 116 | 117 | dir.inode 118 | } 119 | 120 | Entry::File(file) => { 121 | let file = file.read().await; 122 | 123 | file.inode 124 | } 125 | } 126 | } 127 | 128 | fn kind(&self) -> FileType { 129 | if self.is_dir() { 130 | FileType::Directory 131 | } else { 132 | FileType::RegularFile 133 | } 134 | } 135 | 136 | async fn name(&self) -> OsString { 137 | match self { 138 | Entry::Dir(dir) => dir.read().await.name.clone(), 139 | Entry::File(file) => file.read().await.name.clone(), 140 | } 141 | } 142 | } 143 | 144 | #[derive(Debug)] 145 | struct Dir { 146 | inode: u64, 147 | parent: u64, 148 | name: OsString, 149 | children: BTreeMap, 150 | mode: mode_t, 151 | } 152 | 153 | #[derive(Debug)] 154 | struct File { 155 | inode: u64, 156 | parent: u64, 157 | name: OsString, 158 | content: Vec, 159 | mode: mode_t, 160 | } 161 | 162 | #[derive(Debug)] 163 | struct InnerFs { 164 | inode_map: BTreeMap, 165 | inode_gen: AtomicU64, 166 | } 167 | 168 | #[derive(Debug)] 169 | struct Fs(RwLock); 170 | 171 | impl Default for Fs { 172 | fn default() -> Self { 173 | let root = Entry::Dir(Arc::new(RwLock::new(Dir { 174 | inode: 1, 175 | parent: 1, 176 | name: OsString::from("/"), 177 | children: BTreeMap::new(), 178 | mode: 0o755, 179 | }))); 180 | 181 | let mut inode_map = BTreeMap::new(); 182 | 183 | inode_map.insert(1, root); 184 | 185 | Self(RwLock::new(InnerFs { 186 | inode_map, 187 | inode_gen: AtomicU64::new(2), 188 | })) 189 | } 190 | } 191 | 192 | impl Filesystem for Fs { 193 | type DirEntryStream<'a> 194 | = Empty> 195 | where 196 | Self: 'a; 197 | 198 | async fn init(&self, _req: Request) -> Result { 199 | Ok(ReplyInit { 200 | max_write: NonZeroU32::new(16 * 1024).unwrap(), 201 | }) 202 | } 203 | 204 | async fn destroy(&self, _req: Request) { 205 | info!("destroy done") 206 | } 207 | 208 | async fn lookup(&self, _req: Request, parent: u64, name: &OsStr) -> Result { 209 | let inner = self.0.read().await; 210 | 211 | let entry = inner 212 | .inode_map 213 | .get(&parent) 214 | .ok_or_else(|| Errno::from(libc::ENOENT))?; 215 | 216 | if let Entry::Dir(dir) = entry { 217 | let dir = dir.read().await; 218 | 219 | let attr = dir 220 | .children 221 | .get(name) 222 | .ok_or_else(|| Errno::from(libc::ENOENT))? 223 | .attr() 224 | .await; 225 | 226 | Ok(ReplyEntry { 227 | ttl: TTL, 228 | attr, 229 | generation: 0, 230 | }) 231 | } else { 232 | Err(libc::ENOTDIR.into()) 233 | } 234 | } 235 | 236 | async fn forget(&self, _req: Request, _inode: u64, _nlookup: u64) {} 237 | 238 | async fn getattr( 239 | &self, 240 | _req: Request, 241 | inode: u64, 242 | _fh: Option, 243 | _flags: u32, 244 | ) -> Result { 245 | Ok(ReplyAttr { 246 | ttl: TTL, 247 | attr: self 248 | .0 249 | .read() 250 | .await 251 | .inode_map 252 | .get(&inode) 253 | .ok_or_else(|| Errno::from(libc::ENOENT))? 254 | .attr() 255 | .await, 256 | }) 257 | } 258 | 259 | async fn setattr( 260 | &self, 261 | _req: Request, 262 | inode: u64, 263 | _fh: Option, 264 | set_attr: SetAttr, 265 | ) -> Result { 266 | Ok(ReplyAttr { 267 | ttl: TTL, 268 | attr: self 269 | .0 270 | .read() 271 | .await 272 | .inode_map 273 | .get(&inode) 274 | .ok_or_else(|| Errno::from(libc::ENOENT))? 275 | .set_attr(set_attr) 276 | .await, 277 | }) 278 | } 279 | 280 | async fn mkdir( 281 | &self, 282 | _req: Request, 283 | parent: u64, 284 | name: &OsStr, 285 | mode: u32, 286 | _umask: u32, 287 | ) -> Result { 288 | let mut inner = self.0.write().await; 289 | 290 | let entry = inner 291 | .inode_map 292 | .get(&parent) 293 | .ok_or_else(Errno::new_not_exist)?; 294 | 295 | if let Entry::Dir(dir) = entry { 296 | let mut dir = dir.write().await; 297 | 298 | if dir.children.get(name).is_some() { 299 | return Err(libc::EEXIST.into()); 300 | } 301 | 302 | let new_inode = inner.inode_gen.fetch_add(1, Ordering::Relaxed); 303 | 304 | let entry = Entry::Dir(Arc::new(RwLock::new(Dir { 305 | inode: new_inode, 306 | parent, 307 | name: name.to_owned(), 308 | children: BTreeMap::new(), 309 | mode: mode as mode_t, 310 | }))); 311 | 312 | let attr = entry.attr().await; 313 | 314 | dir.children.insert(name.to_os_string(), entry.clone()); 315 | 316 | drop(dir); // fix inner can't borrow as mut next line 317 | 318 | inner.inode_map.insert(new_inode, entry); 319 | 320 | Ok(ReplyEntry { 321 | ttl: TTL, 322 | attr, 323 | generation: 0, 324 | }) 325 | } else { 326 | Err(libc::ENOTDIR.into()) 327 | } 328 | } 329 | 330 | async fn unlink(&self, _req: Request, parent: u64, name: &OsStr) -> Result<()> { 331 | let mut inner = self.0.write().await; 332 | 333 | let entry = inner 334 | .inode_map 335 | .get(&parent) 336 | .ok_or_else(|| Errno::from(libc::ENOENT))?; 337 | 338 | if let Entry::Dir(dir) = entry { 339 | let mut dir = dir.write().await; 340 | 341 | if dir 342 | .children 343 | .get(name) 344 | .ok_or_else(|| Errno::from(libc::ENOENT))? 345 | .is_dir() 346 | { 347 | return Err(libc::EISDIR.into()); 348 | } 349 | 350 | let child_entry = dir.children.remove(name).unwrap(); 351 | 352 | drop(dir); // fix inner can't borrow as mut next line 353 | 354 | if match &child_entry { 355 | Entry::Dir(_) => unreachable!(), 356 | Entry::File(file) => Arc::strong_count(file) == 1, 357 | } { 358 | inner.inode_map.remove(&child_entry.inode().await); 359 | } 360 | 361 | Ok(()) 362 | } else { 363 | Err(libc::ENOTDIR.into()) 364 | } 365 | } 366 | 367 | async fn rmdir(&self, _req: Request, parent: u64, name: &OsStr) -> Result<()> { 368 | let mut inner = self.0.write().await; 369 | 370 | let entry = inner 371 | .inode_map 372 | .get(&parent) 373 | .ok_or_else(|| Errno::from(libc::ENOENT))?; 374 | 375 | if let Entry::Dir(dir) = entry { 376 | let mut dir = dir.write().await; 377 | 378 | if let Entry::Dir(child_dir) = 379 | dir.children.get(name).ok_or_else(Errno::new_not_exist)? 380 | { 381 | if !child_dir.read().await.children.is_empty() { 382 | return Err(Errno::from(libc::ENOTEMPTY)); 383 | } 384 | } else { 385 | return Err(Errno::new_is_not_dir()); 386 | } 387 | 388 | let child_entry = dir.children.remove(name).unwrap(); 389 | 390 | drop(dir); // fix inner can't borrow as mut next line 391 | 392 | if match &child_entry { 393 | Entry::Dir(dir) => Arc::strong_count(dir) == 1, 394 | Entry::File(_) => unreachable!(), 395 | } { 396 | inner.inode_map.remove(&child_entry.inode().await); 397 | } 398 | 399 | Ok(()) 400 | } else { 401 | Err(libc::ENOTDIR.into()) 402 | } 403 | } 404 | 405 | async fn rename( 406 | &self, 407 | _req: Request, 408 | parent: u64, 409 | name: &OsStr, 410 | new_parent: u64, 411 | new_name: &OsStr, 412 | ) -> Result<()> { 413 | let inner = self.0.read().await; 414 | 415 | let parent_entry = inner 416 | .inode_map 417 | .get(&parent) 418 | .ok_or_else(|| Errno::from(libc::ENOENT))?; 419 | 420 | if let Entry::Dir(parent_dir) = parent_entry { 421 | let mut parent_dir = parent_dir.write().await; 422 | 423 | if parent == new_parent { 424 | let entry = parent_dir 425 | .children 426 | .remove(name) 427 | .ok_or_else(|| Errno::from(libc::ENOENT))?; 428 | parent_dir.children.insert(new_name.to_os_string(), entry); 429 | 430 | return Ok(()); 431 | } 432 | 433 | let new_parent_entry = inner 434 | .inode_map 435 | .get(&new_parent) 436 | .ok_or_else(|| Errno::from(libc::ENOENT))?; 437 | 438 | if let Entry::Dir(new_parent_dir) = new_parent_entry { 439 | let mut new_parent_dir = new_parent_dir.write().await; 440 | 441 | let entry = parent_dir 442 | .children 443 | .remove(name) 444 | .ok_or_else(|| Errno::from(libc::ENOENT))?; 445 | new_parent_dir 446 | .children 447 | .insert(new_name.to_os_string(), entry); 448 | 449 | return Ok(()); 450 | } 451 | } 452 | 453 | Err(libc::ENOTDIR.into()) 454 | } 455 | 456 | async fn link( 457 | &self, 458 | _req: Request, 459 | inode: Inode, 460 | new_parent: Inode, 461 | new_name: &OsStr, 462 | ) -> Result { 463 | let inner = self.0.write().await; 464 | 465 | let entry = inner 466 | .inode_map 467 | .get(&inode) 468 | .ok_or_else(Errno::new_not_exist)?; 469 | 470 | let entry_name = entry.name().await; 471 | debug!(?entry_name, "get entry"); 472 | 473 | let new_parent_entry = inner 474 | .inode_map 475 | .get(&new_parent) 476 | .ok_or_else(Errno::new_not_exist)?; 477 | let new_parent_entry_name = new_parent_entry.name().await; 478 | debug!(?new_parent_entry_name, "get new parent entry"); 479 | 480 | match new_parent_entry { 481 | Entry::File(_) => { 482 | return Err(Errno::new_is_not_dir()); 483 | } 484 | 485 | Entry::Dir(dir) => { 486 | let mut dir = dir.write().await; 487 | if dir.children.contains_key(new_name) { 488 | return Err(Errno::new_exist()); 489 | } 490 | 491 | dir.children.insert(new_name.to_os_string(), entry.clone()); 492 | } 493 | } 494 | 495 | Ok(ReplyEntry { 496 | ttl: TTL, 497 | attr: entry.attr().await, 498 | generation: 0, 499 | }) 500 | } 501 | 502 | async fn open(&self, _req: Request, inode: u64, _flags: u32) -> Result { 503 | let inner = self.0.read().await; 504 | 505 | let entry = inner 506 | .inode_map 507 | .get(&inode) 508 | .ok_or_else(|| Errno::from(libc::ENOENT))?; 509 | 510 | if matches!(entry, Entry::File(_)) { 511 | Ok(ReplyOpen { fh: 0, flags: 0 }) 512 | } else { 513 | Err(libc::EISDIR.into()) 514 | } 515 | } 516 | 517 | async fn read( 518 | &self, 519 | _req: Request, 520 | inode: u64, 521 | _fh: u64, 522 | offset: u64, 523 | size: u32, 524 | ) -> Result { 525 | let inner = self.0.read().await; 526 | 527 | let entry = inner 528 | .inode_map 529 | .get(&inode) 530 | .ok_or_else(|| Errno::from(libc::ENOENT))?; 531 | 532 | if let Entry::File(file) = entry { 533 | let file = file.read().await; 534 | 535 | let mut cursor = Cursor::new(&file.content); 536 | cursor.set_position(offset); 537 | 538 | let size = cursor.remaining().min(size as _); 539 | 540 | let mut data = BytesMut::with_capacity(size); 541 | // safety 542 | unsafe { 543 | data.set_len(size); 544 | } 545 | 546 | cursor.read_exact(&mut data).unwrap(); 547 | 548 | Ok(ReplyData { data: data.into() }) 549 | } else { 550 | Err(libc::EISDIR.into()) 551 | } 552 | } 553 | 554 | async fn write( 555 | &self, 556 | _req: Request, 557 | inode: u64, 558 | _fh: u64, 559 | offset: u64, 560 | mut data: &[u8], 561 | _write_flags: u32, 562 | _flags: u32, 563 | ) -> Result { 564 | let inner = self.0.read().await; 565 | 566 | let entry = inner 567 | .inode_map 568 | .get(&inode) 569 | .ok_or_else(|| Errno::from(libc::ENOENT))?; 570 | 571 | if let Entry::File(file) = entry { 572 | let mut file = file.write().await; 573 | 574 | if file.content.len() > offset as _ { 575 | let mut content = &mut file.content[offset as _..]; 576 | 577 | if content.len() > data.len() { 578 | io::copy(&mut data, &mut content).unwrap(); 579 | 580 | return Ok(ReplyWrite { 581 | written: data.len() as _, 582 | }); 583 | } 584 | 585 | let n = io::copy(&mut (&data[..content.len()]), &mut content).unwrap(); 586 | 587 | file.content.extend_from_slice(&data[n as _..]); 588 | 589 | Ok(ReplyWrite { 590 | written: data.len() as _, 591 | }) 592 | } else { 593 | file.content.resize(offset as _, 0); 594 | 595 | file.content.extend_from_slice(data); 596 | 597 | Ok(ReplyWrite { 598 | written: data.len() as _, 599 | }) 600 | } 601 | } else { 602 | Err(libc::EISDIR.into()) 603 | } 604 | } 605 | 606 | async fn release( 607 | &self, 608 | _req: Request, 609 | _inode: u64, 610 | _fh: u64, 611 | _flags: u32, 612 | _lock_owner: u64, 613 | _flush: bool, 614 | ) -> Result<()> { 615 | Ok(()) 616 | } 617 | 618 | async fn fsync(&self, _req: Request, _inode: u64, _fh: u64, _datasync: bool) -> Result<()> { 619 | Ok(()) 620 | } 621 | 622 | async fn flush(&self, _req: Request, _inode: u64, _fh: u64, _lock_owner: u64) -> Result<()> { 623 | Ok(()) 624 | } 625 | 626 | async fn access(&self, _req: Request, _inode: u64, _mask: u32) -> Result<()> { 627 | Ok(()) 628 | } 629 | 630 | async fn create( 631 | &self, 632 | _req: Request, 633 | parent: u64, 634 | name: &OsStr, 635 | mode: u32, 636 | flags: u32, 637 | ) -> Result { 638 | let mut inner = self.0.write().await; 639 | 640 | let entry = inner 641 | .inode_map 642 | .get(&parent) 643 | .ok_or_else(|| Errno::from(libc::ENOENT))?; 644 | 645 | if let Entry::Dir(dir) = entry { 646 | let mut dir = dir.write().await; 647 | 648 | if dir.children.get(name).is_some() { 649 | return Err(libc::EEXIST.into()); 650 | } 651 | 652 | let new_inode = inner.inode_gen.fetch_add(1, Ordering::Relaxed); 653 | 654 | let entry = Entry::File(Arc::new(RwLock::new(File { 655 | inode: new_inode, 656 | parent, 657 | name: name.to_os_string(), 658 | content: vec![], 659 | mode: mode as mode_t, 660 | }))); 661 | 662 | let attr = entry.attr().await; 663 | 664 | dir.children.insert(name.to_os_string(), entry.clone()); 665 | 666 | drop(dir); 667 | 668 | inner.inode_map.insert(new_inode, entry); 669 | 670 | Ok(ReplyCreated { 671 | ttl: TTL, 672 | attr, 673 | generation: 0, 674 | fh: 0, 675 | flags, 676 | }) 677 | } else { 678 | Err(libc::ENOTDIR.into()) 679 | } 680 | } 681 | 682 | async fn interrupt(&self, _req: Request, _unique: u64) -> Result<()> { 683 | Ok(()) 684 | } 685 | 686 | async fn fallocate( 687 | &self, 688 | _req: Request, 689 | inode: u64, 690 | _fh: u64, 691 | offset: u64, 692 | length: u64, 693 | _mode: u32, 694 | ) -> Result<()> { 695 | let inner = self.0.read().await; 696 | 697 | let entry = inner 698 | .inode_map 699 | .get(&inode) 700 | .ok_or_else(|| Errno::from(libc::ENOENT))?; 701 | 702 | if let Entry::File(file) = entry { 703 | let mut file = file.write().await; 704 | 705 | let new_size = (offset + length) as usize; 706 | 707 | let size = file.content.len(); 708 | 709 | if new_size > size { 710 | file.content.reserve(new_size - size); 711 | } else { 712 | file.content.truncate(new_size); 713 | } 714 | 715 | Ok(()) 716 | } else { 717 | Err(libc::EISDIR.into()) 718 | } 719 | } 720 | 721 | type DirEntryPlusStream<'a> 722 | = Iter>> 723 | where 724 | Self: 'a; 725 | 726 | async fn readdirplus( 727 | &self, 728 | _req: Request, 729 | parent: u64, 730 | _fh: u64, 731 | offset: u64, 732 | _lock_owner: u64, 733 | ) -> Result>> { 734 | let inner = self.0.read().await; 735 | 736 | let entry = inner 737 | .inode_map 738 | .get(&parent) 739 | .ok_or_else(|| Errno::from(libc::ENOENT))?; 740 | 741 | if let Entry::Dir(dir) = entry { 742 | let attr = entry.attr().await; 743 | 744 | let dir = dir.read().await; 745 | 746 | let parent_attr = if dir.parent == dir.inode { 747 | attr 748 | } else { 749 | inner 750 | .inode_map 751 | .get(&dir.parent) 752 | .expect("dir parent not exist") 753 | .attr() 754 | .await 755 | }; 756 | 757 | let pre_children = stream::iter( 758 | vec![ 759 | (dir.inode, FileType::Directory, OsString::from("."), attr, 1), 760 | ( 761 | dir.parent, 762 | FileType::Directory, 763 | OsString::from(".."), 764 | parent_attr, 765 | 2, 766 | ), 767 | ] 768 | .into_iter(), 769 | ); 770 | 771 | let children = pre_children 772 | .chain(stream::iter(dir.children.iter()).enumerate().filter_map( 773 | |(i, (name, entry))| async move { 774 | let inode = entry.inode().await; 775 | let attr = entry.attr().await; 776 | 777 | Some((inode, entry.kind(), name.to_os_string(), attr, i as i64 + 3)) 778 | }, 779 | )) 780 | .map(|(inode, kind, name, attr, offset)| DirectoryEntryPlus { 781 | inode, 782 | generation: 0, 783 | kind, 784 | name, 785 | offset, 786 | attr, 787 | entry_ttl: TTL, 788 | attr_ttl: TTL, 789 | }) 790 | .skip(offset as _) 791 | .map(Ok) 792 | .collect::>() 793 | .await; 794 | 795 | Ok(ReplyDirectoryPlus { 796 | entries: stream::iter(children), 797 | }) 798 | } else { 799 | Err(libc::ENOTDIR.into()) 800 | } 801 | } 802 | 803 | async fn rename2( 804 | &self, 805 | req: Request, 806 | parent: u64, 807 | name: &OsStr, 808 | new_parent: u64, 809 | new_name: &OsStr, 810 | _flags: u32, 811 | ) -> Result<()> { 812 | self.rename(req, parent, name, new_parent, new_name).await 813 | } 814 | 815 | async fn lseek( 816 | &self, 817 | _req: Request, 818 | inode: u64, 819 | _fh: u64, 820 | offset: u64, 821 | whence: u32, 822 | ) -> Result { 823 | let inner = self.0.read().await; 824 | 825 | let entry = inner 826 | .inode_map 827 | .get(&inode) 828 | .ok_or_else(|| Errno::from(libc::ENOENT))?; 829 | 830 | let whence = whence as i32; 831 | 832 | if let Entry::File(file) = entry { 833 | let offset = if whence == libc::SEEK_CUR || whence == libc::SEEK_SET { 834 | offset 835 | } else if whence == libc::SEEK_END { 836 | let content_size = file.read().await.content.len(); 837 | 838 | if content_size >= offset as _ { 839 | content_size as u64 - offset 840 | } else { 841 | 0 842 | } 843 | } else { 844 | return Err(libc::EINVAL.into()); 845 | }; 846 | 847 | Ok(ReplyLSeek { offset }) 848 | } else { 849 | Err(libc::EISDIR.into()) 850 | } 851 | } 852 | 853 | async fn copy_file_range( 854 | &self, 855 | req: Request, 856 | inode: u64, 857 | fh_in: u64, 858 | off_in: u64, 859 | inode_out: u64, 860 | fh_out: u64, 861 | off_out: u64, 862 | length: u64, 863 | flags: u64, 864 | ) -> Result { 865 | let data = self.read(req, inode, fh_in, off_in, length as _).await?; 866 | 867 | let data = data.data.as_ref(); 868 | 869 | let ReplyWrite { written } = self 870 | .write(req, inode_out, fh_out, off_out, data, 0, flags as _) 871 | .await?; 872 | 873 | Ok(ReplyCopyFileRange { 874 | copied: u64::from(written), 875 | }) 876 | } 877 | } 878 | 879 | fn log_init() { 880 | let layer = fmt::layer() 881 | .pretty() 882 | .with_target(true) 883 | .with_writer(io::stderr); 884 | 885 | let layered = Registry::default().with(layer).with(LevelFilter::DEBUG); 886 | 887 | subscriber::set_global_default(layered).unwrap(); 888 | } 889 | 890 | #[tokio::main(flavor = "current_thread")] 891 | async fn main() { 892 | log_init(); 893 | 894 | let args = env::args_os().skip(1).take(1).collect::>(); 895 | 896 | let mount_path = args.first(); 897 | 898 | let uid = unsafe { libc::getuid() }; 899 | let gid = unsafe { libc::getgid() }; 900 | 901 | let not_unprivileged = env::var("NOT_UNPRIVILEGED").ok().as_deref() == Some("1"); 902 | 903 | let mut mount_options = MountOptions::default(); 904 | // .allow_other(true) 905 | mount_options 906 | .fs_name("memfs") 907 | .force_readdir_plus(true) 908 | .uid(uid) 909 | .gid(gid); 910 | 911 | let mount_path = mount_path.expect("no mount point specified"); 912 | 913 | let mut mount_handle = if !not_unprivileged { 914 | Session::new(mount_options) 915 | .mount_with_unprivileged(Fs::default(), mount_path) 916 | .await 917 | .unwrap() 918 | } else { 919 | Session::new(mount_options) 920 | .mount(Fs::default(), mount_path) 921 | .await 922 | .unwrap() 923 | }; 924 | 925 | let handle = &mut mount_handle; 926 | 927 | tokio::select! { 928 | res = handle => res.unwrap(), 929 | _ = signal::ctrl_c() => { 930 | mount_handle.unmount().await.unwrap() 931 | } 932 | } 933 | } 934 | -------------------------------------------------------------------------------- /examples/src/poll/main.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{OsStr, OsString}; 2 | use std::iter::Skip; 3 | use std::num::NonZeroU32; 4 | use std::os::unix::io::AsRawFd; 5 | use std::path::PathBuf; 6 | use std::sync::atomic::{AtomicBool, Ordering}; 7 | use std::sync::Arc; 8 | use std::time::{Duration, SystemTime}; 9 | use std::vec::IntoIter; 10 | 11 | use bytes::Bytes; 12 | use fuse3::raw::prelude::*; 13 | use fuse3::{MountOptions, Result}; 14 | use futures_util::stream; 15 | use futures_util::stream::Iter; 16 | use mio::unix::SourceFd; 17 | use mio::{Events, Interest, Token}; 18 | use tokio::time; 19 | use tracing::{debug, info, Level}; 20 | 21 | const CONTENT: &str = "hello world\n"; 22 | 23 | const PARENT_INODE: u64 = 1; 24 | const FILE_INODE: u64 = 2; 25 | const FILE_NAME: &str = "hello-world.txt"; 26 | const PARENT_MODE: u16 = 0o755; 27 | const FILE_MODE: u16 = 0o644; 28 | const TTL: Duration = Duration::from_secs(1); 29 | 30 | #[derive(Debug, Default)] 31 | struct Poll { 32 | ready: Arc, 33 | } 34 | 35 | impl Filesystem for Poll { 36 | async fn init(&self, _req: Request) -> Result { 37 | Ok(ReplyInit { 38 | max_write: NonZeroU32::new(16 * 1024).unwrap(), 39 | }) 40 | } 41 | 42 | async fn destroy(&self, _req: Request) {} 43 | 44 | async fn lookup(&self, _req: Request, parent: u64, name: &OsStr) -> Result { 45 | if parent != PARENT_INODE { 46 | return Err(libc::ENOENT.into()); 47 | } 48 | 49 | if name != OsStr::new(FILE_NAME) { 50 | return Err(libc::ENOENT.into()); 51 | } 52 | 53 | Ok(ReplyEntry { 54 | ttl: TTL, 55 | attr: FileAttr { 56 | ino: FILE_INODE, 57 | size: CONTENT.len() as u64, 58 | blocks: 0, 59 | atime: SystemTime::now().into(), 60 | mtime: SystemTime::now().into(), 61 | ctime: SystemTime::now().into(), 62 | kind: FileType::RegularFile, 63 | perm: FILE_MODE, 64 | nlink: 0, 65 | uid: 0, 66 | gid: 0, 67 | rdev: 0, 68 | blksize: 0, 69 | }, 70 | generation: 0, 71 | }) 72 | } 73 | 74 | async fn getattr( 75 | &self, 76 | _req: Request, 77 | inode: u64, 78 | _fh: Option, 79 | _flags: u32, 80 | ) -> Result { 81 | if inode == PARENT_INODE { 82 | Ok(ReplyAttr { 83 | ttl: TTL, 84 | attr: FileAttr { 85 | ino: PARENT_INODE, 86 | size: 0, 87 | blocks: 0, 88 | atime: SystemTime::now().into(), 89 | mtime: SystemTime::now().into(), 90 | ctime: SystemTime::now().into(), 91 | kind: FileType::Directory, 92 | perm: PARENT_MODE, 93 | nlink: 0, 94 | uid: 0, 95 | gid: 0, 96 | rdev: 0, 97 | blksize: 0, 98 | }, 99 | }) 100 | } else if inode == FILE_INODE { 101 | Ok(ReplyAttr { 102 | ttl: TTL, 103 | attr: FileAttr { 104 | ino: FILE_INODE, 105 | size: CONTENT.len() as _, 106 | blocks: 0, 107 | atime: SystemTime::now().into(), 108 | mtime: SystemTime::now().into(), 109 | ctime: SystemTime::now().into(), 110 | kind: FileType::RegularFile, 111 | perm: FILE_MODE, 112 | nlink: 0, 113 | uid: 0, 114 | gid: 0, 115 | rdev: 0, 116 | blksize: 0, 117 | }, 118 | }) 119 | } else { 120 | Err(libc::ENOENT.into()) 121 | } 122 | } 123 | 124 | async fn open(&self, _req: Request, inode: u64, flags: u32) -> Result { 125 | if inode != PARENT_INODE && inode != FILE_INODE { 126 | return Err(libc::ENOENT.into()); 127 | } 128 | 129 | Ok(ReplyOpen { fh: 1, flags }) 130 | } 131 | 132 | async fn read( 133 | &self, 134 | _req: Request, 135 | inode: u64, 136 | _fh: u64, 137 | offset: u64, 138 | size: u32, 139 | ) -> Result { 140 | if inode != FILE_INODE { 141 | return Err(libc::ENOENT.into()); 142 | } 143 | 144 | if offset as usize >= CONTENT.len() { 145 | Ok(ReplyData { data: Bytes::new() }) 146 | } else { 147 | let mut data = &CONTENT.as_bytes()[offset as usize..]; 148 | 149 | if data.len() > size as usize { 150 | data = &data[..size as usize]; 151 | } 152 | 153 | Ok(ReplyData { 154 | data: Bytes::copy_from_slice(data), 155 | }) 156 | } 157 | } 158 | 159 | type DirEntryStream<'a> 160 | = Iter>>> 161 | where 162 | Self: 'a; 163 | 164 | async fn readdir( 165 | &self, 166 | _req: Request, 167 | inode: u64, 168 | _fh: u64, 169 | offset: i64, 170 | ) -> Result>> { 171 | if inode == FILE_INODE { 172 | return Err(libc::ENOTDIR.into()); 173 | } 174 | 175 | if inode != PARENT_INODE { 176 | return Err(libc::ENOENT.into()); 177 | } 178 | 179 | let entries = vec![ 180 | Ok(DirectoryEntry { 181 | inode: PARENT_INODE, 182 | kind: FileType::Directory, 183 | name: OsString::from("."), 184 | offset: 1, 185 | }), 186 | Ok(DirectoryEntry { 187 | inode: PARENT_INODE, 188 | kind: FileType::Directory, 189 | name: OsString::from(".."), 190 | offset: 2, 191 | }), 192 | Ok(DirectoryEntry { 193 | inode: FILE_INODE, 194 | kind: FileType::RegularFile, 195 | name: OsString::from(FILE_NAME), 196 | offset: 3, 197 | }), 198 | ]; 199 | 200 | Ok(ReplyDirectory { 201 | entries: stream::iter(entries.into_iter().skip(offset as usize)), 202 | }) 203 | } 204 | 205 | async fn access(&self, _req: Request, inode: u64, _mask: u32) -> Result<()> { 206 | if inode != PARENT_INODE && inode != FILE_INODE { 207 | return Err(libc::ENOENT.into()); 208 | } 209 | 210 | Ok(()) 211 | } 212 | 213 | type DirEntryPlusStream<'a> 214 | = Iter>>> 215 | where 216 | Self: 'a; 217 | 218 | async fn readdirplus( 219 | &self, 220 | _req: Request, 221 | parent: u64, 222 | _fh: u64, 223 | offset: u64, 224 | _lock_owner: u64, 225 | ) -> Result>> { 226 | if parent == FILE_INODE { 227 | return Err(libc::ENOTDIR.into()); 228 | } 229 | 230 | if parent != PARENT_INODE { 231 | return Err(libc::ENOENT.into()); 232 | } 233 | 234 | let entries = vec![ 235 | Ok(DirectoryEntryPlus { 236 | inode: PARENT_INODE, 237 | generation: 0, 238 | kind: FileType::Directory, 239 | name: OsString::from("."), 240 | offset: 1, 241 | attr: FileAttr { 242 | ino: PARENT_INODE, 243 | size: 0, 244 | blocks: 0, 245 | atime: SystemTime::now().into(), 246 | mtime: SystemTime::now().into(), 247 | ctime: SystemTime::now().into(), 248 | kind: FileType::Directory, 249 | perm: PARENT_MODE, 250 | nlink: 0, 251 | uid: 0, 252 | gid: 0, 253 | rdev: 0, 254 | blksize: 0, 255 | }, 256 | entry_ttl: TTL, 257 | attr_ttl: TTL, 258 | }), 259 | Ok(DirectoryEntryPlus { 260 | inode: PARENT_INODE, 261 | generation: 0, 262 | kind: FileType::Directory, 263 | name: OsString::from(".."), 264 | offset: 2, 265 | attr: FileAttr { 266 | ino: PARENT_INODE, 267 | size: 0, 268 | blocks: 0, 269 | atime: SystemTime::now().into(), 270 | mtime: SystemTime::now().into(), 271 | ctime: SystemTime::now().into(), 272 | kind: FileType::Directory, 273 | perm: PARENT_MODE, 274 | nlink: 0, 275 | uid: 0, 276 | gid: 0, 277 | rdev: 0, 278 | blksize: 0, 279 | }, 280 | entry_ttl: TTL, 281 | attr_ttl: TTL, 282 | }), 283 | Ok(DirectoryEntryPlus { 284 | inode: FILE_INODE, 285 | generation: 0, 286 | kind: FileType::Directory, 287 | name: OsString::from(FILE_NAME), 288 | offset: 3, 289 | attr: FileAttr { 290 | ino: FILE_INODE, 291 | size: CONTENT.len() as _, 292 | blocks: 0, 293 | atime: SystemTime::now().into(), 294 | mtime: SystemTime::now().into(), 295 | ctime: SystemTime::now().into(), 296 | kind: FileType::RegularFile, 297 | perm: FILE_MODE, 298 | nlink: 0, 299 | uid: 0, 300 | gid: 0, 301 | rdev: 0, 302 | blksize: 0, 303 | }, 304 | entry_ttl: TTL, 305 | attr_ttl: TTL, 306 | }), 307 | ]; 308 | 309 | Ok(ReplyDirectoryPlus { 310 | entries: stream::iter(entries.into_iter().skip(offset as usize)), 311 | }) 312 | } 313 | 314 | async fn poll( 315 | &self, 316 | _req: Request, 317 | inode: u64, 318 | _fh: u64, 319 | kh: Option, 320 | flags: u32, 321 | events: u32, 322 | notify: &Notify, 323 | ) -> Result { 324 | if inode != PARENT_INODE && inode != FILE_INODE { 325 | return Err(libc::ENOENT.into()); 326 | } 327 | 328 | debug!("poll flags {} events {}", flags, events); 329 | 330 | if let Some(kh) = kh { 331 | let ready = self.ready.clone(); 332 | 333 | if ready.load(Ordering::SeqCst) { 334 | return Ok(ReplyPoll { revents: events }); 335 | } 336 | 337 | let notify = notify.clone(); 338 | 339 | tokio::spawn(async move { 340 | debug!("start notify"); 341 | 342 | time::sleep(Duration::from_secs(2)).await; 343 | 344 | ready.store(true, Ordering::SeqCst); 345 | 346 | notify.wakeup(kh).await; 347 | 348 | debug!("notify done"); 349 | }); 350 | } 351 | 352 | Ok(ReplyPoll { revents: 0 }) 353 | } 354 | } 355 | 356 | #[tokio::main(flavor = "current_thread")] 357 | async fn main() { 358 | log_init(); 359 | 360 | let uid = unsafe { libc::getuid() }; 361 | let gid = unsafe { libc::getgid() }; 362 | 363 | let mut mount_options = MountOptions::default(); 364 | mount_options.uid(uid).gid(gid).read_only(true); 365 | 366 | let temp_dir = tempfile::tempdir().unwrap(); 367 | 368 | let mount_path = temp_dir.path(); 369 | 370 | let poll = Poll::default(); 371 | 372 | let session = Session::new(mount_options); 373 | 374 | { 375 | let mount_path = mount_path.as_os_str().to_os_string(); 376 | 377 | std::thread::spawn(move || { 378 | std::thread::sleep(Duration::from_secs(2)); 379 | 380 | poll_file(&mount_path); 381 | }); 382 | } 383 | 384 | session 385 | .mount_with_unprivileged(poll, mount_path) 386 | .await 387 | .unwrap() 388 | .await 389 | .unwrap(); 390 | } 391 | 392 | fn log_init() { 393 | let subscriber = tracing_subscriber::fmt() 394 | .with_max_level(Level::DEBUG) 395 | .finish(); 396 | tracing::subscriber::set_global_default(subscriber).unwrap(); 397 | } 398 | 399 | fn poll_file(mount_path: &OsStr) { 400 | let mut poll = mio::Poll::new().unwrap(); 401 | 402 | let mut path = PathBuf::from(mount_path.to_os_string()); 403 | path.push(FILE_NAME); 404 | 405 | let file = std::fs::File::open(&path).unwrap(); 406 | 407 | let fd = file.as_raw_fd(); 408 | let mut fd = SourceFd(&fd); 409 | 410 | const TOKEN: Token = Token(1); 411 | 412 | poll.registry() 413 | .register(&mut fd, TOKEN, Interest::READABLE) 414 | .unwrap(); 415 | 416 | let mut events = Events::with_capacity(1024); 417 | 418 | poll.poll(&mut events, None).unwrap(); 419 | 420 | for event in events.iter() { 421 | info!("{:?}", event); 422 | } 423 | 424 | poll.registry().deregister(&mut fd).unwrap(); 425 | } 426 | -------------------------------------------------------------------------------- /src/errno.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fmt::{self, Display, Formatter}; 3 | use std::io::Error as IoError; 4 | use std::os::raw::c_int; 5 | 6 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 7 | /// linux errno wrap. 8 | pub struct Errno(c_int); 9 | 10 | impl From for c_int { 11 | fn from(errno: Errno) -> Self { 12 | -errno.0 13 | } 14 | } 15 | 16 | impl From for Errno { 17 | fn from(errno: c_int) -> Self { 18 | Self(errno) 19 | } 20 | } 21 | 22 | /// When raw os error is undefined, will return Errno(libc::EIO) 23 | impl From for Errno { 24 | fn from(err: IoError) -> Self { 25 | if let Some(errno) = err.raw_os_error() { 26 | Self(errno) 27 | } else { 28 | Self(libc::EIO) 29 | } 30 | } 31 | } 32 | 33 | impl From for IoError { 34 | fn from(errno: Errno) -> Self { 35 | IoError::from_raw_os_error(errno.0) 36 | } 37 | } 38 | 39 | impl Display for Errno { 40 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 41 | write!(f, "errno is {}", self.0) 42 | } 43 | } 44 | 45 | impl Errno { 46 | pub fn new_not_exist() -> Self { 47 | Self(libc::ENOENT) 48 | } 49 | 50 | pub fn new_exist() -> Self { 51 | Self(libc::EEXIST) 52 | } 53 | 54 | pub fn new_is_dir() -> Self { 55 | Self(libc::EISDIR) 56 | } 57 | 58 | pub fn new_is_not_dir() -> Self { 59 | Self(libc::ENOTDIR) 60 | } 61 | 62 | pub fn is_not_exist(&self) -> bool { 63 | self.0 == libc::ENOENT 64 | } 65 | 66 | pub fn is_exist(&self) -> bool { 67 | self.0 == libc::EEXIST 68 | } 69 | 70 | pub fn is_dir(&self) -> bool { 71 | self.0 == libc::EISDIR 72 | } 73 | 74 | pub fn is_not_dir(&self) -> bool { 75 | self.0 == libc::ENOTDIR 76 | } 77 | } 78 | 79 | impl Error for Errno {} 80 | -------------------------------------------------------------------------------- /src/helper.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | 3 | use bincode::{DefaultOptions, Options}; 4 | use nix::sys::stat::mode_t; 5 | 6 | use crate::FileType; 7 | 8 | pub trait Apply: Sized { 9 | fn apply(mut self, f: F) -> Self 10 | where 11 | F: FnOnce(&mut Self), 12 | { 13 | f(&mut self); 14 | self 15 | } 16 | } 17 | 18 | impl Apply for T {} 19 | 20 | #[inline] 21 | pub fn get_first_null_position(data: impl AsRef<[u8]>) -> Option { 22 | data.as_ref().iter().position(|char| *char == 0) 23 | } 24 | 25 | // Some platforms like Linux x86_64 have mode_t = u32, and lint warns of a trivial_numeric_casts. 26 | // But others like macOS x86_64 have mode_t = u16, requiring a typecast. So, just silence lint. 27 | #[cfg(target_os = "linux")] 28 | #[allow(trivial_numeric_casts)] 29 | /// returns the mode for a given file kind and permission 30 | pub const fn mode_from_kind_and_perm(kind: FileType, perm: u16) -> u32 { 31 | kind.const_into_mode_t() | perm as mode_t 32 | } 33 | 34 | // Some platforms like Linux x86_64 have mode_t = u32, and lint warns of a trivial_numeric_casts. 35 | // But others like macOS x86_64 have mode_t = u16, requiring a typecast. So, just silence lint. 36 | #[cfg(all( 37 | not(target_os = "linux"), 38 | any(target_os = "freebsd", target_os = "macos") 39 | ))] 40 | #[allow(trivial_numeric_casts)] 41 | /// returns the mode for a given file kind and permission 42 | pub const fn mode_from_kind_and_perm(kind: FileType, perm: u16) -> u32 { 43 | (kind.const_into_mode_t() | perm as mode_t) as u32 44 | } 45 | 46 | /// returns the permission for a given file kind and mode 47 | #[allow(clippy::unnecessary_cast)] // Not unnecessary on all platforms. 48 | pub const fn perm_from_mode_and_kind(kind: FileType, mode: mode_t) -> u16 { 49 | (mode ^ kind.const_into_mode_t()) as u16 50 | } 51 | 52 | #[inline] 53 | pub const fn get_padding_size(dir_entry_size: usize) -> usize { 54 | // 64bit align 55 | let entry_size = (dir_entry_size + mem::size_of::() - 1) & !(mem::size_of::() - 1); 56 | 57 | entry_size - dir_entry_size 58 | } 59 | 60 | pub fn get_bincode_config() -> impl Options { 61 | DefaultOptions::new() 62 | .with_little_endian() 63 | .allow_trailing_bytes() 64 | .with_fixint_encoding() 65 | } 66 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! FUSE user-space library async version implementation. 2 | //! 3 | //! This is an improved rewrite of the FUSE user-space library to fully take advantage of Rust's 4 | //! architecture. 5 | //! 6 | //! This library doesn't depend on `libfuse`, unless enable `unprivileged` feature, this feature 7 | //! will support mount the filesystem without root permission by using `fusermount3` binary. 8 | //! 9 | //! # Features: 10 | //! 11 | //! - `file-lock`: enable POSIX file lock feature. 12 | //! - `async-io-runtime`: use [async_io](https://docs.rs/async-io) and 13 | //! [async-global-executor](https://docs.rs/async-global-executor) to drive async io and task. 14 | //! - `tokio-runtime`: use [tokio](https://docs.rs/tokio) runtime to drive async io and task. 15 | //! - `unprivileged`: allow mount filesystem without root permission by using `fusermount3`. 16 | //! 17 | //! # Notes: 18 | //! 19 | //! You must enable `async-io-runtime` or `tokio-runtime` feature. 20 | 21 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] 22 | 23 | #[cfg(any( 24 | all(target_os = "linux", feature = "unprivileged"), 25 | target_os = "macos" 26 | ))] 27 | use std::io::{self, ErrorKind}; 28 | #[cfg(target_os = "macos")] 29 | use std::path::Path; 30 | #[cfg(any( 31 | all(target_os = "linux", feature = "unprivileged"), 32 | target_os = "macos" 33 | ))] 34 | use std::path::PathBuf; 35 | use std::time::{Duration, SystemTime, UNIX_EPOCH}; 36 | 37 | pub use errno::Errno; 38 | pub use helper::{mode_from_kind_and_perm, perm_from_mode_and_kind}; 39 | pub use mount_options::MountOptions; 40 | use nix::sys::stat::mode_t; 41 | use raw::abi::{ 42 | fuse_setattr_in, FATTR_ATIME, FATTR_ATIME_NOW, FATTR_CTIME, FATTR_GID, FATTR_LOCKOWNER, 43 | FATTR_MODE, FATTR_MTIME, FATTR_MTIME_NOW, FATTR_SIZE, FATTR_UID, 44 | }; 45 | #[cfg(target_os = "macos")] 46 | use raw::abi::{FATTR_BKUPTIME, FATTR_CHGTIME, FATTR_CRTIME, FATTR_FLAGS}; 47 | 48 | mod errno; 49 | mod helper; 50 | mod mount_options; 51 | pub mod notify; 52 | pub mod path; 53 | pub mod raw; 54 | 55 | /// Filesystem Inode. 56 | pub type Inode = u64; 57 | 58 | /// pre-defined Result, the Err type is [`Errno`]. 59 | pub type Result = std::result::Result; 60 | 61 | /// File types 62 | #[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] 63 | pub enum FileType { 64 | /// Named pipe (S_IFIFO) 65 | NamedPipe, 66 | /// Character device (S_IFCHR) 67 | CharDevice, 68 | /// Block device (S_IFBLK) 69 | BlockDevice, 70 | /// Directory (S_IFDIR) 71 | Directory, 72 | /// Regular file (S_IFREG) 73 | RegularFile, 74 | /// Symbolic link (S_IFLNK) 75 | Symlink, 76 | /// Unix domain socket (S_IFSOCK) 77 | Socket, 78 | } 79 | 80 | impl FileType { 81 | /// convert [`FileType`] into [`mode_t`] 82 | pub const fn const_into_mode_t(self) -> mode_t { 83 | match self { 84 | FileType::NamedPipe => libc::S_IFIFO, 85 | FileType::CharDevice => libc::S_IFCHR, 86 | FileType::BlockDevice => libc::S_IFBLK, 87 | FileType::Directory => libc::S_IFDIR, 88 | FileType::RegularFile => libc::S_IFREG, 89 | FileType::Symlink => libc::S_IFLNK, 90 | FileType::Socket => libc::S_IFSOCK, 91 | } 92 | } 93 | } 94 | 95 | impl From for mode_t { 96 | fn from(kind: FileType) -> Self { 97 | kind.const_into_mode_t() 98 | } 99 | } 100 | 101 | /// the setattr argument. 102 | #[derive(Debug, Clone, Default, Eq, PartialEq)] 103 | pub struct SetAttr { 104 | /// set file or directory mode. 105 | pub mode: Option, 106 | /// set file or directory uid. 107 | pub uid: Option, 108 | /// set file or directory gid. 109 | pub gid: Option, 110 | /// set file or directory size. 111 | pub size: Option, 112 | /// the lock_owner argument. 113 | pub lock_owner: Option, 114 | /// set file or directory atime. 115 | pub atime: Option, 116 | /// set file or directory mtime. 117 | pub mtime: Option, 118 | /// set file or directory ctime. 119 | pub ctime: Option, 120 | #[cfg(target_os = "macos")] 121 | pub crtime: Option, 122 | #[cfg(target_os = "macos")] 123 | pub chgtime: Option, 124 | #[cfg(target_os = "macos")] 125 | pub bkuptime: Option, 126 | #[cfg(target_os = "macos")] 127 | pub flags: Option, 128 | } 129 | 130 | /// Helper for constructing Timestamps from fuse_setattr_in, which sign-casts 131 | /// the seconds. 132 | macro_rules! fsai2ts { 133 | ( $secs: expr, $nsecs: expr) => { 134 | Some(Timestamp::new($secs as i64, $nsecs)) 135 | }; 136 | } 137 | 138 | impl From<&fuse_setattr_in> for SetAttr { 139 | fn from(setattr_in: &fuse_setattr_in) -> Self { 140 | let mut set_attr = Self::default(); 141 | 142 | if setattr_in.valid & FATTR_MODE > 0 { 143 | set_attr.mode = Some(setattr_in.mode as mode_t); 144 | } 145 | 146 | if setattr_in.valid & FATTR_UID > 0 { 147 | set_attr.uid = Some(setattr_in.uid); 148 | } 149 | 150 | if setattr_in.valid & FATTR_GID > 0 { 151 | set_attr.gid = Some(setattr_in.gid); 152 | } 153 | 154 | if setattr_in.valid & FATTR_SIZE > 0 { 155 | set_attr.size = Some(setattr_in.size); 156 | } 157 | 158 | if setattr_in.valid & FATTR_ATIME > 0 { 159 | set_attr.atime = fsai2ts!(setattr_in.atime, setattr_in.atimensec); 160 | } 161 | 162 | if setattr_in.valid & FATTR_ATIME_NOW > 0 { 163 | set_attr.atime = Some(SystemTime::now().into()); 164 | } 165 | 166 | if setattr_in.valid & FATTR_MTIME > 0 { 167 | set_attr.mtime = fsai2ts!(setattr_in.mtime, setattr_in.mtimensec); 168 | } 169 | 170 | if setattr_in.valid & FATTR_MTIME_NOW > 0 { 171 | set_attr.mtime = Some(SystemTime::now().into()); 172 | } 173 | 174 | if setattr_in.valid & FATTR_LOCKOWNER > 0 { 175 | set_attr.lock_owner = Some(setattr_in.lock_owner); 176 | } 177 | 178 | if setattr_in.valid & FATTR_CTIME > 0 { 179 | set_attr.ctime = fsai2ts!(setattr_in.ctime, setattr_in.ctimensec); 180 | } 181 | 182 | #[cfg(target_os = "macos")] 183 | if setattr_in.valid & FATTR_CRTIME > 0 { 184 | set_attr.ctime = fsai2ts!(setattr_in.crtime, setattr_in.crtimensec); 185 | } 186 | 187 | #[cfg(target_os = "macos")] 188 | if setattr_in.valid & FATTR_CHGTIME > 0 { 189 | set_attr.ctime = fsai2ts!(setattr_in.chgtime, setattr_in.chgtimensec); 190 | } 191 | 192 | #[cfg(target_os = "macos")] 193 | if setattr_in.valid & FATTR_BKUPTIME > 0 { 194 | set_attr.ctime = fsai2ts!(setattr_in.bkuptime, setattr_in.bkuptimensec); 195 | } 196 | 197 | #[cfg(target_os = "macos")] 198 | if setattr_in.valid & FATTR_FLAGS > 0 { 199 | set_attr.flags = Some(setattr_in.flags); 200 | } 201 | 202 | set_attr 203 | } 204 | } 205 | 206 | /// A file's timestamp, according to FUSE. 207 | /// 208 | /// Nearly the same as a `libc::timespec`, except for the width of the nsec 209 | /// field. 210 | // Could implement From for Duration, and/or libc::timespec, if desired 211 | #[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)] 212 | pub struct Timestamp { 213 | pub sec: i64, 214 | pub nsec: u32, 215 | } 216 | 217 | impl Timestamp { 218 | /// Create a new timestamp from its component parts. 219 | /// 220 | /// `nsec` should be less than 1_000_000_000. 221 | pub fn new(sec: i64, nsec: u32) -> Self { 222 | Timestamp { sec, nsec } 223 | } 224 | } 225 | 226 | impl From for Timestamp { 227 | fn from(t: SystemTime) -> Self { 228 | let d = t 229 | .duration_since(UNIX_EPOCH) 230 | .unwrap_or_else(|_| Duration::from_secs(0)); 231 | Timestamp { 232 | sec: d.as_secs().try_into().unwrap_or(i64::MAX), 233 | nsec: d.subsec_nanos(), 234 | } 235 | } 236 | } 237 | 238 | #[cfg(all(target_os = "linux", feature = "unprivileged"))] 239 | fn find_fusermount3() -> io::Result { 240 | which::which("fusermount3").map_err(|err| { 241 | io::Error::new( 242 | ErrorKind::Other, 243 | format!("find fusermount3 binary failed {err:?}"), 244 | ) 245 | }) 246 | } 247 | 248 | #[cfg(target_os = "macos")] 249 | fn find_macfuse_mount() -> io::Result { 250 | if Path::new("/Library/Filesystems/macfuse.fs/Contents/Resources/mount_macfuse").exists() { 251 | Ok(PathBuf::from( 252 | "/Library/Filesystems/macfuse.fs/Contents/Resources/mount_macfuse", 253 | )) 254 | } else { 255 | Err(io::Error::new( 256 | ErrorKind::NotFound, 257 | "macfuse mount binary not found, Please install macfuse first.", 258 | )) 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /src/mount_options.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::OsString; 2 | #[cfg(any(target_os = "linux", target_os = "macos"))] 3 | use std::os::unix::io::RawFd; 4 | 5 | #[cfg(target_os = "freebsd")] 6 | use nix::mount::Nmount; 7 | #[cfg(any(target_os = "macos", target_os = "linux"))] 8 | use nix::unistd; 9 | 10 | /// mount options. 11 | #[derive(Debug, Clone, Default, Eq, PartialEq)] 12 | pub struct MountOptions { 13 | // Options implemented within fuse3 14 | pub(crate) nonempty: bool, 15 | 16 | // mount options 17 | pub(crate) allow_other: bool, 18 | pub(crate) allow_root: bool, 19 | pub(crate) custom_options: Option, 20 | #[cfg(target_os = "linux")] 21 | pub(crate) dirsync: bool, 22 | pub(crate) default_permissions: bool, 23 | pub(crate) fs_name: Option, 24 | pub(crate) gid: Option, 25 | #[cfg(any(target_os = "macos", target_os = "freebsd"))] 26 | pub(crate) intr: bool, 27 | #[cfg(target_os = "linux")] 28 | pub(crate) nodiratime: bool, 29 | pub(crate) noatime: bool, 30 | #[cfg(target_os = "linux")] 31 | pub(crate) nodev: bool, 32 | pub(crate) noexec: bool, 33 | pub(crate) nosuid: bool, 34 | pub(crate) read_only: bool, 35 | #[cfg(target_os = "freebsd")] 36 | pub(crate) suiddir: bool, 37 | pub(crate) sync: bool, 38 | pub(crate) uid: Option, 39 | 40 | // Optional FUSE features 41 | pub(crate) dont_mask: bool, 42 | pub(crate) no_open_support: bool, 43 | pub(crate) no_open_dir_support: bool, 44 | pub(crate) handle_killpriv: bool, 45 | pub(crate) write_back: bool, 46 | pub(crate) force_readdir_plus: bool, 47 | 48 | // Other FUSE mount options 49 | // default 40000 50 | pub(crate) rootmode: Option, 51 | } 52 | 53 | impl MountOptions { 54 | /// set fuse filesystem mount `user_id`, default is current uid. 55 | pub fn uid(&mut self, uid: u32) -> &mut Self { 56 | self.uid.replace(uid); 57 | 58 | self 59 | } 60 | 61 | /// set fuse filesystem mount `group_id`, default is current gid. 62 | pub fn gid(&mut self, gid: u32) -> &mut Self { 63 | self.gid.replace(gid); 64 | 65 | self 66 | } 67 | 68 | /// set fuse filesystem name, default is **fuse**. 69 | pub fn fs_name(&mut self, name: impl Into) -> &mut Self { 70 | self.fs_name.replace(name.into()); 71 | 72 | self 73 | } 74 | 75 | /// set fuse filesystem `rootmode`, default is 40000. 76 | #[cfg(target_os = "linux")] 77 | pub fn rootmode(&mut self, rootmode: u32) -> &mut Self { 78 | self.rootmode.replace(rootmode); 79 | 80 | self 81 | } 82 | 83 | /// set fuse filesystem `allow_root` mount option, default is disable. 84 | pub fn allow_root(&mut self, allow_root: bool) -> &mut Self { 85 | self.allow_root = allow_root; 86 | 87 | self 88 | } 89 | 90 | /// set fuse filesystem `allow_other` mount option, default is disable. 91 | pub fn allow_other(&mut self, allow_other: bool) -> &mut Self { 92 | self.allow_other = allow_other; 93 | 94 | self 95 | } 96 | 97 | /// set fuse filesystem `ro` mount option, default is disable. 98 | pub fn read_only(&mut self, read_only: bool) -> &mut Self { 99 | self.read_only = read_only; 100 | 101 | self 102 | } 103 | 104 | /// allow fuse filesystem mount on a non-empty directory, default is not allowed. 105 | pub fn nonempty(&mut self, nonempty: bool) -> &mut Self { 106 | self.nonempty = nonempty; 107 | 108 | self 109 | } 110 | 111 | /// set fuse filesystem `default_permissions` mount option, default is disable. 112 | /// 113 | /// When `default_permissions` is set, the [`raw::access`] and [`path::access`] is useless. 114 | /// 115 | /// [`raw::access`]: crate::raw::Filesystem::access 116 | /// [`path::access`]: crate::path::PathFilesystem::access 117 | pub fn default_permissions(&mut self, default_permissions: bool) -> &mut Self { 118 | self.default_permissions = default_permissions; 119 | 120 | self 121 | } 122 | 123 | /// don't apply umask to file mode on create operations, default is disable. 124 | pub fn dont_mask(&mut self, dont_mask: bool) -> &mut Self { 125 | self.dont_mask = dont_mask; 126 | 127 | self 128 | } 129 | 130 | /// make kernel support zero-message opens, default is disable 131 | pub fn no_open_support(&mut self, no_open_support: bool) -> &mut Self { 132 | self.no_open_support = no_open_support; 133 | 134 | self 135 | } 136 | 137 | /// make kernel support zero-message opendir, default is disable 138 | pub fn no_open_dir_support(&mut self, no_open_dir_support: bool) -> &mut Self { 139 | self.no_open_dir_support = no_open_dir_support; 140 | 141 | self 142 | } 143 | 144 | /// fs handle killing `suid`/`sgid`/`cap` on `write`/`chown`/`trunc`, default is disable. 145 | pub fn handle_killpriv(&mut self, handle_killpriv: bool) -> &mut Self { 146 | self.handle_killpriv = handle_killpriv; 147 | 148 | self 149 | } 150 | 151 | /// try to set the `FUSE_WRITEBACK_CACHE` enable write back cache for buffered writes, default 152 | /// is disable. 153 | /// 154 | /// # Notes: 155 | /// 156 | /// if enable this feature, when write flags has `FUSE_WRITE_CACHE`, file handle is guessed. 157 | pub fn write_back(&mut self, write_back: bool) -> &mut Self { 158 | self.write_back = write_back; 159 | 160 | self 161 | } 162 | 163 | /// force filesystem use readdirplus only, when kernel use readdir will return `ENOSYS`, 164 | /// default is disable. 165 | /// 166 | /// # Notes: 167 | /// this may don't work with some old Linux Kernel. 168 | pub fn force_readdir_plus(&mut self, force_readdir_plus: bool) -> &mut Self { 169 | self.force_readdir_plus = force_readdir_plus; 170 | 171 | self 172 | } 173 | 174 | /// set custom options for fuse filesystem, the custom options will be used in mount 175 | pub fn custom_options(&mut self, custom_options: impl Into) -> &mut Self { 176 | self.custom_options = Some(custom_options.into()); 177 | 178 | self 179 | } 180 | 181 | #[cfg(target_os = "freebsd")] 182 | pub(crate) fn build(&self) -> Nmount { 183 | let mut nmount = Nmount::new(); 184 | nmount 185 | .str_opt(c"fstype", c"fusefs") 186 | .str_opt(c"from", c"/dev/fuse"); 187 | if self.allow_other { 188 | nmount.null_opt(c"allow_other"); 189 | } 190 | if self.allow_root { 191 | nmount.null_opt(c"allow_root"); 192 | } 193 | if self.default_permissions { 194 | nmount.null_opt(c"default_permissions"); 195 | } 196 | if let Some(fs_name) = &self.fs_name { 197 | nmount.str_opt_owned(c"subtype=", fs_name.as_str()); 198 | } 199 | if self.intr { 200 | nmount.null_opt(c"intr"); 201 | } 202 | if let Some(custom_options) = self.custom_options.as_ref() { 203 | nmount.null_opt_owned(custom_options.as_os_str()); 204 | } 205 | // TODO: additional options: push_symlinks_in, max_read=, timeout= 206 | nmount 207 | } 208 | 209 | #[cfg(target_os = "linux")] 210 | pub(crate) fn build(&self, fd: RawFd) -> OsString { 211 | let mut opts = vec![ 212 | format!("fd={fd}"), 213 | format!( 214 | "user_id={}", 215 | self.uid.unwrap_or_else(|| unistd::getuid().as_raw()) 216 | ), 217 | format!( 218 | "group_id={}", 219 | self.gid.unwrap_or_else(|| unistd::getgid().as_raw()) 220 | ), 221 | format!("rootmode={}", self.rootmode.unwrap_or(40000)), 222 | ]; 223 | 224 | if self.allow_root { 225 | opts.push("allow_root".to_string()); 226 | } 227 | 228 | if self.allow_other { 229 | opts.push("allow_other".to_string()); 230 | } 231 | 232 | if self.default_permissions { 233 | opts.push("default_permissions".to_string()); 234 | } 235 | 236 | let mut options = OsString::from(opts.join(",")); 237 | 238 | if let Some(custom_options) = &self.custom_options { 239 | options.push(","); 240 | options.push(custom_options); 241 | } 242 | 243 | options 244 | } 245 | 246 | #[cfg(target_os = "macos")] 247 | pub(crate) fn build(&self) -> OsString { 248 | let mut opts = vec![String::from("-o fsname=ofs")]; 249 | 250 | if self.allow_root { 251 | opts.push("-o allow_root".to_string()); 252 | } 253 | 254 | if self.allow_other { 255 | opts.push("-o allow_other".to_string()); 256 | } 257 | 258 | let mut options = OsString::from(opts.join(" ")); 259 | 260 | if let Some(custom_options) = &self.custom_options { 261 | options.push(" "); 262 | options.push(custom_options); 263 | } 264 | 265 | options 266 | } 267 | 268 | #[cfg(all(target_os = "linux", feature = "unprivileged"))] 269 | pub(crate) fn build_with_unprivileged(&self) -> OsString { 270 | let mut opts = vec![ 271 | format!( 272 | "user_id={}", 273 | self.uid.unwrap_or_else(|| unistd::getuid().as_raw()) 274 | ), 275 | format!( 276 | "group_id={}", 277 | self.gid.unwrap_or_else(|| unistd::getgid().as_raw()) 278 | ), 279 | format!("rootmode={}", self.rootmode.unwrap_or(40000)), 280 | format!( 281 | "fsname={}", 282 | self.fs_name.as_ref().unwrap_or(&"fuse".to_string()) 283 | ), 284 | ]; 285 | 286 | if self.allow_root { 287 | opts.push("allow_root".to_string()); 288 | } 289 | 290 | if self.allow_other { 291 | opts.push("allow_other".to_string()); 292 | } 293 | 294 | if self.read_only { 295 | opts.push("ro".to_string()); 296 | } 297 | 298 | if self.default_permissions { 299 | opts.push("default_permissions".to_string()); 300 | } 301 | 302 | let mut options = OsString::from(opts.join(",")); 303 | 304 | if let Some(custom_options) = &self.custom_options { 305 | options.push(","); 306 | options.push(custom_options); 307 | } 308 | 309 | options 310 | } 311 | 312 | #[cfg(target_os = "freebsd")] 313 | pub(crate) fn flags(&self) -> nix::mount::MntFlags { 314 | use nix::mount::MntFlags; 315 | 316 | let mut flags = MntFlags::empty(); 317 | if self.noatime { 318 | flags.insert(MntFlags::MNT_NOATIME); 319 | } 320 | if self.noexec { 321 | flags.insert(MntFlags::MNT_NOEXEC); 322 | } 323 | if self.nosuid { 324 | flags.insert(MntFlags::MNT_NOSUID); 325 | } 326 | if self.read_only { 327 | flags.insert(MntFlags::MNT_RDONLY); 328 | } 329 | if self.suiddir { 330 | flags.insert(MntFlags::MNT_SUIDDIR); 331 | } 332 | if self.sync { 333 | flags.insert(MntFlags::MNT_SYNCHRONOUS); 334 | } 335 | flags 336 | } 337 | 338 | #[cfg(target_os = "macos")] 339 | pub(crate) fn flags(&self) -> nix::mount::MntFlags { 340 | use nix::mount::MntFlags; 341 | 342 | let mut flags = MntFlags::empty(); 343 | if self.noatime { 344 | flags.insert(MntFlags::MNT_NOATIME); 345 | } 346 | if self.noexec { 347 | flags.insert(MntFlags::MNT_NOEXEC); 348 | } 349 | if self.nosuid { 350 | flags.insert(MntFlags::MNT_NOSUID); 351 | } 352 | if self.read_only { 353 | flags.insert(MntFlags::MNT_RDONLY); 354 | } 355 | 356 | if self.sync { 357 | flags.insert(MntFlags::MNT_SYNCHRONOUS); 358 | } 359 | flags 360 | } 361 | 362 | #[cfg(target_os = "linux")] 363 | pub(crate) fn flags(&self) -> nix::mount::MsFlags { 364 | use nix::mount::MsFlags; 365 | 366 | let mut flags = MsFlags::empty(); 367 | if self.dirsync { 368 | flags.insert(MsFlags::MS_DIRSYNC); 369 | } 370 | if self.noatime { 371 | flags.insert(MsFlags::MS_NOATIME); 372 | } 373 | if self.nodev { 374 | flags.insert(MsFlags::MS_NODEV); 375 | } 376 | if self.nodiratime { 377 | flags.insert(MsFlags::MS_NODIRATIME); 378 | } 379 | if self.noexec { 380 | flags.insert(MsFlags::MS_NOEXEC); 381 | } 382 | if self.nosuid { 383 | flags.insert(MsFlags::MS_NOSUID); 384 | } 385 | if self.read_only { 386 | flags.insert(MsFlags::MS_RDONLY); 387 | } 388 | if self.sync { 389 | flags.insert(MsFlags::MS_SYNCHRONOUS); 390 | } 391 | flags 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /src/notify.rs: -------------------------------------------------------------------------------- 1 | //! notify kernel. 2 | 3 | use std::ffi::OsString; 4 | use std::os::unix::ffi::OsStrExt; 5 | 6 | use bincode::Options; 7 | use bytes::{Buf, Bytes}; 8 | use futures_channel::mpsc::UnboundedSender; 9 | use futures_util::future::Either; 10 | use futures_util::sink::SinkExt; 11 | 12 | use crate::helper::get_bincode_config; 13 | use crate::raw::abi::{ 14 | fuse_notify_code, fuse_notify_delete_out, fuse_notify_inval_entry_out, 15 | fuse_notify_inval_inode_out, fuse_notify_poll_wakeup_out, fuse_notify_retrieve_out, 16 | fuse_notify_store_out, fuse_out_header, FUSE_NOTIFY_DELETE_OUT_SIZE, 17 | FUSE_NOTIFY_INVAL_ENTRY_OUT_SIZE, FUSE_NOTIFY_INVAL_INODE_OUT_SIZE, 18 | FUSE_NOTIFY_POLL_WAKEUP_OUT_SIZE, FUSE_NOTIFY_RETRIEVE_OUT_SIZE, FUSE_NOTIFY_STORE_OUT_SIZE, 19 | FUSE_OUT_HEADER_SIZE, 20 | }; 21 | use crate::raw::FuseData; 22 | 23 | #[derive(Debug, Clone)] 24 | /// notify kernel there are something need to handle. 25 | pub struct Notify { 26 | sender: UnboundedSender, 27 | } 28 | 29 | impl Notify { 30 | pub(crate) fn new(sender: UnboundedSender) -> Self { 31 | Self { sender } 32 | } 33 | 34 | /// notify kernel there are something need to handle. If notify failed, the `kind` will be 35 | /// return in `Err`. 36 | async fn notify(&mut self, kind: NotifyKind) -> Result<(), NotifyKind> { 37 | let data = match &kind { 38 | NotifyKind::Wakeup { kh } => { 39 | let out_header = fuse_out_header { 40 | len: (FUSE_OUT_HEADER_SIZE + FUSE_NOTIFY_POLL_WAKEUP_OUT_SIZE) as u32, 41 | error: fuse_notify_code::FUSE_POLL as i32, 42 | unique: 0, 43 | }; 44 | 45 | let wakeup_out = fuse_notify_poll_wakeup_out { kh: *kh }; 46 | 47 | let mut data = 48 | Vec::with_capacity(FUSE_OUT_HEADER_SIZE + FUSE_NOTIFY_POLL_WAKEUP_OUT_SIZE); 49 | 50 | get_bincode_config() 51 | .serialize_into(&mut data, &out_header) 52 | .expect("vec size is not enough"); 53 | get_bincode_config() 54 | .serialize_into(&mut data, &wakeup_out) 55 | .expect("vec size is not enough"); 56 | 57 | Either::Left(data) 58 | } 59 | 60 | NotifyKind::InvalidInode { inode, offset, len } => { 61 | let out_header = fuse_out_header { 62 | len: (FUSE_OUT_HEADER_SIZE + FUSE_NOTIFY_INVAL_INODE_OUT_SIZE) as u32, 63 | error: fuse_notify_code::FUSE_NOTIFY_INVAL_INODE as i32, 64 | unique: 0, 65 | }; 66 | 67 | let invalid_inode_out = fuse_notify_inval_inode_out { 68 | ino: *inode, 69 | off: *offset, 70 | len: *len, 71 | }; 72 | 73 | let mut data = 74 | Vec::with_capacity(FUSE_OUT_HEADER_SIZE + FUSE_NOTIFY_INVAL_INODE_OUT_SIZE); 75 | 76 | get_bincode_config() 77 | .serialize_into(&mut data, &out_header) 78 | .expect("vec size is not enough"); 79 | get_bincode_config() 80 | .serialize_into(&mut data, &invalid_inode_out) 81 | .expect("vec size is not enough"); 82 | 83 | Either::Left(data) 84 | } 85 | 86 | NotifyKind::InvalidEntry { parent, name } => { 87 | let out_header = fuse_out_header { 88 | len: (FUSE_OUT_HEADER_SIZE + FUSE_NOTIFY_INVAL_ENTRY_OUT_SIZE) as u32, 89 | error: fuse_notify_code::FUSE_NOTIFY_INVAL_ENTRY as i32, 90 | unique: 0, 91 | }; 92 | 93 | let invalid_entry_out = fuse_notify_inval_entry_out { 94 | parent: *parent, 95 | namelen: name.len() as _, 96 | _padding: 0, 97 | }; 98 | 99 | let mut data = 100 | Vec::with_capacity(FUSE_OUT_HEADER_SIZE + FUSE_NOTIFY_INVAL_ENTRY_OUT_SIZE); 101 | 102 | get_bincode_config() 103 | .serialize_into(&mut data, &out_header) 104 | .expect("vec size is not enough"); 105 | get_bincode_config() 106 | .serialize_into(&mut data, &invalid_entry_out) 107 | .expect("vec size is not enough"); 108 | 109 | // TODO should I add null at the end? 110 | 111 | Either::Right((data, Bytes::copy_from_slice(name.as_bytes()))) 112 | } 113 | 114 | NotifyKind::Delete { 115 | parent, 116 | child, 117 | name, 118 | } => { 119 | let out_header = fuse_out_header { 120 | len: (FUSE_OUT_HEADER_SIZE + FUSE_NOTIFY_DELETE_OUT_SIZE) as u32, 121 | error: fuse_notify_code::FUSE_NOTIFY_DELETE as i32, 122 | unique: 0, 123 | }; 124 | 125 | let delete_out = fuse_notify_delete_out { 126 | parent: *parent, 127 | child: *child, 128 | namelen: name.len() as _, 129 | _padding: 0, 130 | }; 131 | 132 | let mut data = 133 | Vec::with_capacity(FUSE_OUT_HEADER_SIZE + FUSE_NOTIFY_DELETE_OUT_SIZE); 134 | 135 | get_bincode_config() 136 | .serialize_into(&mut data, &out_header) 137 | .expect("vec size is not enough"); 138 | get_bincode_config() 139 | .serialize_into(&mut data, &delete_out) 140 | .expect("vec size is not enough"); 141 | 142 | // TODO should I add null at the end? 143 | 144 | Either::Right((data, Bytes::copy_from_slice(name.as_bytes()))) 145 | } 146 | 147 | NotifyKind::Store { 148 | inode, 149 | offset, 150 | data, 151 | } => { 152 | let out_header = fuse_out_header { 153 | len: (FUSE_OUT_HEADER_SIZE + FUSE_NOTIFY_STORE_OUT_SIZE) as u32, 154 | error: fuse_notify_code::FUSE_NOTIFY_STORE as i32, 155 | unique: 0, 156 | }; 157 | 158 | let store_out = fuse_notify_store_out { 159 | nodeid: *inode, 160 | offset: *offset, 161 | size: data.len() as _, 162 | _padding: 0, 163 | }; 164 | 165 | let mut data_buf = 166 | Vec::with_capacity(FUSE_OUT_HEADER_SIZE + FUSE_NOTIFY_STORE_OUT_SIZE); 167 | 168 | get_bincode_config() 169 | .serialize_into(&mut data_buf, &out_header) 170 | .expect("vec size is not enough"); 171 | get_bincode_config() 172 | .serialize_into(&mut data_buf, &store_out) 173 | .expect("vec size is not enough"); 174 | 175 | Either::Right((data_buf, data.clone())) 176 | } 177 | 178 | NotifyKind::Retrieve { 179 | notify_unique, 180 | inode, 181 | offset, 182 | size, 183 | } => { 184 | let out_header = fuse_out_header { 185 | len: (FUSE_OUT_HEADER_SIZE + FUSE_NOTIFY_RETRIEVE_OUT_SIZE) as u32, 186 | error: fuse_notify_code::FUSE_NOTIFY_RETRIEVE as i32, 187 | unique: 0, 188 | }; 189 | 190 | let retrieve_out = fuse_notify_retrieve_out { 191 | notify_unique: *notify_unique, 192 | nodeid: *inode, 193 | offset: *offset, 194 | size: *size, 195 | _padding: 0, 196 | }; 197 | 198 | let mut data = 199 | Vec::with_capacity(FUSE_OUT_HEADER_SIZE + FUSE_NOTIFY_RETRIEVE_OUT_SIZE); 200 | 201 | get_bincode_config() 202 | .serialize_into(&mut data, &out_header) 203 | .expect("vec size is not enough"); 204 | get_bincode_config() 205 | .serialize_into(&mut data, &retrieve_out) 206 | .expect("vec size is not enough"); 207 | 208 | Either::Left(data) 209 | } 210 | }; 211 | 212 | self.sender.send(data).await.or(Err(kind)) 213 | } 214 | 215 | /// try to notify kernel the IO is ready, kernel can wakeup the waiting program. 216 | pub async fn wakeup(mut self, kh: u64) { 217 | let _ = self.notify(NotifyKind::Wakeup { kh }).await; 218 | } 219 | 220 | /// try to notify the cache invalidation about an inode. 221 | pub async fn invalid_inode(mut self, inode: u64, offset: i64, len: i64) { 222 | let _ = self 223 | .notify(NotifyKind::InvalidInode { inode, offset, len }) 224 | .await; 225 | } 226 | 227 | /// try to notify the invalidation about a directory entry. 228 | pub async fn invalid_entry(mut self, parent: u64, name: OsString) { 229 | let _ = self.notify(NotifyKind::InvalidEntry { parent, name }).await; 230 | } 231 | 232 | /// try to notify a directory entry has been deleted. 233 | pub async fn delete(mut self, parent: u64, child: u64, name: OsString) { 234 | let _ = self 235 | .notify(NotifyKind::Delete { 236 | parent, 237 | child, 238 | name, 239 | }) 240 | .await; 241 | } 242 | 243 | /// try to push the data in an inode for updating the kernel cache. 244 | pub async fn store(mut self, inode: u64, offset: u64, mut data: impl Buf) { 245 | let _ = self 246 | .notify(NotifyKind::Store { 247 | inode, 248 | offset, 249 | data: data.copy_to_bytes(data.remaining()), 250 | }) 251 | .await; 252 | } 253 | 254 | /// try to retrieve data in an inode from the kernel cache. 255 | pub async fn retrieve(mut self, notify_unique: u64, inode: u64, offset: u64, size: u32) { 256 | let _ = self 257 | .notify(NotifyKind::Retrieve { 258 | notify_unique, 259 | inode, 260 | offset, 261 | size, 262 | }) 263 | .await; 264 | } 265 | } 266 | 267 | #[derive(Debug)] 268 | /// the kind of notify. 269 | enum NotifyKind { 270 | /// notify the IO is ready. 271 | Wakeup { kh: u64 }, 272 | 273 | // TODO need check is right or not 274 | /// notify the cache invalidation about an inode. 275 | InvalidInode { inode: u64, offset: i64, len: i64 }, 276 | 277 | /// notify the invalidation about a directory entry. 278 | InvalidEntry { parent: u64, name: OsString }, 279 | 280 | /// notify a directory entry has been deleted. 281 | Delete { 282 | parent: u64, 283 | child: u64, 284 | name: OsString, 285 | }, 286 | 287 | /// push the data in an inode for updating the kernel cache. 288 | Store { 289 | inode: u64, 290 | offset: u64, 291 | data: Bytes, 292 | }, 293 | 294 | /// retrieve data in an inode from the kernel cache. 295 | Retrieve { 296 | notify_unique: u64, 297 | inode: u64, 298 | offset: u64, 299 | size: u32, 300 | }, 301 | } 302 | -------------------------------------------------------------------------------- /src/path/inode_generator.rs: -------------------------------------------------------------------------------- 1 | use slab::Slab; 2 | 3 | use crate::Inode; 4 | 5 | #[derive(Debug)] 6 | pub struct InodeGenerator { 7 | slab: Slab<()>, 8 | } 9 | 10 | impl InodeGenerator { 11 | pub fn new() -> Self { 12 | let mut slab = Slab::new(); 13 | // drop 0 key 14 | slab.insert(()); 15 | 16 | Self { slab } 17 | } 18 | 19 | pub fn allocate_inode(&mut self) -> Inode { 20 | self.slab.insert(()) as _ 21 | } 22 | 23 | pub fn release_inode(&mut self, inode: Inode) { 24 | if self.slab.contains(inode as _) { 25 | self.slab.remove(inode as _); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/path/mod.rs: -------------------------------------------------------------------------------- 1 | //! path based 2 | //! 3 | //! it is recommend to use path based [`PathFilesystem`] first, [`PathFilesystem`] is more simple 4 | //! than inode based [`Filesystem`][crate::raw::Filesystem]. However if you want to control the 5 | //! inode or do the path<->inode map on yourself, use [`Filesystem`][crate::raw::Filesystem]. 6 | 7 | pub use path_filesystem::PathFilesystem; 8 | pub use session::Session; 9 | 10 | pub use crate::raw::Request; 11 | 12 | mod inode_generator; 13 | mod inode_path_bridge; 14 | mod path_filesystem; 15 | pub mod reply; 16 | mod session; 17 | 18 | pub mod prelude { 19 | pub use super::reply::FileAttr; 20 | pub use super::reply::*; 21 | pub use super::PathFilesystem; 22 | pub use super::Request; 23 | pub use super::Session; 24 | pub use crate::notify::Notify; 25 | pub use crate::FileType; 26 | pub use crate::SetAttr; 27 | } 28 | -------------------------------------------------------------------------------- /src/path/path_filesystem.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::OsStr; 2 | 3 | use bytes::Bytes; 4 | use futures_util::stream::Stream; 5 | 6 | use super::reply::*; 7 | use super::Request; 8 | use crate::notify::Notify; 9 | use crate::{Result, SetAttr}; 10 | 11 | #[allow(unused_variables)] 12 | #[trait_make::make(Send)] 13 | /// Path based filesystem trait. 14 | pub trait PathFilesystem { 15 | /// initialize filesystem. Called before any other filesystem method. 16 | async fn init(&self, req: Request) -> Result; 17 | 18 | /// clean up filesystem. Called on filesystem exit which is fuseblk, in normal fuse filesystem, 19 | /// kernel may call forget for root. There is some discuss for this 20 | /// , 21 | /// 22 | async fn destroy(&self, req: Request); 23 | 24 | /// look up a directory entry by name and get its attributes. 25 | async fn lookup(&self, req: Request, parent: &OsStr, name: &OsStr) -> Result { 26 | Err(libc::ENOSYS.into()) 27 | } 28 | 29 | /// forget an path. The nlookup parameter indicates the number of lookups previously 30 | /// performed on this path. If the filesystem implements path lifetimes, it is recommended 31 | /// that paths acquire a single reference on each lookup, and lose nlookup references on each 32 | /// forget. The filesystem may ignore forget calls, if the paths don't need to have a limited 33 | /// lifetime. On unmount it is not guaranteed, that all referenced paths will receive a forget 34 | /// message. When filesystem is normal(not fuseblk) and unmounting, kernel may send forget 35 | /// request for root and this library will stop session after call forget. There is some 36 | /// discussion for this , 37 | /// 38 | /// 39 | async fn forget(&self, req: Request, parent: &OsStr, nlookup: u64) {} 40 | 41 | /// get file attributes. If `fh` is None, means `fh` is not set. If `path` is None, means the 42 | /// path may be deleted. 43 | async fn getattr( 44 | &self, 45 | req: Request, 46 | path: Option<&OsStr>, 47 | fh: Option, 48 | flags: u32, 49 | ) -> Result { 50 | Err(libc::ENOSYS.into()) 51 | } 52 | 53 | /// set file attributes. If `fh` is None, means `fh` is not set. If `path` is None, means the 54 | /// path may be deleted. 55 | async fn setattr( 56 | &self, 57 | req: Request, 58 | path: Option<&OsStr>, 59 | fh: Option, 60 | set_attr: SetAttr, 61 | ) -> Result { 62 | Err(libc::ENOSYS.into()) 63 | } 64 | 65 | /// read symbolic link. 66 | async fn readlink(&self, req: Request, path: &OsStr) -> Result { 67 | Err(libc::ENOSYS.into()) 68 | } 69 | 70 | /// create a symbolic link. 71 | async fn symlink( 72 | &self, 73 | req: Request, 74 | parent: &OsStr, 75 | name: &OsStr, 76 | link_path: &OsStr, 77 | ) -> Result { 78 | Err(libc::ENOSYS.into()) 79 | } 80 | 81 | /// create file node. Create a regular file, character device, block device, fifo or socket 82 | /// node. When creating file, most cases user only need to implement 83 | /// [`create`][PathFilesystem::create]. 84 | async fn mknod( 85 | &self, 86 | req: Request, 87 | parent: &OsStr, 88 | name: &OsStr, 89 | mode: u32, 90 | rdev: u32, 91 | ) -> Result { 92 | Err(libc::ENOSYS.into()) 93 | } 94 | 95 | /// create a directory. 96 | async fn mkdir( 97 | &self, 98 | req: Request, 99 | parent: &OsStr, 100 | name: &OsStr, 101 | mode: u32, 102 | umask: u32, 103 | ) -> Result { 104 | Err(libc::ENOSYS.into()) 105 | } 106 | 107 | /// remove a file. 108 | async fn unlink(&self, req: Request, parent: &OsStr, name: &OsStr) -> Result<()> { 109 | Err(libc::ENOSYS.into()) 110 | } 111 | 112 | /// remove a directory. 113 | async fn rmdir(&self, req: Request, parent: &OsStr, name: &OsStr) -> Result<()> { 114 | Err(libc::ENOSYS.into()) 115 | } 116 | 117 | /// rename a file or directory. 118 | async fn rename( 119 | &self, 120 | req: Request, 121 | origin_parent: &OsStr, 122 | origin_name: &OsStr, 123 | parent: &OsStr, 124 | name: &OsStr, 125 | ) -> Result<()> { 126 | Err(libc::ENOSYS.into()) 127 | } 128 | 129 | /// create a hard link. 130 | async fn link( 131 | &self, 132 | req: Request, 133 | path: &OsStr, 134 | new_parent: &OsStr, 135 | new_name: &OsStr, 136 | ) -> Result { 137 | Err(libc::ENOSYS.into()) 138 | } 139 | 140 | /// open a file. Open flags (with the exception of `O_CREAT`, `O_EXCL` and `O_NOCTTY`) are 141 | /// available in flags. Filesystem may store an arbitrary file handle (pointer, index, etc) in 142 | /// fh, and use this in other all other file operations (read, write, flush, release, fsync). 143 | /// Filesystem may also implement stateless file I/O and not store anything in fh. There are 144 | /// also some flags (`direct_io`, `keep_cache`) which the filesystem may set, to change the way 145 | /// the file is opened. A file system need not implement this method if it 146 | /// sets [`MountOptions::no_open_support`][crate::MountOptions::no_open_support] and if the 147 | /// kernel supports `FUSE_NO_OPEN_SUPPORT`. 148 | /// 149 | /// # Notes: 150 | /// 151 | /// See `fuse_file_info` structure in 152 | /// [fuse_common.h](https://libfuse.github.io/doxygen/include_2fuse__common_8h_source.html) for 153 | /// more details. 154 | async fn open(&self, req: Request, path: &OsStr, flags: u32) -> Result { 155 | Err(libc::ENOSYS.into()) 156 | } 157 | 158 | /// read data. Read should send exactly the number of bytes requested except on EOF or error, 159 | /// otherwise the rest of the data will be substituted with zeroes. An exception to this is 160 | /// when the file has been opened in `direct_io` mode, in which case the return value of the 161 | /// read system call will reflect the return value of this operation. `fh` will contain the 162 | /// value set by the open method, or will be undefined if the open method didn't set any value. 163 | /// when `path` is None, it means the path may be deleted. 164 | async fn read( 165 | &self, 166 | req: Request, 167 | path: Option<&OsStr>, 168 | fh: u64, 169 | offset: u64, 170 | size: u32, 171 | ) -> Result { 172 | Err(libc::ENOSYS.into()) 173 | } 174 | 175 | /// write data. Write should return exactly the number of bytes requested except on error. An 176 | /// exception to this is when the file has been opened in `direct_io` mode, in which case the 177 | /// return value of the write system call will reflect the return value of this operation. `fh` 178 | /// will contain the value set by the open method, or will be undefined if the open method 179 | /// didn't set any value. When `path` is None, it means the path may be deleted. When 180 | /// `write_flags` contains [`FUSE_WRITE_CACHE`](crate::raw::flags::FUSE_WRITE_CACHE), means the 181 | /// write operation is a delay write. 182 | #[allow(clippy::too_many_arguments)] 183 | async fn write( 184 | &self, 185 | req: Request, 186 | path: Option<&OsStr>, 187 | fh: u64, 188 | offset: u64, 189 | data: &[u8], 190 | write_flags: u32, 191 | flags: u32, 192 | ) -> Result { 193 | Err(libc::ENOSYS.into()) 194 | } 195 | 196 | /// get filesystem statistics. 197 | async fn statfs(&self, req: Request, path: &OsStr) -> Result { 198 | Err(libc::ENOSYS.into()) 199 | } 200 | 201 | /// release an open file. Release is called when there are no more references to an open file: 202 | /// all file descriptors are closed and all memory mappings are unmapped. For every open call 203 | /// there will be exactly one release call. The filesystem may reply with an error, but error 204 | /// values are not returned to `close()` or `munmap()` which triggered the release. `fh` will 205 | /// contain the value set by the open method, or will be undefined if the open method didn't 206 | /// set any value. `flags` will contain the same flags as for open. `flush` means flush the 207 | /// data or not when closing file. when `path` is None, it means the path may be deleted. 208 | async fn release( 209 | &self, 210 | req: Request, 211 | path: Option<&OsStr>, 212 | fh: u64, 213 | flags: u32, 214 | lock_owner: u64, 215 | flush: bool, 216 | ) -> Result<()> { 217 | Err(libc::ENOSYS.into()) 218 | } 219 | 220 | /// synchronize file contents. If the `datasync` is true, then only the user data should be 221 | /// flushed, not the metadata. when `path` is None, it means the path may be deleted. 222 | async fn fsync( 223 | &self, 224 | req: Request, 225 | path: Option<&OsStr>, 226 | fh: u64, 227 | datasync: bool, 228 | ) -> Result<()> { 229 | Ok(()) 230 | } 231 | 232 | /// set an extended attribute. 233 | async fn setxattr( 234 | &self, 235 | req: Request, 236 | path: &OsStr, 237 | name: &OsStr, 238 | value: &[u8], 239 | flags: u32, 240 | position: u32, 241 | ) -> Result<()> { 242 | Err(libc::ENOSYS.into()) 243 | } 244 | 245 | /// get an extended attribute. If size is too small, use [`ReplyXAttr::Size`] to return correct 246 | /// size. If size is enough, use [`ReplyXAttr::Data`] to send it, or return error. 247 | async fn getxattr( 248 | &self, 249 | req: Request, 250 | path: &OsStr, 251 | name: &OsStr, 252 | size: u32, 253 | ) -> Result { 254 | Err(libc::ENOSYS.into()) 255 | } 256 | 257 | /// list extended attribute names. If size is too small, use [`ReplyXAttr::Size`] to return 258 | /// correct size. If size is enough, use [`ReplyXAttr::Data`] to send it, or return error. 259 | async fn listxattr(&self, req: Request, path: &OsStr, size: u32) -> Result { 260 | Err(libc::ENOSYS.into()) 261 | } 262 | 263 | /// remove an extended attribute. 264 | async fn removexattr(&self, req: Request, path: &OsStr, name: &OsStr) -> Result<()> { 265 | Err(libc::ENOSYS.into()) 266 | } 267 | 268 | /// flush method. This is called on each `close()` of the opened file. Since file descriptors 269 | /// can be duplicated (`dup`, `dup2`, `fork`), for one open call there may be many flush calls. 270 | /// Filesystems shouldn't assume that flush will always be called after some writes, or that if 271 | /// will be called at all. `fh` will contain the value set by the open method, or will be 272 | /// undefined if the open method didn't set any value. when `path` is None, it means the path 273 | /// may be deleted. 274 | /// 275 | /// # Notes: 276 | /// 277 | /// the name of the method is misleading, since (unlike fsync) the filesystem is not forced to 278 | /// flush pending writes. One reason to flush data, is if the filesystem wants to return write 279 | /// errors. If the filesystem supports file locking operations ( 280 | /// [`setlk`][PathFilesystem::setlk], [`getlk`][PathFilesystem::getlk]) it should remove all 281 | /// locks belonging to `lock_owner`. 282 | async fn flush( 283 | &self, 284 | req: Request, 285 | path: Option<&OsStr>, 286 | fh: u64, 287 | lock_owner: u64, 288 | ) -> Result<()> { 289 | Err(libc::ENOSYS.into()) 290 | } 291 | 292 | /// open a directory. Filesystem may store an arbitrary file handle (pointer, index, etc) in 293 | /// `fh`, and use this in other all other directory stream operations 294 | /// ([`readdir`][PathFilesystem::readdir], [`releasedir`][PathFilesystem::releasedir], 295 | /// [`fsyncdir`][PathFilesystem::fsyncdir]). Filesystem may also implement stateless directory 296 | /// I/O and not store anything in `fh`. A file system need not implement this method if it 297 | /// sets [`MountOptions::no_open_dir_support`][crate::MountOptions::no_open_dir_support] and if 298 | /// the kernel supports `FUSE_NO_OPENDIR_SUPPORT`. 299 | async fn opendir(&self, req: Request, path: &OsStr, flags: u32) -> Result { 300 | Err(libc::ENOSYS.into()) 301 | } 302 | 303 | /// dir entry stream given by [`readdir`][PathFilesystem::readdir]. 304 | type DirEntryStream<'a>: Stream> + Send + 'a 305 | where 306 | Self: 'a; 307 | 308 | /// read directory. `offset` is used to track the offset of the directory entries. `fh` will 309 | /// contain the value set by the [`opendir`][PathFilesystem::opendir] method, or will be 310 | /// undefined if the [`opendir`][PathFilesystem::opendir] method didn't set any value. 311 | async fn readdir<'a>( 312 | &'a self, 313 | req: Request, 314 | path: &'a OsStr, 315 | fh: u64, 316 | offset: i64, 317 | ) -> Result>> { 318 | Err(libc::ENOSYS.into()) 319 | } 320 | 321 | /// release an open directory. For every [`opendir`][PathFilesystem::opendir] call there will 322 | /// be exactly one `releasedir` call. `fh` will contain the value set by the 323 | /// [`opendir`][PathFilesystem::opendir] method, or will be undefined if the 324 | /// [`opendir`][PathFilesystem::opendir] method didn't set any value. 325 | async fn releasedir(&self, req: Request, path: &OsStr, fh: u64, flags: u32) -> Result<()> { 326 | Ok(()) 327 | } 328 | 329 | /// synchronize directory contents. If the `datasync` is true, then only the directory contents 330 | /// should be flushed, not the metadata. `fh` will contain the value set by the 331 | /// [`opendir`][PathFilesystem::opendir] method, or will be undefined if the 332 | /// [`opendir`][PathFilesystem::opendir] method didn't set any value. 333 | async fn fsyncdir(&self, req: Request, path: &OsStr, fh: u64, datasync: bool) -> Result<()> { 334 | Err(libc::ENOSYS.into()) 335 | } 336 | 337 | #[cfg(feature = "file-lock")] 338 | /// test for a POSIX file lock. 339 | /// 340 | /// # Notes: 341 | /// 342 | /// this is supported on enable **`file-lock`** feature. 343 | #[allow(clippy::too_many_arguments)] 344 | async fn getlk( 345 | &self, 346 | req: Request, 347 | path: Option<&OsStr>, 348 | fh: u64, 349 | lock_owner: u64, 350 | start: u64, 351 | end: u64, 352 | r#type: u32, 353 | pid: u32, 354 | ) -> Result; 355 | 356 | #[cfg(feature = "file-lock")] 357 | /// acquire, modify or release a POSIX file lock. 358 | /// 359 | /// # Notes: 360 | /// 361 | /// this is supported on enable **`file-lock`** feature. 362 | #[allow(clippy::too_many_arguments)] 363 | async fn setlk( 364 | &self, 365 | req: Request, 366 | path: Option<&OsStr>, 367 | fh: u64, 368 | lock_owner: u64, 369 | start: u64, 370 | end: u64, 371 | r#type: u32, 372 | pid: u32, 373 | block: bool, 374 | ) -> Result<()>; 375 | 376 | /// check file access permissions. This will be called for the `access()` system call. If the 377 | /// `default_permissions` mount option is given, this method is not be called. This method is 378 | /// not called under Linux kernel versions 2.4.x. 379 | async fn access(&self, req: Request, path: &OsStr, mask: u32) -> Result<()> { 380 | Err(libc::ENOSYS.into()) 381 | } 382 | 383 | /// create and open a file. If the file does not exist, first create it with the specified 384 | /// mode, and then open it. Open flags (with the exception of `O_NOCTTY`) are available in 385 | /// flags. Filesystem may store an arbitrary file handle (pointer, index, etc) in `fh`, and use 386 | /// this in other all other file operations ([`read`][PathFilesystem::read], 387 | /// [`write`][PathFilesystem::write], [`flush`][PathFilesystem::flush], 388 | /// [`release`][PathFilesystem::release], [`fsync`][PathFilesystem::fsync]). There are also 389 | /// some flags (`direct_io`, `keep_cache`) which the filesystem may set, to change the way the 390 | /// file is opened. If this method is not implemented or under Linux kernel versions earlier 391 | /// than 2.6.15, the [`mknod`][PathFilesystem::mknod] and [`open`][PathFilesystem::open] 392 | /// methods will be called instead. 393 | /// 394 | /// # Notes: 395 | /// 396 | /// See `fuse_file_info` structure in 397 | /// [fuse_common.h](https://libfuse.github.io/doxygen/include_2fuse__common_8h_source.html) for 398 | /// more details. 399 | async fn create( 400 | &self, 401 | req: Request, 402 | parent: &OsStr, 403 | name: &OsStr, 404 | mode: u32, 405 | flags: u32, 406 | ) -> Result { 407 | Err(libc::ENOSYS.into()) 408 | } 409 | 410 | /// handle interrupt. When a operation is interrupted, an interrupt request will send to fuse 411 | /// server with the unique id of the operation. 412 | async fn interrupt(&self, req: Request, unique: u64) -> Result<()> { 413 | Err(libc::ENOSYS.into()) 414 | } 415 | 416 | /// map block index within file to block index within device. 417 | /// 418 | /// # Notes: 419 | /// 420 | /// This may not works because currently this crate doesn't support fuseblk mode yet. 421 | async fn bmap( 422 | &self, 423 | req: Request, 424 | path: &OsStr, 425 | block_size: u32, 426 | idx: u64, 427 | ) -> Result { 428 | Err(libc::ENOSYS.into()) 429 | } 430 | 431 | /*async fn ioctl( 432 | &self, 433 | req: Request, 434 | inode: u64, 435 | fh: u64, 436 | flags: u32, 437 | cmd: u32, 438 | arg: u64, 439 | in_size: u32, 440 | out_size: u32, 441 | ) -> Result { 442 | Err(libc::ENOSYS.into()) 443 | }*/ 444 | 445 | /// poll for IO readiness events. 446 | #[allow(clippy::too_many_arguments)] 447 | async fn poll( 448 | &self, 449 | req: Request, 450 | path: Option<&OsStr>, 451 | fh: u64, 452 | kn: Option, 453 | flags: u32, 454 | envents: u32, 455 | notify: &Notify, 456 | ) -> Result { 457 | Err(libc::ENOSYS.into()) 458 | } 459 | 460 | /// receive notify reply from kernel. 461 | async fn notify_reply( 462 | &self, 463 | req: Request, 464 | path: &OsStr, 465 | offset: u64, 466 | data: Bytes, 467 | ) -> Result<()> { 468 | Err(libc::ENOSYS.into()) 469 | } 470 | 471 | /// forget more than one path. This is a batch version [`forget`][PathFilesystem::forget] 472 | async fn batch_forget(&self, req: Request, paths: &[&OsStr]) {} 473 | 474 | /// allocate space for an open file. This function ensures that required space is allocated for 475 | /// specified file. 476 | /// 477 | /// # Notes: 478 | /// 479 | /// more information about `fallocate`, please see **`man 2 fallocate`** 480 | async fn fallocate( 481 | &self, 482 | req: Request, 483 | path: Option<&OsStr>, 484 | fh: u64, 485 | offset: u64, 486 | length: u64, 487 | mode: u32, 488 | ) -> Result<()> { 489 | Err(libc::ENOSYS.into()) 490 | } 491 | 492 | /// dir entry plus stream given by [`readdirplus`][PathFilesystem::readdirplus]. 493 | type DirEntryPlusStream<'a>: Stream> + Send + 'a 494 | where 495 | Self: 'a; 496 | 497 | /// read directory entries, but with their attribute, like [`readdir`][PathFilesystem::readdir] 498 | /// + [`lookup`][PathFilesystem::lookup] at the same time. 499 | async fn readdirplus<'a>( 500 | &'a self, 501 | req: Request, 502 | parent: &'a OsStr, 503 | fh: u64, 504 | offset: u64, 505 | lock_owner: u64, 506 | ) -> Result>> { 507 | Err(libc::ENOSYS.into()) 508 | } 509 | 510 | /// rename a file or directory with flags. 511 | async fn rename2( 512 | &self, 513 | req: Request, 514 | origin_parent: &OsStr, 515 | origin_name: &OsStr, 516 | parent: &OsStr, 517 | name: &OsStr, 518 | flags: u32, 519 | ) -> Result<()> { 520 | Err(libc::ENOSYS.into()) 521 | } 522 | 523 | /// find next data or hole after the specified offset. 524 | async fn lseek( 525 | &self, 526 | req: Request, 527 | path: Option<&OsStr>, 528 | fh: u64, 529 | offset: u64, 530 | whence: u32, 531 | ) -> Result { 532 | Err(libc::ENOSYS.into()) 533 | } 534 | 535 | /// copy a range of data from one file to another. This can improve performance because it 536 | /// reduce data copy: in normal, data will copy from FUSE server to kernel, then to user-space, 537 | /// then to kernel, finally send back to FUSE server. By implement this method, data will only 538 | /// copy in FUSE server internal. when `from_path` or `to_path` is None, it means the path may 539 | /// be deleted. 540 | #[allow(clippy::too_many_arguments)] 541 | async fn copy_file_range( 542 | &self, 543 | req: Request, 544 | from_path: Option<&OsStr>, 545 | fh_in: u64, 546 | offset_in: u64, 547 | to_path: Option<&OsStr>, 548 | fh_out: u64, 549 | offset_out: u64, 550 | length: u64, 551 | flags: u64, 552 | ) -> Result { 553 | Err(libc::ENOSYS.into()) 554 | } 555 | } 556 | -------------------------------------------------------------------------------- /src/path/reply.rs: -------------------------------------------------------------------------------- 1 | //! reply structures. 2 | use std::ffi::OsString; 3 | use std::num::NonZeroU32; 4 | use std::time::{Duration, SystemTime}; 5 | 6 | use futures_util::stream::Stream; 7 | 8 | #[cfg(feature = "file-lock")] 9 | pub use crate::raw::reply::ReplyLock; 10 | pub use crate::raw::reply::{ 11 | ReplyBmap, ReplyCopyFileRange, ReplyData, ReplyLSeek, ReplyOpen, ReplyPoll, ReplyStatFs, 12 | ReplyWrite, ReplyXAttr, 13 | }; 14 | use crate::{FileType, Inode, Result}; 15 | 16 | /// file attributes 17 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 18 | pub struct FileAttr { 19 | /// Size in bytes 20 | pub size: u64, 21 | /// Size in blocks 22 | pub blocks: u64, 23 | /// Time of last access 24 | pub atime: SystemTime, 25 | /// Time of last modification 26 | pub mtime: SystemTime, 27 | /// Time of last change 28 | pub ctime: SystemTime, 29 | #[cfg(target_os = "macos")] 30 | /// Time of creation (macOS only) 31 | pub crtime: SystemTime, 32 | /// Kind of file (directory, file, pipe, etc) 33 | pub kind: FileType, 34 | /// Permissions 35 | pub perm: u16, 36 | /// Number of hard links 37 | pub nlink: u32, 38 | /// User id 39 | pub uid: u32, 40 | /// Group id 41 | pub gid: u32, 42 | /// Rdev 43 | pub rdev: u32, 44 | #[cfg(target_os = "macos")] 45 | /// Flags (macOS only, see chflags(2)) 46 | pub flags: u32, 47 | pub blksize: u32, 48 | } 49 | 50 | impl From<(Inode, FileAttr)> for crate::raw::reply::FileAttr { 51 | fn from((inode, attr): (u64, FileAttr)) -> Self { 52 | crate::raw::reply::FileAttr { 53 | ino: inode, 54 | size: attr.size, 55 | blocks: attr.blocks, 56 | atime: attr.atime.into(), 57 | mtime: attr.mtime.into(), 58 | ctime: attr.ctime.into(), 59 | #[cfg(target_os = "macos")] 60 | crtime: attr.crtime.into(), 61 | kind: attr.kind, 62 | perm: attr.perm, 63 | nlink: attr.nlink, 64 | uid: attr.uid, 65 | gid: attr.gid, 66 | rdev: attr.rdev, 67 | #[cfg(target_os = "macos")] 68 | flags: attr.flags, 69 | blksize: attr.blksize, 70 | } 71 | } 72 | } 73 | 74 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 75 | /// init reply 76 | pub struct ReplyInit { 77 | /// the max write size 78 | pub max_write: NonZeroU32, 79 | } 80 | 81 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 82 | /// entry reply. 83 | pub struct ReplyEntry { 84 | /// the attribute TTL. 85 | pub ttl: Duration, 86 | /// the attribute. 87 | pub attr: FileAttr, 88 | } 89 | 90 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 91 | /// reply attr. 92 | pub struct ReplyAttr { 93 | /// the attribute TTL. 94 | pub ttl: Duration, 95 | /// the attribute. 96 | pub attr: FileAttr, 97 | } 98 | 99 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 100 | /// crate reply. 101 | pub struct ReplyCreated { 102 | /// the attribute TTL. 103 | pub ttl: Duration, 104 | /// the attribute of file. 105 | pub attr: FileAttr, 106 | /// the generation of file. 107 | pub generation: u64, 108 | /// the file handle. 109 | pub fh: u64, 110 | /// the flags. 111 | pub flags: u32, 112 | } 113 | 114 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 115 | /// directory entry. 116 | pub struct DirectoryEntry { 117 | /// entry kind. 118 | pub kind: FileType, 119 | /// entry name. 120 | pub name: OsString, 121 | /// Directory offset of the _next_ entry 122 | pub offset: i64, 123 | } 124 | 125 | /// readdir reply. 126 | pub struct ReplyDirectory>> { 127 | pub entries: S, 128 | } 129 | 130 | /*#[derive(Debug)] 131 | pub struct ReplyIoctl { 132 | pub result: i32, 133 | pub flags: u32, 134 | pub in_iovs: u32, 135 | pub out_iovs: u32, 136 | }*/ 137 | 138 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 139 | /// directory entry with attribute 140 | pub struct DirectoryEntryPlus { 141 | /// the entry kind. 142 | pub kind: FileType, 143 | /// the entry name. 144 | pub name: OsString, 145 | /// Directory offset of the _next_ entry 146 | pub offset: i64, 147 | /// the entry attribute. 148 | pub attr: FileAttr, 149 | /// the entry TTL. 150 | pub entry_ttl: Duration, 151 | /// the attribute TTL. 152 | pub attr_ttl: Duration, 153 | } 154 | 155 | /// the readdirplus reply. 156 | pub struct ReplyDirectoryPlus>> { 157 | pub entries: S, 158 | } 159 | -------------------------------------------------------------------------------- /src/path/session.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::path::Path; 3 | 4 | use crate::path::inode_path_bridge::InodePathBridge; 5 | use crate::path::path_filesystem::PathFilesystem; 6 | use crate::raw; 7 | use crate::MountOptions; 8 | 9 | #[cfg(any(feature = "async-io-runtime", feature = "tokio-runtime"))] 10 | #[derive(Debug)] 11 | /// fuse filesystem session, path based. 12 | pub struct Session { 13 | mount_options: MountOptions, 14 | } 15 | 16 | #[cfg(any(feature = "async-io-runtime", feature = "tokio-runtime"))] 17 | impl Session { 18 | /// new a fuse filesystem session. 19 | pub fn new(mount_options: MountOptions) -> Self { 20 | Self { mount_options } 21 | } 22 | 23 | #[cfg(feature = "unprivileged")] 24 | /// mount the filesystem without root permission. 25 | pub async fn mount_with_unprivileged( 26 | self, 27 | fs: FS, 28 | mount_path: P, 29 | ) -> io::Result 30 | where 31 | P: AsRef, 32 | FS: PathFilesystem + Send + Sync + 'static, 33 | { 34 | let bridge = InodePathBridge::new(fs); 35 | 36 | raw::Session::new(self.mount_options) 37 | .mount_with_unprivileged(bridge, mount_path) 38 | .await 39 | } 40 | 41 | /// mount the filesystem with root permission. 42 | pub async fn mount(self, fs: FS, mount_path: P) -> io::Result 43 | where 44 | P: AsRef, 45 | FS: PathFilesystem + Send + Sync + 'static, 46 | { 47 | let bridge = InodePathBridge::new(fs); 48 | 49 | raw::Session::new(self.mount_options) 50 | .mount(bridge, mount_path) 51 | .await 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/raw/connection/async_io.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "macos")] 2 | use std::env; 3 | #[cfg(any(target_os = "linux", target_os = "macos"))] 4 | use std::fs::File; 5 | use std::fs::OpenOptions; 6 | use std::io; 7 | #[cfg(any(target_os = "linux", target_os = "macos"))] 8 | use std::io::Write; 9 | use std::io::{IoSlice, IoSliceMut}; 10 | use std::ops::{Deref, DerefMut}; 11 | use std::os::fd::AsFd; 12 | use std::os::fd::BorrowedFd; 13 | #[cfg(any( 14 | all(target_os = "linux", feature = "unprivileged"), 15 | target_os = "freebsd", 16 | target_os = "macos" 17 | ))] 18 | use std::os::fd::OwnedFd; 19 | #[cfg(target_os = "macos")] 20 | use std::os::fd::{AsRawFd, FromRawFd}; 21 | #[cfg(any( 22 | all(target_os = "linux", feature = "unprivileged"), 23 | target_os = "macos" 24 | ))] 25 | use std::os::unix::io::RawFd; 26 | use std::pin::pin; 27 | use std::sync::Arc; 28 | #[cfg(any( 29 | all(target_os = "linux", feature = "unprivileged"), 30 | target_os = "macos" 31 | ))] 32 | use std::{ffi::OsString, path::Path}; 33 | 34 | #[cfg(any( 35 | all(target_os = "linux", feature = "unprivileged"), 36 | target_os = "freebsd" 37 | ))] 38 | use async_io::Async; 39 | use async_lock::Mutex; 40 | use async_notify::Notify; 41 | #[cfg(any( 42 | all(target_os = "linux", feature = "unprivileged"), 43 | target_os = "macos" 44 | ))] 45 | use async_process::Command; 46 | #[cfg(target_os = "macos")] 47 | use futures_util::{join, try_join}; 48 | use futures_util::{select, FutureExt}; 49 | #[cfg(any( 50 | all(target_os = "linux", feature = "unprivileged"), 51 | target_os = "macos" 52 | ))] 53 | use nix::sys::socket::{self, AddressFamily, ControlMessageOwned, MsgFlags, SockFlag, SockType}; 54 | #[cfg(any( 55 | all(target_os = "linux", feature = "unprivileged"), 56 | target_os = "freebsd", 57 | target_os = "macos" 58 | ))] 59 | use nix::sys::uio; 60 | #[cfg(any( 61 | all(target_os = "linux", feature = "unprivileged"), 62 | target_os = "macos" 63 | ))] 64 | use tracing::debug; 65 | 66 | #[cfg(all(target_os = "linux", feature = "unprivileged"))] 67 | use crate::find_fusermount3; 68 | use crate::raw::connection::CompleteIoResult; 69 | #[cfg(any( 70 | all(target_os = "linux", feature = "unprivileged"), 71 | target_os = "macos" 72 | ))] 73 | use crate::MountOptions; 74 | #[derive(Debug)] 75 | pub struct FuseConnection { 76 | unmount_notify: Arc, 77 | mode: ConnectionMode, 78 | } 79 | 80 | impl FuseConnection { 81 | #[cfg(any(target_os = "linux", target_os = "freebsd"))] 82 | pub fn new(unmount_notify: Arc) -> io::Result { 83 | #[cfg(target_os = "freebsd")] 84 | { 85 | let connection = NonBlockFuseConnection::new()?; 86 | 87 | Ok(Self { 88 | unmount_notify, 89 | mode: ConnectionMode::NonBlock(connection), 90 | }) 91 | } 92 | 93 | #[cfg(target_os = "linux")] 94 | { 95 | let connection = BlockFuseConnection::new()?; 96 | 97 | Ok(Self { 98 | unmount_notify, 99 | mode: ConnectionMode::Block(connection), 100 | }) 101 | } 102 | } 103 | 104 | #[cfg(all(target_os = "linux", feature = "unprivileged"))] 105 | pub async fn new_with_unprivileged( 106 | mount_options: MountOptions, 107 | mount_path: impl AsRef, 108 | unmount_notify: Arc, 109 | ) -> io::Result { 110 | let connection = 111 | NonBlockFuseConnection::new_with_unprivileged(mount_options, mount_path).await?; 112 | 113 | Ok(Self { 114 | unmount_notify, 115 | mode: ConnectionMode::NonBlock(connection), 116 | }) 117 | } 118 | 119 | #[cfg(target_os = "macos")] 120 | pub async fn new_with_unprivileged( 121 | mount_options: MountOptions, 122 | mount_path: impl AsRef, 123 | unmount_notify: Arc, 124 | ) -> io::Result { 125 | let connection = 126 | BlockFuseConnection::new_with_unprivileged(mount_options, mount_path).await?; 127 | 128 | Ok(Self { 129 | unmount_notify, 130 | mode: ConnectionMode::Block(connection), 131 | }) 132 | } 133 | 134 | pub async fn read_vectored + Send + 'static>( 135 | &self, 136 | header_buf: Vec, 137 | data_buf: T, 138 | ) -> Option, T), usize>> { 139 | let mut unmount_fut = pin!(self.unmount_notify.notified().fuse()); 140 | let mut read_fut = pin!(self.inner_read_vectored(header_buf, data_buf).fuse()); 141 | 142 | select! { 143 | _ = unmount_fut => None, 144 | res = read_fut => Some(res) 145 | } 146 | } 147 | 148 | async fn inner_read_vectored + Send + 'static>( 149 | &self, 150 | header_buf: Vec, 151 | data_buf: T, 152 | ) -> CompleteIoResult<(Vec, T), usize> { 153 | match &self.mode { 154 | #[cfg(any(target_os = "linux", target_os = "macos"))] 155 | ConnectionMode::Block(connection) => { 156 | connection.read_vectored(header_buf, data_buf).await 157 | } 158 | #[cfg(any( 159 | all(target_os = "linux", feature = "unprivileged"), 160 | target_os = "freebsd" 161 | ))] 162 | ConnectionMode::NonBlock(connection) => { 163 | connection.read_vectored(header_buf, data_buf).await 164 | } 165 | } 166 | } 167 | 168 | pub async fn write_vectored + Send, U: Deref + Send>( 169 | &self, 170 | data: T, 171 | body_extend_data: Option, 172 | ) -> CompleteIoResult<(T, Option), usize> { 173 | match &self.mode { 174 | #[cfg(any(target_os = "linux", target_os = "macos"))] 175 | ConnectionMode::Block(connection) => { 176 | connection.write_vectored(data, body_extend_data).await 177 | } 178 | #[cfg(any( 179 | all(target_os = "linux", feature = "unprivileged"), 180 | target_os = "freebsd" 181 | ))] 182 | ConnectionMode::NonBlock(connection) => { 183 | connection.write_vectored(data, body_extend_data).await 184 | } 185 | } 186 | } 187 | } 188 | 189 | #[derive(Debug)] 190 | enum ConnectionMode { 191 | #[cfg(any(target_os = "linux", target_os = "macos"))] 192 | Block(BlockFuseConnection), 193 | #[cfg(any( 194 | all(target_os = "linux", feature = "unprivileged"), 195 | target_os = "freebsd" 196 | ))] 197 | NonBlock(NonBlockFuseConnection), 198 | } 199 | 200 | #[cfg(any(target_os = "linux", target_os = "macos"))] 201 | #[derive(Debug)] 202 | struct BlockFuseConnection { 203 | file: File, 204 | read: Mutex<()>, 205 | write: Mutex<()>, 206 | } 207 | 208 | #[cfg(any(target_os = "linux", target_os = "macos"))] 209 | impl BlockFuseConnection { 210 | #[cfg(target_os = "linux")] 211 | pub fn new() -> io::Result { 212 | const DEV_FUSE: &str = "/dev/fuse"; 213 | 214 | let file = OpenOptions::new().write(true).read(true).open(DEV_FUSE)?; 215 | 216 | Ok(Self { 217 | file, 218 | read: Mutex::new(()), 219 | write: Mutex::new(()), 220 | }) 221 | } 222 | 223 | #[cfg(target_os = "macos")] 224 | async fn new_with_unprivileged( 225 | mount_options: MountOptions, 226 | mount_path: impl AsRef, 227 | ) -> io::Result { 228 | use std::{thread, time::Duration}; 229 | 230 | use crate::find_macfuse_mount; 231 | 232 | let (sock0, sock1) = match socket::socketpair( 233 | AddressFamily::Unix, 234 | SockType::Stream, 235 | None, 236 | SockFlag::empty(), 237 | ) { 238 | Err(err) => return Err(err.into()), 239 | 240 | Ok((sock0, sock1)) => (sock0, sock1), 241 | }; 242 | 243 | let binary_path = find_macfuse_mount()?; 244 | 245 | const ENV: &str = "_FUSE_COMMFD"; 246 | 247 | let options = mount_options.build(); 248 | 249 | debug!("mount options {:?}", options); 250 | 251 | let exec_path = match env::current_exe() { 252 | Ok(path) => path, 253 | Err(err) => return Err(err), 254 | }; 255 | 256 | let mount_path = mount_path.as_ref().as_os_str().to_os_string(); 257 | async_global_executor::spawn(async move { 258 | debug!("mount_thread start"); 259 | let fd0 = sock0.as_raw_fd(); 260 | let mut binding = Command::new(binary_path); 261 | let mut child = binding 262 | .env(ENV, fd0.to_string()) 263 | .env("_FUSE_CALL_BY_LIB", "1") 264 | .env("_FUSE_COMMVERS", "2") 265 | .env("_FUSE_DAEMON_PATH", exec_path) 266 | .args(vec![options, mount_path]) 267 | .spawn()?; 268 | if !child.status().await?.success() { 269 | return Err(io::Error::new( 270 | io::ErrorKind::Other, 271 | "fusermount run failed", 272 | )); 273 | } 274 | Ok(()) 275 | }); 276 | 277 | let fd1 = sock1.as_raw_fd(); 278 | let fd = async_global_executor::spawn_blocking(move || { 279 | debug!("wait_thread start"); 280 | // wait for macfuse mount command start 281 | // it seems that socket::recvmsg will not block to wait for the message 282 | // so we need to sleep for a while 283 | thread::sleep(Duration::from_secs(1)); 284 | // let mut buf = vec![0; 10000]; // buf should large enough 285 | let mut buf = vec![]; // it seems 0 len still works well 286 | 287 | let mut cmsg_buf = nix::cmsg_space!([RawFd; 1]); 288 | 289 | let mut bufs = [IoSliceMut::new(&mut buf)]; 290 | 291 | let msg = match socket::recvmsg::<()>( 292 | fd1, 293 | &mut bufs[..], 294 | Some(&mut cmsg_buf), 295 | MsgFlags::empty(), 296 | ) { 297 | Err(err) => return Err(err.into()), 298 | 299 | Ok(msg) => msg, 300 | }; 301 | 302 | let mut cmsgs = match msg.cmsgs() { 303 | Err(err) => return Err(err.into()), 304 | Ok(cmsgs) => cmsgs, 305 | }; 306 | 307 | let fd = if let Some(ControlMessageOwned::ScmRights(fds)) = cmsgs.next() { 308 | if fds.is_empty() { 309 | return Err(io::Error::new(io::ErrorKind::Other, "no fuse fd")); 310 | } 311 | 312 | fds[0] 313 | } else { 314 | return Err(io::Error::new(io::ErrorKind::Other, "get fuse fd failed")); 315 | }; 316 | 317 | Ok(fd) 318 | }) 319 | .await 320 | .unwrap(); 321 | 322 | // Safety: fd is valid 323 | let file = unsafe { File::from_raw_fd(fd) }; 324 | 325 | Ok(Self { 326 | file, 327 | read: Mutex::new(()), 328 | write: Mutex::new(()), 329 | }) 330 | } 331 | 332 | async fn read_vectored + Send + 'static>( 333 | &self, 334 | mut header_buf: Vec, 335 | mut data_buf: T, 336 | ) -> CompleteIoResult<(Vec, T), usize> { 337 | use std::io::Read; 338 | use std::mem::ManuallyDrop; 339 | use std::os::fd::{AsRawFd, FromRawFd}; 340 | 341 | let _guard = self.read.lock().await; 342 | let fd = self.file.as_raw_fd(); 343 | 344 | let ((header_buf, data_buf), res) = async_global_executor::spawn_blocking(move || { 345 | // Safety: when we call read, the fd is still valid, when fd is closed and file is 346 | // dropped, the read operation will return error 347 | let file = unsafe { File::from_raw_fd(fd) }; 348 | // avoid close the file 349 | let mut file = ManuallyDrop::new(file); 350 | 351 | let res = file.read_vectored(&mut [ 352 | IoSliceMut::new(&mut header_buf), 353 | IoSliceMut::new(&mut data_buf), 354 | ]); 355 | 356 | ((header_buf, data_buf), res) 357 | }) 358 | .await; 359 | 360 | ((header_buf, data_buf), res) 361 | } 362 | 363 | async fn write_vectored + Send, U: Deref + Send>( 364 | &self, 365 | data: T, 366 | body_extend_data: Option, 367 | ) -> CompleteIoResult<(T, Option), usize> { 368 | let _guard = self.write.lock().await; 369 | 370 | let res = { 371 | let body_extend_data = body_extend_data.as_deref(); 372 | 373 | match body_extend_data { 374 | None => (&self.file).write_vectored(&[IoSlice::new(data.deref())]), 375 | 376 | Some(body_extend_data) => (&self.file) 377 | .write_vectored(&[IoSlice::new(data.deref()), IoSlice::new(body_extend_data)]), 378 | } 379 | }; 380 | 381 | match res { 382 | Err(err) => ((data, body_extend_data), Err(err)), 383 | Ok(n) => ((data, body_extend_data), Ok(n)), 384 | } 385 | } 386 | } 387 | 388 | #[cfg(any( 389 | all(target_os = "linux", feature = "unprivileged"), 390 | target_os = "freebsd" 391 | ))] 392 | #[derive(Debug)] 393 | struct NonBlockFuseConnection { 394 | fd: Async, 395 | read: Mutex<()>, 396 | write: Mutex<()>, 397 | } 398 | 399 | #[cfg(any( 400 | all(target_os = "linux", feature = "unprivileged"), 401 | target_os = "freebsd" 402 | ))] 403 | impl NonBlockFuseConnection { 404 | #[cfg(target_os = "freebsd")] 405 | fn new() -> io::Result { 406 | const DEV_FUSE: &str = "/dev/fuse"; 407 | 408 | let file = OpenOptions::new().write(true).read(true).open(DEV_FUSE)?; 409 | 410 | Ok(Self { 411 | fd: Async::new(file.into())?, 412 | read: Mutex::new(()), 413 | write: Mutex::new(()), 414 | }) 415 | } 416 | 417 | #[cfg(all(target_os = "linux", feature = "unprivileged"))] 418 | async fn new_with_unprivileged( 419 | mount_options: MountOptions, 420 | mount_path: impl AsRef, 421 | ) -> io::Result { 422 | use std::os::fd::{AsRawFd, FromRawFd}; 423 | 424 | let (sock0, sock1) = match socket::socketpair( 425 | AddressFamily::Unix, 426 | SockType::SeqPacket, 427 | None, 428 | SockFlag::empty(), 429 | ) { 430 | Err(err) => return Err(err.into()), 431 | 432 | Ok((sock0, sock1)) => (sock0, sock1), 433 | }; 434 | 435 | let binary_path = find_fusermount3()?; 436 | 437 | const ENV: &str = "_FUSE_COMMFD"; 438 | 439 | let options = mount_options.build_with_unprivileged(); 440 | 441 | debug!("mount options {:?}", options); 442 | 443 | let mount_path = mount_path.as_ref().as_os_str().to_os_string(); 444 | 445 | let fd0 = sock0.as_raw_fd(); 446 | let mut child = Command::new(binary_path) 447 | .env(ENV, fd0.to_string()) 448 | .args(vec![OsString::from("-o"), options, mount_path]) 449 | .spawn()?; 450 | 451 | if !child.status().await?.success() { 452 | return Err(io::Error::new( 453 | io::ErrorKind::Other, 454 | "fusermount run failed", 455 | )); 456 | } 457 | 458 | let fd1 = sock1.as_raw_fd(); 459 | let fd = async_global_executor::spawn_blocking(move || { 460 | // let mut buf = vec![0; 10000]; // buf should large enough 461 | let mut buf = vec![]; // it seems 0 len still works well 462 | 463 | let mut cmsg_buf = nix::cmsg_space!([RawFd; 1]); 464 | 465 | let mut bufs = [IoSliceMut::new(&mut buf)]; 466 | 467 | let msg = match socket::recvmsg::<()>( 468 | fd1, 469 | &mut bufs[..], 470 | Some(&mut cmsg_buf), 471 | MsgFlags::empty(), 472 | ) { 473 | Err(err) => return Err(err.into()), 474 | 475 | Ok(msg) => msg, 476 | }; 477 | 478 | let fd = if let Some(ControlMessageOwned::ScmRights(fds)) = msg.cmsgs()?.next() { 479 | if fds.is_empty() { 480 | return Err(io::Error::new(io::ErrorKind::Other, "no fuse fd")); 481 | } 482 | 483 | fds[0] 484 | } else { 485 | return Err(io::Error::new(io::ErrorKind::Other, "get fuse fd failed")); 486 | }; 487 | 488 | Ok(fd) 489 | }) 490 | .await?; 491 | 492 | // Safety: fd is valid 493 | let fd = unsafe { OwnedFd::from_raw_fd(fd) }; 494 | 495 | Ok(Self { 496 | fd: Async::new(fd)?, 497 | read: Mutex::new(()), 498 | write: Mutex::new(()), 499 | }) 500 | } 501 | 502 | async fn read_vectored + Send + 'static>( 503 | &self, 504 | mut header_buf: Vec, 505 | mut data_buf: T, 506 | ) -> CompleteIoResult<(Vec, T), usize> { 507 | let _guard = self.read.lock().await; 508 | 509 | let res = self 510 | .fd 511 | .read_with(|fd| { 512 | uio::readv( 513 | fd, 514 | &mut [ 515 | IoSliceMut::new(&mut header_buf), 516 | IoSliceMut::new(&mut data_buf), 517 | ], 518 | ) 519 | .map_err(Into::into) 520 | }) 521 | .await; 522 | 523 | ((header_buf, data_buf), res) 524 | } 525 | 526 | async fn write_vectored + Send, U: Deref + Send>( 527 | &self, 528 | data: T, 529 | body_extend_data: Option, 530 | ) -> CompleteIoResult<(T, Option), usize> { 531 | let _guard = self.write.lock().await; 532 | 533 | let res = { 534 | let body_extend_data = body_extend_data.as_deref(); 535 | 536 | match body_extend_data { 537 | None => uio::writev(&self.fd, &[IoSlice::new(data.deref())]), 538 | 539 | Some(body_extend_data) => uio::writev( 540 | &self.fd, 541 | &[IoSlice::new(data.deref()), IoSlice::new(body_extend_data)], 542 | ), 543 | } 544 | }; 545 | 546 | match res { 547 | Err(err) => ((data, body_extend_data), Err(err.into())), 548 | Ok(n) => ((data, body_extend_data), Ok(n)), 549 | } 550 | } 551 | } 552 | 553 | impl AsFd for FuseConnection { 554 | fn as_fd(&self) -> BorrowedFd<'_> { 555 | match &self.mode { 556 | #[cfg(any(target_os = "linux", target_os = "macos"))] 557 | ConnectionMode::Block(connection) => { 558 | // Safety: we own the File 559 | connection.file.as_fd() 560 | } 561 | 562 | #[cfg(any( 563 | all(target_os = "linux", feature = "unprivileged"), 564 | target_os = "freebsd" 565 | ))] 566 | ConnectionMode::NonBlock(connection) => connection.fd.as_fd(), 567 | } 568 | } 569 | } 570 | -------------------------------------------------------------------------------- /src/raw/connection/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | #[cfg(all(not(feature = "tokio-runtime"), feature = "async-io-runtime"))] 4 | pub use async_io::FuseConnection; 5 | #[cfg(all(not(feature = "async-io-runtime"), feature = "tokio-runtime"))] 6 | pub use tokio::FuseConnection; 7 | 8 | #[cfg(feature = "async-io-runtime")] 9 | mod async_io; 10 | #[cfg(feature = "tokio-runtime")] 11 | mod tokio; 12 | 13 | pub(crate) type CompleteIoResult = (T, io::Result); 14 | -------------------------------------------------------------------------------- /src/raw/connection/tokio.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "macos")] 2 | use std::env; 3 | #[cfg(any(target_os = "linux", target_os = "macos"))] 4 | use std::fs::File; 5 | use std::fs::OpenOptions; 6 | use std::io; 7 | #[cfg(any( 8 | all(target_os = "linux", feature = "unprivileged"), 9 | target_os = "freebsd", 10 | target_os = "macos", 11 | ))] 12 | use std::io::ErrorKind; 13 | #[cfg(any(target_os = "linux", target_os = "macos"))] 14 | use std::io::Write; 15 | use std::io::{IoSlice, IoSliceMut}; 16 | use std::ops::{Deref, DerefMut}; 17 | #[cfg(any( 18 | all(target_os = "linux", feature = "unprivileged"), 19 | target_os = "freebsd", 20 | target_os = "macos", 21 | ))] 22 | use std::os::fd::OwnedFd; 23 | use std::os::fd::{AsFd, BorrowedFd}; 24 | #[cfg(any(target_os = "freebsd", target_os = "macos"))] 25 | use std::os::unix::fs::OpenOptionsExt; 26 | #[cfg(any( 27 | all(target_os = "linux", feature = "unprivileged"), 28 | target_os = "macos" 29 | ))] 30 | use std::os::unix::io::RawFd; 31 | #[cfg(target_os = "macos")] 32 | use std::os::unix::io::{AsRawFd, FromRawFd}; 33 | use std::pin::pin; 34 | use std::sync::Arc; 35 | #[cfg(any( 36 | all(target_os = "linux", feature = "unprivileged"), 37 | target_os = "macos" 38 | ))] 39 | use std::{ffi::OsString, path::Path}; 40 | 41 | use async_notify::Notify; 42 | use futures_util::lock::Mutex; 43 | use futures_util::{select, FutureExt}; 44 | #[cfg(any( 45 | all(target_os = "linux", feature = "unprivileged"), 46 | target_os = "freebsd", 47 | target_os = "macos", 48 | ))] 49 | use nix::sys::uio; 50 | #[cfg(any( 51 | all(target_os = "linux", feature = "unprivileged"), 52 | target_os = "macos" 53 | ))] 54 | use nix::{ 55 | fcntl::{FcntlArg, OFlag}, 56 | sys::socket::{self, AddressFamily, ControlMessageOwned, MsgFlags, SockFlag, SockType}, 57 | }; 58 | #[cfg(any( 59 | all(target_os = "linux", feature = "unprivileged"), 60 | target_os = "freebsd", 61 | target_os = "macos", 62 | ))] 63 | use tokio::io::unix::AsyncFd; 64 | use tokio::io::Interest; 65 | #[cfg(any( 66 | all(target_os = "linux", feature = "unprivileged"), 67 | target_os = "macos" 68 | ))] 69 | use tokio::process::Command; 70 | #[cfg(any(target_os = "linux", target_os = "macos"))] 71 | use tokio::task; 72 | #[cfg(any( 73 | all(target_os = "linux", feature = "unprivileged"), 74 | target_os = "macos" 75 | ))] 76 | use tracing::debug; 77 | #[cfg(any(target_os = "freebsd", target_os = "macos"))] 78 | use tracing::warn; 79 | 80 | use super::CompleteIoResult; 81 | #[cfg(all(target_os = "linux", feature = "unprivileged"))] 82 | use crate::find_fusermount3; 83 | #[cfg(any( 84 | all(target_os = "linux", feature = "unprivileged"), 85 | target_os = "macos" 86 | ))] 87 | use crate::MountOptions; 88 | 89 | #[derive(Debug)] 90 | pub struct FuseConnection { 91 | unmount_notify: Arc, 92 | mode: ConnectionMode, 93 | } 94 | 95 | impl FuseConnection { 96 | #[cfg(any(target_os = "linux", target_os = "freebsd"))] 97 | pub fn new(unmount_notify: Arc) -> io::Result { 98 | #[cfg(target_os = "freebsd")] 99 | { 100 | let connection = NonBlockFuseConnection::new()?; 101 | 102 | Ok(Self { 103 | unmount_notify, 104 | mode: ConnectionMode::NonBlock(connection), 105 | }) 106 | } 107 | 108 | #[cfg(target_os = "linux")] 109 | { 110 | let connection = BlockFuseConnection::new()?; 111 | 112 | Ok(Self { 113 | unmount_notify, 114 | mode: ConnectionMode::Block(connection), 115 | }) 116 | } 117 | } 118 | 119 | #[cfg(all(target_os = "linux", feature = "unprivileged"))] 120 | pub async fn new_with_unprivileged( 121 | mount_options: MountOptions, 122 | mount_path: impl AsRef, 123 | unmount_notify: Arc, 124 | ) -> io::Result { 125 | let connection = 126 | NonBlockFuseConnection::new_with_unprivileged(mount_options, mount_path).await?; 127 | 128 | Ok(Self { 129 | unmount_notify, 130 | mode: ConnectionMode::NonBlock(connection), 131 | }) 132 | } 133 | 134 | #[cfg(target_os = "macos")] 135 | pub async fn new_with_unprivileged( 136 | mount_options: MountOptions, 137 | mount_path: impl AsRef, 138 | unmount_notify: Arc, 139 | ) -> io::Result { 140 | let connection = 141 | BlockFuseConnection::new_with_unprivileged(mount_options, mount_path).await?; 142 | 143 | Ok(Self { 144 | unmount_notify, 145 | mode: ConnectionMode::Block(connection), 146 | }) 147 | } 148 | 149 | pub async fn read_vectored + Send + 'static>( 150 | &self, 151 | header_buf: Vec, 152 | data_buf: T, 153 | ) -> Option, T), usize>> { 154 | let mut unmount_fut = pin!(self.unmount_notify.notified().fuse()); 155 | let mut read_fut = pin!(self.inner_read_vectored(header_buf, data_buf).fuse()); 156 | 157 | select! { 158 | _ = unmount_fut => None, 159 | res = read_fut => Some(res) 160 | } 161 | } 162 | 163 | async fn inner_read_vectored + Send + 'static>( 164 | &self, 165 | header_buf: Vec, 166 | data_buf: T, 167 | ) -> CompleteIoResult<(Vec, T), usize> { 168 | match &self.mode { 169 | #[cfg(any(target_os = "linux", target_os = "macos"))] 170 | ConnectionMode::Block(connection) => { 171 | connection.read_vectored(header_buf, data_buf).await 172 | } 173 | #[cfg(any( 174 | all(target_os = "linux", feature = "unprivileged"), 175 | target_os = "freebsd", 176 | ))] 177 | ConnectionMode::NonBlock(connection) => { 178 | connection.read_vectored(header_buf, data_buf).await 179 | } 180 | } 181 | } 182 | 183 | pub async fn write_vectored + Send, U: Deref + Send>( 184 | &self, 185 | data: T, 186 | body_extend_data: Option, 187 | ) -> CompleteIoResult<(T, Option), usize> { 188 | match &self.mode { 189 | #[cfg(any(target_os = "linux", target_os = "macos"))] 190 | ConnectionMode::Block(connection) => { 191 | connection.write_vectored(data, body_extend_data).await 192 | } 193 | #[cfg(any( 194 | all(target_os = "linux", feature = "unprivileged"), 195 | target_os = "freebsd", 196 | ))] 197 | ConnectionMode::NonBlock(connection) => { 198 | connection.write_vectored(data, body_extend_data).await 199 | } 200 | } 201 | } 202 | } 203 | 204 | #[derive(Debug)] 205 | enum ConnectionMode { 206 | #[cfg(any(target_os = "linux", target_os = "macos"))] 207 | Block(BlockFuseConnection), 208 | #[cfg(any( 209 | all(target_os = "linux", feature = "unprivileged"), 210 | target_os = "freebsd", 211 | ))] 212 | NonBlock(NonBlockFuseConnection), 213 | } 214 | 215 | #[cfg(any(target_os = "linux", target_os = "macos"))] 216 | #[derive(Debug)] 217 | struct BlockFuseConnection { 218 | file: File, 219 | read: Mutex<()>, 220 | write: Mutex<()>, 221 | } 222 | 223 | #[cfg(any(target_os = "linux", target_os = "macos"))] 224 | impl BlockFuseConnection { 225 | #[cfg(target_os = "linux")] 226 | pub fn new() -> io::Result { 227 | const DEV_FUSE: &str = "/dev/fuse"; 228 | 229 | let file = OpenOptions::new().write(true).read(true).open(DEV_FUSE)?; 230 | 231 | Ok(Self { 232 | file, 233 | read: Mutex::new(()), 234 | write: Mutex::new(()), 235 | }) 236 | } 237 | 238 | #[cfg(target_os = "macos")] 239 | async fn new_with_unprivileged( 240 | mount_options: MountOptions, 241 | mount_path: impl AsRef, 242 | ) -> io::Result { 243 | use std::{thread, time::Duration}; 244 | 245 | use tokio::time::sleep; 246 | 247 | use crate::find_macfuse_mount; 248 | 249 | let (sock0, sock1) = match socket::socketpair( 250 | AddressFamily::Unix, 251 | SockType::Stream, 252 | None, 253 | SockFlag::empty(), 254 | ) { 255 | Err(err) => return Err(err.into()), 256 | 257 | Ok((sock0, sock1)) => (sock0, sock1), 258 | }; 259 | 260 | let binary_path = find_macfuse_mount()?; 261 | 262 | const ENV: &str = "_FUSE_COMMFD"; 263 | 264 | let options = mount_options.build(); 265 | 266 | debug!("mount options {:?}", options); 267 | 268 | let exec_path = match env::current_exe() { 269 | Ok(path) => path, 270 | Err(err) => return Err(err), 271 | }; 272 | 273 | let mount_path = mount_path.as_ref().as_os_str().to_os_string(); 274 | // macfuse_mound will block until fuse init done, so we can not join it in the current function 275 | tokio::spawn(async move { 276 | debug!("mount_thread start"); 277 | let fd0 = sock0.as_raw_fd(); 278 | let mut binding = Command::new(binary_path); 279 | let child = binding 280 | .env(ENV, fd0.to_string()) 281 | .env("_FUSE_CALL_BY_LIB", "1") 282 | .env("_FUSE_COMMVERS", "2") 283 | .env("_FUSE_DAEMON_PATH", exec_path) 284 | .args(vec![options, mount_path]); 285 | let status = child.spawn()?.wait().await?; 286 | 287 | if status.success() { 288 | Ok(()) 289 | } else { 290 | Err(io::Error::new( 291 | io::ErrorKind::Other, 292 | "fusermount run failed", 293 | )) 294 | } 295 | }); 296 | 297 | let fd1 = sock1.as_raw_fd(); 298 | // wait for macfuse mount 299 | let fd = task::spawn_blocking(move || { 300 | debug!("wait_thread start"); 301 | // wait for macfuse mount command start 302 | // it seems that socket::recvmsg will not block to wait for the message 303 | // so we need to sleep for a while 304 | thread::sleep(Duration::from_secs(1)); 305 | // let mut buf = vec![0; 10000]; // buf should large enough 306 | let mut buf = vec![]; // it seems 0 len still works well 307 | 308 | let mut cmsg_buf = nix::cmsg_space!([RawFd; 1]); 309 | 310 | let mut bufs = [IoSliceMut::new(&mut buf)]; 311 | 312 | let msg = match socket::recvmsg::<()>( 313 | fd1, 314 | &mut bufs[..], 315 | Some(&mut cmsg_buf), 316 | MsgFlags::empty(), 317 | ) { 318 | Err(err) => return Err(err.into()), 319 | 320 | Ok(msg) => msg, 321 | }; 322 | 323 | let mut cmsgs = match msg.cmsgs() { 324 | Err(err) => return Err(err.into()), 325 | Ok(cmsgs) => cmsgs, 326 | }; 327 | let fd = if let Some(ControlMessageOwned::ScmRights(fds)) = cmsgs.next() { 328 | if fds.is_empty() { 329 | return Err(io::Error::new(ErrorKind::Other, "no fuse fd")); 330 | } 331 | 332 | fds[0] 333 | } else { 334 | return Err(io::Error::new(ErrorKind::Other, "get fuse fd failed")); 335 | }; 336 | 337 | Ok(fd) 338 | }) 339 | .await 340 | .unwrap()?; 341 | 342 | let file = unsafe { File::from_raw_fd(fd) }; 343 | Ok(Self { 344 | file, 345 | read: Mutex::new(()), 346 | write: Mutex::new(()), 347 | }) 348 | } 349 | 350 | async fn read_vectored + Send + 'static>( 351 | &self, 352 | mut header_buf: Vec, 353 | mut data_buf: T, 354 | ) -> CompleteIoResult<(Vec, T), usize> { 355 | use std::io::Read; 356 | use std::mem::ManuallyDrop; 357 | use std::os::fd::{AsRawFd, FromRawFd}; 358 | 359 | let _guard = self.read.lock().await; 360 | let fd = self.file.as_raw_fd(); 361 | 362 | let ((header_buf, data_buf), res) = task::spawn_blocking(move || { 363 | // Safety: when we call read, the fd is still valid, when fd is closed and file is 364 | // dropped, the read operation will return error 365 | let file = unsafe { File::from_raw_fd(fd) }; 366 | // avoid close the file 367 | let mut file = ManuallyDrop::new(file); 368 | 369 | let res = file.read_vectored(&mut [ 370 | IoSliceMut::new(&mut header_buf), 371 | IoSliceMut::new(&mut data_buf), 372 | ]); 373 | 374 | ((header_buf, data_buf), res) 375 | }) 376 | .await 377 | .unwrap(); 378 | 379 | ((header_buf, data_buf), res) 380 | } 381 | 382 | async fn write_vectored + Send, U: Deref + Send>( 383 | &self, 384 | data: T, 385 | body_extend_data: Option, 386 | ) -> CompleteIoResult<(T, Option), usize> { 387 | let _guard = self.write.lock().await; 388 | 389 | let res = { 390 | let body_extend_data = body_extend_data.as_deref(); 391 | 392 | match body_extend_data { 393 | None => (&self.file).write_vectored(&[IoSlice::new(data.deref())]), 394 | 395 | Some(body_extend_data) => (&self.file) 396 | .write_vectored(&[IoSlice::new(data.deref()), IoSlice::new(body_extend_data)]), 397 | } 398 | }; 399 | 400 | match res { 401 | Err(err) => ((data, body_extend_data), Err(err)), 402 | Ok(n) => ((data, body_extend_data), Ok(n)), 403 | } 404 | } 405 | } 406 | 407 | #[cfg(any( 408 | all(target_os = "linux", feature = "unprivileged"), 409 | target_os = "freebsd", 410 | ))] 411 | #[derive(Debug)] 412 | struct NonBlockFuseConnection { 413 | fd: AsyncFd, 414 | read: Mutex<()>, 415 | write: Mutex<()>, 416 | } 417 | 418 | #[cfg(any( 419 | all(target_os = "linux", feature = "unprivileged"), 420 | target_os = "freebsd", 421 | ))] 422 | impl NonBlockFuseConnection { 423 | #[cfg(any(target_os = "freebsd", target_os = "macos"))] 424 | fn new() -> io::Result { 425 | #[cfg(target_os = "freebsd")] 426 | const DEV_FUSE: &str = "/dev/fuse"; 427 | 428 | match OpenOptions::new() 429 | .write(true) 430 | .read(true) 431 | .custom_flags(libc::O_NONBLOCK) 432 | .open(DEV_FUSE) 433 | { 434 | Err(e) => { 435 | if e.kind() == ErrorKind::NotFound { 436 | warn!("Cannot open {}. Is the module loaded?", DEV_FUSE); 437 | } 438 | warn!("Cannot open {}. err: {:?}", DEV_FUSE, e); 439 | Err(e) 440 | } 441 | Ok(file) => Ok(Self { 442 | fd: AsyncFd::new(file.into())?, 443 | read: Mutex::new(()), 444 | write: Mutex::new(()), 445 | }), 446 | } 447 | } 448 | 449 | #[cfg(all(target_os = "linux", feature = "unprivileged"))] 450 | async fn new_with_unprivileged( 451 | mount_options: MountOptions, 452 | mount_path: impl AsRef, 453 | ) -> io::Result { 454 | use std::os::fd::{AsRawFd, FromRawFd}; 455 | 456 | let (sock0, sock1) = match socket::socketpair( 457 | AddressFamily::Unix, 458 | SockType::SeqPacket, 459 | None, 460 | SockFlag::empty(), 461 | ) { 462 | Err(err) => return Err(err.into()), 463 | 464 | Ok((sock0, sock1)) => (sock0, sock1), 465 | }; 466 | 467 | let binary_path = find_fusermount3()?; 468 | 469 | const ENV: &str = "_FUSE_COMMFD"; 470 | 471 | let options = mount_options.build_with_unprivileged(); 472 | 473 | debug!("mount options {:?}", options); 474 | 475 | let mount_path = mount_path.as_ref().as_os_str().to_os_string(); 476 | 477 | let fd0 = sock0.as_raw_fd(); 478 | let mut child = Command::new(binary_path) 479 | .env(ENV, fd0.to_string()) 480 | .args(vec![OsString::from("-o"), options, mount_path]) 481 | .spawn()?; 482 | 483 | if !child.wait().await?.success() { 484 | return Err(io::Error::new( 485 | io::ErrorKind::Other, 486 | "fusermount run failed", 487 | )); 488 | } 489 | 490 | let fd1 = sock1.as_raw_fd(); 491 | let fd = task::spawn_blocking(move || { 492 | // let mut buf = vec![0; 10000]; // buf should large enough 493 | let mut buf = vec![]; // it seems 0 len still works well 494 | 495 | let mut cmsg_buf = nix::cmsg_space!([RawFd; 1]); 496 | 497 | let mut bufs = [IoSliceMut::new(&mut buf)]; 498 | 499 | let msg = match socket::recvmsg::<()>( 500 | fd1, 501 | &mut bufs[..], 502 | Some(&mut cmsg_buf), 503 | MsgFlags::empty(), 504 | ) { 505 | Err(err) => return Err(err.into()), 506 | 507 | Ok(msg) => msg, 508 | }; 509 | 510 | let fd = if let Some(ControlMessageOwned::ScmRights(fds)) = msg.cmsgs()?.next() { 511 | if fds.is_empty() { 512 | return Err(io::Error::new(ErrorKind::Other, "no fuse fd")); 513 | } 514 | 515 | fds[0] 516 | } else { 517 | return Err(io::Error::new(ErrorKind::Other, "get fuse fd failed")); 518 | }; 519 | 520 | Ok(fd) 521 | }) 522 | .await 523 | .unwrap()?; 524 | 525 | Self::set_fd_non_blocking(fd)?; 526 | 527 | // Safety: fd is valid 528 | let fd = unsafe { OwnedFd::from_raw_fd(fd) }; 529 | 530 | Ok(Self { 531 | fd: AsyncFd::new(fd)?, 532 | read: Mutex::new(()), 533 | write: Mutex::new(()), 534 | }) 535 | } 536 | 537 | #[cfg(any( 538 | all(target_os = "linux", feature = "unprivileged"), 539 | target_os = "macos" 540 | ))] 541 | fn set_fd_non_blocking(fd: RawFd) -> io::Result<()> { 542 | let flags = nix::fcntl::fcntl(fd, FcntlArg::F_GETFL).map_err(io::Error::from)?; 543 | debug!( 544 | "set fd {:?} to non-blocking", 545 | OFlag::from_bits_truncate(flags) 546 | ); 547 | let flags = OFlag::from_bits_truncate(flags) | OFlag::O_NONBLOCK; 548 | 549 | debug!("set fd {:?} to non-blocking", flags); 550 | nix::fcntl::fcntl(fd, FcntlArg::F_SETFL(flags)).map_err(io::Error::from)?; 551 | 552 | Ok(()) 553 | } 554 | 555 | async fn read_vectored + Send>( 556 | &self, 557 | mut header_buf: Vec, 558 | mut data_buf: T, 559 | ) -> CompleteIoResult<(Vec, T), usize> { 560 | let _guard = self.read.lock().await; 561 | 562 | loop { 563 | let mut read_guard = match self.fd.ready(Interest::READABLE | Interest::ERROR).await { 564 | Err(err) => return ((header_buf, data_buf), Err(err)), 565 | Ok(read_guard) => read_guard, 566 | }; 567 | 568 | if let Ok(result) = read_guard.try_io(|fd| { 569 | uio::readv( 570 | fd, 571 | &mut [ 572 | IoSliceMut::new(&mut header_buf), 573 | IoSliceMut::new(&mut data_buf), 574 | ], 575 | ) 576 | .map_err(io::Error::from) 577 | }) { 578 | return ((header_buf, data_buf), result); 579 | } else { 580 | continue; 581 | } 582 | } 583 | } 584 | 585 | async fn write_vectored + Send, U: Deref + Send>( 586 | &self, 587 | data: T, 588 | body_extend_data: Option, 589 | ) -> CompleteIoResult<(T, Option), usize> { 590 | let _guard = self.write.lock().await; 591 | 592 | let res = { 593 | let body_extend_data = body_extend_data.as_deref(); 594 | 595 | match body_extend_data { 596 | None => uio::writev(&self.fd, &[IoSlice::new(data.deref())]), 597 | 598 | Some(body_extend_data) => uio::writev( 599 | &self.fd, 600 | &[IoSlice::new(data.deref()), IoSlice::new(body_extend_data)], 601 | ), 602 | } 603 | }; 604 | 605 | match res { 606 | Err(err) => ((data, body_extend_data), Err(err.into())), 607 | Ok(n) => ((data, body_extend_data), Ok(n)), 608 | } 609 | } 610 | } 611 | 612 | impl AsFd for FuseConnection { 613 | fn as_fd(&self) -> BorrowedFd<'_> { 614 | match &self.mode { 615 | #[cfg(any(target_os = "linux", target_os = "macos"))] 616 | ConnectionMode::Block(connection) => connection.file.as_fd(), 617 | 618 | #[cfg(any( 619 | all(target_os = "linux", feature = "unprivileged"), 620 | target_os = "freebsd", 621 | ))] 622 | ConnectionMode::NonBlock(connection) => connection.fd.as_fd(), 623 | } 624 | } 625 | } 626 | -------------------------------------------------------------------------------- /src/raw/filesystem.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::OsStr; 2 | 3 | use bytes::Bytes; 4 | use futures_util::stream::Stream; 5 | 6 | use crate::notify::Notify; 7 | use crate::raw::reply::*; 8 | use crate::raw::request::Request; 9 | use crate::{Inode, Result, SetAttr}; 10 | 11 | #[allow(unused_variables)] 12 | #[trait_make::make(Send)] 13 | /// Inode based filesystem trait. 14 | pub trait Filesystem { 15 | /// initialize filesystem. Called before any other filesystem method. 16 | async fn init(&self, req: Request) -> Result; 17 | 18 | /// clean up filesystem. Called on filesystem exit which is fuseblk, in normal fuse filesystem, 19 | /// kernel may call forget for root. There is some discuss for this 20 | /// , 21 | /// 22 | async fn destroy(&self, req: Request); 23 | 24 | /// look up a directory entry by name and get its attributes. 25 | async fn lookup(&self, req: Request, parent: Inode, name: &OsStr) -> Result { 26 | Err(libc::ENOSYS.into()) 27 | } 28 | 29 | /// forget an inode. The nlookup parameter indicates the number of lookups previously 30 | /// performed on this inode. If the filesystem implements inode lifetimes, it is recommended 31 | /// that inodes acquire a single reference on each lookup, and lose nlookup references on each 32 | /// forget. The filesystem may ignore forget calls, if the inodes don't need to have a limited 33 | /// lifetime. On unmount it is not guaranteed, that all referenced inodes will receive a forget 34 | /// message. When filesystem is normal(not fuseblk) and unmounting, kernel may send forget 35 | /// request for root and this library will stop session after call forget. There is some 36 | /// discussion for this , 37 | /// 38 | async fn forget(&self, req: Request, inode: Inode, nlookup: u64) {} 39 | 40 | /// get file attributes. If `fh` is None, means `fh` is not set. 41 | async fn getattr( 42 | &self, 43 | req: Request, 44 | inode: Inode, 45 | fh: Option, 46 | flags: u32, 47 | ) -> Result { 48 | Err(libc::ENOSYS.into()) 49 | } 50 | 51 | /// set file attributes. If `fh` is None, means `fh` is not set. 52 | async fn setattr( 53 | &self, 54 | req: Request, 55 | inode: Inode, 56 | fh: Option, 57 | set_attr: SetAttr, 58 | ) -> Result { 59 | Err(libc::ENOSYS.into()) 60 | } 61 | 62 | /// read symbolic link. 63 | async fn readlink(&self, req: Request, inode: Inode) -> Result { 64 | Err(libc::ENOSYS.into()) 65 | } 66 | 67 | /// create a symbolic link. 68 | async fn symlink( 69 | &self, 70 | req: Request, 71 | parent: Inode, 72 | name: &OsStr, 73 | link: &OsStr, 74 | ) -> Result { 75 | Err(libc::ENOSYS.into()) 76 | } 77 | 78 | /// create file node. Create a regular file, character device, block device, fifo or socket 79 | /// node. When creating file, most cases user only need to implement 80 | /// [`create`][Filesystem::create]. 81 | async fn mknod( 82 | &self, 83 | req: Request, 84 | parent: Inode, 85 | name: &OsStr, 86 | mode: u32, 87 | rdev: u32, 88 | ) -> Result { 89 | Err(libc::ENOSYS.into()) 90 | } 91 | 92 | /// create a directory. 93 | async fn mkdir( 94 | &self, 95 | req: Request, 96 | parent: Inode, 97 | name: &OsStr, 98 | mode: u32, 99 | umask: u32, 100 | ) -> Result { 101 | Err(libc::ENOSYS.into()) 102 | } 103 | 104 | /// remove a file. 105 | async fn unlink(&self, req: Request, parent: Inode, name: &OsStr) -> Result<()> { 106 | Err(libc::ENOSYS.into()) 107 | } 108 | 109 | /// remove a directory. 110 | async fn rmdir(&self, req: Request, parent: Inode, name: &OsStr) -> Result<()> { 111 | Err(libc::ENOSYS.into()) 112 | } 113 | 114 | /// rename a file or directory. 115 | async fn rename( 116 | &self, 117 | req: Request, 118 | parent: Inode, 119 | name: &OsStr, 120 | new_parent: Inode, 121 | new_name: &OsStr, 122 | ) -> Result<()> { 123 | Err(libc::ENOSYS.into()) 124 | } 125 | 126 | /// create a hard link. 127 | async fn link( 128 | &self, 129 | req: Request, 130 | inode: Inode, 131 | new_parent: Inode, 132 | new_name: &OsStr, 133 | ) -> Result { 134 | Err(libc::ENOSYS.into()) 135 | } 136 | 137 | /// open a file. Open flags (with the exception of `O_CREAT`, `O_EXCL` and `O_NOCTTY`) are 138 | /// available in flags. Filesystem may store an arbitrary file handle (pointer, index, etc) in 139 | /// fh, and use this in other all other file operations (read, write, flush, release, fsync). 140 | /// Filesystem may also implement stateless file I/O and not store anything in fh. There are 141 | /// also some flags (`direct_io`, `keep_cache`) which the filesystem may set, to change the way 142 | /// the file is opened. A filesystem need not implement this method if it 143 | /// sets [`MountOptions::no_open_support`][crate::MountOptions::no_open_support] and if the 144 | /// kernel supports `FUSE_NO_OPEN_SUPPORT`. 145 | /// 146 | /// # Notes: 147 | /// 148 | /// See `fuse_file_info` structure in 149 | /// [fuse_common.h](https://libfuse.github.io/doxygen/include_2fuse__common_8h_source.html) for 150 | /// more details. 151 | async fn open(&self, req: Request, inode: Inode, flags: u32) -> Result { 152 | Err(libc::ENOSYS.into()) 153 | } 154 | 155 | /// read data. Read should send exactly the number of bytes requested except on EOF or error, 156 | /// otherwise the rest of the data will be substituted with zeroes. An exception to this is 157 | /// when the file has been opened in `direct_io` mode, in which case the return value of the 158 | /// read system call will reflect the return value of this operation. `fh` will contain the 159 | /// value set by the open method, or will be undefined if the open method didn't set any value. 160 | async fn read( 161 | &self, 162 | req: Request, 163 | inode: Inode, 164 | fh: u64, 165 | offset: u64, 166 | size: u32, 167 | ) -> Result { 168 | Err(libc::ENOSYS.into()) 169 | } 170 | 171 | /// write data. Write should return exactly the number of bytes requested except on error. An 172 | /// exception to this is when the file has been opened in `direct_io` mode, in which case the 173 | /// return value of the write system call will reflect the return value of this operation. `fh` 174 | /// will contain the value set by the open method, or will be undefined if the open method 175 | /// didn't set any value. When `write_flags` contains 176 | /// [`FUSE_WRITE_CACHE`](crate::raw::flags::FUSE_WRITE_CACHE), means the write operation is a 177 | /// delay write. 178 | #[allow(clippy::too_many_arguments)] 179 | async fn write( 180 | &self, 181 | req: Request, 182 | inode: Inode, 183 | fh: u64, 184 | offset: u64, 185 | data: &[u8], 186 | write_flags: u32, 187 | flags: u32, 188 | ) -> Result { 189 | Err(libc::ENOSYS.into()) 190 | } 191 | 192 | /// get filesystem statistics. 193 | async fn statfs(&self, req: Request, inode: Inode) -> Result { 194 | Err(libc::ENOSYS.into()) 195 | } 196 | 197 | /// release an open file. Release is called when there are no more references to an open file: 198 | /// all file descriptors are closed and all memory mappings are unmapped. For every open call 199 | /// there will be exactly one release call. The filesystem may reply with an error, but error 200 | /// values are not returned to `close()` or `munmap()` which triggered the release. `fh` will 201 | /// contain the value set by the open method, or will be undefined if the open method didn't 202 | /// set any value. `flags` will contain the same flags as for open. `flush` means flush the 203 | /// data or not when closing file. 204 | async fn release( 205 | &self, 206 | req: Request, 207 | inode: Inode, 208 | fh: u64, 209 | flags: u32, 210 | lock_owner: u64, 211 | flush: bool, 212 | ) -> Result<()> { 213 | Err(libc::ENOSYS.into()) 214 | } 215 | 216 | /// synchronize file contents. If the `datasync` is true, then only the user data should be 217 | /// flushed, not the metadata. 218 | async fn fsync(&self, req: Request, inode: Inode, fh: u64, datasync: bool) -> Result<()> { 219 | Ok(()) 220 | } 221 | 222 | /// set an extended attribute. 223 | async fn setxattr( 224 | &self, 225 | req: Request, 226 | inode: Inode, 227 | name: &OsStr, 228 | value: &[u8], 229 | flags: u32, 230 | position: u32, 231 | ) -> Result<()> { 232 | Err(libc::ENOSYS.into()) 233 | } 234 | 235 | /// Get an extended attribute. If `size` is too small, return `Err`. 236 | /// Otherwise, use [`ReplyXAttr::Data`] to send the attribute data, or 237 | /// return an error. 238 | async fn getxattr( 239 | &self, 240 | req: Request, 241 | inode: Inode, 242 | name: &OsStr, 243 | size: u32, 244 | ) -> Result { 245 | Err(libc::ENOSYS.into()) 246 | } 247 | 248 | /// List extended attribute names. 249 | /// 250 | /// If `size` is too small, return `Err`. Otherwise, use 251 | /// [`ReplyXAttr::Data`] to send the attribute list, or return an error. 252 | async fn listxattr(&self, req: Request, inode: Inode, size: u32) -> Result { 253 | Err(libc::ENOSYS.into()) 254 | } 255 | 256 | /// remove an extended attribute. 257 | async fn removexattr(&self, req: Request, inode: Inode, name: &OsStr) -> Result<()> { 258 | Err(libc::ENOSYS.into()) 259 | } 260 | 261 | /// flush method. This is called on each `close()` of the opened file. Since file descriptors 262 | /// can be duplicated (`dup`, `dup2`, `fork`), for one open call there may be many flush calls. 263 | /// Filesystems shouldn't assume that flush will always be called after some writes, or that if 264 | /// will be called at all. `fh` will contain the value set by the open method, or will be 265 | /// undefined if the open method didn't set any value. 266 | /// 267 | /// # Notes: 268 | /// 269 | /// the name of the method is misleading, since (unlike fsync) the filesystem is not forced to 270 | /// flush pending writes. One reason to flush data, is if the filesystem wants to return write 271 | /// errors. If the filesystem supports file locking operations ([`setlk`][Filesystem::setlk], 272 | /// [`getlk`][Filesystem::getlk]) it should remove all locks belonging to `lock_owner`. 273 | async fn flush(&self, req: Request, inode: Inode, fh: u64, lock_owner: u64) -> Result<()> { 274 | Err(libc::ENOSYS.into()) 275 | } 276 | 277 | /// open a directory. Filesystem may store an arbitrary file handle (pointer, index, etc) in 278 | /// `fh`, and use this in other all other directory stream operations 279 | /// ([`readdir`][Filesystem::readdir], [`releasedir`][Filesystem::releasedir], 280 | /// [`fsyncdir`][Filesystem::fsyncdir]). Filesystem may also implement stateless directory 281 | /// I/O and not store anything in `fh`. A file system need not implement this method if it 282 | /// sets [`MountOptions::no_open_dir_support`][crate::MountOptions::no_open_dir_support] and 283 | /// if the kernel supports `FUSE_NO_OPENDIR_SUPPORT`. 284 | async fn opendir(&self, req: Request, inode: Inode, flags: u32) -> Result { 285 | Err(libc::ENOSYS.into()) 286 | } 287 | 288 | /// dir entry stream given by [`readdir`][Filesystem::readdir]. 289 | type DirEntryStream<'a>: Stream> + Send + 'a 290 | where 291 | Self: 'a; 292 | 293 | /// read directory. `offset` is used to track the offset of the directory entries. `fh` will 294 | /// contain the value set by the [`opendir`][Filesystem::opendir] method, or will be 295 | /// undefined if the [`opendir`][Filesystem::opendir] method didn't set any value. 296 | async fn readdir<'a>( 297 | &'a self, 298 | req: Request, 299 | parent: Inode, 300 | fh: u64, 301 | offset: i64, 302 | ) -> Result>> { 303 | Err(libc::ENOSYS.into()) 304 | } 305 | 306 | /// release an open directory. For every [`opendir`][Filesystem::opendir] call there will 307 | /// be exactly one `releasedir` call. `fh` will contain the value set by the 308 | /// [`opendir`][Filesystem::opendir] method, or will be undefined if the 309 | /// [`opendir`][Filesystem::opendir] method didn't set any value. 310 | async fn releasedir(&self, req: Request, inode: Inode, fh: u64, flags: u32) -> Result<()> { 311 | Ok(()) 312 | } 313 | 314 | /// synchronize directory contents. If the `datasync` is true, then only the directory contents 315 | /// should be flushed, not the metadata. `fh` will contain the value set by the 316 | /// [`opendir`][Filesystem::opendir] method, or will be undefined if the 317 | /// [`opendir`][Filesystem::opendir] method didn't set any value. 318 | async fn fsyncdir(&self, req: Request, inode: Inode, fh: u64, datasync: bool) -> Result<()> { 319 | Err(libc::ENOSYS.into()) 320 | } 321 | 322 | #[cfg(feature = "file-lock")] 323 | /// test for a POSIX file lock. 324 | /// 325 | /// # Notes: 326 | /// 327 | /// this is supported on enable **`file-lock`** feature. 328 | #[allow(clippy::too_many_arguments)] 329 | async fn getlk( 330 | &self, 331 | req: Request, 332 | inode: Inode, 333 | fh: u64, 334 | lock_owner: u64, 335 | start: u64, 336 | end: u64, 337 | r#type: u32, 338 | pid: u32, 339 | ) -> Result; 340 | 341 | #[cfg(feature = "file-lock")] 342 | /// acquire, modify or release a POSIX file lock. 343 | /// 344 | /// # Notes: 345 | /// 346 | /// this is supported on enable **`file-lock`** feature. 347 | #[allow(clippy::too_many_arguments)] 348 | async fn setlk( 349 | &self, 350 | req: Request, 351 | inode: Inode, 352 | fh: u64, 353 | lock_owner: u64, 354 | start: u64, 355 | end: u64, 356 | r#type: u32, 357 | pid: u32, 358 | block: bool, 359 | ) -> Result<()>; 360 | 361 | /// check file access permissions. This will be called for the `access()` system call. If the 362 | /// `default_permissions` mount option is given, this method is not be called. This method is 363 | /// not called under Linux kernel versions 2.4.x. 364 | async fn access(&self, req: Request, inode: Inode, mask: u32) -> Result<()> { 365 | Err(libc::ENOSYS.into()) 366 | } 367 | 368 | /// create and open a file. If the file does not exist, first create it with the specified 369 | /// mode, and then open it. Open flags (with the exception of `O_NOCTTY`) are available in 370 | /// flags. Filesystem may store an arbitrary file handle (pointer, index, etc) in `fh`, and use 371 | /// this in other all other file operations ([`read`][Filesystem::read], 372 | /// [`write`][Filesystem::write], [`flush`][Filesystem::flush], 373 | /// [`release`][Filesystem::release], [`fsync`][Filesystem::fsync]). There are also some flags 374 | /// (`direct_io`, `keep_cache`) which the filesystem may set, to change the way the file is 375 | /// opened. If this method is not implemented or under Linux kernel versions earlier than 376 | /// 2.6.15, the [`mknod`][Filesystem::mknod] and [`open`][Filesystem::open] methods will be 377 | /// called instead. 378 | /// 379 | /// # Notes: 380 | /// 381 | /// See `fuse_file_info` structure in 382 | /// [fuse_common.h](https://libfuse.github.io/doxygen/include_2fuse__common_8h_source.html) for 383 | /// more details. 384 | async fn create( 385 | &self, 386 | req: Request, 387 | parent: Inode, 388 | name: &OsStr, 389 | mode: u32, 390 | flags: u32, 391 | ) -> Result { 392 | Err(libc::ENOSYS.into()) 393 | } 394 | 395 | /// handle interrupt. When a operation is interrupted, an interrupt request will send to fuse 396 | /// server with the unique id of the operation. 397 | async fn interrupt(&self, req: Request, unique: u64) -> Result<()> { 398 | Err(libc::ENOSYS.into()) 399 | } 400 | 401 | /// map block index within file to block index within device. 402 | /// 403 | /// # Notes: 404 | /// 405 | /// This may not works because currently this crate doesn't support fuseblk mode yet. 406 | async fn bmap( 407 | &self, 408 | req: Request, 409 | inode: Inode, 410 | blocksize: u32, 411 | idx: u64, 412 | ) -> Result { 413 | Err(libc::ENOSYS.into()) 414 | } 415 | 416 | /*async fn ioctl( 417 | &self, 418 | req: Request, 419 | inode: Inode, 420 | fh: u64, 421 | flags: u32, 422 | cmd: u32, 423 | arg: u64, 424 | in_size: u32, 425 | out_size: u32, 426 | ) -> Result { 427 | Err(libc::ENOSYS.into()) 428 | }*/ 429 | 430 | /// poll for IO readiness events. 431 | #[allow(clippy::too_many_arguments)] 432 | async fn poll( 433 | &self, 434 | req: Request, 435 | inode: Inode, 436 | fh: u64, 437 | kh: Option, 438 | flags: u32, 439 | events: u32, 440 | notify: &Notify, 441 | ) -> Result { 442 | Err(libc::ENOSYS.into()) 443 | } 444 | 445 | /// receive notify reply from kernel. 446 | async fn notify_reply( 447 | &self, 448 | req: Request, 449 | inode: Inode, 450 | offset: u64, 451 | data: Bytes, 452 | ) -> Result<()> { 453 | Err(libc::ENOSYS.into()) 454 | } 455 | 456 | /// forget more than one inode. This is a batch version [`forget`][Filesystem::forget] 457 | async fn batch_forget(&self, req: Request, inodes: &[Inode]) {} 458 | 459 | /// allocate space for an open file. This function ensures that required space is allocated for 460 | /// specified file. 461 | /// 462 | /// # Notes: 463 | /// 464 | /// more information about `fallocate`, please see **`man 2 fallocate`** 465 | async fn fallocate( 466 | &self, 467 | req: Request, 468 | inode: Inode, 469 | fh: u64, 470 | offset: u64, 471 | length: u64, 472 | mode: u32, 473 | ) -> Result<()> { 474 | Err(libc::ENOSYS.into()) 475 | } 476 | 477 | /// dir entry plus stream given by [`readdirplus`][Filesystem::readdirplus]. 478 | type DirEntryPlusStream<'a>: Stream> + Send + 'a 479 | where 480 | Self: 'a; 481 | 482 | /// read directory entries, but with their attribute, like [`readdir`][Filesystem::readdir] 483 | /// + [`lookup`][Filesystem::lookup] at the same time. 484 | async fn readdirplus<'a>( 485 | &'a self, 486 | req: Request, 487 | parent: Inode, 488 | fh: u64, 489 | offset: u64, 490 | lock_owner: u64, 491 | ) -> Result>> { 492 | Err(libc::ENOSYS.into()) 493 | } 494 | 495 | /// rename a file or directory with flags. 496 | async fn rename2( 497 | &self, 498 | req: Request, 499 | parent: Inode, 500 | name: &OsStr, 501 | new_parent: Inode, 502 | new_name: &OsStr, 503 | flags: u32, 504 | ) -> Result<()> { 505 | Err(libc::ENOSYS.into()) 506 | } 507 | 508 | /// find next data or hole after the specified offset. 509 | async fn lseek( 510 | &self, 511 | req: Request, 512 | inode: Inode, 513 | fh: u64, 514 | offset: u64, 515 | whence: u32, 516 | ) -> Result { 517 | Err(libc::ENOSYS.into()) 518 | } 519 | 520 | /// copy a range of data from one file to another. This can improve performance because it 521 | /// reduce data copy: in normal, data will copy from FUSE server to kernel, then to user-space, 522 | /// then to kernel, finally send back to FUSE server. By implement this method, data will only 523 | /// copy in FUSE server internal. 524 | #[allow(clippy::too_many_arguments)] 525 | async fn copy_file_range( 526 | &self, 527 | req: Request, 528 | inode: Inode, 529 | fh_in: u64, 530 | off_in: u64, 531 | inode_out: Inode, 532 | fh_out: u64, 533 | off_out: u64, 534 | length: u64, 535 | flags: u64, 536 | ) -> Result { 537 | Err(libc::ENOSYS.into()) 538 | } 539 | 540 | // TODO setupmapping and removemapping 541 | } 542 | -------------------------------------------------------------------------------- /src/raw/flags.rs: -------------------------------------------------------------------------------- 1 | //! request flags. 2 | 3 | pub use crate::raw::abi::FUSE_IOCTL_32BIT; 4 | pub use crate::raw::abi::FUSE_IOCTL_COMPAT; 5 | pub use crate::raw::abi::FUSE_IOCTL_DIR; 6 | pub use crate::raw::abi::FUSE_IOCTL_MAX_IOV; 7 | pub use crate::raw::abi::FUSE_IOCTL_RETRY; 8 | pub use crate::raw::abi::FUSE_IOCTL_UNRESTRICTED; 9 | pub use crate::raw::abi::FUSE_POLL_SCHEDULE_NOTIFY; 10 | pub use crate::raw::abi::FUSE_READ_LOCKOWNER; 11 | pub use crate::raw::abi::FUSE_WRITE_CACHE; 12 | pub use crate::raw::abi::FUSE_WRITE_LOCKOWNER; 13 | -------------------------------------------------------------------------------- /src/raw/mod.rs: -------------------------------------------------------------------------------- 1 | //! inode based 2 | //! 3 | //! it is not recommend to use this inode based [`Filesystem`] first, you need to handle inode 4 | //! allocate, recycle and sometimes map to the path, [`PathFilesystem`][crate::path::PathFilesystem] 5 | //! helps you do those jobs so you can pay more attention to your filesystem design. However if you 6 | //! want to control the inode or do the path<->inode map on yourself, [`Filesystem`] is the only one 7 | //! choose. 8 | 9 | use bytes::Bytes; 10 | pub use filesystem::Filesystem; 11 | use futures_util::future::Either; 12 | pub use request::Request; 13 | #[cfg(any(feature = "async-io-runtime", feature = "tokio-runtime"))] 14 | pub use session::{MountHandle, Session}; 15 | 16 | pub(crate) type FuseData = Either, (Vec, Bytes)>; 17 | 18 | pub(crate) mod abi; 19 | mod connection; 20 | mod filesystem; 21 | pub mod flags; 22 | pub mod reply; 23 | mod request; 24 | pub(crate) mod session; 25 | 26 | pub mod prelude { 27 | pub use super::reply::FileAttr; 28 | pub use super::reply::*; 29 | pub use super::Filesystem; 30 | pub use super::Request; 31 | pub use super::Session; 32 | pub use crate::notify::Notify; 33 | pub use crate::FileType; 34 | pub use crate::SetAttr; 35 | } 36 | -------------------------------------------------------------------------------- /src/raw/reply.rs: -------------------------------------------------------------------------------- 1 | //! reply structures. 2 | use std::ffi::OsString; 3 | use std::num::NonZeroU32; 4 | use std::time::Duration; 5 | 6 | use bytes::Bytes; 7 | use futures_util::stream::Stream; 8 | 9 | use crate::helper::mode_from_kind_and_perm; 10 | use crate::raw::abi::{ 11 | fuse_attr, fuse_attr_out, fuse_bmap_out, fuse_entry_out, fuse_kstatfs, fuse_lseek_out, 12 | fuse_open_out, fuse_poll_out, fuse_statfs_out, fuse_write_out, 13 | }; 14 | #[cfg(feature = "file-lock")] 15 | use crate::raw::abi::{fuse_file_lock, fuse_lk_out}; 16 | use crate::{FileType, Result, Timestamp}; 17 | 18 | /// file attributes 19 | #[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] 20 | pub struct FileAttr { 21 | /// Inode number 22 | pub ino: u64, 23 | /// Size in bytes 24 | pub size: u64, 25 | /// Size in blocks 26 | pub blocks: u64, 27 | /// Time of last access 28 | pub atime: Timestamp, 29 | /// Time of last modification 30 | pub mtime: Timestamp, 31 | /// Time of last change 32 | pub ctime: Timestamp, 33 | #[cfg(target_os = "macos")] 34 | /// Time of creation (macOS only) 35 | pub crtime: Timestamp, 36 | /// Kind of file (directory, file, pipe, etc) 37 | pub kind: FileType, 38 | /// Permissions 39 | pub perm: u16, 40 | /// Number of hard links 41 | pub nlink: u32, 42 | /// User id 43 | pub uid: u32, 44 | /// Group id 45 | pub gid: u32, 46 | /// Rdev 47 | pub rdev: u32, 48 | #[cfg(target_os = "macos")] 49 | /// Flags (macOS only, see chflags(2)) 50 | pub flags: u32, 51 | pub blksize: u32, 52 | } 53 | 54 | impl From for fuse_attr { 55 | fn from(attr: FileAttr) -> Self { 56 | fuse_attr { 57 | ino: attr.ino, 58 | size: attr.size, 59 | blocks: attr.blocks, 60 | // NB: fuse_kernel.h defines the seconds fields as "uint64_t", but 61 | // they actually get cast to time_t (e.g. int64_t) inside the 62 | // kernel. 63 | atime: attr.atime.sec as u64, 64 | mtime: attr.mtime.sec as u64, 65 | ctime: attr.ctime.sec as u64, 66 | #[cfg(target_os = "macos")] 67 | crtime: attr.crtime.sec as u64, 68 | atimensec: attr.atime.nsec, 69 | mtimensec: attr.mtime.nsec, 70 | ctimensec: attr.ctime.nsec, 71 | #[cfg(target_os = "macos")] 72 | crtimensec: attr.crtime.nsec, 73 | mode: mode_from_kind_and_perm(attr.kind, attr.perm), 74 | nlink: attr.nlink, 75 | uid: attr.uid, 76 | gid: attr.gid, 77 | rdev: attr.rdev, 78 | blksize: attr.blksize, 79 | #[cfg(target_os = "macos")] 80 | flags: attr.flags, 81 | _padding: 0, 82 | } 83 | } 84 | } 85 | 86 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 87 | /// init reply 88 | pub struct ReplyInit { 89 | /// the max write size 90 | pub max_write: NonZeroU32, 91 | } 92 | 93 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 94 | /// entry reply. 95 | pub struct ReplyEntry { 96 | /// the attribute TTL. 97 | pub ttl: Duration, 98 | /// the attribute. 99 | pub attr: FileAttr, 100 | /// the generation. 101 | pub generation: u64, 102 | } 103 | 104 | impl From for fuse_entry_out { 105 | fn from(entry: ReplyEntry) -> Self { 106 | let attr = entry.attr; 107 | 108 | fuse_entry_out { 109 | nodeid: attr.ino, 110 | generation: entry.generation, 111 | entry_valid: entry.ttl.as_secs(), 112 | attr_valid: entry.ttl.as_secs(), 113 | entry_valid_nsec: entry.ttl.subsec_nanos(), 114 | attr_valid_nsec: entry.ttl.subsec_nanos(), 115 | attr: attr.into(), 116 | } 117 | } 118 | } 119 | 120 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 121 | /// reply attr. 122 | pub struct ReplyAttr { 123 | /// the attribute TTL. 124 | pub ttl: Duration, 125 | /// the attribute. 126 | pub attr: FileAttr, 127 | } 128 | 129 | impl From for fuse_attr_out { 130 | fn from(attr: ReplyAttr) -> Self { 131 | fuse_attr_out { 132 | attr_valid: attr.ttl.as_secs(), 133 | attr_valid_nsec: attr.ttl.subsec_nanos(), 134 | dummy: 0, 135 | attr: attr.attr.into(), 136 | } 137 | } 138 | } 139 | 140 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 141 | /// data reply. 142 | pub struct ReplyData { 143 | /// the data. 144 | pub data: Bytes, 145 | } 146 | 147 | impl From for ReplyData { 148 | fn from(data: Bytes) -> Self { 149 | Self { data } 150 | } 151 | } 152 | 153 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 154 | /// open reply. 155 | pub struct ReplyOpen { 156 | /// the file handle id. 157 | /// 158 | /// # Notes: 159 | /// 160 | /// if set fh 0, means use stateless IO. 161 | pub fh: u64, 162 | /// the flags. 163 | pub flags: u32, 164 | } 165 | 166 | impl From for fuse_open_out { 167 | fn from(opened: ReplyOpen) -> Self { 168 | fuse_open_out { 169 | fh: opened.fh, 170 | open_flags: opened.flags, 171 | _padding: 0, 172 | } 173 | } 174 | } 175 | 176 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 177 | /// write reply. 178 | pub struct ReplyWrite { 179 | /// the data written. 180 | pub written: u32, 181 | } 182 | 183 | impl From for fuse_write_out { 184 | fn from(written: ReplyWrite) -> Self { 185 | fuse_write_out { 186 | size: written.written, 187 | _padding: 0, 188 | } 189 | } 190 | } 191 | 192 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 193 | /// statfs reply. 194 | pub struct ReplyStatFs { 195 | /// the number of blocks in the filesystem. 196 | pub blocks: u64, 197 | /// the number of free blocks. 198 | pub bfree: u64, 199 | /// the number of free blocks for non-priviledge users. 200 | pub bavail: u64, 201 | /// the number of inodes. 202 | pub files: u64, 203 | /// the number of free inodes. 204 | pub ffree: u64, 205 | /// the block size. 206 | pub bsize: u32, 207 | /// the maximum length of file name. 208 | pub namelen: u32, 209 | /// the fragment size. 210 | pub frsize: u32, 211 | } 212 | 213 | impl From for fuse_statfs_out { 214 | fn from(stat_fs: ReplyStatFs) -> Self { 215 | fuse_statfs_out { 216 | st: fuse_kstatfs { 217 | blocks: stat_fs.blocks, 218 | bfree: stat_fs.bfree, 219 | bavail: stat_fs.bavail, 220 | files: stat_fs.files, 221 | ffree: stat_fs.ffree, 222 | bsize: stat_fs.bsize, 223 | namelen: stat_fs.namelen, 224 | frsize: stat_fs.frsize, 225 | _padding: 0, 226 | spare: [0; 6], 227 | }, 228 | } 229 | } 230 | } 231 | 232 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 233 | /// xattr reply. 234 | pub enum ReplyXAttr { 235 | Size(u32), 236 | Data(Bytes), 237 | } 238 | 239 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 240 | /// directory entry. 241 | pub struct DirectoryEntry { 242 | /// entry inode. 243 | pub inode: u64, 244 | /// entry kind. 245 | pub kind: FileType, 246 | /// entry name. 247 | pub name: OsString, 248 | /// Directory offset of the _next_ entry 249 | pub offset: i64, 250 | } 251 | 252 | /// readdir reply. 253 | pub struct ReplyDirectory>> { 254 | pub entries: S, 255 | } 256 | 257 | impl> + std::fmt::Debug> std::fmt::Debug 258 | for ReplyDirectory 259 | { 260 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 261 | f.debug_struct("ReplyDirectory") 262 | .field("entries", &self.entries) 263 | .finish() 264 | } 265 | } 266 | 267 | #[cfg(feature = "file-lock")] 268 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 269 | /// file lock reply. 270 | pub struct ReplyLock { 271 | /// starting offset for lock. 272 | pub start: u64, 273 | /// end offset for lock. 274 | pub end: u64, 275 | /// type of lock, such as: [`F_RDLCK`], [`F_WRLCK`] and [`F_UNLCK`] 276 | /// 277 | /// [`F_RDLCK`]: libc::F_RDLCK 278 | /// [`F_WRLCK`]: libc::F_WRLCK 279 | /// [`F_UNLCK`]: libc::F_UNLCK 280 | pub r#type: u32, 281 | /// PID of process blocking our lock 282 | pub pid: u32, 283 | } 284 | 285 | #[cfg(feature = "file-lock")] 286 | impl From for fuse_lk_out { 287 | fn from(lock: ReplyLock) -> Self { 288 | fuse_lk_out { 289 | lk: fuse_file_lock { 290 | start: lock.start, 291 | end: lock.end, 292 | r#type: lock.r#type, 293 | pid: lock.pid, 294 | }, 295 | } 296 | } 297 | } 298 | 299 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 300 | /// crate reply. 301 | pub struct ReplyCreated { 302 | /// the attribute TTL. 303 | pub ttl: Duration, 304 | /// the attribute of file. 305 | pub attr: FileAttr, 306 | /// the generation of file. 307 | pub generation: u64, 308 | /// the file handle. 309 | pub fh: u64, 310 | /// the flags. 311 | pub flags: u32, 312 | } 313 | 314 | impl From for (fuse_entry_out, fuse_open_out) { 315 | fn from(created: ReplyCreated) -> Self { 316 | let attr = created.attr; 317 | 318 | let entry_out = fuse_entry_out { 319 | nodeid: attr.ino, 320 | generation: created.generation, 321 | entry_valid: created.ttl.as_secs(), 322 | attr_valid: created.ttl.as_secs(), 323 | entry_valid_nsec: created.ttl.subsec_micros(), 324 | attr_valid_nsec: created.ttl.subsec_micros(), 325 | attr: attr.into(), 326 | }; 327 | 328 | let open_out = fuse_open_out { 329 | fh: created.fh, 330 | open_flags: created.flags, 331 | _padding: 0, 332 | }; 333 | 334 | (entry_out, open_out) 335 | } 336 | } 337 | 338 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 339 | // TODO need more detail 340 | /// bmap reply. 341 | pub struct ReplyBmap { 342 | pub block: u64, 343 | } 344 | 345 | impl From for fuse_bmap_out { 346 | fn from(bmap: ReplyBmap) -> Self { 347 | fuse_bmap_out { block: bmap.block } 348 | } 349 | } 350 | 351 | /*#[derive(Debug)] 352 | pub struct ReplyIoctl { 353 | pub result: i32, 354 | pub flags: u32, 355 | pub in_iovs: u32, 356 | pub out_iovs: u32, 357 | }*/ 358 | 359 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 360 | // TODO need more detail 361 | /// poll reply 362 | pub struct ReplyPoll { 363 | pub revents: u32, 364 | } 365 | 366 | impl From for fuse_poll_out { 367 | fn from(poll: ReplyPoll) -> Self { 368 | fuse_poll_out { 369 | revents: poll.revents, 370 | _padding: 0, 371 | } 372 | } 373 | } 374 | 375 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 376 | /// directory entry with attribute 377 | pub struct DirectoryEntryPlus { 378 | /// the entry inode. 379 | pub inode: u64, 380 | /// the entry generation. 381 | pub generation: u64, 382 | /// the entry kind. 383 | pub kind: FileType, 384 | /// the entry name. 385 | pub name: OsString, 386 | /// Directory offset of the _next_ entry 387 | pub offset: i64, 388 | /// the entry attribute. 389 | pub attr: FileAttr, 390 | /// the entry TTL. 391 | pub entry_ttl: Duration, 392 | /// the attribute TTL. 393 | pub attr_ttl: Duration, 394 | } 395 | 396 | /// the readdirplus reply. 397 | pub struct ReplyDirectoryPlus>> { 398 | pub entries: S, 399 | } 400 | 401 | impl> + std::fmt::Debug> std::fmt::Debug 402 | for ReplyDirectoryPlus 403 | { 404 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 405 | f.debug_struct("ReplyDirectoryPlus") 406 | .field("entries", &self.entries) 407 | .finish() 408 | } 409 | } 410 | 411 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 412 | /// the lseek reply. 413 | pub struct ReplyLSeek { 414 | /// lseek offset. 415 | pub offset: u64, 416 | } 417 | 418 | impl From for fuse_lseek_out { 419 | fn from(seek: ReplyLSeek) -> Self { 420 | fuse_lseek_out { 421 | offset: seek.offset, 422 | } 423 | } 424 | } 425 | 426 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 427 | /// copy_file_range reply. 428 | pub struct ReplyCopyFileRange { 429 | /// data copied size. 430 | pub copied: u64, 431 | } 432 | 433 | impl From for fuse_write_out { 434 | fn from(copied: ReplyCopyFileRange) -> Self { 435 | fuse_write_out { 436 | size: copied.copied as u32, 437 | _padding: 0, 438 | } 439 | } 440 | } 441 | -------------------------------------------------------------------------------- /src/raw/request.rs: -------------------------------------------------------------------------------- 1 | use crate::raw::abi::fuse_in_header; 2 | 3 | #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] 4 | /// Request data 5 | pub struct Request { 6 | /// the unique identifier of this request. 7 | pub unique: u64, 8 | /// the uid of this request. 9 | pub uid: u32, 10 | /// the gid of this request. 11 | pub gid: u32, 12 | /// the pid of this request. 13 | pub pid: u32, 14 | } 15 | 16 | impl From<&fuse_in_header> for Request { 17 | fn from(header: &fuse_in_header) -> Self { 18 | Self { 19 | unique: header.unique, 20 | uid: header.uid, 21 | gid: header.gid, 22 | pid: header.pid, 23 | } 24 | } 25 | } 26 | --------------------------------------------------------------------------------