Skip to content

Commit

Permalink
Tar+Kernel: Extract file from tar file and complete tar parser
Browse files Browse the repository at this point in the history
  • Loading branch information
corigan01 committed Jan 19, 2025
1 parent eb14ef7 commit 94a9f22
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 2 deletions.
1 change: 1 addition & 0 deletions crates/mem/src/phys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub enum PhysMemoryKind {
AcpiReclaimable,
Bootloader,
Kernel,
InitFs,
PageTables,
Broken,
}
Expand Down
1 change: 1 addition & 0 deletions crates/tar/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ description.workspace = true
documentation.workspace = true

[dependencies]
lldebug = {workspace = true}
105 changes: 105 additions & 0 deletions crates/tar/src/header.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
____ __ __ _ __
/ __ \__ _____ ____ / /___ ____ _ / / (_) /
/ /_/ / // / _ `/ _ \/ __/ // / ' \ / /__/ / _ \
\___\_\_,_/\_,_/_//_/\__/\_,_/_/_/_/ /____/_/_.__/
Part of the Quantum OS Project
Copyright 2025 Gavin Kellam
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

use crate::TarError;

#[repr(C)]
#[derive(Debug)]
pub struct TarHeader {
filename: [u8; 100],
mode: [u8; 8],
uid: [u8; 8],
gid: [u8; 8],
size: [u8; 12],
mtime: [u8; 12],
checksum: [u8; 8],
typeflag: [u8; 1],
reserved: [u8; 355],
}

impl<'a> TryFrom<&'a [u8]> for &'a TarHeader {
type Error = TarError;

fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
if value.len() < size_of::<TarHeader>() {
return Err(TarError::NotEnoughBytesForHeader);
}

if !value.as_ptr().is_aligned_to(align_of::<TarHeader>()) {
return Err(TarError::BytesNotAligned);
}

Ok(unsafe { &*value.as_ptr().cast() })
}
}

impl TarHeader {
/// Check if this header is empty.
pub fn is_empty(&self) -> bool {
self.filename.iter().copied().max().unwrap_or(0) == 0
}

pub fn filename(&self) -> Result<&str, TarError> {
core::ffi::CStr::from_bytes_until_nul(&self.filename)
.map_err(|convert_error| TarError::StrConvertError(convert_error))
.and_then(|c_str| {
c_str
.to_str()
.map_err(|convert_error| TarError::Utf8Error(convert_error))
})
}

/// Checks if this file is of the same name as `compare_name`
pub fn is_file(&self, compare_name: &str) -> bool {
if compare_name.len() > self.filename.len() {
return false;
}

self.filename()
.is_ok_and(|filename| filename == compare_name)
}

/// Attempt to get the filesize.
pub fn filesize(&self) -> Result<usize, TarError> {
let mut size = 0;

for (i, octal_byte) in self
.size
.iter()
.rev()
.copied()
.filter(|b| *b != 0)
.enumerate()
{
if octal_byte > b'8' || octal_byte < b'0' {
return Err(TarError::NotOctal);
}

size += 8_usize.pow(i as u32) * ((octal_byte - b'0') as usize);
}

Ok(size)
}
}
105 changes: 105 additions & 0 deletions crates/tar/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,108 @@ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWA
*/

#![no_std]
#![feature(pointer_is_aligned_to)]

use core::{ffi::FromBytesUntilNulError, str::Utf8Error};
use lldebug::hexdump::HexPrint;

Check warning on line 30 in crates/tar/src/lib.rs

View workflow job for this annotation

GitHub Actions / Build OS

unused import: `lldebug::hexdump::HexPrint`

use header::TarHeader;
use lldebug::logln;

Check warning on line 33 in crates/tar/src/lib.rs

View workflow job for this annotation

GitHub Actions / Build OS

unused import: `lldebug::logln`

mod header;

#[derive(Clone, Debug)]
pub enum TarError {
NotEnoughBytesForHeader,
BytesNotAligned,
StrConvertError(FromBytesUntilNulError),
Utf8Error(Utf8Error),
NotOctal,
OutOfRange,
}

#[derive(Clone, Copy)]
pub struct Tar<'a> {
tar_file: &'a [u8],
}

