Initial Commit

This commit is contained in:
4lDO2
2021-02-19 23:29:04 +01:00
commit 2eefa1a16f
6 changed files with 328 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
/target
Cargo.lock
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "redox-initfs"
version = "0.1.0"
authors = ["4lDO2 <4lDO2@protonmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
[workspace]
members = [
"utils",
]
+8
View File
@@ -0,0 +1,8 @@
//! A super simple initfs, only meant to be loaded into RAM by the bootloader, and then directly be
//! read.
pub mod types;
pub struct InitFs<'a> {
base: &'a [u8],
}
+50
View File
@@ -0,0 +1,50 @@
pub const MAGIC_LEN: usize = 8;
pub const MAGIC: [u8; 8] = *b"RedoxFtw";
#[repr(transparent)]
#[derive(Clone, Copy, Debug)]
pub struct Magic(pub [u8; MAGIC_LEN]);
#[repr(transparent)]
#[derive(Clone, Copy, Debug)]
pub struct Offset(pub u32);
#[repr(transparent)]
#[derive(Clone, Copy, Debug)]
pub struct Length(pub u32);
#[repr(transparent)]
#[derive(Clone, Copy, Debug)]
pub struct Inode(pub u16);
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct Timespec {
pub sec: u64,
pub nsec: u32,
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct Header {
pub magic: Magic,
pub inode_table_offset: Offset,
pub creation_time: Timespec,
pub inode_count: u16,
}
#[repr(C)]
pub struct InodeHeader {
pub type_and_mode: u32,
pub length: u32,
pub offset: Offset,
pub uid: u32,
pub gid: u32,
}
#[repr(C)]
pub struct DirEntry {
pub inode: Inode,
pub name_len: u16,
pub name_offset: Offset,
}
+17
View File
@@ -0,0 +1,17 @@
[package]
name = "utils"
version = "0.1.0"
authors = ["4lDO2 <4lDO2@protonmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bin]]
path = "src/redox_initfs_package.rs"
name = "redox-initfs-package"
[dependencies]
anyhow = "1"
clap = "2.33"
redox-initfs = { path = ".." }
+237
View File
@@ -0,0 +1,237 @@
use std::convert::{TryFrom, TryInto};
use std::fs::{DirEntry, File, FileType, OpenOptions, ReadDir};
use std::path::{Path, PathBuf};
use std::os::unix::ffi::OsStringExt;
use std::os::unix::fs::{FileExt, FileTypeExt};
use anyhow::{anyhow, Context, Result};
use clap::{App, Arg};
use redox_initfs::types as initfs;
const DEFAULT_MAX_SIZE: u64 = 8 * 1024 * 1024;
enum Entry {
File(File),
Dir(Dir),
}
struct Child {
name: Vec<u8>,
entry: Entry,
}
struct Dir {
children: Vec<Child>,
}
struct State {
file: File,
offset: u64,
max_size: u64,
inode_count: u16,
}
fn read_directory(state: &mut State, path: &Path) -> Result<Dir> {
let read_dir = path.read_dir().map_err(|error| {
anyhow!(
"failed to read directory `{}`: {}",
path.to_string_lossy(),
error
)
})?;
let children = read_dir
.map(|result| {
let entry = result.map_err(|error| {
anyhow!(
"failed to get a directory entry from `{}`: {}",
path.to_string_lossy(),
error
)
})?;
let file_type = entry.file_type().map_err(|error| {
anyhow!(
"failed to determine file type for `{}`: {}",
entry.path().to_string_lossy(),
error
)
})?;
let unsupported_type = |ty: &str, entry: &DirEntry| {
Err(anyhow!(
"failed to include {} at `{}`: not supported by redox-initfs",
ty,
entry.path().to_string_lossy()
))
};
let name = entry.path().into_os_string().into_vec();
let entry = if file_type.is_symlink() {
return unsupported_type("symlink", &entry);
} else if file_type.is_socket() {
return unsupported_type("socket", &entry);
} else if file_type.is_fifo() {
return unsupported_type("FIFO", &entry);
} else if file_type.is_block_device() {
return unsupported_type("block device", &entry);
} 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| {
anyhow!(
"failed to open file `{}`: {}",
entry.path().to_string_lossy(),
error
)
})?)
} else if file_type.is_file() {
Entry::Dir(read_directory(state, &entry.path())?)
} else {
return Err(anyhow!(
"unknown file type at `{}`",
entry.path().to_string_lossy()
));
};
// TODO: Allow the user to specify a lower limit than u16::MAX.
state.inode_count = state.inode_count.checked_add(1)
.ok_or_else(|| anyhow!("exceeded the maximum inode limit"))?;
Ok(Child { entry, name })
})
.collect::<Result<Vec<_>>>()?;
Ok(Dir { children })
}
fn bump_alloc(state: &mut State, size: u64) -> Result<u64> {
if state.offset + size <= state.max_size {
let offset = state.offset;
state.offset += size;
Ok(offset)
} else {
Err(anyhow!("Bump allocation failed: max limit reached"))
}
}
fn main() -> Result<()> {
let matches = App::new("redox_initfs_package")
.help("Package a Redox initfs")
.arg(
Arg::with_name("MAX_SIZE")
.long("--max-size")
.short("-m")
.takes_value(true)
.required(false)
.help("Set the upper limit for how large the image can become (default 8 MiB)."),
)
.arg(
Arg::with_name("SOURCE")
.takes_value(true)
.required(true)
.help("Specify the source directory to build the image from."),
)
.arg(
Arg::with_name("OUTPUT")
.takes_value(true)
.required(true)
.long("--output")
.short("-o")
.help("Specify the path of the new image file."),
)
.get_matches();
let max_size = if let Some(max_size_str) = matches.value_of("MAX_SIZE") {
max_size_str
.parse::<u64>()
.context("expected an integer for MAX_SIZE")?
} else {
DEFAULT_MAX_SIZE
};
let source = matches
.value_of("SOURCE")
.expect("expected the required arg SOURCE to exist");
let destination = matches
.value_of("OUTPUT")
.expect("expected the required arg OUTPUT to exist");
let destination_path = Path::new(destination);
let previous_extension = destination_path.extension().map_or("", |ext| {
ext.to_str()
.expect("expected destination path to be valid UTF-8")
});
if !destination_path
.metadata()
.map_or(true, |metadata| metadata.is_file())
{
return Err(anyhow!("Destination file must be a file"));
}
let destination_temp_path =
destination_path.with_extension(format!("{}.partial", previous_extension));
let destination_temp_file = OpenOptions::new()
.read(false)
.write(true)
.create(true)
.create_new(false)
.open(destination_temp_path)
.context("failed to open destination file")?;
let mut state = State {
file: destination_temp_file,
offset: 0,
max_size,
inode_count: 0,
};
let root = read_directory(&mut state, Path::new(source)).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()
.expect("expected header size to fit"),
)?;
let inode_table_length = {
let inode_entry_size: u64 = std::mem::size_of::<initfs::DirEntry>()
.try_into()
.expect("expected table entry size to fit");
inode_entry_size
.checked_mul(u64::from(state.inode_count))
.ok_or_else(|| anyhow!("inode table too large"))?
};
let inode_table_offset = bump_alloc(
&mut state,
inode_table_length,
)?;
let inode_table_offset = initfs::Offset(
u32::try_from(inode_table_offset)
.map_err(|_| anyhow!("inode table located too far away"))?
);
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,
};
Ok(())
}