mod chunk_reader; mod superblock; use core::{fmt::Debug, mem::MaybeUninit}; use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec}; use limine::ModuleRequest; use super::vfs::{FsOps, VNode, VNodeOperations, VNodeType}; pub static MODULE_REQUEST: ModuleRequest = ModuleRequest::new(0); pub fn init() -> Squashfs<'static> { // TODO: Put the module request stuff in another file? if MODULE_REQUEST.get_response().get().is_none() { panic!("Module request in none!"); } let module_response = MODULE_REQUEST.get_response().get().unwrap(); let mut initramfs = None; let module_name = "initramfs.img"; for module in module_response.modules() { let c_path = module.path.to_str(); if c_path.is_none() { continue; } if !c_path.unwrap().to_str().unwrap().contains(module_name) { continue; } initramfs = Some(module); } // End TODO if initramfs.is_none() { panic!("Initramfs was not found!"); } let initramfs = initramfs.unwrap(); let squashfs = Squashfs::new(initramfs.base.as_ptr().unwrap()); if squashfs.is_err() { panic!("Initramfs in corrupt!"); } let squashfs = squashfs.unwrap(); return squashfs; } #[repr(u8)] #[derive(Clone, Copy)] enum Table { Inode, Dir, Frag, Export, ID, Xattr, } #[repr(C)] // #[derive(Debug)] pub struct Squashfs<'a> { pub superblock: superblock::SquashfsSuperblock, start: *mut u8, decompressor: Box Result, ()>>, data_table: &'a [u8], inode_table: chunk_reader::ChunkReader<'a, Box Result, ()>>>, directory_table: chunk_reader::ChunkReader<'a, Box Result, ()>>>, fragment_table: Option<&'a [u8]>, export_table: Option<&'a [u8]>, id_table: &'a [u8], xattr_table: Option<&'a [u8]>, } impl Squashfs<'_> { fn new(ptr: *mut u8) -> Result, ()> { // crate::log_info!("Parsing initramfs at {:p}", ptr); // 40 is the offset for bytes used by the archive in the superblock let length = unsafe { u64::from_le(*(ptr.add(40) as *const u64)) as usize }; let squashfs_data: &[u8] = unsafe { core::slice::from_raw_parts(ptr, length) }; let superblock = superblock::SquashfsSuperblock::new(squashfs_data)?; let decompressor = match superblock.compressor() { superblock::SquashfsCompressionType::Gzip => { Box::new(crate::libs::gzip::uncompress_data) } compressor => panic!("Unsupported SquashFS decompressor {compressor:?}"), }; // The easy part with none of this metadata nonesense let data_table = &squashfs_data[core::mem::size_of::() ..superblock.inode_table as usize]; let mut tables: Vec<(Table, u64)> = Vec::new(); // todo: there's probably a better way to do this tables.push((Table::Inode, superblock.inode_table)); tables.push((Table::Dir, superblock.dir_table)); if superblock.frag_table != u64::MAX { tables.push((Table::Frag, superblock.frag_table)); } if superblock.export_table != u64::MAX { tables.push((Table::Export, superblock.export_table)); } tables.push((Table::ID, superblock.id_table)); if superblock.xattr_table != u64::MAX { tables.push((Table::Xattr, superblock.xattr_table)); } let mut inode_table: MaybeUninit< chunk_reader::ChunkReader<'static, Box Result, ()>>>, > = MaybeUninit::uninit(); let mut directory_table: MaybeUninit< chunk_reader::ChunkReader<'static, Box Result, ()>>>, > = MaybeUninit::uninit(); let mut fragment_table = None; let mut export_table = None; let mut id_table: &[u8] = &[]; let mut xattr_table = None; for (i, &(table, offset)) in tables.iter().enumerate() { let whole_table = if i == tables.len() - 1 { &squashfs_data[offset as usize..] } else { &squashfs_data[offset as usize..tables[i + 1].1 as usize] }; match table { Table::Inode => { inode_table = MaybeUninit::new(chunk_reader::ChunkReader::new( whole_table, decompressor.clone(), )); } Table::Dir => { directory_table = MaybeUninit::new(chunk_reader::ChunkReader::new( whole_table, decompressor.clone(), )); } Table::Frag => { fragment_table = Some(whole_table); } Table::Export => export_table = Some(whole_table), Table::ID => id_table = whole_table, Table::Xattr => xattr_table = Some(whole_table), } } return Ok(Squashfs { superblock, start: ptr, decompressor, data_table, inode_table: unsafe { inode_table.assume_init() }, directory_table: unsafe { directory_table.assume_init() }, fragment_table, export_table, id_table, xattr_table, }); } fn get_inode_block_offset(&self, inode: u64) -> (u64, u16) { let inode_block = ((inode >> 16) & 0x0000FFFFFFFFFFFF) as u64; let inode_offset = (inode & 0xFFFF) as u16; (inode_block, inode_offset) } fn read_root_dir(&mut self) -> Inode { self.read_inode(self.superblock.root_inode) } fn read_inode(&mut self, inode: u64) -> Inode { let (inode_block, inode_offset) = self.get_inode_block_offset(inode); let file_type = InodeFileType::from(u16::from_le_bytes( self.inode_table .get_slice(inode_block, inode_offset, 2) .try_into() .unwrap(), )); let inode_size = match file_type { InodeFileType::BasicDirectory => core::mem::size_of::(), InodeFileType::ExtendedDirectory => core::mem::size_of::(), InodeFileType::BasicFile => core::mem::size_of::(), inode_type => unimplemented!("Inode type {inode_type:?}"), }; let inode_bytes: &[u8] = &self .inode_table .get_slice(inode_block, inode_offset, inode_size); Inode::from(inode_bytes) } fn find_entry_in_directory(&mut self, dir: Inode, name: &str) -> Result { let dir_inode = match dir { Inode::BasicDirectory(dir) => { (dir.block_index as usize) << 16 | dir.block_offset as usize } Inode::ExtendedDirectory(dir) => { (dir.block_index as usize) << 16 | dir.block_offset as usize } _ => return Err(()), }; let dir_size = match dir { Inode::BasicDirectory(dir) => dir.file_size as usize, Inode::ExtendedDirectory(dir) => dir.file_size as usize, _ => return Err(()), }; if dir_size == 0 { // directory has no entries return Err(()); } let (directory_block, directory_offset) = self.get_inode_block_offset(dir_inode as u64); let mut directory_table_header = { let bytes: &[u8] = &self.directory_table.get_slice( directory_block, directory_offset, core::mem::size_of::(), ); DirectoryTableHeader::from(bytes) }; let mut offset = core::mem::size_of::(); let mut i = 0; loop { if i == directory_table_header.entry_count && offset != dir_size { //read second table directory_table_header = { let bytes: &[u8] = &self.directory_table.get_slice( directory_block, directory_offset + offset as u16, core::mem::size_of::(), ); DirectoryTableHeader::from(bytes) }; i = 0; offset += core::mem::size_of::(); continue; } if offset >= dir_size { break; } let name_size = u16::from_le_bytes( self.directory_table .get_slice( directory_block, directory_offset + (offset as u16 + 6), 2 ) .try_into() .unwrap(), ) as usize // the name is stored off-by-one + 1; let directory_entry = DirectoryTableEntry::from_bytes(&self.directory_table.get_slice( directory_block, directory_offset + offset as u16, 8 + name_size, )); offset += 8 + name_size; if directory_entry.name == name { let directory_entry_inode = (directory_table_header.start as usize) << 16 | (directory_entry.offset as usize); return Ok(self.read_inode(directory_entry_inode as u64)); } i += 1; } return Err(()); } // metadata_block takes a tuple, the first element is whether the array is a metadata block, // and the second element is a is_compressed override if the array is not a metadata block. fn get_decompressed_table( &self, table: &[u8], metadata_block: (bool, Option), ) -> Vec { // the bottom 15 bits, I think the last bit indicates whether the data is uncompressed let header = u16::from_le_bytes(table[0..2].try_into().unwrap()); let table_is_compressed = if !metadata_block.0 { metadata_block.1.unwrap() } else { header & 0x8000 == 0 }; // let table_size = header & 0x7FFF; // if table.len() >= 8192 { // panic!("Inode block is not less than 8KiB!"); // } let mut buffer: Vec = Vec::new(); let bytes = if metadata_block.0 { &table[2..] } else { table }; if table_is_compressed { match self.superblock.compressor() { superblock::SquashfsCompressionType::Gzip => { buffer.extend_from_slice( &crate::libs::gzip::uncompress_data(bytes).unwrap_or(bytes.to_vec()), ); } _ => { crate::println!("Unsupported compression type") } } } else { buffer.extend(bytes); } return buffer; } } impl<'a> FsOps for Squashfs<'a> { fn mount(&mut self, _path: &str, data: &mut *mut u8, _vfspp: *const super::vfs::Vfs) { // STUB // not recommended:tm: *data = core::ptr::addr_of!(*self) as *mut u8; } fn unmount(&mut self, _vfsp: *const super::vfs::Vfs) { // STUB } fn root(&mut self, vfsp: *const super::vfs::Vfs) -> super::vfs::VNode { let root_dir = self.read_root_dir(); super::vfs::VNode { flags: 0, ref_count: 0, shared_lock_count: 0, exclusive_lock_count: 0, vfs_mounted_here: None, ops: Box::new(root_dir), node_data: None, parent: vfsp, typ: super::vfs::VNodeType::Directory, data: core::ptr::null_mut(), } } fn fid(&mut self, _path: &str, _vfspp: *const super::vfs::Vfs) -> Option { todo!(); } fn statfs(&mut self, _vfsp: *const super::vfs::Vfs) -> super::vfs::StatFs { todo!(); } fn sync(&mut self, _vfsp: *const super::vfs::Vfs) { todo!(); } fn vget( &mut self, _fid: super::vfs::FileId, _vfsp: *const super::vfs::Vfs, ) -> super::vfs::VNode { todo!(); } } #[derive(Clone, Copy, Debug)] enum Inode { BasicFile(BasicFileInode), BasicDirectory(BasicDirectoryInode), ExtendedDirectory(ExtendedDirectoryInode), } impl From<&[u8]> for Inode { fn from(value: &[u8]) -> Self { let file_type = InodeFileType::from(u16::from_le_bytes(value[0..2].try_into().unwrap())); match file_type { InodeFileType::BasicDirectory => { Inode::BasicDirectory(BasicDirectoryInode::from_bytes(value)) } InodeFileType::ExtendedDirectory => { Inode::ExtendedDirectory(ExtendedDirectoryInode::from_bytes(value)) } InodeFileType::BasicFile => Inode::BasicFile(BasicFileInode::from_bytes(value)), _ => unimplemented!("Inode from bytes"), } } } impl VNodeOperations for Inode { fn open( &mut self, _f: u32, _c: super::vfs::UserCred, vp: *const VNode, ) -> Result, ()> { let squashfs = unsafe { (*(*vp).parent).data.cast::() }; match self { Inode::BasicFile(file) => unsafe { // TODO: is this really how you're supposed to do this? let mut block_data: Vec = Vec::with_capacity(file.file_size as usize); let data_table: Vec; let block_offset = if file.frag_idx == u32::MAX { data_table = (*squashfs).get_decompressed_table( (*squashfs).data_table, ( false, Some(!(*squashfs).superblock.features().uncompressed_data_blocks), ), ); file.block_offset as usize } else { // Tail end packing let fragment_table = (*squashfs).get_decompressed_table( (*squashfs).fragment_table.unwrap(), ( false, Some(!(*squashfs).superblock.features().uncompressed_fragments), ), ); let fragment_pointer = ((*squashfs).start as u64 + u64::from_le_bytes( fragment_table[file.frag_idx as usize..(file.frag_idx + 8) as usize] .try_into() .unwrap(), )) as *mut u8; // build array since fragment_pointer is not guaranteed to be 0x02 aligned // We add two since fragment_pointer points to the beginning of the fragment block, // Which is a metadata block, and we get the size, but that excludes the two header bytes, // And since we are building the array due to unaligned pointer shenanigans we need to // include the header bytes otherwise we are short by two bytes let fragment_block_size = (u16::from_le(core::ptr::read_unaligned(fragment_pointer as *mut u16)) & 0x7FFF) + 2; let mut fragment_block_raw = Vec::new(); for i in 0..fragment_block_size as usize { fragment_block_raw.push(core::ptr::read_unaligned(fragment_pointer.add(i))) } let fragment_block = (*squashfs).get_decompressed_table(&fragment_block_raw, (true, None)); let fragment_start = u64::from_le_bytes(fragment_block[0..8].try_into().unwrap()); let fragment_size = u32::from_le_bytes(fragment_block[8..12].try_into().unwrap()); let fragment_compressed = fragment_size & 1 << 24 == 0; let fragment_size = fragment_size & 0xFEFFFFFF; let data_table_raw = core::slice::from_raw_parts( ((*squashfs).start as u64 + fragment_start) as *mut u8, fragment_size as usize, ) .to_vec(); data_table = (*squashfs).get_decompressed_table( &data_table_raw, (false, Some(fragment_compressed)), ); file.block_offset as usize }; block_data .extend(&data_table[block_offset..(block_offset + file.file_size as usize)]); return Ok(Arc::from(block_data)); }, _ => panic!("Tried to open non-file"), } } fn close(&mut self, _f: u32, _c: super::vfs::UserCred, _vp: *const VNode) { todo!() } fn rdwr( &mut self, _uiop: *const super::vfs::UIO, _direction: super::vfs::IODirection, _f: u32, _c: super::vfs::UserCred, _vp: *const VNode, ) { todo!() } fn ioctl( &mut self, _com: u32, _d: *mut u8, _f: u32, _c: super::vfs::UserCred, _vp: *const VNode, ) { todo!() } fn select(&mut self, _w: super::vfs::IODirection, _c: super::vfs::UserCred, _vp: *const VNode) { todo!() } fn getattr(&mut self, _c: super::vfs::UserCred, _vp: *const VNode) -> super::vfs::VAttr { todo!() } fn setattr(&mut self, _va: super::vfs::VAttr, _c: super::vfs::UserCred, _vp: *const VNode) { todo!() } fn access(&mut self, _m: u32, _c: super::vfs::UserCred, _vp: *const VNode) { todo!() } fn lookup( &mut self, nm: &str, _c: super::vfs::UserCred, vp: *const VNode, ) -> Result { let squashfs = unsafe { (*(*vp).parent).data.cast::() }; match self { Inode::BasicDirectory(_) | Inode::ExtendedDirectory(_) => unsafe { let inode = (*squashfs).find_entry_in_directory(*self, nm)?; let vnode_type = match inode { Inode::BasicDirectory(_) | Inode::ExtendedDirectory(_) => VNodeType::Directory, Inode::BasicFile(_) => VNodeType::Regular, }; let vnode = VNode { flags: 0, ref_count: 0, shared_lock_count: 0, exclusive_lock_count: 0, vfs_mounted_here: None, ops: Box::new(inode), node_data: None, parent: (*vp).parent, typ: vnode_type, data: core::ptr::null_mut(), }; return Ok(vnode); }, _ => panic!("tried to lookup on non directory"), } } fn create( &mut self, _nm: &str, _va: super::vfs::VAttr, _e: u32, _m: u32, _c: super::vfs::UserCred, _vp: *const VNode, ) -> Result { todo!() } fn link( &mut self, _target_dir: *mut super::vfs::VNode, _target_name: &str, _c: super::vfs::UserCred, _vp: *const VNode, ) { todo!() } fn rename( &mut self, _nm: &str, _target_dir: *mut super::vfs::VNode, _target_name: &str, _c: super::vfs::UserCred, _vp: *const VNode, ) { todo!() } fn mkdir( &mut self, _nm: &str, _va: super::vfs::VAttr, _c: super::vfs::UserCred, _vp: *const VNode, ) -> Result { todo!() } fn readdir( &mut self, _uiop: *const super::vfs::UIO, _c: super::vfs::UserCred, _vp: *const VNode, ) { todo!() } fn symlink( &mut self, _link_name: &str, _va: super::vfs::VAttr, _target_name: &str, _c: super::vfs::UserCred, _vp: *const VNode, ) { todo!() } fn readlink( &mut self, _uiop: *const super::vfs::UIO, _c: super::vfs::UserCred, _vp: *const VNode, ) { todo!() } fn fsync(&mut self, _c: super::vfs::UserCred, _vp: *const VNode) { todo!() } fn inactive(&mut self, _c: super::vfs::UserCred, _vp: *const VNode) { todo!() } fn bmap(&mut self, _block_number: u32, _bnp: (), _vp: *const VNode) -> super::vfs::VNode { todo!() } fn strategy(&mut self, _bp: (), _vp: *const VNode) { todo!() } fn bread(&mut self, _block_number: u32, _vp: *const VNode) -> Arc<[u8]> { todo!() } } macro_rules! inode_enum_try_into { ($inode_type:ty, $inode_name:ident) => { impl<'a> TryInto<$inode_type> for Inode { type Error = (); fn try_into(self) -> Result<$inode_type, Self::Error> { match self { Inode::$inode_name(inode) => { return Ok(inode); } _ => { return Err(()); } } } } }; } inode_enum_try_into!(BasicFileInode, BasicFile); inode_enum_try_into!(BasicDirectoryInode, BasicDirectory); inode_enum_try_into!(ExtendedDirectoryInode, ExtendedDirectory); #[repr(C)] #[derive(Clone, Copy, Debug)] struct InodeHeader { file_type: InodeFileType, _reserved: [u16; 3], mtime: u32, inode_num: u32, } impl InodeHeader { fn from_bytes(bytes: &[u8]) -> Self { let file_type = u16::from_le_bytes(bytes[0..2].try_into().unwrap()).into(); let mtime = u32::from_le_bytes(bytes[8..12].try_into().unwrap()); let inode_num = u32::from_le_bytes(bytes[12..16].try_into().unwrap()); return Self { // squashfs, file_type, _reserved: [0; 3], mtime, inode_num, }; } } #[repr(C)] #[derive(Clone, Copy, Debug)] struct BasicDirectoryInode { header: InodeHeader, block_index: u32, // 4 link_count: u32, // 8 file_size: u16, // 10 block_offset: u16, // 12 parent_inode: u32, // 16 } impl BasicDirectoryInode { fn from_bytes(bytes: &[u8]) -> Self { let header = InodeHeader::from_bytes(bytes); let block_index = u32::from_le_bytes(bytes[16..20].try_into().unwrap()); let link_count = u32::from_le_bytes(bytes[20..24].try_into().unwrap()); let file_size = u16::from_le_bytes(bytes[24..26].try_into().unwrap()); let block_offset = u16::from_le_bytes(bytes[26..28].try_into().unwrap()); let parent_inode = u32::from_le_bytes(bytes[28..32].try_into().unwrap()); return Self { header, block_index, link_count, file_size, block_offset, parent_inode, }; } // #[allow(dead_code)] // fn entries(&self) -> Arc<[Inode]> { // let mut entries: Vec = Vec::new(); // let directory_table = &self // .header // .squashfs // .get_decompressed_table(self.header.squashfs.directory_table, (true, None)); // let directory_table_header = // DirectoryTableHeader::from_bytes(&directory_table[self.block_offset as usize..]); // // TODO: cheap hack, fix it when I have more hours of sleep. // let mut offset = self.block_offset as usize + core::mem::size_of::(); // for _ in 0..directory_table_header.entry_count as usize { // let directory_table_entry = DirectoryTableEntry::from_bytes(&directory_table[offset..]); // offset += 8 + directory_table_entry.name.len(); // let file_inode = self // .header // .squashfs // .read_inode(directory_table_entry.offset as u32); // entries.push(file_inode); // } // return Arc::from(entries); // } // fn find(&self, name: &str) -> Option> { // let directory_table = &self // .header // .squashfs // .get_decompressed_table(self.header.squashfs.directory_table, (true, None)); // let directory_table_header = // DirectoryTableHeader::from_bytes(&directory_table[self.block_offset as usize..]); // // TODO: cheap hack, fix it when I have more hours of sleep. // let mut offset = self.block_offset as usize + core::mem::size_of::(); // InodeHeader // for _ in 0..directory_table_header.entry_count as usize { // let directory_table_entry = DirectoryTableEntry::from_bytes(&directory_table[offset..]); // offset += 8 + directory_table_entry.name.len(); // if directory_table_entry.name == name { // return Some( // self.header // .squashfs // .read_inode(directory_table_entry.offset as u32), // ); // } // } // return None; // } } #[repr(C)] #[derive(Clone, Copy, Debug)] struct ExtendedDirectoryInode { header: InodeHeader, link_count: u32, // 8 file_size: u32, // 10 block_index: u32, // 4 parent_inode: u32, // 16 index_count: u16, block_offset: u16, // 12 xattr_index: u32, } impl ExtendedDirectoryInode { fn from_bytes(bytes: &[u8]) -> Self { let header = InodeHeader::from_bytes(bytes); let link_count = u32::from_le_bytes(bytes[16..20].try_into().unwrap()); let file_size = u32::from_le_bytes(bytes[20..24].try_into().unwrap()); let block_index = u32::from_le_bytes(bytes[24..28].try_into().unwrap()); let parent_inode = u32::from_le_bytes(bytes[28..32].try_into().unwrap()); let index_count = u16::from_le_bytes(bytes[32..34].try_into().unwrap()); let block_offset = u16::from_le_bytes(bytes[34..36].try_into().unwrap()); let xattr_index = u32::from_le_bytes(bytes[36..40].try_into().unwrap()); return Self { header, link_count, file_size, block_index, parent_inode, index_count, block_offset, xattr_index, }; } } #[repr(C)] #[derive(Clone, Copy, Debug)] struct BasicFileInode { header: InodeHeader, block_start: u32, // 4 frag_idx: u32, // 8 block_offset: u32, // 12 file_size: u32, // 16 // block_sizes: *const u32, } impl BasicFileInode { fn from_bytes(bytes: &[u8]) -> Self { let header = InodeHeader::from_bytes(bytes); let block_start = u32::from_le_bytes(bytes[16..20].try_into().unwrap()); let frag_idx = u32::from_le_bytes(bytes[20..24].try_into().unwrap()); let block_offset = u32::from_le_bytes(bytes[24..28].try_into().unwrap()); let file_size = u32::from_le_bytes(bytes[28..32].try_into().unwrap()); // let block_sizes = bytes[32..].as_ptr() as *const u32; return Self { header, block_start, frag_idx, block_offset, file_size, // block_sizes, }; } } #[repr(C)] #[derive(Debug)] struct DirectoryTableHeader { entry_count: u32, start: u32, inode_num: u32, } impl From<&[u8]> for DirectoryTableHeader { fn from(value: &[u8]) -> Self { // count is off by 1 entry let entry_count = u32::from_le_bytes(value[0..4].try_into().unwrap()) + 1; let start = u32::from_le_bytes(value[4..8].try_into().unwrap()); let inode_num = u32::from_le_bytes(value[8..12].try_into().unwrap()); return Self { entry_count, start, inode_num, }; } } #[repr(C)] #[derive(Debug)] struct DirectoryTableEntry { offset: u16, inode_offset: i16, inode_type: InodeFileType, name_size: u16, name: String, // the file name length is name_size + 1 bytes } impl DirectoryTableEntry { fn from_bytes(bytes: &[u8]) -> Self { let offset = u16::from_le_bytes(bytes[0..2].try_into().unwrap()); let inode_offset = i16::from_le_bytes(bytes[2..4].try_into().unwrap()); let inode_type = u16::from_le_bytes(bytes[4..6].try_into().unwrap()).into(); let name_size = u16::from_le_bytes(bytes[6..8].try_into().unwrap()); let name = String::from_utf8(bytes[8..((name_size as usize) + 1) + 8].to_vec()).unwrap(); // let name = core::str::from_utf8(&bytes[8..((name_size as usize) + 1) + 8]) // .expect("Failed to make DirectoryHeader name"); return Self { offset, inode_offset, inode_type, name_size, name, }; } } #[repr(u16)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum InodeFileType { BasicDirectory = 1, BasicFile = 2, BasicSymlink = 3, BasicBlockDevice = 4, BasicCharDevice = 5, BasicPipe = 6, BasicSocked = 7, ExtendedDirectory = 8, ExtendedFile = 9, ExtendedSymlink = 10, ExtendedBlockDevice = 11, ExtendedPipe = 12, ExtendedSocked = 13, } impl From for InodeFileType { fn from(value: u16) -> Self { match value { 1 => Self::BasicDirectory, 2 => Self::BasicFile, 3 => Self::BasicSymlink, 4 => Self::BasicBlockDevice, 5 => Self::BasicCharDevice, 6 => Self::BasicPipe, 7 => Self::BasicSocked, 8 => Self::ExtendedDirectory, 9 => Self::ExtendedFile, 10 => Self::ExtendedSymlink, 11 => Self::ExtendedBlockDevice, 12 => Self::ExtendedPipe, 13 => Self::ExtendedSocked, _ => panic!("Unexpected Inode file type {value}!"), } } }