Skip to main content

mountpoint_s3_fs/metablock/
stat.rs

1//! Core types for the metablock module
2use std::time::Duration;
3
4use time::OffsetDateTime;
5
6use super::Expiry;
7
8pub type InodeNo = u64;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum InodeKind {
12    File,
13    Directory,
14}
15
16impl InodeKind {
17    pub fn as_str(&self) -> &'static str {
18        match self {
19            InodeKind::File => "file",
20            InodeKind::Directory => "directory",
21        }
22    }
23}
24
25impl From<InodeKind> for fuser::FileType {
26    fn from(kind: InodeKind) -> Self {
27        match kind {
28            InodeKind::File => fuser::FileType::RegularFile,
29            InodeKind::Directory => fuser::FileType::Directory,
30        }
31    }
32}
33
34#[derive(Debug, Clone)]
35pub struct InodeStat {
36    /// Time this stat becomes invalid and needs to be refreshed
37    pub expiry: Expiry,
38
39    /// Size in bytes
40    pub size: usize,
41
42    /// Time of last file content modification
43    pub mtime: OffsetDateTime,
44    /// Time of last file metadata (or content) change
45    pub ctime: OffsetDateTime,
46    /// Time of last access
47    pub atime: OffsetDateTime,
48    /// Etag for the file (object)
49    pub etag: Option<Box<str>>,
50    /// Inodes corresponding to S3 objects with GLACIER or DEEP_ARCHIVE storage classes
51    /// are only readable after restoration. For objects with other storage classes
52    /// this field should be always `true`.
53    pub is_readable: bool,
54}
55
56impl InodeStat {
57    pub fn is_valid(&self) -> bool {
58        !self.expiry.is_expired()
59    }
60
61    pub fn update_validity(&mut self, validity: Duration) {
62        self.expiry = Expiry::from_now(validity);
63    }
64
65    /// Objects in flexible retrieval storage classes can't be accessed via GetObject unless they are
66    /// restored, and so we override their permissions to 000 and reject reads to them. We also warn
67    /// the first time we see an object like this, because FUSE enforces the 000 permissions on our
68    /// behalf so we might not see an attempted `open` call.
69    fn is_readable(
70        storage_class: Option<&str>,
71        restore_status: Option<mountpoint_s3_client::types::RestoreStatus>,
72    ) -> bool {
73        use crate::sync::atomic::{AtomicBool, Ordering};
74        use std::time::SystemTime;
75
76        static HAS_SENT_WARNING: AtomicBool = AtomicBool::new(false);
77        match storage_class {
78            Some("GLACIER") | Some("DEEP_ARCHIVE") => {
79                let restored = matches!(restore_status, Some(mountpoint_s3_client::types::RestoreStatus::Restored { expiry }) if expiry > SystemTime::now());
80                if !restored && !HAS_SENT_WARNING.swap(true, Ordering::SeqCst) {
81                    tracing::warn!(
82                        "objects in the GLACIER and DEEP_ARCHIVE storage classes are only accessible if restored"
83                    );
84                }
85                restored
86            }
87            _ => true,
88        }
89    }
90
91    /// Initialize an [InodeStat] for a file, given some metadata.
92    pub fn for_file(
93        size: usize,
94        datetime: OffsetDateTime,
95        etag: Option<Box<str>>,
96        storage_class: Option<&str>,
97        restore_status: Option<mountpoint_s3_client::types::RestoreStatus>,
98        validity: Duration,
99    ) -> InodeStat {
100        let is_readable = Self::is_readable(storage_class, restore_status);
101        InodeStat {
102            expiry: Expiry::from_now(validity),
103            size,
104            atime: datetime,
105            ctime: datetime,
106            mtime: datetime,
107            etag,
108            is_readable,
109        }
110    }
111
112    /// Initialize an [InodeStat] for a directory, given some metadata.
113    pub fn for_directory(datetime: OffsetDateTime, validity: Duration) -> InodeStat {
114        InodeStat {
115            expiry: Expiry::from_now(validity),
116            size: 0,
117            atime: datetime,
118            ctime: datetime,
119            mtime: datetime,
120            etag: None,
121            is_readable: true,
122        }
123    }
124}