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