impl<'a> Tar<'a> {
pub const fn new(tar_file: &'a [u8]) -> Self {
Self { tar_file }
}

/// Get an iterator of TarFileHeaders to iterate over the tar file
pub const fn iter(&self) -> TarFileIter<'a> {
TarFileIter {
offset: 0,
tar: *self,
}
}
}

pub struct TarFileIter<'a> {
offset: usize,
tar: Tar<'a>,
}

impl<'a> Iterator for TarFileIter<'a> {
type Item = TarFile<'a>;

fn next(&mut self) -> Option<Self::Item> {
let tar_header = <&TarHeader as TryFrom<_>>::try_from(
self.tar.tar_file.get(self.offset..self.offset + 512)?,
)
.ok()?;

if tar_header.is_empty() {
return None;
}

let offset = self.offset;
self.offset += ((tar_header.filesize().ok()? + size_of::<TarHeader>()) / 512 + 1) * 512;

Some(TarFile {
tar: self.tar,
offset,
header: tar_header,
})
}
}

pub struct TarFile<'a> {
tar: Tar<'a>,
offset: usize,
header: &'a header::TarHeader,
}

impl<'a> TarFile<'a> {
/// Check if this header is empty.
pub fn is_empty(&self) -> bool {
self.header.is_empty()
}

pub fn filename(&self) -> Result<&str, TarError> {
self.header.filename()
}

/// Checks if this file is of the same name as `compare_name`
pub fn is_file(&self, compare_name: &str) -> bool {
self.header.is_file(compare_name)
}

/// Attempt to get the filesize.
pub fn filesize(&self) -> Result<usize, TarError> {
self.header.filesize()
}

/// Get the inner file contents
pub fn file(&self) -> Result<&'a [u8], TarError> {
let file_offset = self.offset + 512;
let file_size = self.filesize()?;

self.tar
.tar_file
.get(file_offset..file_offset + file_size)
.ok_or(TarError::OutOfRange)
}
}
17 changes: 16 additions & 1 deletion kernel/src/scheduler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ use mem::{
backing::{KernelVmObject, VmRegionObject},
},
};
use tar::Tar;
use util::consts::{PAGE_2M, PAGE_4K};

pub struct Process {
Expand Down Expand Up @@ -64,7 +65,21 @@ impl Scheduler {
}

pub fn add_initfs(&mut self, initfs: &[u8]) -> Result<(), MemoryError> {
let elf_owned = ElfOwned::new_from_slice(initfs);
let tar_file = Tar::new(initfs);

for header in tar_file.iter() {
logln!("Found file: {:?}", header.filename());
}

logln!("Done!");

let elf_owned = ElfOwned::new_from_slice(
tar_file
.iter()
.find(|t| t.is_file("dummy"))
.map(|t| t.file().unwrap())
.unwrap(),
);

// Kernel Process Stack
self.kernel.vm.add_vm_object(KernelVmObject::new_boxed(
Expand Down
6 changes: 5 additions & 1 deletion meta/src/artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ pub async fn build_initfs_file(initfs_files: &[(PathBuf, PathBuf)]) -> Result<Pa
let mut ar = tar::Builder::new(tar_backed);

for (init_elf, to_loc) in initfs_files {
println!("{:?} {:?}", init_elf, to_loc);
let mut elf_file = std::fs::OpenOptions::new().read(true).open(init_elf)?;
ar.append_file(to_loc, &mut elf_file)?;
}
Expand Down Expand Up @@ -234,7 +235,10 @@ pub async fn build_project(multiboot_mode: bool) -> Result<Artifacts> {
build_bootloader_config(),
)?;

let ue_slice = [(dummy_userspace, PathBuf::from("./dummy"))];
let ue_slice = [
(dummy_userspace, PathBuf::from("./dummy")),
(stage_16bit.clone(), PathBuf::from("./stage")),
];

let (bootsector, stage_16, stage_32, stage_64, initfs) = tokio::try_join!(
convert_bin(&stage_bootsector, ArchSelect::I386),
Expand Down

0 comments on commit 94a9f22

Please sign in to comment.