• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2024 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 //! Defines the inode structure.
6 
7 use std::mem::MaybeUninit;
8 use std::os::unix::fs::MetadataExt;
9 
10 use anyhow::bail;
11 use anyhow::Result;
12 use enumn::N;
13 use zerocopy::FromBytes;
14 use zerocopy::Immutable;
15 use zerocopy::IntoBytes;
16 use zerocopy::KnownLayout;
17 
18 use crate::arena::Arena;
19 use crate::arena::BlockId;
20 use crate::blockgroup::GroupMetaData;
21 use crate::xattr::InlineXattrs;
22 
23 /// Types of inodes.
24 #[derive(Debug, PartialEq, Eq, Clone, Copy, N)]
25 pub enum InodeType {
26     Fifo = 0x1,
27     Char = 0x2,
28     Directory = 0x4,
29     Block = 0x6,
30     Regular = 0x8,
31     Symlink = 0xa,
32     Socket = 0xc,
33 }
34 
35 impl InodeType {
36     /// Converts to a file type for directory entry.
37     /// The value is defined in "Table 4.2. Defined Inode File Type Values" in the spec.
into_dir_entry_file_type(self) -> u838     pub fn into_dir_entry_file_type(self) -> u8 {
39         match self {
40             InodeType::Regular => 1,
41             InodeType::Directory => 2,
42             InodeType::Char => 3,
43             InodeType::Block => 4,
44             InodeType::Fifo => 5,
45             InodeType::Socket => 6,
46             InodeType::Symlink => 7,
47         }
48     }
49 }
50 
51 // Represents an inode number.
52 // This is 1-indexed.
53 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
54 pub(crate) struct InodeNum(pub u32);
55 
56 impl InodeNum {
new(inode: u32) -> Result<Self>57     pub fn new(inode: u32) -> Result<Self> {
58         if inode == 0 {
59             bail!("inode number is 1-indexed");
60         }
61         Ok(Self(inode))
62     }
63 
64     // Returns index in the inode table.
to_table_index(self) -> usize65     pub fn to_table_index(self) -> usize {
66         // (num - 1) because inode is 1-indexed.
67         self.0 as usize - 1
68     }
69 }
70 
71 impl From<InodeNum> for u32 {
from(inode: InodeNum) -> Self72     fn from(inode: InodeNum) -> Self {
73         inode.0
74     }
75 }
76 
77 impl From<InodeNum> for usize {
from(inode: InodeNum) -> Self78     fn from(inode: InodeNum) -> Self {
79         inode.0 as usize
80     }
81 }
82 
83 /// Size of the `block` field in Inode.
84 const INODE_BLOCK_LEN: usize = 60;
85 /// Represents 60-byte region for block in Inode.
86 /// This region is used for various ways depending on the file type.
87 /// For regular files and directories, it's used for storing 32-bit indices of blocks.
88 ///
89 /// This is a wrapper of `[u8; 60]` to implement `Default` manually.
90 #[repr(C)]
91 #[derive(Debug, Copy, Clone, FromBytes, Immutable, IntoBytes, KnownLayout)]
92 pub(crate) struct InodeBlock(pub [u8; INODE_BLOCK_LEN]);
93 
94 impl Default for InodeBlock {
default() -> Self95     fn default() -> Self {
96         Self([0; INODE_BLOCK_LEN])
97     }
98 }
99 
100 impl InodeBlock {
101     // Each inode contains 12 direct pointers (0-11), one singly indirect pointer (12), one
102     // doubly indirect block pointer (13), and one triply indirect pointer (14).
103     pub const NUM_DIRECT_BLOCKS: usize = 12;
104     const INDIRECT_BLOCK_TABLE_ID: usize = Self::NUM_DIRECT_BLOCKS;
105     const DOUBLE_INDIRECT_BLOCK_TABLE_ID: usize = 13;
106 
107     /// Set a block id at the given index.
set_block_id(&mut self, index: usize, block_id: &BlockId) -> Result<()>108     pub fn set_block_id(&mut self, index: usize, block_id: &BlockId) -> Result<()> {
109         let offset = index * std::mem::size_of::<BlockId>();
110         let bytes = block_id.as_bytes();
111         if self.0.len() < offset + bytes.len() {
112             bail!("index out of bounds when setting block_id to InodeBlock: index={index}, block_id: {:?}", block_id);
113         }
114         self.0[offset..offset + bytes.len()].copy_from_slice(bytes);
115         Ok(())
116     }
117 
118     /// Set an array of direct block IDs.
set_direct_blocks_from( &mut self, start_idx: usize, block_ids: &[BlockId], ) -> Result<()>119     pub fn set_direct_blocks_from(
120         &mut self,
121         start_idx: usize,
122         block_ids: &[BlockId],
123     ) -> Result<()> {
124         let bytes = block_ids.as_bytes();
125         if bytes.len() + start_idx * 4 > self.0.len() {
126             bail!(
127                 "length of direct blocks is {} bytes, but it must not exceed {}",
128                 bytes.len(),
129                 self.0.len()
130             );
131         }
132         self.0[start_idx * 4..(start_idx * 4 + bytes.len())].copy_from_slice(bytes);
133         Ok(())
134     }
135 
136     /// Set an array of direct block IDs.
set_direct_blocks(&mut self, block_ids: &[BlockId]) -> Result<()>137     pub fn set_direct_blocks(&mut self, block_ids: &[BlockId]) -> Result<()> {
138         self.set_direct_blocks_from(0, block_ids)
139     }
140 
141     /// Set a block id to be used as the indirect block table.
set_indirect_block_table(&mut self, block_id: &BlockId) -> Result<()>142     pub fn set_indirect_block_table(&mut self, block_id: &BlockId) -> Result<()> {
143         self.set_block_id(Self::INDIRECT_BLOCK_TABLE_ID, block_id)
144     }
145 
146     /// Set a block id to be used as the double indirect block table.
set_double_indirect_block_table(&mut self, block_id: &BlockId) -> Result<()>147     pub fn set_double_indirect_block_table(&mut self, block_id: &BlockId) -> Result<()> {
148         self.set_block_id(Self::DOUBLE_INDIRECT_BLOCK_TABLE_ID, block_id)
149     }
150 
151     /// Returns the max length of symbolic links that can be stored in the inode data.
152     /// This length contains the trailing `\0`.
max_inline_symlink_len() -> usize153     pub const fn max_inline_symlink_len() -> usize {
154         INODE_BLOCK_LEN
155     }
156 
157     /// Stores a given string as an inlined symbolic link data.
set_inline_symlink(&mut self, symlink: &str) -> Result<()>158     pub fn set_inline_symlink(&mut self, symlink: &str) -> Result<()> {
159         let bytes = symlink.as_bytes();
160         if bytes.len() >= Self::max_inline_symlink_len() {
161             bail!(
162                 "symlink '{symlink}' exceeds or equals tomax length: {} >= {}",
163                 bytes.len(),
164                 Self::max_inline_symlink_len()
165             );
166         }
167         self.0[..bytes.len()].copy_from_slice(bytes);
168         Ok(())
169     }
170 }
171 
172 /// The ext2 inode.
173 ///
174 /// The field names are based on [the specification](https://www.nongnu.org/ext2-doc/ext2.html#inode-table).
175 #[repr(C)]
176 #[derive(Debug, Copy, Clone, FromBytes, Immutable, IntoBytes, KnownLayout)]
177 pub(crate) struct Inode {
178     mode: u16,
179     uid: u16,
180     pub size: u32,
181     atime: u32,
182     ctime: u32,
183     mtime: u32,
184     _dtime: u32,
185     gid: u16,
186     pub links_count: u16,
187     pub blocks: InodeBlocksCount,
188     _flags: u32,
189     _osd1: u32,
190     pub block: InodeBlock,
191     _generation: u32,
192     _file_acl: u32,
193     _dir_acl: u32,
194     _faddr: u32,
195     _fragment_num: u8,
196     _fragment_size: u8,
197     _reserved1: u16,
198     uid_high: u16,
199     gid_high: u16,
200     _reserved2: u32, // 128-th byte
201 
202     // We don't use any inode metadata region beyond the basic 128 bytes.
203     // However set `extra_size` to the minimum value to let Linux kernel know that there are
204     // inline extended attribute data. The minimum possible is 4 bytes, so define extra_size
205     // and add the next padding.
206     pub extra_size: u16,
207     _paddings: u16, // padding for 32-bit alignment
208 }
209 
210 impl Default for Inode {
default() -> Self211     fn default() -> Self {
212         // SAFETY: zero-filled value is a valid value.
213         let mut r: Self = unsafe { MaybeUninit::zeroed().assume_init() };
214         // Set extra size to 4 for `extra_size` and `paddings` fields.
215         r.extra_size = 4;
216         r
217     }
218 }
219 
220 /// Used in `Inode` to represent how many 512-byte blocks are used by a file.
221 ///
222 /// The block size '512' byte is fixed and not related to the actual block size of the file system.
223 /// For more details, see notes for `i_blocks_lo` in the specification.
224 #[repr(C)]
225 #[derive(Default, Debug, Copy, Clone, FromBytes, Immutable, IntoBytes, KnownLayout)]
226 pub struct InodeBlocksCount(u32);
227 
228 impl InodeBlocksCount {
229     const INODE_BLOCKS_SIZE: u32 = 512;
230 
from_bytes_len(len: u32) -> Self231     pub fn from_bytes_len(len: u32) -> Self {
232         Self(len / Self::INODE_BLOCKS_SIZE)
233     }
234 
add(&mut self, v: u32)235     pub fn add(&mut self, v: u32) {
236         self.0 += v / Self::INODE_BLOCKS_SIZE;
237     }
238 }
239 
240 impl Inode {
241     /// Size of the inode record in bytes.
242     ///
243     /// From ext2 revision 1, inode size larger than 128 bytes is supported.
244     /// We use 256 byte here, which is the default value for ext4.
245     ///
246     /// Note that inode "record" size can be larger that inode "structure" size.
247     /// The gap between the end of the inode structure and the end of the inode record can be used
248     /// to store extended attributes.
249     pub const INODE_RECORD_SIZE: usize = 256;
250 
251     /// Size of the region that inline extended attributes can be written.
252     pub const XATTR_AREA_SIZE: usize = Inode::INODE_RECORD_SIZE - std::mem::size_of::<Inode>();
253 
new<'a>( arena: &'a Arena<'a>, group: &mut GroupMetaData, inode_num: InodeNum, typ: InodeType, size: u32, xattr: Option<InlineXattrs>, ) -> Result<&'a mut Self>254     pub fn new<'a>(
255         arena: &'a Arena<'a>,
256         group: &mut GroupMetaData,
257         inode_num: InodeNum,
258         typ: InodeType,
259         size: u32,
260         xattr: Option<InlineXattrs>,
261     ) -> Result<&'a mut Self> {
262         const EXT2_S_IRUSR: u16 = 0x0100; // user read
263         const EXT2_S_IXUSR: u16 = 0x0040; // user execute
264         const EXT2_S_IRGRP: u16 = 0x0020; // group read
265         const EXT2_S_IXGRP: u16 = 0x0008; // group execute
266         const EXT2_S_IROTH: u16 = 0x0004; // others read
267         const EXT2_S_IXOTH: u16 = 0x0001; // others execute
268 
269         let inode_offset = inode_num.to_table_index() * Inode::INODE_RECORD_SIZE;
270         let inode =
271             arena.allocate::<Inode>(BlockId::from(group.group_desc.inode_table), inode_offset)?;
272 
273         // Give read and execute permissions
274         let mode = ((typ as u16) << 12)
275             | EXT2_S_IRUSR
276             | EXT2_S_IXUSR
277             | EXT2_S_IRGRP
278             | EXT2_S_IXGRP
279             | EXT2_S_IROTH
280             | EXT2_S_IXOTH;
281 
282         let now = std::time::SystemTime::now()
283             .duration_since(std::time::UNIX_EPOCH)?
284             .as_secs() as u32;
285 
286         // SAFETY: geteuid never fail.
287         let uid = unsafe { libc::geteuid() };
288         let uid_high = (uid >> 16) as u16;
289         let uid_low = uid as u16;
290         // SAFETY: getegid never fail.
291         let gid = unsafe { libc::getegid() };
292         let gid_high = (gid >> 16) as u16;
293         let gid_low = gid as u16;
294 
295         *inode = Self {
296             mode,
297             size,
298             atime: now,
299             ctime: now,
300             mtime: now,
301             uid: uid_low,
302             gid: gid_low,
303             uid_high,
304             gid_high,
305             ..Default::default()
306         };
307         if let Some(xattr) = xattr {
308             Self::add_xattr(arena, group, inode, inode_offset, xattr)?;
309         }
310 
311         Ok(inode)
312     }
313 
from_metadata<'a>( arena: &'a Arena<'a>, group: &mut GroupMetaData, inode_num: InodeNum, m: &std::fs::Metadata, size: u32, links_count: u16, blocks: InodeBlocksCount, block: InodeBlock, xattr: Option<InlineXattrs>, ) -> Result<&'a mut Self>314     pub fn from_metadata<'a>(
315         arena: &'a Arena<'a>,
316         group: &mut GroupMetaData,
317         inode_num: InodeNum,
318         m: &std::fs::Metadata,
319         size: u32,
320         links_count: u16,
321         blocks: InodeBlocksCount,
322         block: InodeBlock,
323         xattr: Option<InlineXattrs>,
324     ) -> Result<&'a mut Self> {
325         let inodes_per_group = group.inode_bitmap.len();
326         // (inode_num - 1) because inode is 1-indexed.
327         let inode_offset =
328             ((usize::from(inode_num) - 1) % inodes_per_group) * Inode::INODE_RECORD_SIZE;
329         let inode =
330             arena.allocate::<Inode>(BlockId::from(group.group_desc.inode_table), inode_offset)?;
331 
332         let mode = m.mode() as u16;
333 
334         let uid = m.uid();
335         let uid_high = (uid >> 16) as u16;
336         let uid_low: u16 = uid as u16;
337         let gid = m.gid();
338         let gid_high = (gid >> 16) as u16;
339         let gid_low: u16 = gid as u16;
340 
341         let atime = m.atime() as u32;
342         let ctime = m.ctime() as u32;
343         let mtime = m.mtime() as u32;
344 
345         *inode = Inode {
346             mode,
347             uid: uid_low,
348             gid: gid_low,
349             size,
350             atime,
351             ctime,
352             mtime,
353             links_count,
354             blocks,
355             block,
356             uid_high,
357             gid_high,
358             ..Default::default()
359         };
360 
361         if let Some(xattr) = xattr {
362             Self::add_xattr(arena, group, inode, inode_offset, xattr)?;
363         }
364 
365         Ok(inode)
366     }
367 
add_xattr<'a>( arena: &'a Arena<'a>, group: &mut GroupMetaData, inode: &mut Inode, inode_offset: usize, xattr: InlineXattrs, ) -> Result<()>368     fn add_xattr<'a>(
369         arena: &'a Arena<'a>,
370         group: &mut GroupMetaData,
371         inode: &mut Inode,
372         inode_offset: usize,
373         xattr: InlineXattrs,
374     ) -> Result<()> {
375         let xattr_region = arena.allocate::<[u8; Inode::XATTR_AREA_SIZE]>(
376             BlockId::from(group.group_desc.inode_table),
377             inode_offset + std::mem::size_of::<Inode>(),
378         )?;
379 
380         if !xattr.entry_table.is_empty() {
381             // Linux and debugfs uses extra_size to check if inline xattr is stored so we need to
382             // set a positive value here. 4 (= sizeof(extra_size) + sizeof(_paddings))
383             // is the smallest value.
384             inode.extra_size = 4;
385             let InlineXattrs {
386                 entry_table,
387                 values,
388             } = xattr;
389 
390             if entry_table.len() + values.len() > Inode::XATTR_AREA_SIZE {
391                 bail!("xattr size is too large for inline store: entry_table.len={}, values.len={}, inline region size={}",
392                         entry_table.len(), values.len(), Inode::XATTR_AREA_SIZE);
393             }
394             // `entry_table` should be aligned to the beginning of the region.
395             xattr_region[..entry_table.len()].copy_from_slice(&entry_table);
396             xattr_region[Inode::XATTR_AREA_SIZE - values.len()..].copy_from_slice(&values);
397         }
398         Ok(())
399     }
400 
update_metadata(&mut self, m: &std::fs::Metadata)401     pub fn update_metadata(&mut self, m: &std::fs::Metadata) {
402         self.mode = m.mode() as u16;
403 
404         let uid: u32 = m.uid();
405         self.uid_high = (uid >> 16) as u16;
406         self.uid = uid as u16;
407         let gid = m.gid();
408         self.gid_high = (gid >> 16) as u16;
409         self.gid = gid as u16;
410 
411         self.atime = m.atime() as u32;
412         self.ctime = m.ctime() as u32;
413         self.mtime = m.mtime() as u32;
414     }
415 
typ(&self) -> Option<InodeType>416     pub fn typ(&self) -> Option<InodeType> {
417         InodeType::n((self.mode >> 12) as u8)
418     }
419 }
420 
421 #[cfg(test)]
422 mod tests {
423     use super::*;
424 
425     #[test]
test_inode_size()426     fn test_inode_size() {
427         assert_eq!(std::mem::offset_of!(Inode, extra_size), 128);
428         // Check that no implicit paddings is inserted after the padding field.
429         assert_eq!(
430             std::mem::offset_of!(Inode, _paddings) + std::mem::size_of::<u16>(),
431             std::mem::size_of::<Inode>()
432         );
433 
434         assert!(128 < std::mem::size_of::<Inode>());
435         assert!(std::mem::size_of::<Inode>() <= Inode::INODE_RECORD_SIZE);
436     }
437 }
438