Skip to main content

async_fuser/
lib_async.rs

1//! Experimental Asynchronous API for fuser. This is gated behind the "async" feature,
2//! and is not yet considered stable. The API may change without a major version bump.
3
4#![allow(unused_variables, unused_mut, clippy::too_many_arguments)]
5
6use std::ffi::OsStr;
7use std::path::Path;
8use std::time::SystemTime;
9
10use log::{debug, warn};
11use tokio::io;
12
13use crate::{
14    AccessFlags, BsdFileFlags, Config, CopyFileRangeFlags, Errno, FileHandle, INodeNo,
15    KernelConfig, LockOwner, OpenFlags, RenameFlags, Request, TimeOrNow, WriteFlags,
16    reply_async::{
17        AttrResponse, CreateResponse, DataResponse, DirectoryResponse, EntryResponse,
18        GetAttrResponse, LookupResponse, OpenResponse, ReadResponse, StatfsResponse, WriteResponse,
19        XattrResponse,
20    },
21    session_async::AsyncSessionBuilder,
22};
23
24/// Async filesystem trait. This is the async version of [`crate::Filesystem`]. It follows a more
25/// Rust-idiomatic async API design rather than the C-like, callback-based interface used
26/// by [`crate::Filesystem`]. It is not intended to be a thin wrapper over that API.
27///
28/// Instead of callbacks, this uses a call request -> return result response model, which allows
29/// for more straightforward control flow and improved error handling.
30///
31/// Internally, it operates on an async-aware wrapper ([AsyncFD](https://docs.rs/tokio/latest/tokio/io/unix/struct.AsyncFd.html)) around the FUSE device, enabling
32/// better integration and performance with async runtimes.
33///
34/// For the majority of use cases, users should prefer the [`crate::Filesystem`] API. It is more
35/// stable and generally performs better in typical scenarios where the primary bottleneck
36/// is the kernel round-trip.
37///
38/// This API is intended for more IO-bound workloads (e.g. network filesystems), where an
39/// async model can improve performance by allowing concurrent request handling and
40/// integrating cleanly with other asynchronous systems.
41#[async_trait::async_trait]
42pub trait AsyncFilesystem: Send + Sync + 'static {
43    /// Initialize the filesystem. This is called before the kernel is ready to start sending requests
44    /// and to let the filesystem know certain configuration details.
45    async fn init(&mut self, _req: &Request, _config: &mut KernelConfig) -> io::Result<()> {
46        Ok(())
47    }
48
49    /// Clean up filesystem. This is where you drop any resources allocated during the filesystem's
50    /// lifetime that won't be automatically cleaned up.
51    fn destroy(&mut self) {}
52
53    /// Look up an entry by name and get its attributes. This is called
54    /// by the kernel when it needs to know if a file exists and what its attributes are.
55    async fn lookup(
56        &self,
57        context: &Request,
58        parent: INodeNo,
59        name: &OsStr,
60    ) -> Result<LookupResponse, Errno> {
61        warn!(
62            "lookup not implemented for parent inode {}, name {:?}",
63            parent, name
64        );
65        Err(Errno::ENOTSUP)
66    }
67
68    /// Get the attributes of an entry. This is called by the kernel when it needs to know the attributes of
69    /// a file, either by inode number or by file handle (created on [`crate::AsyncFilesystem::open`]).
70    async fn getattr(
71        &self,
72        context: &Request,
73        ino: INodeNo,
74        file_handle: Option<FileHandle>,
75    ) -> Result<GetAttrResponse, Errno> {
76        warn!("getattr not implemented for inode {}", ino);
77        Err(Errno::ENOTSUP)
78    }
79
80    /// Forget about an inode. Internal only, the kernel is just making us aware for
81    /// bookkeeping.
82    async fn forget(&self, req: &Request, ino: INodeNo, nlookup: u64) {
83        debug!("forget not implemented for inode {}", ino);
84    }
85
86    /// Set file attributes.
87    async fn setattr(
88        &self,
89        req: &Request,
90        ino: INodeNo,
91        mode: Option<u32>,
92        uid: Option<u32>,
93        gid: Option<u32>,
94        size: Option<u64>,
95        atime: Option<TimeOrNow>,
96        mtime: Option<TimeOrNow>,
97        ctime: Option<SystemTime>,
98        fh: Option<FileHandle>,
99        crtime: Option<SystemTime>,
100        chgtime: Option<SystemTime>,
101        bkuptime: Option<SystemTime>,
102        flags: Option<BsdFileFlags>,
103    ) -> Result<AttrResponse, Errno> {
104        warn!(
105            "setattr not implemented for inode {}, mode {:?}, uid {:?}, gid {:?}, size {:?}, fh {:?}, flags {:?}",
106            ino, mode, uid, gid, size, fh, flags
107        );
108        Err(Errno::ENOTSUP)
109    }
110
111    /// Read a symbolic link.
112    async fn readlink(&self, req: &Request, ino: INodeNo) -> Result<DataResponse, Errno> {
113        warn!("readlink not implemented for inode {}", ino);
114        Err(Errno::ENOTSUP)
115    }
116
117    /// Create file node.
118    async fn mknod(
119        &self,
120        req: &Request,
121        parent: INodeNo,
122        name: &OsStr,
123        mode: u32,
124        umask: u32,
125        rdev: u32,
126    ) -> Result<EntryResponse, Errno> {
127        warn!(
128            "mknod not implemented for parent inode {}, name {:?}, mode {}, umask {}, rdev {}",
129            parent, name, mode, umask, rdev
130        );
131        Err(Errno::ENOTSUP)
132    }
133
134    /// Create a directory.
135    async fn mkdir(
136        &self,
137        req: &Request,
138        parent: INodeNo,
139        name: &OsStr,
140        mode: u32,
141        umask: u32,
142    ) -> Result<EntryResponse, Errno> {
143        warn!(
144            "mkdir not implemented for parent inode {}, name {:?}, mode {}, umask {}",
145            parent, name, mode, umask
146        );
147        Err(Errno::ENOTSUP)
148    }
149
150    /// Remove a file.
151    async fn unlink(&self, req: &Request, parent: INodeNo, name: &OsStr) -> Result<(), Errno> {
152        warn!(
153            "unlink not implemented for parent inode {}, name {:?}",
154            parent, name
155        );
156        Err(Errno::ENOTSUP)
157    }
158
159    /// Remove a directory.
160    async fn rmdir(&self, req: &Request, parent: INodeNo, name: &OsStr) -> Result<(), Errno> {
161        warn!(
162            "rmdir not implemented for parent inode {}, name {:?}",
163            parent, name
164        );
165        Err(Errno::ENOTSUP)
166    }
167
168    /// Create a symbolic link.
169    async fn symlink(
170        &self,
171        req: &Request,
172        parent: INodeNo,
173        link_name: &OsStr,
174        target: &Path,
175    ) -> Result<EntryResponse, Errno> {
176        warn!(
177            "symlink not implemented for parent inode {}, link_name {:?}, target {:?}",
178            parent, link_name, target
179        );
180        Err(Errno::ENOTSUP)
181    }
182
183    /// Rename a file.
184    async fn rename(
185        &self,
186        req: &Request,
187        parent: INodeNo,
188        name: &OsStr,
189        newparent: INodeNo,
190        newname: &OsStr,
191        flags: RenameFlags,
192    ) -> Result<(), Errno> {
193        warn!(
194            "rename not implemented for parent inode {}, name {:?}, newparent {}, newname {:?}, flags {:?}",
195            parent, name, newparent, newname, flags
196        );
197        Err(Errno::ENOTSUP)
198    }
199
200    /// Create a hard link.
201    async fn link(
202        &self,
203        req: &Request,
204        ino: INodeNo,
205        newparent: INodeNo,
206        newname: &OsStr,
207    ) -> Result<EntryResponse, Errno> {
208        warn!(
209            "link not implemented for inode {}, newparent {}, newname {:?}",
210            ino, newparent, newname
211        );
212        Err(Errno::ENOTSUP)
213    }
214
215    /// Open a file.
216    async fn open(
217        &self,
218        req: &Request,
219        ino: INodeNo,
220        flags: OpenFlags,
221    ) -> Result<OpenResponse, Errno> {
222        warn!("open not implemented for inode {}, flags {:?}", ino, flags);
223        Err(Errno::ENOTSUP)
224    }
225
226    /// Return the data of a file. This is called by the kernel when it needs to read the contents
227    /// of a file.
228    async fn read(
229        &self,
230        req: &Request,
231        ino: INodeNo,
232        file_handle: FileHandle,
233        offset: u64,
234        size: u32,
235        flags: OpenFlags,
236        lock: Option<LockOwner>,
237    ) -> Result<ReadResponse, Errno> {
238        warn!(
239            "read not implemented for inode {}, offset {}, size {}",
240            ino, offset, size
241        );
242        Err(Errno::ENOTSUP)
243    }
244
245    /// Write data.
246    ///
247    /// Write should return exactly the number of bytes requested except on error. An
248    /// exception to this is when the file has been opened in `direct_io` mode, in
249    /// which case the return value of the write system call will reflect the return
250    /// value of this operation. fh will contain the value set by the open method, or
251    /// will be undefined if the open method didn't set any value.
252    ///
253    /// `write_flags`: will contain `FUSE_WRITE_CACHE`, if this write is from the page cache. If set,
254    /// the pid, uid, gid, and fh may not match the value that would have been sent if write cachin
255    /// is disabled
256    /// flags: these are the file flags, such as `O_SYNC`. Only supported with ABI >= 7.9
257    /// `lock_owner`: only supported with ABI >= 7.9
258    async fn write(
259        &self,
260        req: &Request,
261        ino: INodeNo,
262        fh: FileHandle,
263        offset: u64,
264        data: &[u8],
265        write_flags: WriteFlags,
266        flags: OpenFlags,
267        lock_owner: Option<LockOwner>,
268    ) -> Result<WriteResponse, Errno> {
269        warn!(
270            "write not implemented for inode {}, offset {}, size {}",
271            ino,
272            offset,
273            data.len()
274        );
275        Err(Errno::ENOTSUP)
276    }
277
278    /// Release an open file.
279    async fn release(
280        &self,
281        req: &Request,
282        ino: INodeNo,
283        fh: FileHandle,
284        flags: OpenFlags,
285        lock_owner: Option<LockOwner>,
286        flush: bool,
287    ) -> Result<(), Errno> {
288        warn!("release not implemented for inode {}, fh {:?}", ino, fh);
289        Err(Errno::ENOTSUP)
290    }
291
292    /// Open a directory.
293    async fn opendir(
294        &self,
295        req: &Request,
296        ino: INodeNo,
297        flags: OpenFlags,
298    ) -> Result<OpenResponse, Errno> {
299        warn!(
300            "opendir not implemented for inode {}, flags {:?}",
301            ino, flags
302        );
303        Err(Errno::ENOTSUP)
304    }
305
306    /// Construct a directory listing response for the given directory inode. This is called by
307    /// the kernel when it needs to read the contents of a directory.
308    async fn readdir(
309        &self,
310        req: &Request,
311        ino: INodeNo,
312        file_handle: FileHandle,
313        size: u32,
314        offset: u64,
315    ) -> Result<DirectoryResponse, Errno> {
316        warn!(
317            "readdir not implemented for inode {}, offset {}, size {}",
318            ino, offset, size
319        );
320        Err(Errno::ENOTSUP)
321    }
322
323    /// Release an open directory.
324    async fn releasedir(
325        &self,
326        req: &Request,
327        ino: INodeNo,
328        fh: FileHandle,
329        flags: OpenFlags,
330    ) -> Result<(), Errno> {
331        warn!("releasedir not implemented for inode {}, fh {:?}", ino, fh);
332        Err(Errno::ENOTSUP)
333    }
334
335    /// Get file system statistics.
336    async fn statfs(&self, req: &Request, ino: INodeNo) -> Result<StatfsResponse, Errno> {
337        warn!("statfs not implemented for inode {}", ino);
338        Err(Errno::ENOTSUP)
339    }
340
341    /// Set an extended attribute.
342    async fn setxattr(
343        &self,
344        req: &Request,
345        ino: INodeNo,
346        name: &OsStr,
347        value: &[u8],
348        flags: i32,
349        position: u32,
350    ) -> Result<(), Errno> {
351        warn!(
352            "setxattr not implemented for inode {}, name {:?}, flags {}, position {}",
353            ino, name, flags, position
354        );
355        Err(Errno::ENOTSUP)
356    }
357
358    /// Get an extended attribute.
359    async fn getxattr(
360        &self,
361        req: &Request,
362        ino: INodeNo,
363        name: &OsStr,
364        size: u32,
365    ) -> Result<XattrResponse, Errno> {
366        warn!(
367            "getxattr not implemented for inode {}, name {:?}, size {}",
368            ino, name, size
369        );
370        Err(Errno::ENOTSUP)
371    }
372
373    /// List extended attribute names.
374    async fn listxattr(
375        &self,
376        req: &Request,
377        ino: INodeNo,
378        size: u32,
379    ) -> Result<XattrResponse, Errno> {
380        warn!("listxattr not implemented for inode {}, size {}", ino, size);
381        Err(Errno::ENOTSUP)
382    }
383
384    /// Remove an extended attribute.
385    async fn removexattr(&self, req: &Request, ino: INodeNo, name: &OsStr) -> Result<(), Errno> {
386        warn!(
387            "removexattr not implemented for inode {}, name {:?}",
388            ino, name
389        );
390        Err(Errno::ENOTSUP)
391    }
392
393    /// Check file access permissions.
394    async fn access(&self, req: &Request, ino: INodeNo, mask: AccessFlags) -> Result<(), Errno> {
395        warn!("access not implemented for inode {}, mask {:?}", ino, mask);
396        Err(Errno::ENOTSUP)
397    }
398
399    /// Create and open a file.
400    async fn create(
401        &self,
402        req: &Request,
403        parent: INodeNo,
404        name: &OsStr,
405        mode: u32,
406        umask: u32,
407        flags: i32,
408    ) -> Result<CreateResponse, Errno> {
409        warn!(
410            "create not implemented for parent inode {}, name {:?}, mode {}, umask {}, flags {}",
411            parent, name, mode, umask, flags
412        );
413        Err(Errno::ENOTSUP)
414    }
415
416    /// Preallocate or deallocate file space.
417    async fn fallocate(
418        &self,
419        req: &Request,
420        ino: INodeNo,
421        fh: FileHandle,
422        offset: u64,
423        length: u64,
424        mode: i32,
425    ) -> Result<(), Errno> {
426        warn!(
427            "fallocate not implemented for inode {}, offset {}, length {}, mode {}",
428            ino, offset, length, mode
429        );
430        Err(Errno::ENOTSUP)
431    }
432
433    /// Copy a file range.
434    async fn copy_file_range(
435        &self,
436        req: &Request,
437        ino_in: INodeNo,
438        fh_in: FileHandle,
439        offset_in: u64,
440        ino_out: INodeNo,
441        fh_out: FileHandle,
442        offset_out: u64,
443        len: u64,
444        flags: CopyFileRangeFlags,
445    ) -> Result<WriteResponse, Errno> {
446        warn!(
447            "copy_file_range not implemented for src ({}, {}, {}), dest ({}, {}, {}), len {}, flags {:?}",
448            ino_in, fh_in, offset_in, ino_out, fh_out, offset_out, len, flags
449        );
450        Err(Errno::ENOTSUP)
451    }
452}
453
454/// Mount the given async filesystem to the given mountpoint. This function will
455/// not return until the filesystem is unmounted.
456///
457/// # Errors
458/// Returns an error if the options are incorrect, or if the fuse device can't be mounted,
459/// and any final error when the session comes to an end.
460pub async fn mount_async<FS: AsyncFilesystem, P: AsRef<Path>>(
461    filesystem: FS,
462    mountpoint: P,
463    options: &Config,
464) -> io::Result<()> {
465    let session = AsyncSessionBuilder::new()
466        .filesystem(filesystem)
467        .mountpoint(mountpoint)
468        .options(options.clone())?
469        .build()
470        .await?;
471    session.run().await
472}