easy_fuser 0.7.1

A flexible and idiomatic Fuse implementation for Rust
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
//! in FUSE filesystem operations. It includes definitions for file attributes,
//! device types, filesystem statistics, and request information.
//!
//! # Key Types
//!
//! - [`DeviceType`]: Represents POSIX file kinds and device numbers.
//! - [`StatFs`]: Represents file system statistics, similar to the POSIX `statvfs` structure.
//! - [`RequestInfo`]: Encapsulates essential information about a FUSE request.
//! - [`FileAttribute`]: Represents file attributes for FUSE operations with optional caching parameters.
//! - [`SetAttrRequest`]: Represents a request to set file attributes in a FUSE file system.
//!
//! # Functions
//!
//! - [`seek_from_raw`]: Converts raw seek parameters to a `SeekFrom` enum.
//!
//! This module also re-exports `SeekFrom` from the standard library for convenience.

use std::time::{Duration, SystemTime};

use fuser::FileAttr as FuseFileAttr;
pub use fuser::RequestId;
use fuser::{FileType, Request, TimeOrNow, INodeNo, BsdFileFlags};

use super::LockType;

pub use std::io::SeekFrom;

pub fn seek_from_raw(whence: Option<i32>, offset: i64) -> SeekFrom {
    match whence {
        Some(libc::SEEK_SET) => SeekFrom::Start(
            offset
                .try_into()
                .expect("Invalid negative seek offset for file start"),
        ),
        Some(libc::SEEK_CUR) => SeekFrom::Current(offset),
        Some(libc::SEEK_END) => SeekFrom::End(offset),
        Some(_) => panic!("Invalid seek code"),
        None => SeekFrom::Start(
            offset
                .try_into()
                .expect("Invalid negative seek offset for file start"),
        ),
    }
}

/// Represents POSIX file kinds and, for devices, their major and minor numbers.
///
/// This enum encapsulates various file system object types, including:
/// - Regular files and directories
/// - Character and block devices (with major/minor numbers)
/// - Named pipes, sockets, and symbolic links
#[derive(Debug, Clone, Copy)]
pub enum DeviceType {
    RegularFile,
    Directory,
    CharacterDevice { major: u32, minor: u32 },
    BlockDevice { major: u32, minor: u32 },
    NamedPipe,
    Socket,
    Symlink,
    Unknown,
}

impl DeviceType {
    pub fn from_file_type_and_rdev(file_type: FileType, rdev: libc::dev_t) -> Self {
        let major = libc::major(rdev) as u32;
        let minor = libc::minor(rdev) as u32;

        match file_type {
            FileType::RegularFile => DeviceType::RegularFile,
            FileType::Directory => DeviceType::Directory,
            FileType::CharDevice => DeviceType::CharacterDevice { major, minor },
            FileType::BlockDevice => DeviceType::BlockDevice { major, minor },
            FileType::NamedPipe => DeviceType::NamedPipe,
            FileType::Socket => DeviceType::Socket,
            FileType::Symlink => DeviceType::Symlink,
        }
    }

    pub fn to_rdev(&self) -> libc::dev_t {
        match self {
            DeviceType::RegularFile => 0,
            DeviceType::Directory => 0,
            DeviceType::CharacterDevice { major, minor } => libc::makedev(*major as _, *minor as _),
            DeviceType::BlockDevice { major, minor } => libc::makedev(*major as _, *minor as _),
            DeviceType::NamedPipe => 0,
            DeviceType::Socket => 0,
            DeviceType::Symlink => 0,
            DeviceType::Unknown => 0, // Represents an unknown device
        }
    }
}

#[cfg(test)]
mod device_type_tests {
    use super::*;

    #[test]
    fn device_numbers_round_trip() {
        let rdev = libc::makedev(0x12, 0x3456);
        let character = DeviceType::from_file_type_and_rdev(FileType::CharDevice, rdev);
        assert!(matches!(character, DeviceType::CharacterDevice { major: 0x12, minor: 0x3456 }));
        assert_eq!(character.to_rdev(), rdev);

        let block = DeviceType::from_file_type_and_rdev(FileType::BlockDevice, rdev);
        assert!(matches!(block, DeviceType::BlockDevice { major: 0x12, minor: 0x3456 }));
        assert_eq!(block.to_rdev(), rdev);
    }

    #[test]
    fn non_device_uses_file_kind_and_has_no_rdev() {
        let kind = DeviceType::from_file_type_and_rdev(FileType::NamedPipe, 0);
        assert!(matches!(kind, DeviceType::NamedPipe));
        assert_eq!(kind.to_rdev(), 0);
    }
}

