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::FromBytes; 14 use zerocopy::Immutable; 15 use zerocopy::IntoBytes; 16 use zerocopy::KnownLayout; 17 18 use crate::arena::Arena; 19 use crate::arena::BlockId; 20 use crate::blockgroup::GroupMetaData; 21 use crate::xattr::InlineXattrs; 22 23 /// Types of inodes. 24 #[derive(Debug, PartialEq, Eq, Clone, Copy, N)] 25 pub 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 35 impl 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. into_dir_entry_file_type(self) -> u838 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)] 54 pub(crate) struct InodeNum(pub u32); 55 56 impl InodeNum { new(inode: u32) -> Result<Self>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. to_table_index(self) -> usize65 pub fn to_table_index(self) -> usize { 66 // (num - 1) because inode is 1-indexed. 67 self.0 as usize - 1 68 } 69 } 70 71 impl From<InodeNum> for u32 { from(inode: InodeNum) -> Self72 fn from(inode: InodeNum) -> Self { 73 inode.0 74 } 75 } 76 77 impl From<InodeNum> for usize { from(inode: InodeNum) -> Self78 fn from(inode: InodeNum) -> Self { 79 inode.0 as usize 80 } 81 } 82 83 /// Size of the `block` field in Inode. 84 const 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)] 92 pub(crate) struct InodeBlock(pub [u8; INODE_BLOCK_LEN]); 93 94 impl Default for InodeBlock { default() -> Self95 fn default() -> Self { 96 Self([0; INODE_BLOCK_LEN]) 97 } 98 } 99 100 impl 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. set_block_id(&mut self, index: usize, block_id: &BlockId) -> Result<()>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. set_direct_blocks_from( &mut self, start_idx: usize, block_ids: &[BlockId], ) -> Result<()>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. set_direct_blocks(&mut self, block_ids: &[BlockId]) -> Result<()>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. set_indirect_block_table(&mut self, block_id: &BlockId) -> Result<()>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. set_double_indirect_block_table(&mut self, block_id: &BlockId) -> Result<()>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`. max_inline_symlink_len() -> usize153 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. set_inline_symlink(&mut self, symlink: &str) -> Result<()>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)] 177 pub(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 210 impl Default for Inode { default() -> Self211 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)] 226 pub struct InodeBlocksCount(u32); 227 228 impl InodeBlocksCount { 229 const INODE_BLOCKS_SIZE: u32 = 512; 230 from_bytes_len(len: u32) -> Self231 pub fn from_bytes_len(len: u32) -> Self { 232 Self(len / Self::INODE_BLOCKS_SIZE) 233 } 234 add(&mut self, v: u32)235 pub fn add(&mut self, v: u32) { 236 self.0 += v / Self::INODE_BLOCKS_SIZE; 237 } 238 } 239 240 impl 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 new<'a>( arena: &'a Arena<'a>, group: &mut GroupMetaData, inode_num: InodeNum, typ: InodeType, size: u32, xattr: Option<InlineXattrs>, ) -> Result<&'a mut Self>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 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>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 add_xattr<'a>( arena: &'a Arena<'a>, group: &mut GroupMetaData, inode: &mut Inode, inode_offset: usize, xattr: InlineXattrs, ) -> Result<()>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 update_metadata(&mut self, m: &std::fs::Metadata)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 typ(&self) -> Option<InodeType>416 pub fn typ(&self) -> Option<InodeType> { 417 InodeType::n((self.mode >> 12) as u8) 418 } 419 } 420 421 #[cfg(test)] 422 mod tests { 423 use super::*; 424 425 #[test] test_inode_size()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 } 438