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