blob: 3c20a9593aaf6f1296dad6defb053bd908a0411e [file]
// Copyright 2024 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Defines the inode structure.
use std::mem::MaybeUninit;
use std::os::unix::fs::MetadataExt;
use anyhow::bail;
use anyhow::Result;
use enumn::N;
use zerocopy::FromBytes;
use zerocopy::Immutable;
use zerocopy::IntoBytes;
use zerocopy::KnownLayout;
use crate::arena::Arena;
use crate::arena::BlockId;
use crate::blockgroup::GroupMetaData;
use crate::xattr::InlineXattrs;
/// Types of inodes.
#[derive(Debug, PartialEq, Eq, Clone, Copy, N)]
pub enum InodeType {
Fifo = 0x1,
Char = 0x2,
Directory = 0x4,
Block = 0x6,
Regular = 0x8,
Symlink = 0xa,
Socket = 0xc,
}
impl InodeType {
/// Converts to a file type for directory entry.
/// The value is defined in "Table 4.2. Defined Inode File Type Values" in the spec.
pub fn into_dir_entry_file_type(self) -> u8 {
match self {
InodeType::Regular => 1,
InodeType::Directory => 2,
InodeType::Char => 3,
InodeType::Block => 4,
InodeType::Fifo => 5,
InodeType::Socket => 6,
InodeType::Symlink => 7,
}
}
}
// Represents an inode number.
// This is 1-indexed.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct InodeNum(pub u32);
impl InodeNum {
pub fn new(inode: u32) -> Result<Self> {
if inode == 0 {
bail!("inode number is 1-indexed");
}
Ok(Self(inode))
}
// Returns index in the inode table.
pub fn to_table_index(self) -> usize {
// (num - 1) because inode is 1-indexed.
self.0 as usize - 1
}
}
impl From<InodeNum> for u32 {
fn from(inode: InodeNum) -> Self {
inode.0
}
}
impl From<InodeNum> for usize {
fn from(inode: InodeNum) -> Self {
inode.0 as usize
}
}
/// Size of the `block` field in Inode.
const INODE_BLOCK_LEN: usize = 60;
/// Represents 60-byte region for block in Inode.
/// This region is used for various ways depending on the file type.
/// For regular files and directories, it's used for storing 32-bit indices of blocks.
///
/// This is a wrapper of `[u8; 60]` to implement `Default` manually.
#[repr(C)]
#[derive(Debug, Copy, Clone, FromBytes, Immutable, IntoBytes, KnownLayout)]
pub(crate) struct InodeBlock(pub [u8; INODE_BLOCK_LEN]);
impl Default for InodeBlock {
fn default() -> Self {
Self([0; INODE_BLOCK_LEN])
}
}
impl InodeBlock {
// Each inode contains 12 direct pointers (0-11), one singly indirect pointer (12), one
// doubly indirect block pointer (13), and one triply indirect pointer (14).
pub const NUM_DIRECT_BLOCKS: usize = 12;
const INDIRECT_BLOCK_TABLE_ID: usize = Self::NUM_DIRECT_BLOCKS;
const DOUBLE_INDIRECT_BLOCK_TABLE_ID: usize = 13;
/// Set a block id at the given index.
pub fn set_block_id(&mut self, index: usize, block_id: &BlockId) -> Result<()> {
let offset = index * std::mem::size_of::<BlockId>();
let bytes = block_id.as_bytes();
if self.0.len() < offset + bytes.len() {
bail!("index out of bounds when setting block_id to InodeBlock: index={index}, block_id: {:?}", block_id);
}
self.0[offset..offset + bytes.len()].copy_from_slice(bytes);
Ok(())
}
/// Set an array of direct block IDs.
pub fn set_direct_blocks_from(
&mut self,
start_idx: usize,
block_ids: &[BlockId],
) -> Result<()> {
let bytes = block_ids.as_bytes();
if bytes.len() + start_idx * 4 > self.0.len() {
bail!(
"length of direct blocks is {} bytes, but it must not exceed {}",
bytes.len(),
self.0.len()
);
}
self.0[start_idx * 4..(start_idx * 4 + bytes.len())].copy_from_slice(bytes);
Ok(())
}
/// Set an array of direct block IDs.
pub fn set_direct_blocks(&mut self, block_ids: &[BlockId]) -> Result<()> {
self.set_direct_blocks_from(0, block_ids)
}
/// Set a block id to be used as the indirect block table.
pub fn set_indirect_block_table(&mut self, block_id: &BlockId) -> Result<()> {
self.set_block_id(Self::INDIRECT_BLOCK_TABLE_ID, block_id)
}
/// Set a block id to be used as the double indirect block table.
pub fn set_double_indirect_block_table(&mut self, block_id: &BlockId) -> Result<()> {
self.set_block_id(Self::DOUBLE_INDIRECT_BLOCK_TABLE_ID, block_id)
}
/// Returns the max length of symbolic links that can be stored in the inode data.
/// This length contains the trailing `\0`.
pub const fn max_inline_symlink_len() -> usize {
INODE_BLOCK_LEN
}
/// Stores a given string as an inlined symbolic link data.
pub fn set_inline_symlink(&mut self, symlink: &str) -> Result<()> {
let bytes = symlink.as_bytes();
if bytes.len() >= Self::max_inline_symlink_len() {
bail!(
"symlink '{symlink}' exceeds or equals tomax length: {} >= {}",
bytes.len(),
Self::max_inline_symlink_len()
);
}
self.0[..bytes.len()].copy_from_slice(bytes);
Ok(())
}
}
/// The ext2 inode.
///
/// The field names are based on [the specification](https://www.nongnu.org/ext2-doc/ext2.html#inode-table).
#[repr(C)]
#[derive(Debug, Copy, Clone, FromBytes, Immutable, IntoBytes, KnownLayout)]
pub(crate) struct Inode {
mode: u16,
uid: u16,
pub size: u32,
atime: u32,
ctime: u32,
mtime: u32,
_dtime: u32,
gid: u16,
pub links_count: u16,
pub blocks: InodeBlocksCount,
_flags: u32,
_osd1: u32,
pub block: InodeBlock,
_generation: u32,
_file_acl: u32,
_dir_acl: u32,
_faddr: u32,
_fragment_num: u8,
_fragment_size: u8,
_reserved1: u16,
uid_high: u16,
gid_high: u16,
_reserved2: u32, // 128-th byte
// We don't use any inode metadata region beyond the basic 128 bytes.
// However set `extra_size` to the minimum value to let Linux kernel know that there are
// inline extended attribute data. The minimum possible is 4 bytes, so define extra_size
// and add the next padding.
pub extra_size: u16,
_paddings: u16, // padding for 32-bit alignment
}
impl Default for Inode {
fn default() -> Self {
// SAFETY: zero-filled value is a valid value.
let mut r: Self = unsafe { MaybeUninit::zeroed().assume_init() };
// Set extra size to 4 for `extra_size` and `paddings` fields.
r.extra_size = 4;
r
}
}
/// Used in `Inode` to represent how many 512-byte blocks are used by a file.
///
/// The block size '512' byte is fixed and not related to the actual block size of the file system.
/// For more details, see notes for `i_blocks_lo` in the specification.
#[repr(C)]
#[derive(Default, Debug, Copy, Clone, FromBytes, Immutable, IntoBytes, KnownLayout)]
pub struct InodeBlocksCount(u32);
impl InodeBlocksCount {
const INODE_BLOCKS_SIZE: u32 = 512;
pub fn from_bytes_len(len: u32) -> Self {
Self(len / Self::INODE_BLOCKS_SIZE)
}
pub fn add(&mut self, v: u32) {
self.0 += v / Self::INODE_BLOCKS_SIZE;
}
}
impl Inode {
/// Size of the inode record in bytes.
///
/// From ext2 revision 1, inode size larger than 128 bytes is supported.
/// We use 256 byte here, which is the default value for ext4.
///
/// Note that inode "record" size can be larger that inode "structure" size.
/// The gap between the end of the inode structure and the end of the inode record can be used
/// to store extended attributes.
pub const INODE_RECORD_SIZE: usize = 256;
/// Size of the region that inline extended attributes can be written.
pub const XATTR_AREA_SIZE: usize = Inode::INODE_RECORD_SIZE - std::mem::size_of::<Inode>();
pub fn new<'a>(
arena: &'a Arena<'a>,
group: &mut GroupMetaData,
inode_num: InodeNum,
typ: InodeType,
size: u32,
xattr: Option<InlineXattrs>,
) -> Result<&'a mut Self> {
const EXT2_S_IRUSR: u16 = 0x0100; // user read
const EXT2_S_IXUSR: u16 = 0x0040; // user execute
const EXT2_S_IRGRP: u16 = 0x0020; // group read
const EXT2_S_IXGRP: u16 = 0x0008; // group execute
const EXT2_S_IROTH: u16 = 0x0004; // others read
const EXT2_S_IXOTH: u16 = 0x0001; // others execute
let inode_offset = inode_num.to_table_index() * Inode::INODE_RECORD_SIZE;
let inode =
arena.allocate::<Inode>(BlockId::from(group.group_desc.inode_table), inode_offset)?;
// Give read and execute permissions
let mode = ((typ as u16) << 12)
| EXT2_S_IRUSR
| EXT2_S_IXUSR
| EXT2_S_IRGRP
| EXT2_S_IXGRP
| EXT2_S_IROTH
| EXT2_S_IXOTH;
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as u32;
// SAFETY: geteuid never fail.
let uid = unsafe { libc::geteuid() };
let uid_high = (uid >> 16) as u16;
let uid_low = uid as u16;
// SAFETY: getegid never fail.
let gid = unsafe { libc::getegid() };
let gid_high = (gid >> 16) as u16;
let gid_low = gid as u16;
*inode = Self {
mode,
size,
atime: now,
ctime: now,
mtime: now,
uid: uid_low,
gid: gid_low,
uid_high,
gid_high,
..Default::default()
};
if let Some(xattr) = xattr {
Self::add_xattr(arena, group, inode, inode_offset, xattr)?;
}
Ok(inode)
}
pub fn 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> {
let inodes_per_group = group.inode_bitmap.len();
// (inode_num - 1) because inode is 1-indexed.
let inode_offset =
((usize::from(inode_num) - 1) % inodes_per_group) * Inode::INODE_RECORD_SIZE;
let inode =
arena.allocate::<Inode>(BlockId::from(group.group_desc.inode_table), inode_offset)?;
let mode = m.mode() as u16;
let uid = m.uid();
let uid_high = (uid >> 16) as u16;
let uid_low: u16 = uid as u16;
let gid = m.gid();
let gid_high = (gid >> 16) as u16;
let gid_low: u16 = gid as u16;
let atime = m.atime() as u32;
let ctime = m.ctime() as u32;
let mtime = m.mtime() as u32;
*inode = Inode {
mode,
uid: uid_low,
gid: gid_low,
size,
atime,
ctime,
mtime,
links_count,
blocks,
block,
uid_high,
gid_high,
..Default::default()
};
if let Some(xattr) = xattr {
Self::add_xattr(arena, group, inode, inode_offset, xattr)?;
}
Ok(inode)
}
fn add_xattr<'a>(
arena: &'a Arena<'a>,
group: &mut GroupMetaData,
inode: &mut Inode,
inode_offset: usize,
xattr: InlineXattrs,
) -> Result<()> {
let xattr_region = arena.allocate::<[u8; Inode::XATTR_AREA_SIZE]>(
BlockId::from(group.group_desc.inode_table),
inode_offset + std::mem::size_of::<Inode>(),
)?;
if !xattr.entry_table.is_empty() {
// Linux and debugfs uses extra_size to check if inline xattr is stored so we need to
// set a positive value here. 4 (= sizeof(extra_size) + sizeof(_paddings))
// is the smallest value.
inode.extra_size = 4;
let InlineXattrs {
entry_table,
values,
} = xattr;
if entry_table.len() + values.len() > Inode::XATTR_AREA_SIZE {
bail!("xattr size is too large for inline store: entry_table.len={}, values.len={}, inline region size={}",
entry_table.len(), values.len(), Inode::XATTR_AREA_SIZE);
}
// `entry_table` should be aligned to the beginning of the region.
xattr_region[..entry_table.len()].copy_from_slice(&entry_table);
xattr_region[Inode::XATTR_AREA_SIZE - values.len()..].copy_from_slice(&values);
}
Ok(())
}
pub fn update_metadata(&mut self, m: &std::fs::Metadata) {
self.mode = m.mode() as u16;
let uid: u32 = m.uid();
self.uid_high = (uid >> 16) as u16;
self.uid = uid as u16;
let gid = m.gid();
self.gid_high = (gid >> 16) as u16;
self.gid = gid as u16;
self.atime = m.atime() as u32;
self.ctime = m.ctime() as u32;
self.mtime = m.mtime() as u32;
}
pub fn typ(&self) -> Option<InodeType> {
InodeType::n((self.mode >> 12) as u8)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_inode_size() {
assert_eq!(std::mem::offset_of!(Inode, extra_size), 128);
// Check that no implicit paddings is inserted after the padding field.
assert_eq!(
std::mem::offset_of!(Inode, _paddings) + std::mem::size_of::<u16>(),
std::mem::size_of::<Inode>()
);
assert!(128 < std::mem::size_of::<Inode>());
assert!(std::mem::size_of::<Inode>() <= Inode::INODE_RECORD_SIZE);
}
}