ext2/
inode.rs

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
7use std::mem::MaybeUninit;
8use std::os::unix::fs::MetadataExt;
9
10use anyhow::bail;
11use anyhow::Result;
12use enumn::N;
13use zerocopy::FromBytes;
14use zerocopy::Immutable;
15use zerocopy::IntoBytes;
16use zerocopy::KnownLayout;
17
18use crate::arena::Arena;
19use crate::arena::BlockId;
20use crate::blockgroup::GroupMetaData;
21use crate::xattr::InlineXattrs;
22
23/// Types of inodes.
24#[derive(Debug, PartialEq, Eq, Clone, Copy, N)]
25pub 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
35impl 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.
38    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)]
54pub(crate) struct InodeNum(pub u32);
55
56impl InodeNum {
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.
65    pub fn to_table_index(self) -> usize {
66        // (num - 1) because inode is 1-indexed.
67        self.0 as usize - 1
68    }
69}
70
71impl From<InodeNum> for u32 {
72    fn from(inode: InodeNum) -> Self {
73        inode.0
74    }
75}
76
77impl From<InodeNum> for usize {
78    fn from(inode: InodeNum) -> Self {
79        inode.0 as usize
80    }
81}
82
83/// Size of the `block` field in Inode.
84const 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)]
92pub(crate) struct InodeBlock(pub [u8; INODE_BLOCK_LEN]);
93
94impl Default for InodeBlock {
95    fn default() -> Self {
96        Self([0; INODE_BLOCK_LEN])
97    }
98}
99
100impl 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.
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.
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.
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.
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.
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`.
153    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.
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)]
177pub(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
210impl Default for Inode {
211    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)]
226pub struct InodeBlocksCount(u32);
227
228impl InodeBlocksCount {
229    const INODE_BLOCKS_SIZE: u32 = 512;
230
231    pub fn from_bytes_len(len: u32) -> Self {
232        Self(len / Self::INODE_BLOCKS_SIZE)
233    }
234
235    pub fn add(&mut self, v: u32) {
236        self.0 += v / Self::INODE_BLOCKS_SIZE;
237    }
238}
239
240impl 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
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
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
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
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
416    pub fn typ(&self) -> Option<InodeType> {
417        InodeType::n((self.mode >> 12) as u8)
418    }
419}
420
421#[cfg(test)]
422mod tests {
423    use super::*;
424
425    #[test]
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}