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