aster_forge_cloud_files_linux/
inode.rs

1//! Stable Linux inode and generation records restored by a product-owned store.
2
3use std::collections::HashMap;
4
5use aster_forge_cloud_files_core::{CloudItemKey, CloudScope};
6
7use crate::{LinuxCloudFilesError, Result};
8
9/// Linux FUSE root inode.
10pub const LINUX_ROOT_INODE: LinuxInode = LinuxInode(1);
11
12/// Non-zero inode number scoped to one mounted filesystem.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub struct LinuxInode(u64);
15
16impl LinuxInode {
17    /// Creates a non-zero Linux inode.
18    /// # Errors
19    ///
20    /// Returns an error when validation fails or an underlying backend, store, or platform
21    /// operation fails.
22    pub const fn new(value: u64) -> Result<Self> {
23        if value == 0 {
24            return Err(LinuxCloudFilesError::ZeroInode);
25        }
26        Ok(Self(value))
27    }
28
29    /// Returns the native inode number.
30    #[must_use]
31    pub const fn get(self) -> u64 {
32        self.0
33    }
34}
35
36/// Non-zero generation fence paired with one restored Linux inode.
37#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
38pub struct LinuxInodeGeneration(u64);
39
40impl LinuxInodeGeneration {
41    /// Creates a non-zero inode generation fence.
42    /// # Errors
43    ///
44    /// Returns an error when validation fails or an underlying backend, store, or platform
45    /// operation fails.
46    pub const fn new(value: u64) -> Result<Self> {
47        if value == 0 {
48            return Err(LinuxCloudFilesError::ZeroGeneration);
49        }
50        Ok(Self(value))
51    }
52
53    /// Returns the native generation value.
54    #[must_use]
55    pub const fn get(self) -> u64 {
56        self.0
57    }
58}
59
60/// One durable mapping from a scoped stable cloud identity to a FUSE inode and generation.
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct LinuxInodeRecord {
63    key: CloudItemKey,
64    inode: LinuxInode,
65    generation: LinuxInodeGeneration,
66}
67
68impl LinuxInodeRecord {
69    /// Creates one restored inode record.
70    #[must_use]
71    pub const fn new(
72        key: CloudItemKey,
73        inode: LinuxInode,
74        generation: LinuxInodeGeneration,
75    ) -> Self {
76        Self {
77            key,
78            inode,
79            generation,
80        }
81    }
82
83    /// Returns the stable scoped cloud identity.
84    #[must_use]
85    pub const fn key(&self) -> &CloudItemKey {
86        &self.key
87    }
88
89    /// Returns the restored native inode.
90    #[must_use]
91    pub const fn inode(&self) -> LinuxInode {
92        self.inode
93    }
94
95    /// Returns the restored inode generation fence.
96    #[must_use]
97    pub const fn generation(&self) -> LinuxInodeGeneration {
98        self.generation
99    }
100
101    /// Consumes the record into its durable fields.
102    #[must_use]
103    pub fn into_parts(self) -> (CloudItemKey, LinuxInode, LinuxInodeGeneration) {
104        (self.key, self.inode, self.generation)
105    }
106}
107
108/// Immutable mapping restored before exposing a FUSE mount to the kernel.
109///
110/// Product code persists and restores these records. The table deliberately does not derive inode
111/// values from names, paths, or hashes: a rename and a daemon restart must retain the same record.
112#[derive(Debug, Clone)]
113pub struct LinuxInodeTable {
114    scope: CloudScope,
115    root: LinuxInodeRecord,
116    by_key: HashMap<CloudItemKey, LinuxInodeRecord>,
117    by_inode: HashMap<LinuxInode, LinuxInodeRecord>,
118}
119
120impl LinuxInodeTable {
121    /// Restores an immutable table. `records` must exclude the root record.
122    /// # Errors
123    ///
124    /// Returns an error when validation fails or an underlying backend, store, or platform
125    /// operation fails.
126    pub fn new(
127        root: LinuxInodeRecord,
128        records: impl IntoIterator<Item = LinuxInodeRecord>,
129    ) -> Result<Self> {
130        if root.inode() != LINUX_ROOT_INODE {
131            return Err(LinuxCloudFilesError::RootInodeMismatch);
132        }
133        let scope = root.key().scope().clone();
134        let mut by_key = HashMap::new();
135        let mut by_inode = HashMap::new();
136        for record in records {
137            if record.key().scope() != &scope {
138                return Err(LinuxCloudFilesError::ScopeMismatch);
139            }
140            if record.inode() == LINUX_ROOT_INODE {
141                return Err(LinuxCloudFilesError::RootInodeMismatch);
142            }
143            if by_key
144                .insert(record.key().clone(), record.clone())
145                .is_some()
146            {
147                return Err(LinuxCloudFilesError::DuplicateItem);
148            }
149            if by_inode.insert(record.inode(), record.clone()).is_some() {
150                return Err(LinuxCloudFilesError::DuplicateInode {
151                    inode: record.inode().get(),
152                });
153            }
154        }
155        Ok(Self {
156            scope,
157            root,
158            by_key,
159            by_inode,
160        })
161    }
162
163    /// Returns the scope shared by every restored record.
164    #[must_use]
165    pub const fn scope(&self) -> &CloudScope {
166        &self.scope
167    }
168
169    /// Returns the root record.
170    #[must_use]
171    pub const fn root(&self) -> &LinuxInodeRecord {
172        &self.root
173    }
174
175    /// Returns the restored record for one stable key.
176    #[must_use]
177    pub fn by_key(&self, key: &CloudItemKey) -> Option<&LinuxInodeRecord> {
178        if key == self.root.key() {
179            Some(&self.root)
180        } else {
181            self.by_key.get(key)
182        }
183    }
184
185    /// Returns the restored record for one native inode.
186    #[must_use]
187    pub fn by_inode(&self, inode: LinuxInode) -> Option<&LinuxInodeRecord> {
188        if inode == LINUX_ROOT_INODE {
189            Some(&self.root)
190        } else {
191            self.by_inode.get(&inode)
192        }
193    }
194
195    /// Returns every non-root record in arbitrary map order for persistence checks.
196    pub fn records(&self) -> impl Iterator<Item = &LinuxInodeRecord> {
197        self.by_inode.values()
198    }
199}