use std::mem::MaybeUninit;
use std::os::unix::fs::MetadataExt;
use anyhow::bail;
use anyhow::Result;
use enumn::N;
use zerocopy::AsBytes;
use zerocopy::FromBytes;
use zerocopy::FromZeroes;
use crate::arena::Arena;
use crate::arena::BlockId;
use crate::blockgroup::GroupMetaData;
use crate::xattr::InlineXattrs;
#[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 {
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,
}
}
}
#[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))
}
pub fn to_table_index(self) -> usize {
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
}
}
const INODE_BLOCK_LEN: usize = 60;
#[repr(C)]
#[derive(Debug, Copy, Clone, FromZeroes, FromBytes, AsBytes)]
pub(crate) struct InodeBlock(pub [u8; INODE_BLOCK_LEN]);
impl Default for InodeBlock {
fn default() -> Self {
Self([0; INODE_BLOCK_LEN])
}
}
impl InodeBlock {
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;
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(())
}
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(())
}
pub fn set_direct_blocks(&mut self, block_ids: &[BlockId]) -> Result<()> {
self.set_direct_blocks_from(0, block_ids)
}
pub fn set_indirect_block_table(&mut self, block_id: &BlockId) -> Result<()> {
self.set_block_id(Self::INDIRECT_BLOCK_TABLE_ID, block_id)
}
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)
}
pub const fn max_inline_symlink_len() -> usize {
INODE_BLOCK_LEN
}
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(())
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone, FromZeroes, FromBytes, AsBytes)]
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, pub extra_size: u16,
_paddings: u16, }
impl Default for Inode {
fn default() -> Self {
let mut r: Self = unsafe { MaybeUninit::zeroed().assume_init() };
r.extra_size = 4;
r
}
}
#[repr(C)]
#[derive(Default, Debug, Copy, Clone, FromZeroes, FromBytes, AsBytes)]
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 {
pub const INODE_RECORD_SIZE: usize = 256;
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; const EXT2_S_IXUSR: u16 = 0x0040; const EXT2_S_IRGRP: u16 = 0x0020; const EXT2_S_IXGRP: u16 = 0x0008; const EXT2_S_IROTH: u16 = 0x0004; const EXT2_S_IXOTH: u16 = 0x0001; 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)?;
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;
let uid = unsafe { libc::geteuid() };
let uid_high = (uid >> 16) as u16;
let uid_low = uid as u16;
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();
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() {
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);
}
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);
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);
}
}