diff --git a/Makefile b/Makefile index b79e0fa..871545d 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,7 @@ copy-initramfs-files: # Stub for now ;) touch ${INITRAMFS_PATH}/example.txt echo "Hello World from Initramfs" > ${INITRAMFS_PATH}/example.txt + echo "Second file for testing" > ${INITRAMFS_PATH}/example2.txt compile-initramfs: copy-initramfs-files # Make squashfs without compression temporaily so I can get it working before I have to write a gzip driver diff --git a/README.md b/README.md index c32748b..11875dd 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ CappuccinOS is a small x86-64 operating system written from scratch in rust. Thi - [X] Hardware interrupts - [X] PS/2 Keyboard support - [X] ANSI color codes in console +- [X] Heap allocation - [ ] Externalized kernel modules - [ ] Initramfs - [ ] SMP @@ -36,7 +37,6 @@ CappuccinOS is a small x86-64 operating system written from scratch in rust. Thi - [ ] User authentication - [ ] Power management - [ ] Paging -- [ ] Heap allocation - [ ] Hardware abstraction layer - [ ] RTC Clock @@ -105,6 +105,7 @@ Inspiration was mainly from [JDH's Tetris OS](https://www.youtube.com/watch?v=Fa Some Resources I used over the creation of CappuccinOS: - [OSDev wiki](https://wiki.osdev.org) - Wikipedia on various random things +- [Squashfs Binary Format](https://dr-emann.github.io/squashfs/squashfs.html) And mostly for examples of how people did stuff I used these (projects made by people who actually have a clue what they're doing). - [MOROS](https://github.com/vinc/moros) diff --git a/src/drivers/fs/initramfs/mod.rs b/src/drivers/fs/initramfs/mod.rs index e0bd5cf..e253590 100755 --- a/src/drivers/fs/initramfs/mod.rs +++ b/src/drivers/fs/initramfs/mod.rs @@ -1,5 +1,10 @@ pub mod compressors; +use core::{ + fmt::{self, write}, + ops::{Index, Range}, +}; + use alloc::vec::Vec; use limine::ModuleRequest; @@ -64,17 +69,18 @@ pub fn init() { squashfs.test(); } +#[repr(C)] #[derive(Debug)] struct Squashfs<'a> { ptr: *mut u8, superblock: SquashfsSuperblock, data_table: &'a [u8], - inode_table: &'a [u8], - directory_table: &'a [u8], - fragment_table: Option<&'a [u8]>, - export_table: Option<&'a [u8]>, - id_table: &'a [u8], - xattr_table: Option<&'a [u8]>, + inode_table: MetadataArray<'a>, + directory_table: MetadataArray<'a>, + fragment_table: Option>, + export_table: Option>, + id_table: MetadataArray<'a>, + xattr_table: Option>, } impl Squashfs<'_> { @@ -91,35 +97,41 @@ impl Squashfs<'_> { let data_table = &squashfs_data [core::mem::size_of::()..superblock.inode_table as usize]; - let inode_table = - &squashfs_data[superblock.inode_table as usize..superblock.dir_table as usize]; + let inode_table = MetadataArray::from( + &squashfs_data[superblock.inode_table as usize..superblock.dir_table as usize], + ); - let directory_table = - &squashfs_data[superblock.dir_table as usize..superblock.frag_table as usize]; + let directory_table = MetadataArray::from( + &squashfs_data[superblock.dir_table as usize..superblock.frag_table as usize], + ); - let mut fragment_table: Option<&[u8]> = None; + let mut fragment_table: Option = None; if superblock.frag_table != u64::MAX { - fragment_table = Some( + fragment_table = Some(MetadataArray::from( &squashfs_data[superblock.frag_table as usize..superblock.export_table as usize], - ); + )); } - let mut export_table: Option<&[u8]> = None; + let mut export_table: Option = None; if superblock.export_table != u64::MAX { - export_table = Some( + export_table = Some(MetadataArray::from( &squashfs_data[superblock.export_table as usize..superblock.id_table as usize], - ); + )); } - let mut id_table: &[u8] = &squashfs_data[superblock.id_table as usize..]; - let mut xattr_table: Option<&[u8]> = None; + let mut id_table: MetadataArray = + MetadataArray::from(&squashfs_data[superblock.id_table as usize..]); + let mut xattr_table: Option = None; if superblock.xattr_table != u64::MAX { - id_table = - &squashfs_data[superblock.id_table as usize..superblock.xattr_table as usize]; - xattr_table = Some(&squashfs_data[superblock.xattr_table as usize..]); + id_table = MetadataArray::from( + &squashfs_data[superblock.id_table as usize..superblock.xattr_table as usize], + ); + xattr_table = Some(MetadataArray::from( + &squashfs_data[superblock.xattr_table as usize..], + )); } return Ok(Squashfs { @@ -138,7 +150,7 @@ impl Squashfs<'_> { // big function that does stuff hard coded-ly before I rip it all out pub fn test(&self) { // the bottom 15 bits, I think the last bit indicates whether the data is uncompressed - let inode_table_header = u16::from_le_bytes(self.inode_table[0..2].try_into().unwrap()); + let inode_table_header = u16::from_le_bytes(*self.inode_table.get_header()); let inode_is_compressed = inode_table_header & 0x80 != 0; let inode_table_size = inode_table_header & 0x7FFF; @@ -146,98 +158,119 @@ impl Squashfs<'_> { panic!("Inode block is not less than 8KiB!"); } - let mut buffer: Vec = Vec::with_capacity(8192); + let mut inode_table_buffer: Vec = Vec::with_capacity(8192); if inode_is_compressed { todo!("uncompress zlib data") } else { unsafe { core::ptr::copy_nonoverlapping( - self.inode_table.as_ptr().add(2), - buffer.as_mut_ptr(), + self.inode_table.as_ptr(), + inode_table_buffer.as_mut_ptr(), inode_table_size as usize, ); - buffer.set_len(inode_table_size as usize); + inode_table_buffer.set_len(inode_table_size as usize); } } let root_inode_block = self.superblock.root_inode_block as usize; let root_inode_offset = self.superblock.root_inode_offset as usize; - let root_inode_header = self.read_inode(root_inode_offset as u32); - - let root_inode_header = InodeHeader { - file_type: u16::from_le_bytes( - buffer[root_inode_offset..root_inode_offset + 2] - .try_into() - .unwrap(), - ) - .into(), - _reserved: [ - u16::from_le_bytes( - buffer[root_inode_offset + 2..root_inode_offset + 4] - .try_into() - .unwrap(), - ), - u16::from_le_bytes( - buffer[root_inode_offset + 4..root_inode_offset + 6] - .try_into() - .unwrap(), - ), - u16::from_le_bytes( - buffer[root_inode_offset + 6..root_inode_offset + 8] - .try_into() - .unwrap(), - ), - ], - mtime: u32::from_le_bytes( - buffer[root_inode_offset + 8..root_inode_offset + 12] - .try_into() - .unwrap(), - ), - inode_num: u32::from_le_bytes( - buffer[root_inode_offset + 12..root_inode_offset + 16] - .try_into() - .unwrap(), - ), - }; + let root_inode_header = self + .read_inode(root_inode_offset as u32) + .expect("Failed to read root directory header!"); crate::println!("{:X?}", root_inode_header); - let root_inode = DirectoryInode { - block_index: u32::from_le_bytes( - buffer[root_inode_offset + 16..root_inode_offset + 20] - .try_into() - .unwrap(), - ), - link_count: u32::from_le_bytes( - buffer[root_inode_offset + 20..root_inode_offset + 24] - .try_into() - .unwrap(), - ), - file_size: u16::from_le_bytes( - buffer[root_inode_offset + 24..root_inode_offset + 26] - .try_into() - .unwrap(), - ), - block_offset: u16::from_le_bytes( - buffer[root_inode_offset + 26..root_inode_offset + 28] - .try_into() - .unwrap(), - ), - parent_inode: u32::from_le_bytes( - buffer[root_inode_offset + 28..root_inode_offset + 32] - .try_into() - .unwrap(), - ), - }; + let root_inode = DirectoryInode::from_bytes(&inode_table_buffer[root_inode_offset + 16..]); - crate::println!("{:?}", root_inode); + crate::println!("root_inode: {:X?}", root_inode); + + let directory_table_header = u16::from_le_bytes(*self.directory_table.get_header()); + let directory_is_compressed = directory_table_header & 0x80 != 0; + let directory_table_size = directory_table_header & 0x7FFF; + + if directory_table_size >= 8192 { + panic!("Directory block is not less than 8KiB!"); + } + + let mut directory_table_buffer: Vec = Vec::with_capacity(8192); + + if directory_is_compressed { + todo!("uncompress zlib data") + } else { + unsafe { + core::ptr::copy_nonoverlapping( + self.directory_table.as_ptr(), + directory_table_buffer.as_mut_ptr(), + directory_table_size as usize, + ); + + directory_table_buffer.set_len(directory_table_size as usize); + } + } + + let root_directory = DirectoryTableHeader::from_bytes( + &directory_table_buffer[root_inode.block_offset as usize..], + ); + crate::println!("{:?}", root_directory); + + // TODO: cheap hack, fix it when I have more hours of sleep. + let mut offset = + root_inode.block_offset as usize + core::mem::size_of::(); + + for i in 0..root_directory.count as usize { + let root_dir_entry = DirectoryTableEntry::from_bytes(&directory_table_buffer[offset..]); + + offset += 8 + root_dir_entry.name.len(); + + crate::println!("{:?}", root_dir_entry); + + let file_inode_header = self + .read_inode(root_dir_entry.offset as u32) + .expect("Directory has files with missing inodes"); + + crate::println!("{:?}", file_inode_header); + + let file_inode = + FileInode::from_bytes(&inode_table_buffer[root_dir_entry.offset as usize + 16..]); + + crate::println!("{:?}", file_inode); + + // TODO: get a real ceil function instead of this horse shit + // TODO: handle tail end packing (somehow?) + let block_count = + ((file_inode.file_size as f32 / self.superblock.block_size as f32) + 1.0) as usize; + + // TODO: is this really how you're supposed to do this? + let mut block_data: Vec = Vec::with_capacity(8192 * block_count); + + unsafe { + core::ptr::copy_nonoverlapping( + self.data_table + .as_ptr() + .add(file_inode.block_offset as usize), + block_data.as_mut_ptr(), + file_inode.file_size as usize, + ); + + block_data.set_len(file_inode.file_size as usize); + } + + crate::println!("{:?}", block_count); + + crate::println!( + "{:X?} \033[93m{:?}\033[0m", + block_data, + core::str::from_utf8(&block_data) + ); + } } + // TODO: decompress stuff fn read_inode(&self, inode_num: u32) -> Option { - let inode_offset = inode_num as usize + 2; + let inode_offset = inode_num as usize; if inode_offset + core::mem::size_of::() > self.inode_table.len() { return None; @@ -252,6 +285,7 @@ impl Squashfs<'_> { } } +#[repr(C, packed)] #[derive(Debug)] struct InodeHeader { file_type: InodeFileType, @@ -261,36 +295,123 @@ struct InodeHeader { } impl InodeHeader { - fn from_bytes(bytes: &[u8]) -> Option { + fn from_bytes(bytes: &[u8]) -> Option { 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()); - Some(InodeHeader { + return Some(Self { file_type, _reserved: [0; 3], mtime, inode_num, - }) + }); } } +#[repr(C, packed)] #[derive(Debug)] struct DirectoryInode { - block_index: u32, - link_count: u32, - file_size: u16, - block_offset: u16, - parent_inode: u32, + block_index: u32, // 4 + link_count: u32, // 8 + file_size: u16, // 10 + block_offset: u16, // 12 + parent_inode: u32, // 16 } +impl DirectoryInode { + fn from_bytes(bytes: &[u8]) -> Self { + let block_index = u32::from_le_bytes(bytes[0..4].try_into().unwrap()); + let link_count = u32::from_le_bytes(bytes[4..8].try_into().unwrap()); + let file_size = u16::from_le_bytes(bytes[8..10].try_into().unwrap()); + let block_offset = u16::from_le_bytes(bytes[10..12].try_into().unwrap()); + let parent_inode = u32::from_le_bytes(bytes[12..16].try_into().unwrap()); + + return Self { + block_index, + link_count, + file_size, + block_offset, + parent_inode, + }; + } +} + +#[repr(C, packed)] #[derive(Debug)] struct FileInode { - block_start: u32, - frag_idx: u32, - block_offset: u32, - file_size: u32, - block_size: [u32; 0], + block_start: u32, // 4 + frag_idx: u32, // 8 + block_offset: u32, // 12 + file_size: u32, // 16 +} + +impl FileInode { + fn from_bytes(bytes: &[u8]) -> Self { + let block_start = u32::from_le_bytes(bytes[0..4].try_into().unwrap()); + let frag_idx = u32::from_le_bytes(bytes[4..8].try_into().unwrap()); + let block_offset = u32::from_le_bytes(bytes[8..12].try_into().unwrap()); + let file_size = u32::from_le_bytes(bytes[12..16].try_into().unwrap()); + + return Self { + block_start, + frag_idx, + block_offset, + file_size, + }; + } +} + +#[repr(C, packed)] +#[derive(Debug)] +struct DirectoryTableHeader { + count: u32, + start: u32, + inode_num: u32, +} + +impl DirectoryTableHeader { + fn from_bytes(bytes: &[u8]) -> Self { + // count is off by 2 entry + let count = u32::from_le_bytes(bytes[0..4].try_into().unwrap()) + 1; + let start = u32::from_le_bytes(bytes[4..8].try_into().unwrap()); + let inode_num = u32::from_le_bytes(bytes[8..12].try_into().unwrap()); + + return Self { + count, + start, + inode_num, + }; + } +} + +#[repr(C, packed)] +#[derive(Debug)] +struct DirectoryTableEntry<'a> { + offset: u16, + inode_offset: i16, + inode_type: InodeFileType, + name_size: u16, + name: &'a str, // the file name length is name_size + 1 bytes +} + +impl<'a> DirectoryTableEntry<'a> { + fn from_bytes(bytes: &'a [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 = 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)] @@ -389,7 +510,7 @@ impl Into for u16 { } } -#[repr(C)] +#[repr(C, packed)] #[derive(Clone, Copy, Debug)] struct SquashfsSuperblock { magic: u32, // 0x73717368 @@ -506,3 +627,64 @@ impl SquashfsSuperblock { }; } } + +// Helper functions to deal with metadata arrays +struct MetadataArray<'a> { + header: &'a [u8; 2], + data: &'a [u8], +} + +impl<'a> MetadataArray<'a> { + // Function to access the header + pub fn get_header(&self) -> &'a [u8; 2] { + &self.header + } + + pub fn as_ptr(&self) -> *const u8 { + return self.data.as_ptr(); + } + + pub fn len(&self) -> usize { + return self.data.len(); + } +} + +impl<'a> From<&'a [u8]> for MetadataArray<'a> { + fn from(value: &'a [u8]) -> Self { + let (header, data) = value.split_at(2); + MetadataArray { + header: header.try_into().unwrap(), + data, + } + } +} + +impl<'a> Index for MetadataArray<'a> { + type Output = u8; + + fn index(&self, index: usize) -> &Self::Output { + if index > self.data.len() { + panic!("Index out of bounds"); + } + + return &self.data[index]; + } +} + +impl<'a> Index> for MetadataArray<'a> { + type Output = [u8]; + + fn index(&self, index: Range) -> &Self::Output { + if index.end > self.data.len() || index.start > self.data.len() { + panic!("Index out of bounds"); + } + + return &self.data[index]; + } +} + +impl<'a> fmt::Debug for MetadataArray<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.data) + } +} diff --git a/src/main.rs b/src/main.rs index 8ad2fac..20ea398 100755 --- a/src/main.rs +++ b/src/main.rs @@ -113,6 +113,11 @@ fn parse_kernel_cmdline() -> KernelFeatures { #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { crate::log_error!("{}", info); + let rbp: u64; + unsafe { + core::arch::asm!("mov {0:r}, rbp", out(reg) rbp); + }; + crate::arch::stack_trace::print_stack_trace(6, rbp); hcf(); } diff --git a/test.log b/test.log new file mode 100644 index 0000000..7fccb4a --- /dev/null +++ b/test.log @@ -0,0 +1,43 @@ +[ * ] Serial Driver successfully initialized +[ ? ] Initialized heap with 4MiB of memory at 0x4E8000 +[ ? ] Initialized framebuffer mirroring with 3MiB at 0x100000 +====== MEMORY MAP ====== +[ 0x0000000000001000..0x0000000000052000 ] Type: BootloaderReclaimable Size: 324KiB +[ 0x0000000000053000..0x000000000009F000 ] Type: Usable Size: 304KiB +[ 0x000000000009FC00..0x00000000000A0000 ] Type: Reserved Size: 1KiB +[ 0x00000000000F0000..0x0000000000100000 ] Type: Reserved Size: 64KiB +[ 0x0000000000100000..0x0000000007AC3000 ] Type: Usable Size: 121MiB +[ 0x0000000007AC3000..0x0000000007AC4000 ] Type: KernelAndModules Size: 4KiB +[ 0x0000000007AC4000..0x0000000007AC5000 ] Type: BootloaderReclaimable Size: 4KiB +[ 0x0000000007AC5000..0x0000000007AC8000 ] Type: Usable Size: 12KiB +[ 0x0000000007AC8000..0x0000000007AD0000 ] Type: BootloaderReclaimable Size: 32KiB +[ 0x0000000007AD0000..0x0000000007AFE000 ] Type: KernelAndModules Size: 184KiB +[ 0x0000000007AFE000..0x0000000007B3C000 ] Type: BootloaderReclaimable Size: 248KiB +[ 0x0000000007B3C000..0x0000000007F26000 ] Type: Usable Size: 3MiB +[ 0x0000000007F26000..0x0000000007FE0000 ] Type: BootloaderReclaimable Size: 744KiB +[ 0x0000000007FE0000..0x0000000008000000 ] Type: Reserved Size: 128KiB +[ 0x00000000FD000000..0x00000000FD3E8000 ] Type: Framebuffer Size: 3MiB +[ 0x00000000FFFC0000..0x0000000100000000 ] Type: Reserved Size: 256KiB +[ 0x000000FD00000000..0x0000010000000000 ] Type: Reserved Size: 12GiB +Initramfs is located at: 0xffff800007ac3000..0xffff800007ac4000 +Parsing initramfs fs at 0xffff800007ac3000 +Squashfs { ptr: 0xffff800007ac3060, superblock: SquashfsSuperblock { magic: 73717368, inode_count: 2, mod_time: 6566B6A1, block_size: 20000, frag_count: 1, compressor: GZIP, block_log: 11, flags: 1CB, id_count: 1, ver_major: 4, ver_minor: 0, root_inode_offset: 20, root_inode_block: 0, _reserved: 0, bytes_used: 120, id_table: 118, xattr_table: FFFFFFFFFFFFFFFF, inode_table: 7B, dir_table: BD, frag_table: F0, export_table: 10A }, data_table: [48, 65, 6C, 6C, 6F, 20, 57, 6F, 72, 6C, 64, 20, 66, 72, 6F, 6D, 20, 49, 6E, 69, 74, 72, 61, 6D, 66, 73, A], inode_table: [40, 80, 2, 0, A4, 1, 0, 0, 0, 0, A1, B6, 66, 65, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1B, 0, 0, 0, 1, 0, ED, 1, 0, 0, 0, 0, A1, B6, 66, 65, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 22, 0, 0, 0, 3, 0, 0, 0], directory_table: [1F, 80, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, A, 0, 65, 78, 61, 6D, 70, 6C, 65, 2E, 74, 78, 74, 10, 80, 60, 0, 0, 0, 0, 0, 0, 0, 1B, 0, 0, 1, 0, 0, 0, 0], fragment_table: Some([DE, 0, 0, 0, 0, 0, 0, 0, 10, 80, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0]), export_table: Some([F8, 0, 0, 0, 0, 0, 0, 0, 4, 80, E8, 3, 0, 0]), id_table: [12, 1, 0, 0, 0, 0, 0, 0], xattr_table: None } +SquashfsFeatures { uncompressed_inodes: true, uncompressed_data_blocks: true, _reserved: false, uncompressed_fragments: true, unused_fragments: false, fragments_always_present: false, deduplicated_data: true, nfs_table_present: true, uncompressed_xattrs: true, no_xattrs: false, compressor_options_present: false, uncompressed_id_table: false } +[1, 0, ED, 1, 0, 0, 0, 0, A1, B6, 66, 65, 2, 0, 0, 0] +InodeHeader { file_type: BasicDirectory, _reserved: [0, 0, 0], mtime: 6566B6A1, inode_num: 2 } +root_inode: DirectoryInode { block_index: 0, link_count: 2, file_size: 22, block_offset: 0, parent_inode: 3 } +====== PCI DEVICES ====== +Entry 0: Bus: 0 Device: 0 Function: 0 VendorID: 0x8086 DeviceID: 0x1237 ClassCode: 0x06 SubclassCode: 0x00 ProgIF: 0x00 +Entry 1: Bus: 0 Device: 1 Function: 0 VendorID: 0x8086 DeviceID: 0x7000 ClassCode: 0x06 SubclassCode: 0x01 ProgIF: 0x00 +Entry 2: Bus: 0 Device: 1 Function: 1 VendorID: 0x8086 DeviceID: 0x7010 ClassCode: 0x01 SubclassCode: 0x01 ProgIF: 0x80 +Entry 3: Bus: 0 Device: 1 Function: 3 VendorID: 0x8086 DeviceID: 0x7113 ClassCode: 0x06 SubclassCode: 0x80 ProgIF: 0x00 +Entry 4: Bus: 0 Device: 2 Function: 0 VendorID: 0x1234 DeviceID: 0x1111 ClassCode: 0x03 SubclassCode: 0x00 ProgIF: 0x00 +Entry 5: Bus: 0 Device: 3 Function: 0 VendorID: 0x8086 DeviceID: 0x100E ClassCode: 0x02 SubclassCode: 0x00 ProgIF: 0x00 +[ ? ] ATA: Detected 1 drive +[ ? ] ATA: Drive 0 has 131072 sectors (64 MB) +11, 44, 128 +[ ? ] WARNING: FAT is not being stored in memory, this feature is experimental and file reads are expected to be slower. +Found Fat32 FS +[GPTPartitionEntry { partition_type_guid: [40, 115, 42, 193, 31, 248, 210, 17, 186, 75, 0, 160, 201, 62, 201, 59], start_sector: 2048, end_sector: 34815 }, GPTPartitionEntry { partition_type_guid: [175, 61, 198, 15, 131, 132, 114, 71, 142, 121, 61, 105, 216, 71, 125, 228], start_sector: 34816, end_sector: 131038 }] +Total memory: 125MiB +> \ No newline at end of file