Skip to main content

fuse/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! A Rust helper library for creating File-system in User Space (FUSE) daemons, aimed in
5//! particular at virtio-fs.
6
7#![expect(missing_docs)]
8
9#[cfg(unix)]
10mod conn;
11pub mod protocol;
12mod reply;
13mod request;
14mod session;
15mod util;
16
17#[cfg(target_os = "linux")]
18pub use conn::Connection;
19pub use reply::DirEntryWriter;
20pub use reply::ReplySender;
21pub use request::FuseOperation;
22pub use request::Request;
23pub use request::RequestReader;
24pub use request::check_name;
25pub use session::Session;
26pub use session::SessionInfo;
27
28use lx::LxStr;
29use lx::LxString;
30use protocol::*;
31use std::time::Duration;
32use zerocopy::FromBytes;
33use zerocopy::FromZeros;
34use zerocopy::Immutable;
35use zerocopy::IntoBytes;
36use zerocopy::KnownLayout;
37
38/// Reply data for the `create` operation.
39///
40/// The `create` operation includes two values in its reply, but fuse.h has no wrapper for the
41/// combination of these values as they're just passed as separate arguments to `fuse_reply_create`
42/// in libfuse.
43#[repr(C)]
44#[derive(Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
45pub struct CreateOut {
46    pub entry: fuse_entry_out,
47    pub open: fuse_open_out,
48}
49
50/// Trait that FUSE file systems must implement.
51///
52/// Most operations are loosely based on `fuse_lowlevel_ops` in libfuse, so check the [official
53/// libfuse documentation](http://libfuse.github.io/doxygen/index.html) for more information on how
54/// these operations should behave.
55///
56/// For many operations, a reply of `ENOSYS` is taken as permanent failure, preventing the client
57/// from ever issuing that operation again.
58pub trait Fuse {
59    /// Looks up a child of an inode.
60    ///
61    /// This increases the lookup count of the found entry by one.
62    fn lookup(&self, _request: &Request, _name: &LxStr) -> lx::Result<fuse_entry_out> {
63        Err(lx::Error::ENOSYS)
64    }
65
66    /// Tells the FUSE file system to reduce the lookup count of an inode by the specified amount.
67    ///
68    /// # Note
69    ///
70    /// The client is not guaranteed to send a forget message for every inode if the file system
71    /// is unmounted.
72    fn forget(&self, _node_id: u64, _lookup_count: u64) {
73        // No reply is allowed from this message, not even error.
74    }
75
76    /// Retrieves the attributes of a file.
77    ///
78    /// # Note
79    ///
80    /// If attributes are retrieved through an open file descriptor (i.e. using `fstat`), the
81    /// `fh` parameter will be set to the file handle returned by the `open` call.
82    fn get_attr(&self, _request: &Request, _flags: u32, _fh: u64) -> lx::Result<fuse_attr_out> {
83        Err(lx::Error::ENOSYS)
84    }
85
86    /// Changes the attributes of a file.
87    fn set_attr(&self, _request: &Request, _arg: &fuse_setattr_in) -> lx::Result<fuse_attr_out> {
88        Err(lx::Error::ENOSYS)
89    }
90
91    /// Reads the target of a symbolic link.
92    fn read_link(&self, _request: &Request) -> lx::Result<LxString> {
93        Err(lx::Error::ENOSYS)
94    }
95
96    /// Creates a symbolic link as a child of the specified inode.
97    fn symlink(
98        &self,
99        _request: &Request,
100        _name: &LxStr,
101        _target: &LxStr,
102    ) -> lx::Result<fuse_entry_out> {
103        Err(lx::Error::ENOSYS)
104    }
105
106    /// Creates a regular file, fifo, socket, or character or block device node as a child of
107    /// the specified inode.
108    fn mknod(
109        &self,
110        _request: &Request,
111        _name: &LxStr,
112        _arg: &fuse_mknod_in,
113    ) -> lx::Result<fuse_entry_out> {
114        Err(lx::Error::ENOSYS)
115    }
116
117    /// Creates a directory as a child of the specified inode.
118    fn mkdir(
119        &self,
120        _request: &Request,
121        _name: &LxStr,
122        _arg: &fuse_mkdir_in,
123    ) -> lx::Result<fuse_entry_out> {
124        Err(lx::Error::ENOSYS)
125    }
126
127    /// Removes a non-directory child from the specified inode.
128    fn unlink(&self, _request: &Request, _name: &LxStr) -> lx::Result<()> {
129        Err(lx::Error::ENOSYS)
130    }
131
132    /// Removes a directory child from the specified inode.
133    fn rmdir(&self, _request: &Request, _name: &LxStr) -> lx::Result<()> {
134        Err(lx::Error::ENOSYS)
135    }
136
137    /// Renames a file.
138    ///
139    /// The file's original parent is the request's inode, while the new parent is indicated using
140    /// `new_dir`.
141    fn rename(
142        &self,
143        _request: &Request,
144        _name: &LxStr,
145        _new_dir: u64,
146        _new_name: &LxStr,
147        _flags: u32,
148    ) -> lx::Result<()> {
149        Err(lx::Error::ENOSYS)
150    }
151
152    /// Creates a hard-link to an existing inode, as a child of the specified inode..
153    fn link(&self, _request: &Request, _name: &LxStr, _target: u64) -> lx::Result<fuse_entry_out> {
154        Err(lx::Error::ENOSYS)
155    }
156
157    /// Opens a file.
158    ///
159    /// If not implemented, this call will succeed, which can be used if the file system doesn't
160    /// need any state for open files, since the inode number is also provided to functions
161    /// such as `read` and `write`.
162    fn open(&self, _request: &Request, _flags: u32) -> lx::Result<fuse_open_out> {
163        Err(lx::Error::ENOSYS)
164    }
165
166    /// Reads data from an open file.
167    fn read(&self, _request: &Request, _arg: &fuse_read_in) -> lx::Result<Vec<u8>> {
168        Err(lx::Error::ENOSYS)
169    }
170
171    /// Writes data to an open file.
172    fn write(&self, _request: &Request, _arg: &fuse_write_in, _data: &[u8]) -> lx::Result<usize> {
173        Err(lx::Error::ENOSYS)
174    }
175
176    /// Retrieves the attributes of the file system.
177    fn statfs(&self, _request: &Request) -> lx::Result<fuse_kstatfs> {
178        Err(lx::Error::ENOSYS)
179    }
180
181    /// Closes an open file.
182    ///
183    /// If not implemented, this call will succeed. Won't be called if `open`
184    /// returned `ENOSYS` (the default).
185    fn release(&self, _request: &Request, _arg: &fuse_release_in) -> lx::Result<()> {
186        Ok(())
187    }
188
189    /// Synchronize file contents.
190    fn fsync(&self, _request: &Request, _fh: u64, _flags: u32) -> lx::Result<()> {
191        Err(lx::Error::ENOSYS)
192    }
193
194    /// Add or change an extended attribute on an inode.
195    fn set_xattr(
196        &self,
197        _request: &Request,
198        _name: &LxStr,
199        _value: &[u8],
200        _flags: u32,
201    ) -> lx::Result<()> {
202        Err(lx::Error::ENOSYS)
203    }
204
205    /// Retrieve an extended attribute on an inode.
206    fn get_xattr(&self, _request: &Request, _name: &LxStr, _size: u32) -> lx::Result<Vec<u8>> {
207        Err(lx::Error::ENOSYS)
208    }
209
210    /// Retrieve the size of an extended attribute on an inode.
211    fn get_xattr_size(&self, _request: &Request, _name: &LxStr) -> lx::Result<u32> {
212        Err(lx::Error::ENOSYS)
213    }
214
215    /// List all extended attributes on an inode.
216    fn list_xattr(&self, _request: &Request, _size: u32) -> lx::Result<Vec<u8>> {
217        Err(lx::Error::ENOSYS)
218    }
219
220    /// Retrieve the size of the list of extended attributes on an inode.
221    fn list_xattr_size(&self, _request: &Request) -> lx::Result<u32> {
222        Err(lx::Error::ENOSYS)
223    }
224
225    /// Remove an extended attribute from an inode.
226    fn remove_xattr(&self, _request: &Request, _name: &LxStr) -> lx::Result<()> {
227        Err(lx::Error::ENOSYS)
228    }
229
230    /// Called on each `close()` of a file descriptor for an opened file.
231    ///
232    /// This is called for every file descriptor, so may be called more than once.
233    ///
234    /// Use `release` to know when the last file descriptor was closed.
235    fn flush(&self, _request: &Request, _arg: &fuse_flush_in) -> lx::Result<()> {
236        Err(lx::Error::ENOSYS)
237    }
238
239    /// Negotiate file system parameters with the client.
240    fn init(&self, _info: &mut SessionInfo) {}
241
242    /// Opens a directory.
243    ///
244    /// If not implemented, this call will succeed, which can be used if the file system doesn't
245    /// need any state for open files, since the inode number is also provided to functions
246    /// such as `read_dir`.
247    fn open_dir(&self, _request: &Request, _flags: u32) -> lx::Result<fuse_open_out> {
248        Err(lx::Error::ENOSYS)
249    }
250
251    /// Reads the contents of a directory.
252    ///
253    /// Use `DirEntryWriter` to create a buffer containing directory entries.
254    fn read_dir(&self, _request: &Request, _arg: &fuse_read_in) -> lx::Result<Vec<u8>> {
255        Err(lx::Error::ENOSYS)
256    }
257
258    /// Closes a directory.
259    ///
260    /// If not implemented, this call will succeed. Won't be called if `opendir`
261    /// returned ENOSYS (the default).
262    fn release_dir(&self, _request: &Request, _arg: &fuse_release_in) -> lx::Result<()> {
263        Ok(())
264    }
265
266    /// Synchronize directory contents.
267    fn fsync_dir(&self, _request: &Request, _fh: u64, _flags: u32) -> lx::Result<()> {
268        Err(lx::Error::ENOSYS)
269    }
270
271    /// Test for a POSIX file lock.
272    fn get_lock(&self, _request: &Request, _arg: &fuse_lk_in) -> lx::Result<fuse_file_lock> {
273        Err(lx::Error::ENOSYS)
274    }
275
276    /// Acquire, modify or release a POSIX file lock.
277    ///
278    /// If not implemented, the client still allows for local file locking.
279    fn set_lock(&self, _request: &Request, _arg: &fuse_lk_in, _sleep: bool) -> lx::Result<()> {
280        Err(lx::Error::ENOSYS)
281    }
282
283    /// Check file access permissions.
284    fn access(&self, _request: &Request, _mask: u32) -> lx::Result<()> {
285        Err(lx::Error::ENOSYS)
286    }
287
288    /// Create and open a file.
289    ///
290    /// If not implemented, the client will use `mknod` followed by `open`.
291    fn create(
292        &self,
293        _request: &Request,
294        _name: &LxStr,
295        _arg: &fuse_create_in,
296    ) -> lx::Result<CreateOut> {
297        Err(lx::Error::ENOSYS)
298    }
299
300    /// Map a file block index to a device block index.
301    ///
302    /// This method is only relevant for file systems mounted using `fuseblk`.
303    fn block_map(&self, _request: &Request, _block: u64, _block_size: u32) -> lx::Result<u64> {
304        Err(lx::Error::ENOSYS)
305    }
306
307    /// Clean up the file system.
308    ///
309    /// For regular FUSE, the client only calls this for file systems mounted using `fuseblk`, but
310    /// for other file systems `Connection` will call it when the `/dev/fuse` connection is closed.
311    ///
312    /// For virtio-fs, the client will call this when the file system is unmounted. After receiving
313    /// destroy, another `init` call can be received if the file system is mounted again.
314    fn destroy(&self) {}
315
316    /// Submit an ioctl.
317    ///
318    /// # Note
319    ///
320    /// This is a somewhat limited subset of the ioctl functionality of libfuse; the additional
321    /// functionality seems to only apply to CUSE, however.
322    fn ioctl(
323        &self,
324        _request: &Request,
325        _arg: &fuse_ioctl_in,
326        _data: &[u8],
327    ) -> lx::Result<(i32, Vec<u8>)> {
328        Err(lx::Error::ENOSYS)
329    }
330
331    /// Allocate requested space.
332    fn fallocate(&self, _request: &Request, _arg: &fuse_fallocate_in) -> lx::Result<()> {
333        Err(lx::Error::ENOSYS)
334    }
335
336    /// Reads the contents of a directory, and performs a lookup on each entry.
337    ///
338    /// This function increases the lookup count of each entry in the directory by one.
339    ///
340    /// If you implement this, you must set `FUSE_DO_READDIRPLUS` in `init`. If you implement both
341    /// read_dir_plus and read_dir, also set `FUSE_READDIRPLUS_AUTO`.
342    fn read_dir_plus(&self, _request: &Request, _arg: &fuse_read_in) -> lx::Result<Vec<u8>> {
343        Err(lx::Error::ENOSYS)
344    }
345
346    /// Find data holes in a sparse file.
347    fn lseek(&self, _request: &Request, _fh: u64, _offset: u64, _whence: u32) -> lx::Result<u64> {
348        Err(lx::Error::ENOSYS)
349    }
350
351    /// Copy data from one file to another without needing to send data through the FUSE kernel
352    /// module.
353    fn copy_file_range(
354        &self,
355        _request: &Request,
356        _arg: &fuse_copy_file_range_in,
357    ) -> lx::Result<usize> {
358        Err(lx::Error::ENOSYS)
359    }
360
361    /// Create a DAX memory mapping.
362    fn setup_mapping(
363        &self,
364        request: &Request,
365        mapper: &dyn Mapper,
366        arg: &fuse_setupmapping_in,
367    ) -> lx::Result<()> {
368        let _ = (request, mapper, arg);
369        Err(lx::Error::ENOSYS)
370    }
371
372    /// Remove a DAX memory mapping.
373    fn remove_mapping(
374        &self,
375        request: &Request,
376        mapper: &dyn Mapper,
377        moffset: u64,
378        len: u64,
379    ) -> lx::Result<()> {
380        let _ = (request, mapper, moffset, len);
381        Err(lx::Error::ENOSYS)
382    }
383
384    /// Retrieves the statx details of a file.
385    ///
386    /// # Note
387    ///
388    /// If information is retrieved through an open file descriptor (i.e. using `fstat`), the
389    /// `fh` parameter will be set to the file handle returned by the `open` call.
390    fn get_statx(
391        &self,
392        _request: &Request,
393        _fh: u64,
394        _getattr_flags: u32,
395        _flags: StatxFlags,
396        _mask: lx::StatExMask,
397    ) -> lx::Result<fuse_statx_out> {
398        Err(lx::Error::ENOSYS)
399    }
400}
401
402#[cfg(windows)]
403pub type FileRef<'a> = std::os::windows::io::BorrowedHandle<'a>;
404#[cfg(unix)]
405pub type FileRef<'a> = std::os::unix::io::BorrowedFd<'a>;
406
407/// Trait for mapping files into a shared memory region.
408///
409/// This is used to support DAX with virtio-fs.
410pub trait Mapper {
411    /// Map memory into the region at `offset`.
412    fn map(
413        &self,
414        offset: u64,
415        file: FileRef<'_>,
416        file_offset: u64,
417        len: u64,
418        writable: bool,
419    ) -> lx::Result<()>;
420
421    /// Unmaps any memory in the given range.
422    fn unmap(&self, offset: u64, len: u64) -> lx::Result<()>;
423
424    /// Clears any mappings in the range.
425    fn clear(&self);
426}
427
428impl fuse_entry_out {
429    /// Create a new `fuse_entry_out`.
430    pub fn new(node_id: u64, entry_valid: Duration, attr_valid: Duration, attr: fuse_attr) -> Self {
431        Self {
432            nodeid: node_id,
433            generation: 0,
434            entry_valid: entry_valid.as_secs(),
435            entry_valid_nsec: entry_valid.subsec_nanos(),
436            attr_valid: attr_valid.as_secs(),
437            attr_valid_nsec: attr_valid.subsec_nanos(),
438            attr,
439        }
440    }
441
442    pub fn new_dot(ino: u64, mode: u32) -> Self {
443        let mut entry = Self::new_zeroed();
444        entry.attr.ino = ino;
445        entry.attr.mode = mode;
446        entry
447    }
448}
449
450impl fuse_attr_out {
451    /// Create a new `fuse_attr_out`.
452    pub fn new(valid: Duration, attr: fuse_attr) -> Self {
453        Self {
454            attr_valid: valid.as_secs(),
455            attr_valid_nsec: valid.subsec_nanos(),
456            dummy: 0,
457            attr,
458        }
459    }
460}
461
462impl fuse_statx_out {
463    /// Create a new `fuse_statx_out`.
464    pub fn new(valid: Duration, flags: StatxFlags, statx: fuse_statx) -> Self {
465        Self {
466            attr_valid: valid.as_secs(),
467            attr_valid_nsec: valid.subsec_nanos(),
468            flags,
469            statx,
470            _rsvd: [0; 2],
471        }
472    }
473}
474
475impl fuse_open_out {
476    /// Create a new `fuse_open_out`.
477    pub fn new(fh: u64, open_flags: u32) -> Self {
478        Self {
479            fh,
480            open_flags,
481            padding: 0,
482        }
483    }
484}
485
486impl fuse_kstatfs {
487    /// Create a new `fuse_kstatfs`.
488    pub fn new(
489        blocks: u64,
490        bfree: u64,
491        bavail: u64,
492        files: u64,
493        ffree: u64,
494        bsize: u32,
495        namelen: u32,
496        frsize: u32,
497    ) -> Self {
498        Self {
499            blocks,
500            bfree,
501            bavail,
502            files,
503            ffree,
504            bsize,
505            namelen,
506            frsize,
507            padding: 0,
508            spare: Default::default(),
509        }
510    }
511}