async_fusex/
fs_util.rs

1//! The implementation of filesystem related utilities
2use std::path::PathBuf;
3use std::time::{SystemTime, UNIX_EPOCH};
4
5use anyhow::Context;
6use clippy_utilities::Cast;
7use nix::errno::Errno;
8use nix::fcntl::OFlag;
9use nix::sys::stat::{Mode, SFlag};
10use serde::{Deserialize, Serialize};
11use tracing::debug;
12
13use crate::protocol::FuseAttr;
14use crate::error::{AsyncFusexError, AsyncFusexResult};
15use crate::util;
16
17/// Build error result from `nix` error code
18/// # Errors
19///
20/// Return the built `Err(anyhow::Error(..))`
21pub fn build_error_result_from_errno<T>(error_code: Errno, err_msg: String) -> AsyncFusexResult<T> {
22    Err(AsyncFusexError::from(
23        anyhow::Error::new(error_code).context(err_msg),
24    ))
25}
26
27/// The node ID of the root inode
28pub const ROOT_ID: u64 = 1;
29
30/// The type of i-number
31pub type INum = u64;
32
33/// POSIX statvfs parameters
34#[derive(Debug)]
35pub struct StatFsParam {
36    /// The number of blocks in the filesystem
37    pub blocks: u64,
38    /// The number of free blocks
39    pub bfree: u64,
40    /// The number of free blocks for non-privilege users
41    pub bavail: u64,
42    /// The number of inodes
43    pub files: u64,
44    /// The number of free inodes
45    pub f_free: u64,
46    /// Block size
47    pub bsize: u32,
48    /// Maximum file name length
49    pub namelen: u32,
50    /// Fragment size
51    pub frsize: u32,
52}
53
54/// Set attribute parameters
55#[derive(Debug)]
56pub struct SetAttrParam {
57    /// FUSE set attribute bit mask
58    pub valid: u32,
59    /// File handler
60    pub fh: Option<u64>,
61    /// File mode
62    pub mode: Option<u32>,
63    /// User ID
64    pub u_id: Option<u32>,
65    /// Group ID
66    pub g_id: Option<u32>,
67    /// File size
68    pub size: Option<u64>,
69    /// Lock owner
70    #[cfg(feature = "abi-7-9")]
71    pub lock_owner: Option<u64>,
72    /// Access time
73    pub a_time: Option<SystemTime>,
74    /// Content modified time
75    pub m_time: Option<SystemTime>,
76    /// Meta-data changed time seconds
77    #[cfg(feature = "abi-7-23")]
78    pub c_time: Option<SystemTime>,
79}
80
81/// Create parameters
82#[derive(Debug)]
83pub struct CreateParam {
84    /// Parent directory i-number
85    pub parent: INum,
86    /// File name
87    pub name: String,
88    /// File mode
89    pub mode: u32,
90    /// File flags
91    pub rdev: u32,
92    /// User ID
93    pub uid: u32,
94    /// Group ID
95    pub gid: u32,
96    /// Type
97    pub node_type: SFlag,
98    /// For symlink
99    pub link: Option<PathBuf>,
100}
101
102/// Rename parameters
103#[derive(Serialize, Deserialize, Debug)]
104pub struct RenameParam {
105    /// Old parent directory i-number
106    pub old_parent: INum,
107    /// Old name
108    pub old_name: String,
109    /// New parent directory i-number
110    pub new_parent: INum,
111    /// New name
112    pub new_name: String,
113    /// Rename flags
114    pub flags: u32,
115}
116
117/// POSIX file lock parameters
118#[derive(Debug)]
119pub struct FileLockParam {
120    /// File handler
121    pub fh: u64,
122    /// Lock owner
123    pub lock_owner: u64,
124    /// Start offset
125    pub start: u64,
126    /// End offset
127    pub end: u64,
128    /// Lock type
129    pub typ: u32,
130    /// The process ID of the lock
131    pub pid: u32,
132}
133
134/// File attributes
135#[derive(Copy, Clone, Debug)]
136pub struct FileAttr {
137    /// Inode number
138    pub ino: INum,
139    /// Size in bytes
140    pub size: u64,
141    /// Size in blocks
142    pub blocks: u64,
143    /// Time of last access
144    pub atime: SystemTime,
145    /// Time of last modification
146    pub mtime: SystemTime,
147    /// Time of last change
148    pub ctime: SystemTime,
149    /// Kind of file (directory, file, pipe, etc)
150    pub kind: SFlag,
151    /// Permissions
152    pub perm: u16,
153    /// Number of hard links
154    pub nlink: u32,
155    /// User id
156    pub uid: u32,
157    /// Group id
158    pub gid: u32,
159    /// Rdev
160    pub rdev: u32,
161    /// Version
162    pub version: u64,
163}
164
165/// Whether to check permission.
166/// If fuse mount with `-o default_permissions`, then we should not check
167/// permission. Otherwise, we should check permission.
168/// TODO: add a feature flag to control this
169pub const NEED_CHECK_PERM: bool = false;
170
171impl FileAttr {
172    /// New a `FileAttr`
173    #[allow(dead_code)]
174    pub(crate) fn now() -> Self {
175        let now = SystemTime::now();
176        Self {
177            ino: 0,
178            size: 4096,
179            blocks: 8,
180            atime: now,
181            mtime: now,
182            ctime: now,
183            kind: SFlag::S_IFREG,
184            perm: 0o775,
185            nlink: 0,
186            uid: 0,
187            gid: 0,
188            rdev: 0,
189            version: 0,
190        }
191    }
192
193    /// Precheck before set attr
194    #[allow(dead_code)]
195    pub(crate) fn setattr_precheck(
196        &self,
197        param: &SetAttrParam,
198        context_uid: u32,
199        context_gid: u32,
200    ) -> AsyncFusexResult<Option<FileAttr>> {
201        let cur_attr = *self;
202        let mut dirty_attr = cur_attr;
203
204        let st_now = SystemTime::now();
205        let mut attr_changed = false;
206
207        let check_permission = || -> AsyncFusexResult<()> {
208            if NEED_CHECK_PERM {
209                //  owner is root check the uid
210                if cur_attr.uid == 0 && context_uid != 0 {
211                    return build_error_result_from_errno(
212                        Errno::EPERM,
213                        "setattr() cannot change atime".to_owned(),
214                    );
215                }
216                cur_attr.check_perm(context_uid, context_gid, 2)?;
217                if context_uid != cur_attr.uid {
218                    return build_error_result_from_errno(
219                        Errno::EACCES,
220                        "setattr() cannot change atime".to_owned(),
221                    );
222                }
223                Ok(())
224            } else {
225                // We don't need to check permission
226                Ok(())
227            }
228        };
229
230        if let Some(gid) = param.g_id {
231            if context_uid != 0 && cur_attr.uid != context_uid {
232                return build_error_result_from_errno(
233                    Errno::EPERM,
234                    "setattr() cannot change gid".to_owned(),
235                );
236            }
237
238            if cur_attr.gid != gid {
239                dirty_attr.gid = gid;
240                attr_changed = true;
241            }
242        }
243
244        if let Some(uid) = param.u_id {
245            if cur_attr.uid != uid {
246                if context_uid != 0 {
247                    return build_error_result_from_errno(
248                        Errno::EPERM,
249                        "setattr() cannot change uid".to_owned(),
250                    );
251                }
252                dirty_attr.uid = uid;
253                attr_changed = true;
254            }
255        }
256
257        if let Some(mode) = param.mode {
258            let mode: u16 = mode.cast();
259            if mode != cur_attr.perm {
260                if context_uid != 0 && context_uid != cur_attr.uid {
261                    return build_error_result_from_errno(
262                        Errno::EPERM,
263                        "setattr() cannot change mode".to_owned(),
264                    );
265                }
266                dirty_attr.perm = mode;
267                attr_changed = true;
268            }
269        }
270
271        if let Some(atime) = param.a_time {
272            check_permission()?;
273            if atime != cur_attr.atime {
274                dirty_attr.atime = atime;
275                attr_changed = true;
276            }
277        }
278
279        if let Some(mtime) = param.m_time {
280            check_permission()?;
281            if mtime != cur_attr.mtime {
282                dirty_attr.mtime = mtime;
283                attr_changed = true;
284            }
285        }
286
287        if let Some(file_size) = param.size {
288            dirty_attr.size = file_size;
289            dirty_attr.mtime = st_now;
290            attr_changed = true;
291        }
292
293        if attr_changed {
294            dirty_attr.ctime = st_now;
295        }
296
297        // The `ctime` can be changed implicitly, but if it's specified, just use the
298        // specified one.
299        #[cfg(feature = "abi-7-23")]
300        if let Some(ctime) = param.c_time {
301            check_permission()?;
302            if ctime != cur_attr.ctime {
303                dirty_attr.ctime = ctime;
304                attr_changed = true;
305            }
306        }
307
308        Ok(attr_changed.then_some(dirty_attr))
309    }
310
311    /// File permissions in Unix/Linux systems are represented as a 12-bit structure,
312    /// laid out as follows:
313    /// ┌───────────────┬─────────┬─────────┬─────────┐
314    /// │   Special     │  User   │  Group  │  Other  │
315    /// ├───────────────┼─────────┼─────────┼─────────┤
316    /// │   3 Bits      │ 3 Bits  │ 3 Bits  │ 3 Bits  │
317    /// ├───────────────┼─────────┼─────────┼─────────┤
318    /// │ suid|sgid|stky│  r w x  │  r w x  │  r w x  │
319    /// └──────┬───────┴────┬────┴────┬────┴────┬────┘
320    ///        │             │         │         │
321    ///        │             │         │         └─ Other: Read, Write, Execute permissions for other users.
322    ///        │             │         └─ Group: Read, Write, Execute permissions for group members.
323    ///        │             └─ User:  Read, Write, Execute permissions for the owner of the file.
324    ///        └─ Special: Set User ID (suid), Set Group ID (sgid), and Sticky Bit (stky).
325    /// When Sticky Bit set on a directory, files in that directory may only be unlinked or -
326    /// renamed by root or the directory owner or the file owner.
327    pub fn check_perm(&self, uid: u32, gid: u32, access_mode: u8) -> AsyncFusexResult<()> {
328        if NEED_CHECK_PERM {
329            self.check_perm_inner(uid, gid, access_mode)
330        } else {
331            Ok(())
332        }
333    }
334
335    /// If `NEED_CHECK_PERM` is true, then check permission by ourselves not
336    /// rely on kernel.
337    #[inline]
338    fn check_perm_inner(&self, uid: u32, gid: u32, access_mode: u8) -> AsyncFusexResult<()> {
339        debug_assert!(
340            access_mode <= 0o7 && access_mode != 0,
341            "check_perm() found access_mode={access_mode} invalid",
342        );
343        if uid == 0 {
344            return Ok(());
345        }
346
347        let file_mode = self.get_access_mode(uid, gid);
348        debug!(
349            "check_perm() got access_mode={access_mode} and file_mode={file_mode} \
350            from uid={uid} gid={gid}",
351        );
352        if (file_mode & access_mode) != access_mode {
353            return build_error_result_from_errno(
354                Errno::EACCES,
355                format!("check_perm() failed {uid} {gid} {file_mode}"),
356            );
357        }
358        Ok(())
359    }
360
361    /// For given uid and gid, get the access mode of the file
362    #[allow(clippy::default_numeric_fallback)]
363    #[allow(clippy::arithmetic_side_effects)]
364    fn get_access_mode(&self, uid: u32, gid: u32) -> u8 {
365        let perm = self.perm;
366        let mode = if uid == self.uid {
367            (perm >> 6) & 0o7
368        } else if gid == self.gid {
369            (perm >> 3) & 0o7
370        } else {
371            perm & 0o7
372        };
373        mode.cast()
374    }
375}
376
377impl Default for FileAttr {
378    fn default() -> Self {
379        Self {
380            ino: 0,
381            size: 4096,
382            blocks: 8,
383            atime: SystemTime::UNIX_EPOCH,
384            mtime: SystemTime::UNIX_EPOCH,
385            ctime: SystemTime::UNIX_EPOCH,
386            kind: SFlag::S_IFREG,
387            perm: 0o775,
388            nlink: 0,
389            uid: 0,
390            gid: 0,
391            rdev: 0,
392            version: 0,
393        }
394    }
395}
396
397/// Parse `OFlag`
398pub fn parse_oflag(flags: u32) -> OFlag {
399    debug_assert!(
400        flags < std::i32::MAX.cast::<u32>(),
401        "helper_parse_oflag() found flags={flags} overflow, larger than u16::MAX",
402    );
403    let o_flags = OFlag::from_bits_truncate(flags.cast());
404    debug!("helper_parse_oflag() read file flags={:?}", o_flags);
405    o_flags
406}
407
408/// Parse file mode
409pub fn parse_mode(mode: u32) -> Mode {
410    debug_assert!(
411        mode < std::u16::MAX.cast::<u32>(),
412        "helper_parse_mode() found mode={mode} overflow, larger than u16::MAX",
413    );
414
415    #[cfg(target_os = "linux")]
416    let file_mode = Mode::from_bits_truncate(mode);
417    debug!("parse_mode() read mode={:?}", file_mode);
418    file_mode
419}
420
421/// Parse file mode bits
422#[must_use]
423pub fn parse_mode_bits(mode: u32) -> u16 {
424    #[cfg(target_os = "linux")]
425    let bits = parse_mode(mode).bits().cast();
426
427    bits
428}
429
430/// Convert system time to timestamp in seconds and nano-seconds
431#[must_use]
432pub fn time_from_system_time(system_time: &SystemTime) -> (u64, u32) {
433    let duration = system_time
434        .duration_since(UNIX_EPOCH)
435        .context(format!(
436            "failed to convert SystemTime={system_time:?} to Duration"
437        ))
438        .unwrap_or_else(|e| {
439            panic!(
440                "time_from_system_time() failed to convert SystemTime={:?} \
441                to timestamp(seconds, nano-seconds), the error is: {}",
442                system_time,
443                util::format_anyhow_error(&e),
444            )
445        });
446    (duration.as_secs(), duration.subsec_nanos())
447}
448
449/// Convert `FileAttr` to `FuseAttr`
450#[must_use]
451pub fn convert_to_fuse_attr(attr: FileAttr) -> FuseAttr {
452    let (a_time_secs, a_time_nanos) = time_from_system_time(&attr.atime);
453    let (m_time_secs, m_time_nanos) = time_from_system_time(&attr.mtime);
454    let (c_time_secs, c_time_nanos) = time_from_system_time(&attr.ctime);
455
456    FuseAttr {
457        ino: attr.ino,
458        size: attr.size,
459        blocks: attr.blocks,
460        atime: a_time_secs,
461        mtime: m_time_secs,
462        ctime: c_time_secs,
463        atimensec: a_time_nanos,
464        mtimensec: m_time_nanos,
465        ctimensec: c_time_nanos,
466        mode: crate::util::mode_from_kind_and_perm(attr.kind, attr.perm),
467        nlink: attr.nlink,
468        uid: attr.uid,
469        gid: attr.gid,
470        rdev: attr.rdev,
471        #[cfg(feature = "abi-7-9")]
472        blksize: 0, // TODO: find a proper way to set block size
473        #[cfg(feature = "abi-7-9")]
474        padding: 0,
475    }
476}
477
478#[cfg(test)]
479mod tests {
480    use super::*;
481
482    #[test]
483    #[allow(clippy::assertions_on_result_states)]
484    fn test_permission_check() {
485        let file = FileAttr {
486            ino: 0,
487            size: 0,
488            blocks: 0,
489            atime: SystemTime::now(),
490            mtime: SystemTime::now(),
491            ctime: SystemTime::now(),
492            kind: SFlag::S_IFREG,
493            perm: 0o741,
494            nlink: 0,
495            uid: 1000,
496            gid: 1000,
497            rdev: 0,
498            version: 0,
499        };
500
501        // Owner permission checks
502        assert!(file.check_perm_inner(1000, 1001, 7).is_ok());
503        assert!(file.check_perm_inner(1000, 1001, 6).is_ok());
504        assert!(file.check_perm_inner(1000, 1001, 5).is_ok());
505        assert!(file.check_perm_inner(1000, 1001, 4).is_ok());
506        assert!(file.check_perm_inner(1000, 1001, 3).is_ok());
507        assert!(file.check_perm_inner(1000, 1001, 2).is_ok());
508        assert!(file.check_perm_inner(1000, 1001, 1).is_ok());
509
510        // Group permission checks
511        assert!(file.check_perm_inner(1001, 1000, 7).is_err());
512        assert!(file.check_perm_inner(1001, 1000, 6).is_err());
513        assert!(file.check_perm_inner(1001, 1000, 5).is_err());
514        assert!(file.check_perm_inner(1001, 1000, 4).is_ok());
515        assert!(file.check_perm_inner(1001, 1000, 3).is_err());
516        assert!(file.check_perm_inner(1001, 1000, 2).is_err());
517        assert!(file.check_perm_inner(1001, 1000, 1).is_err());
518
519        // Other permission checks
520        assert!(file.check_perm_inner(1002, 1002, 7).is_err());
521        assert!(file.check_perm_inner(1002, 1002, 6).is_err());
522        assert!(file.check_perm_inner(1002, 1002, 5).is_err());
523        assert!(file.check_perm_inner(1002, 1002, 4).is_err());
524        assert!(file.check_perm_inner(1002, 1002, 3).is_err());
525        assert!(file.check_perm_inner(1002, 1002, 2).is_err());
526        assert!(file.check_perm_inner(1002, 1002, 1).is_ok());
527    }
528}