WIP: (probably) complete basic initfs-ar util.

This commit is contained in:
4lDO2
2021-02-20 11:51:33 +01:00
parent 2eefa1a16f
commit 8b5474189d
4 changed files with 351 additions and 45 deletions
+1
View File
@@ -7,6 +7,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
plain = "0.2"
[workspace]
members = [
+74 -15
View File
@@ -1,50 +1,109 @@
pub const MAGIC_LEN: usize = 8;
pub const MAGIC: [u8; 8] = *b"RedoxFtw";
macro_rules! primitive(
($wrapper:ident, $bits:expr, $primitive:ident) => {
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Default)]
pub struct $wrapper([u8; $bits / 8]);
impl $wrapper {
#[inline]
pub fn get(&self) -> $primitive {
<$primitive>::from_le_bytes(self.0)
}
#[inline]
pub fn set(&mut self, new: $primitive) {
self.0 = <$primitive>::to_le_bytes(new);
}
#[inline]
pub fn new(primitive: $primitive) -> Self {
let mut this = Self::default();
this.set(primitive);
this
}
}
impl From<$primitive> for $wrapper {
fn from(primitive: $primitive) -> Self {
Self::new(primitive)
}
}
impl From<$wrapper> for $primitive {
fn from(wrapper: $wrapper) -> Self {
wrapper.get()
}
}
}
);
primitive!(U16, 16, u16);
primitive!(U32, 32, u32);
primitive!(U64, 64, u64);
#[repr(transparent)]
#[derive(Clone, Copy, Debug)]
pub struct Magic(pub [u8; MAGIC_LEN]);
#[repr(transparent)]
#[derive(Clone, Copy, Debug)]
pub struct Offset(pub u32);
pub struct Offset(pub U32);
#[repr(transparent)]
#[derive(Clone, Copy, Debug)]
pub struct Length(pub u32);
pub struct Length(pub U32);
#[repr(transparent)]
#[derive(Clone, Copy, Debug)]
pub struct Inode(pub u16);
pub struct Inode(pub U16);
#[repr(C)]
#[repr(packed)]
#[derive(Clone, Copy, Debug)]
pub struct Timespec {
pub sec: u64,
pub nsec: u32,
pub sec: U64,
pub nsec: U32,
}
#[repr(C)]
#[repr(packed)]
#[derive(Clone, Copy, Debug)]
pub struct Header {
pub magic: Magic,
pub inode_table_offset: Offset,
pub creation_time: Timespec,
pub inode_count: u16,
pub inode_count: U16,
}
#[repr(C)]
#[repr(packed)]
#[derive(Clone, Copy, Debug)]
pub struct InodeHeader {
pub type_and_mode: u32,
pub length: u32,
pub type_and_mode: U32,
pub length: U32,
pub offset: Offset,
pub uid: u32,
pub gid: u32,
pub uid: U32,
pub gid: U32,
}
#[repr(C)]
pub const MODE_MASK: u32 = 0xFFF;
pub const MODE_SHIFT: u8 = 0;
pub const TYPE_SHIFT: u8 = 28;
pub const TYPE_MASK: u32 = 0xF000_0000;
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
pub enum InodeType {
RegularFile = 0x0,
Dir = 0x1,
// All other bit patterns are reserved... for now. TODO: Add symlinks?
}
#[repr(packed)]
#[derive(Clone, Copy, Debug)]
pub struct DirEntry {
pub inode: Inode,
pub name_len: u16,
pub name_len: U16,
pub name_offset: Offset,
}
unsafe impl plain::Plain for Header {}
unsafe impl plain::Plain for InodeHeader {}
unsafe impl plain::Plain for DirEntry {}
+1
View File
@@ -13,5 +13,6 @@ name = "redox-initfs-package"
[dependencies]
anyhow = "1"
clap = "2.33"
plain = "0.2"
redox-initfs = { path = ".." }
+275 -30
View File
@@ -1,9 +1,10 @@
use std::convert::{TryFrom, TryInto};
use std::fs::{DirEntry, File, FileType, OpenOptions, ReadDir};
use std::fs::{DirEntry, File, FileType, Metadata, OpenOptions, ReadDir};
use std::io::{prelude::*, SeekFrom};
use std::path::{Path, PathBuf};
use std::os::unix::ffi::OsStringExt;
use std::os::unix::fs::{FileExt, FileTypeExt};
use std::os::unix::fs::{FileExt, FileTypeExt, MetadataExt};
use anyhow::{anyhow, Context, Result};
use clap::{App, Arg};
@@ -12,16 +13,18 @@ use redox_initfs::types as initfs;
const DEFAULT_MAX_SIZE: u64 = 8 * 1024 * 1024;
enum Entry {
enum EntryKind {
File(File),
Dir(Dir),
}
struct Child {
struct Entry {
name: Vec<u8>,
entry: Entry,
kind: EntryKind,
metadata: Metadata,
}
struct Dir {
children: Vec<Child>,
entries: Vec<Entry>,
}
struct State {
@@ -29,6 +32,7 @@ struct State {
offset: u64,
max_size: u64,
inode_count: u16,
buffer: Box<[u8]>,
}
fn read_directory(state: &mut State, path: &Path) -> Result<Dir> {
@@ -40,7 +44,7 @@ fn read_directory(state: &mut State, path: &Path) -> Result<Dir> {
)
})?;
let children = read_dir
let entries = read_dir
.map(|result| {
let entry = result.map_err(|error| {
anyhow!(
@@ -50,13 +54,14 @@ fn read_directory(state: &mut State, path: &Path) -> Result<Dir> {
)
})?;
let file_type = entry.file_type().map_err(|error| {
let metadata = entry.metadata().map_err(|error| {
anyhow!(
"failed to determine file type for `{}`: {}",
"failed to get metadata for `{}`: {}",
entry.path().to_string_lossy(),
error
)
})?;
let file_type = metadata.file_type();
let unsupported_type = |ty: &str, entry: &DirEntry| {
Err(anyhow!(
@@ -66,7 +71,7 @@ fn read_directory(state: &mut State, path: &Path) -> Result<Dir> {
))
};
let name = entry.path().into_os_string().into_vec();
let entry = if file_type.is_symlink() {
let entry_kind = if file_type.is_symlink() {
return unsupported_type("symlink", &entry);
} else if file_type.is_socket() {
return unsupported_type("socket", &entry);
@@ -77,7 +82,7 @@ fn read_directory(state: &mut State, path: &Path) -> Result<Dir> {
} else if file_type.is_char_device() {
return unsupported_type("character device", &entry);
} else if file_type.is_dir() {
Entry::File(File::open(&entry.path()).map_err(|error| {
EntryKind::File(File::open(&entry.path()).map_err(|error| {
anyhow!(
"failed to open file `{}`: {}",
entry.path().to_string_lossy(),
@@ -85,7 +90,7 @@ fn read_directory(state: &mut State, path: &Path) -> Result<Dir> {
)
})?)
} else if file_type.is_file() {
Entry::Dir(read_directory(state, &entry.path())?)
EntryKind::Dir(read_directory(state, &entry.path())?)
} else {
return Err(anyhow!(
"unknown file type at `{}`",
@@ -94,14 +99,20 @@ fn read_directory(state: &mut State, path: &Path) -> Result<Dir> {
};
// TODO: Allow the user to specify a lower limit than u16::MAX.
state.inode_count = state.inode_count.checked_add(1)
state.inode_count = state
.inode_count
.checked_add(1)
.ok_or_else(|| anyhow!("exceeded the maximum inode limit"))?;
Ok(Child { entry, name })
Ok(Entry {
kind: entry_kind,
metadata,
name,
})
})
.collect::<Result<Vec<_>>>()?;
Ok(Dir { children })
Ok(Dir { entries })
}
fn bump_alloc(state: &mut State, size: u64) -> Result<u64> {
@@ -113,6 +124,214 @@ fn bump_alloc(state: &mut State, size: u64) -> Result<u64> {
Err(anyhow!("Bump allocation failed: max limit reached"))
}
}
struct WriteResult {
size: u32,
offset: u32,
}
fn allocate_and_write_file(state: &mut State, mut file: &File) -> Result<WriteResult> {
let size = file
.seek(SeekFrom::End(0))
.context("failed to seek to end")?;
let size: u32 = size.try_into().context("file too large")?;
let offset: u32 = bump_alloc(state, size.into())
.context("failed to allocate space for file")?
.try_into()
.context("file offset too high")?;
let buffer_size: u32 = state.buffer.len().try_into().context("buffer too large")?;
file.seek(SeekFrom::Start(0))
.context("failed to seek to start")?;
state
.file
.seek(SeekFrom::Start(offset.into()))
.context("failed to seek to offset for image")?;
let mut relative_offset = 0;
// TODO: If this would ever turn out to be a bottleneck, then perhaps we could use
// copy_file_range in `nix`.
while relative_offset < size {
let allowed_length = std::cmp::min(buffer_size, size - relative_offset);
let allowed_length =
usize::try_from(allowed_length).expect("expected buffer size not to be outside usize");
file.read(&mut state.buffer[..allowed_length])
.context("failed to read from source file")?;
state
.file
.write(&state.buffer[..allowed_length])
.context("failed to write source file into destination image")?;
relative_offset += buffer_size;
}
Ok(WriteResult { size, offset })
}
fn write_inode(
state: &mut State,
ty: initfs::InodeType,
metadata: &Metadata,
write_result: WriteResult,
inode_table_offset: u32,
index: u16,
) -> Result<()> {
let inode_size: u32 = std::mem::size_of::<initfs::InodeHeader>()
.try_into()
.expect("inode header length cannot fit within u32");
let type_and_mode = ((ty as u32) << initfs::TYPE_SHIFT) | u32::from(metadata.mode() & 0xFFF);
// TODO: Use main buffer and write in bulk.
let mut inode_buf = [0_u8; std::mem::size_of::<initfs::InodeHeader>()];
let inode = plain::from_mut_bytes::<initfs::InodeHeader>(&mut inode_buf)
.expect("expected inode struct to have alignment 1, and buffer size to match");
*inode = initfs::InodeHeader {
type_and_mode: type_and_mode.into(),
length: write_result.size.into(),
offset: initfs::Offset(write_result.offset.into()),
gid: metadata.gid().into(),
uid: metadata.uid().into(),
};
state
.file
.write_all_at(
&inode_buf,
u64::from(inode_table_offset + u32::from(index) * inode_size),
)
.context("failed to write inode struct to disk image")
}
fn allocate_and_write_dir(
state: &mut State,
dir: &Dir,
inode_table_offset: u32,
start_inode: &mut u16,
) -> Result<WriteResult> {
let entry_size =
u16::try_from(std::mem::size_of::<initfs::DirEntry>()).context("entry size too large")?;
let entry_count = u16::try_from(dir.entries.len()).context("too many subdirectories")?;
let entry_table_length = u32::from(entry_count)
.checked_mul(u32::from(entry_size))
.ok_or_else(|| anyhow!("entry table length too large when multiplying by size"))?;
let entry_table_offset: u32 = bump_alloc(state, entry_table_length.into())
.context("failed to allocate entry table")?
.try_into()
.context("directory entries offset too high")?;
let this_start_inode = *start_inode;
let inode_size: u32 = std::mem::size_of::<initfs::InodeHeader>()
.try_into()
.expect("inode header length cannot fit within u32");
*start_inode += entry_count;
let inode_table_offset =
inode_table_offset + u32::from(this_start_inode) * u32::from(inode_size);
for (index, entry) in dir.entries.iter().enumerate() {
let (write_result, ty) = match entry.kind {
EntryKind::Dir(ref subdir) => {
let write_result =
allocate_and_write_dir(state, subdir, inode_table_offset, start_inode)
.context("failed to copy directory entries into image")?;
(write_result, initfs::InodeType::Dir)
}
EntryKind::File(ref file) => {
let write_result = allocate_and_write_file(state, file)
.context("failed to copy file into image")?;
(write_result, initfs::InodeType::RegularFile)
}
};
let index: u16 = index
.try_into()
.expect("expected dir entry count not to exceed u32");
write_inode(
state,
ty,
&entry.metadata,
write_result,
inode_table_offset,
index,
)?;
let (name_offset, name_len) = {
let name_len: u16 = entry.name.len().try_into().context("file name too long")?;
let offset: u32 = bump_alloc(state, u64::from(name_len))
.context("failed to allocate space for file name")?
.try_into()
.context("file name offset too high up")?;
(offset, name_len)
};
{
let mut direntry_buf = [0_u8; std::mem::size_of::<initfs::DirEntry>()];
let direntry = plain::from_mut_bytes::<initfs::DirEntry>(&mut direntry_buf)
.expect("expected dir entry struct to have alignment 1, and buffer size to match");
let inode = this_start_inode + index;
*direntry = initfs::DirEntry {
inode: initfs::Inode(inode.into()),
name_len: name_len.into(),
name_offset: initfs::Offset(name_offset.into()),
};
state
.file
.write_all_at(
&direntry_buf,
u64::from(entry_table_offset + u32::from(index) * u32::from(entry_size)),
)
.context("failed to write dir entry struct to image")?;
}
}
Ok(WriteResult {
size: entry_table_length,
offset: entry_table_offset,
})
}
fn allocate_contents_and_write_inodes(
state: &mut State,
dir: &Dir,
inode_table_offset: u32,
root_metadata: Metadata,
) -> Result<()> {
let index = 0;
let mut start_inode = 1;
let write_result = allocate_and_write_dir(state, dir, inode_table_offset, &mut start_inode)
.context("failed to allocate and write all directories and files")?;
write_inode(
state,
initfs::InodeType::Dir,
&root_metadata,
write_result,
inode_table_offset,
index,
)
}
fn main() -> Result<()> {
let matches = App::new("redox_initfs_package")
@@ -182,19 +401,27 @@ fn main() -> Result<()> {
.open(destination_temp_path)
.context("failed to open destination file")?;
const BUFFER_SIZE: usize = 8192;
let mut state = State {
file: destination_temp_file,
offset: 0,
max_size,
inode_count: 0,
buffer: vec![0_u8; BUFFER_SIZE].into_boxed_slice(),
};
let root = read_directory(&mut state, Path::new(source)).context("failed to read root")?;
let root_path = Path::new(source);
let root_metadata = root_path
.metadata()
.context("failed to obtain metadata for root")?;
let root = read_directory(&mut state, root_path).context("failed to read root")?;
// NOTE: The header is always stored at offset zero.
let header_offset = bump_alloc(
&mut state,
std::mem::size_of::<initfs::Header>().try_into()
std::mem::size_of::<initfs::Header>()
.try_into()
.expect("expected header size to fit"),
)?;
@@ -208,30 +435,48 @@ fn main() -> Result<()> {
.ok_or_else(|| anyhow!("inode table too large"))?
};
let inode_table_offset = bump_alloc(
&mut state,
inode_table_length,
)?;
let inode_table_offset = bump_alloc(&mut state, inode_table_length)?;
// Finally, write the header to the disk image.
let inode_table_offset = initfs::Offset(
u32::try_from(inode_table_offset)
.map_err(|_| anyhow!("inode table located too far away"))?
.into(),
);
allocate_contents_and_write_inodes(
&mut state,
&root,
inode_table_offset.0.get(),
root_metadata,
)?;
let current_system_time = std::time::SystemTime::now();
let time_since_epoch = current_system_time
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.context("could not calculate timestamp")?;
let header = initfs::Header {
magic: initfs::Magic(initfs::MAGIC),
creation_time: initfs::Timespec {
sec: time_since_epoch.as_secs(),
nsec: time_since_epoch.subsec_nanos(),
},
inode_count: state.inode_count,
inode_table_offset,
};
{
let mut header_bytes = [0_u8; std::mem::size_of::<initfs::Header>()];
let header = plain::from_mut_bytes(&mut header_bytes)
.expect("expected header size to be sufficient and alignment to be 1");
*header = initfs::Header {
magic: initfs::Magic(initfs::MAGIC),
creation_time: initfs::Timespec {
sec: time_since_epoch.as_secs().into(),
nsec: time_since_epoch.subsec_nanos().into(),
},
inode_count: state.inode_count.into(),
inode_table_offset,
};
state
.file
.write_all_at(&header_bytes, header_offset)
.context("failed to write header")?;
}
Ok(())
}