/// Represents file system statistics, similar to the POSIX `statvfs` structure.
///
/// Used to report file system status in FUSE operations.
#[derive(Debug, Clone)]
pub struct StatFs {
    /// Total number of blocks
    pub total_blocks: u64,
    /// Number of free blocks
    pub free_blocks: u64,
    /// Number of blocks available to non-root users
    pub available_blocks: u64,
    /// Total number of files
    pub total_files: u64,
    /// Number of free file nodes
    pub free_files: u64,
    /// Size of a block in bytes
    pub block_size: u32,
    /// Maximum length of a filename
    pub max_filename_length: u32,
    /// Fragment size in bytes
    pub fragment_size: u32,
}

impl StatFs {
    /// Creates a default `StatFs` instance with maximum capacity values.
    ///
    /// This method returns a `StatFs` struct with:
    /// - Maximum values for block and file counts
    /// - Standard block size (4096 bytes)
    /// - Typical maximum filename length (255 characters)
    /// - Fragment size matching block size
    ///
    /// Note: These values are placeholders and may not reflect actual file system limits.
    /// For accurate statistics, use the `helper::statfs` function instead.
    pub fn default() -> Self {
        StatFs {
            total_blocks: u64::MAX,
            free_blocks: u64::MAX,
            available_blocks: u64::MAX,
            total_files: u64::MAX / 2,
            free_files: u64::MAX / 2,
            block_size: 4096,
            max_filename_length: 255,
            fragment_size: 4096,
        }
    }
}

/// Encapsulates essential information about a FUSE request.
///
/// Fields:
/// - `id`: Unique identifier for the request
/// - `uid`: User ID of the process that initiated the request
/// - `gid`: Group ID of the process that initiated the request
/// - `pid`: Process ID of the process that initiated the request
#[derive(Debug, Clone)]
pub struct RequestInfo {
    pub id: RequestId,
    pub uid: u32,
    pub gid: u32,
    pub pid: u32,
}
impl From<&Request> for RequestInfo {
    fn from(req: &Request) -> Self {
        Self {
            id: req.unique(),
            uid: req.uid(),
            gid: req.gid(),
            pid: req.pid(),
        }
    }
}

/// Represents file attributes for FUSE operations with optional caching parameters.
#[derive(Debug, PartialEq, Clone)]
pub struct FileAttribute {
    /// File size in bytes
    pub size: u64,
    /// Number of 512-byte blocks allocated
    pub blocks: u64,
    /// Last access time
    pub atime: SystemTime,
    /// Last modification time
    pub mtime: SystemTime,
    /// Last status change time
    pub ctime: SystemTime,
    /// Creation time
    pub crtime: SystemTime,
    /// File type (regular file, directory, etc.)
    pub kind: FileType,
    /// File permissions
    pub perm: u16,
    /// Number of hard links
    pub nlink: u32,
    /// User ID of the file owner
    pub uid: u32,
    /// Group ID of the file owner
    pub gid: u32,
    /// Device ID (if special file)
    pub rdev: u32,
    /// Preferred block size for file system I/O
    pub blksize: u32,
    /// File flags
    pub flags: u32,
    /// Metadata timeout for FUSE replies containing this attribute.
    ///
    /// `None` uses `FuseHandler::get_default_ttl()`. For `getattr` and
    /// `setattr`, the timeout controls attribute caching. For entry replies
    /// (`lookup`, `create`, `mkdir`, `mknod`, `symlink`, `link`, and
    /// `readdirplus`), `easy_fuser` passes this same timeout for both attribute
    /// validity and name-to-inode entry validity. Although fuser 0.18 provides
    /// [`ReplyEntry::entry_with_ttls`](https://docs.rs/fuser/0.18.0/fuser/struct.ReplyEntry.html#method.entry_with_ttls)
    /// for separate timeouts, this crate's handler API currently exposes only
    /// one. This is separate from file-data caching.
    ///
    /// **Warning:** Keep the timeout policy predictable; do not randomize it
    /// per reply, because that makes cache expiry and revalidation timing
    /// unpredictable. FUSE accepts timeout values on individual replies, so
    /// this is guidance for predictable behavior rather than a protocol rule.
    /// See libfuse's [`attr_timeout`](https://libfuse.github.io/doxygen/struct_fuse_entry_param.html#attr_timeout)
    /// and [`entry_timeout`](https://libfuse.github.io/doxygen/struct_fuse_entry_param.html#entry_timeout)
    /// descriptions, and fuser's [`ReplyEntry::entry`](https://docs.rs/fuser/0.18.0/fuser/struct.ReplyEntry.html#method.entry).
    pub ttl: Option<Duration>,
    /// File generation number for this object, or `None` to use the crate's default (`0`).
    /// Zero is accepted by fuser and is this crate's default when no generation
    /// is supplied.
    ///
    /// Keep this value stable for the same object's lifetime. Do not generate
    /// a new random value on each lookup: the kernel can interpret a changed
    /// generation as a changed object during revalidation. If the filesystem
    /// is exported over NFS, `(inode, generation)` pairs must be unique over
    /// the filesystem's lifetime; if an inode number is reused, assign a new,
    /// previously unused generation. See the [`fuser::Generation` contract](https://docs.rs/fuser/0.18.0/fuser/struct.Generation.html)
    /// and the rationale in [PR #107](https://github.com/Alogani/easy_fuser/pull/107).
    pub generation: Option<u64>,
}

/// `FuseFileAttr`, `Option<ttl>`, `Option<generation>`
impl FileAttribute {
    pub(crate) fn to_fuse(&self, ino: INodeNo) -> (FuseFileAttr, Option<Duration>, Option<u64>) {
        (
            FuseFileAttr {
                ino,
                size: self.size,
                blocks: self.blocks,
                atime: self.atime,
                mtime: self.mtime,
                ctime: self.ctime,
                crtime: self.crtime,
                kind: self.kind,
                perm: self.perm,
                nlink: self.nlink,
                uid: self.uid,
                gid: self.gid,
                rdev: self.rdev,
                blksize: self.blksize,
                flags: self.flags,
            },
            self.ttl,
            self.generation,
        )
    }
}

/// Represents a request to set file attributes in a FUSE file system.
///
/// This struct uses the builder pattern to construct a request with optional fields.
/// Each field corresponds to a file attribute that can be modified.
#[derive(Debug)]
pub struct SetAttrRequest {
    /// File mode (permissions)
    pub mode: Option<u32>,
    /// User ID of the file owner
    pub uid: Option<u32>,
    /// Group ID of the file owner
    pub gid: Option<u32>,
    /// File size in bytes
    pub size: Option<u64>,
    /// Last access time
    pub atime: Option<TimeOrNow>,
    /// Last modification time
    pub mtime: Option<TimeOrNow>,
    /// Last status change time
    pub ctime: Option<SystemTime>,
    /// Creation time
    pub crtime: Option<SystemTime>,
    /// Change time (for BSD systems)
    pub chgtime: Option<SystemTime>,
    /// Backup time (for macOS)
    pub bkuptime: Option<SystemTime>,
    /// File flags (unused in FUSE)
    pub flags: Option<BsdFileFlags>,
}

impl Default for SetAttrRequest {
    fn default() -> Self {
        Self::new()
    }
}

impl SetAttrRequest {
    pub fn new() -> Self {
        Self {
            mode: None,
            uid: None,
            gid: None,
            size: None,
            atime: None,
            mtime: None,
            ctime: None,
            crtime: None,
            chgtime: None,
            bkuptime: None,
            flags: None,
        }
    }

    pub fn mode(mut self, mode: u32) -> Self {
        self.mode = Some(mode);
        self
    }

    pub fn uid(mut self, uid: u32) -> Self {
        self.uid = Some(uid);
        self
    }

    pub fn gid(mut self, gid: u32) -> Self {
        self.gid = Some(gid);
        self
    }

    pub fn size(mut self, size: u64) -> Self {
        self.size = Some(size);
        self
    }

    pub fn atime(mut self, atime: TimeOrNow) -> Self {
        self.atime = Some(atime);
        self
    }

    pub fn mtime(mut self, mtime: TimeOrNow) -> Self {
        self.mtime = Some(mtime);
        self
    }

    pub fn ctime(mut self, ctime: SystemTime) -> Self {
        self.ctime = Some(ctime);
        self
    }

    pub fn crtime(mut self, crtime: SystemTime) -> Self {
        self.crtime = Some(crtime);
        self
    }

    pub fn chgtime(mut self, chgtime: SystemTime) -> Self {
        self.chgtime = Some(chgtime);
        self
    }

    pub fn bkuptime(mut self, bkuptime: SystemTime) -> Self {
        self.bkuptime = Some(bkuptime);
        self
    }

    /// Unused by FUSE
    pub fn flags(mut self, flags: BsdFileFlags) -> Self {
        self.flags = Some(flags);
        self
    }
}

/// Represents file locking information for FUSE operations.
#[derive(Debug)]
pub struct LockInfo {
    /// Starting offset of the lock range in bytes
    pub start: u64,
    /// Ending offset of the lock range in bytes (exclusive)
    pub end: u64,
    /// Type of lock (e.g., Read, Write, or Unlock)
    pub lock_type: LockType,
    /// Process ID of the lock owner
    pub pid: u32,
}