Add support to prepare the initramfs from CPIO file

This commit is contained in:
LI Qing 2023-03-03 10:47:14 +08:00 committed by Tate, Hongliang Tian
parent ece1a545ce
commit 7f80df621d
11 changed files with 187 additions and 50 deletions

4
.gitignore vendored
View File

@ -11,3 +11,7 @@ target/
**/*.rs.bk
**/.DS_Store
# Ramdisk file
src/ramdisk/initramfs/
src/ramdisk/build/

View File

@ -9,6 +9,7 @@ setup:
@cargo install mdbook
build:
@make --no-print-directory -C src/ramdisk
@cd src && cargo kbuild
@cd src && cargo kimage
@ -33,3 +34,4 @@ check:
clean:
@cd src && cargo clean
@cd docs && mdbook clean
@make --no-print-directory -C src/ramdisk clean

33
src/ramdisk/Makefile Normal file
View File

@ -0,0 +1,33 @@
APPS := ../apps
BUILD_DIR := ./build
INITRAMFS := ./initramfs
RAMDISK := $(BUILD_DIR)/ramdisk.cpio
SHELL := /bin/bash
ifneq (, $(wildcard $(APPS)/. ))
APPS_DIRS := $(shell find $(APPS) -type d 2>/dev/null | sed 's/ /\\ /g' | sed 's/:/\\:/g' || true)
APPS_FILES := $(shell find $(APPS) -type f 2>/dev/null | sed 's/ /\\ /g' | sed 's/:/\\:/g' || true)
endif
ifneq (, $(wildcard $(INITRAMFS)/. ))
INITRAMFS_DIRS := $(shell find $(INITRAMFS) -type d 2>/dev/null | sed 's/ /\\ /g' | sed 's/:/\\:/g' || true)
INITRAMFS_FILES := $(shell find $(INITRAMFS) -type f 2>/dev/null | sed 's/ /\\ /g' | sed 's/:/\\:/g' || true)
endif
.PHONY: all clean
all: $(RAMDISK)
$(INITRAMFS): $(APPS) $(APPS_DIRS) $(APPS_FILES)
@mkdir -p $@
@cp -a $(APPS)/* $@
@cd $@ && find . \( -name "*.s" -o -name "*.c" -o -name "Makefile" -o -name "README.md" \) -delete
$(RAMDISK): $(INITRAMFS) $(INITRAMFS_DIRS) $(INITRAMFS_FILES)
@echo "Generating the ramdisk image..."
@mkdir -p $(BUILD_DIR)
@./mkinitramfs $(INITRAMFS) $@
clean:
@rm -rf $(INITRAMFS) $(BUILD_DIR)

15
src/ramdisk/mkinitramfs Executable file
View File

@ -0,0 +1,15 @@
#!/bin/bash
set -e
if [ $# -ne 2 ]; then
echo "Usage: mkinitramfs <dir> <cpio>"
exit 1
fi
if [ -d "$1" ]; then
echo "Creating $2 from $1"
(cd "$1"; find . | cpio -o -H newc) > "$2"
else
echo "The first argument must be a directory"
exit 1
fi

View File

@ -16,6 +16,7 @@ typeflags = {path="../typeflags"}
typeflags-util = {path="../typeflags-util"}
jinux-rights-proc = {path="../jinux-rights-proc"}
jinux-util = {path="../jinux-util"}
cpio-decoder = {path="../cpio-decoder"}
virtio-input-decoder = "0.1.4"
ascii = { version = "1.1", default-features = false, features = ["alloc"] }
intrusive-collections = "0.9.5"

View File

@ -211,6 +211,31 @@ impl From<core::ffi::FromBytesWithNulError> for Error {
}
}
impl From<cpio_decoder::error::Error> for Error {
fn from(cpio_error: cpio_decoder::error::Error) -> Self {
match cpio_error {
cpio_decoder::error::Error::MagicError => {
Error::with_message(Errno::EINVAL, "CPIO invalid magic number")
}
cpio_decoder::error::Error::Utf8Error => {
Error::with_message(Errno::EINVAL, "CPIO invalid utf-8 string")
}
cpio_decoder::error::Error::ParseIntError => {
Error::with_message(Errno::EINVAL, "CPIO parse int error")
}
cpio_decoder::error::Error::FileTypeError => {
Error::with_message(Errno::EINVAL, "CPIO invalid file type")
}
cpio_decoder::error::Error::FileNameError => {
Error::with_message(Errno::EINVAL, "CPIO invalid file name")
}
cpio_decoder::error::Error::BufferShortError => {
Error::with_message(Errno::EINVAL, "CPIO buffer is too short")
}
}
}
}
impl From<Error> for jinux_frame::Error {
fn from(error: Error) -> Self {
match error.errno {

View File

@ -89,6 +89,10 @@ impl InodeHandle_ {
*offset
}
pub fn len(&self) -> usize {
self.dentry.vnode().len()
}
pub fn access_mode(&self) -> AccessMode {
self.access_mode
}
@ -128,6 +132,10 @@ impl<R> InodeHandle<R> {
self.0.offset()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn access_mode(&self) -> AccessMode {
self.0.access_mode()
}

View File

@ -0,0 +1,55 @@
use crate::prelude::*;
use super::fs_resolver::{FsPath, FsResolver};
use super::utils::{InodeMode, InodeType};
use cpio_decoder::{CpioDecoder, FileType};
/// Unpack and prepare the fs from the ramdisk CPIO buffer.
pub fn init(ramdisk_buf: &[u8]) -> Result<()> {
let decoder = CpioDecoder::new(ramdisk_buf);
let fs = FsResolver::new();
for entry_result in decoder.decode_entries() {
let entry = entry_result?;
// Make sure the name is a relative path, and is not end with "/".
let entry_name = entry.name().trim_start_matches('/').trim_end_matches('/');
if entry_name.is_empty() {
return_errno_with_message!(Errno::EINVAL, "invalid entry name");
}
if entry_name == "." {
continue;
}
// Here we assume that the directory referred by "prefix" must has been created.
// The basis of this assumption is
// The mkinitramfs script uses `find` command to ensure that the entries are
// sorted that a directory always appears before its child directories and files.
let (parent, name) = if let Some((prefix, last)) = entry_name.rsplit_once('/') {
(fs.lookup(&FsPath::try_from(prefix)?)?, last)
} else {
(fs.root().clone(), entry_name)
};
let metadata = entry.metadata();
let mode = InodeMode::from_bits_truncate(metadata.permission_mode());
match metadata.file_type() {
FileType::File => {
let dentry = parent.create(name, InodeType::File, mode)?;
dentry.vnode().write_at(0, entry.data())?;
}
FileType::Dir => {
let _ = parent.create(name, InodeType::Dir, mode)?;
}
FileType::Link => {
let dentry = parent.create(name, InodeType::SymLink, mode)?;
let link_content = core::str::from_utf8(entry.data())?;
dentry.vnode().write_link(link_content)?;
}
type_ => {
warn!("unsupported file type = {:?} in initramfs", type_);
}
}
}
Ok(())
}

View File

@ -1,6 +1,7 @@
pub mod file_handle;
pub mod file_table;
pub mod fs_resolver;
pub mod initramfs;
pub mod ramfs;
pub mod stdio;
pub mod utils;

View File

@ -55,6 +55,7 @@ pub fn init() {
driver::init();
process::fifo_scheduler::init();
jinux_frame::enable_interrupts();
fs::initramfs::init(read_ramdisk_content()).unwrap();
}
pub fn init_thread() {
@ -79,15 +80,16 @@ pub fn init_thread() {
println!("[kernel] Running test programs");
println!("");
// Run test apps
for app in get_all_apps().into_iter() {
for app in get_all_apps().unwrap().into_iter() {
let UserApp {
elf_path: app_name,
app_content,
argv,
envp,
} = app;
let app_content = app_content.into_boxed_slice();
println!("[jinux-std/lib.rs] spwan {:?} process", app_name);
Process::spawn_user_process(app_name.clone(), app_content, argv, Vec::new());
Process::spawn_user_process(app_name.clone(), Box::leak(app_content), argv, Vec::new());
}
// Run busybox ash
@ -96,10 +98,11 @@ pub fn init_thread() {
app_content,
argv,
envp,
} = get_busybox_app();
} = get_busybox_app().unwrap();
let app_content = app_content.into_boxed_slice();
println!("");
println!("BusyBox v1.35.0 built-in shell (ash)\n");
Process::spawn_user_process(app_name.clone(), app_content, argv, Vec::new());
Process::spawn_user_process(app_name.clone(), Box::leak(app_content), argv, Vec::new());
loop {
// We don't have preemptive scheduler now.
@ -108,6 +111,10 @@ pub fn init_thread() {
}
}
fn read_ramdisk_content() -> &'static [u8] {
include_bytes!("../../../../ramdisk/build/ramdisk.cpio")
}
/// first process never return
#[controlled]
pub fn run_first_process() -> ! {

View File

@ -1,21 +1,35 @@
use crate::fs::{
fs_resolver::{FsPath, FsResolver},
utils::AccessMode,
};
use crate::prelude::*;
pub struct UserApp {
pub elf_path: CString,
pub app_content: &'static [u8],
pub app_content: Vec<u8>,
pub argv: Vec<CString>,
pub envp: Vec<CString>,
}
impl UserApp {
pub fn new(elf_path: &str, app_content: &'static [u8]) -> Self {
pub fn new(elf_path: &str) -> Result<Self> {
let app_name = CString::new(elf_path).unwrap();
UserApp {
let app_content = {
let fs = FsResolver::new();
let file = fs.open(&FsPath::try_from(elf_path)?, AccessMode::O_RDONLY as u32, 0)?;
let mut content = Vec::new();
let len = file.read_to_end(&mut content)?;
if len != file.len() {
return_errno_with_message!(Errno::EINVAL, "read len is not equal to file size");
}
content
};
Ok(UserApp {
elf_path: app_name,
app_content,
argv: Vec::new(),
envp: Vec::new(),
}
})
}
pub fn set_argv(&mut self, argv: Vec<CString>) {
@ -27,45 +41,45 @@ impl UserApp {
}
}
pub fn get_all_apps() -> Vec<UserApp> {
pub fn get_all_apps() -> Result<Vec<UserApp>> {
let mut res = Vec::with_capacity(16);
// Most simple hello world, written in assembly
let asm_hello_world = UserApp::new("hello_world", read_hello_world_content());
let asm_hello_world = UserApp::new("hello_world/hello_world")?;
res.push(asm_hello_world);
// Hello world, written in C language.
// Since glibc requires the elf path starts with "/", and we don't have filesystem now.
// So we manually add a leading "/" for app written in C language.
let hello_c = UserApp::new("/hello_c", read_hello_c_content());
let hello_c = UserApp::new("/hello_c/hello")?;
res.push(hello_c);
// Fork process, written in assembly
let asm_fork = UserApp::new("fork", read_fork_content());
let asm_fork = UserApp::new("fork/fork")?;
res.push(asm_fork);
// Execve, written in C language.
let execve_c = UserApp::new("/execve", read_execve_content());
let execve_c = UserApp::new("/execve/execve")?;
res.push(execve_c);
// Fork new process, written in C language. (Fork in glibc uses syscall clone actually)
let fork_c = UserApp::new("/fork", read_fork_c_content());
let fork_c = UserApp::new("/fork_c/fork")?;
res.push(fork_c);
// signal test
let signal_test = UserApp::new("/signal_test", read_signal_test_content());
let signal_test = UserApp::new("/signal_c/signal_test")?;
res.push(signal_test);
// pthread test
let pthread_test = UserApp::new("/pthread_test", read_pthread_test_content());
let pthread_test = UserApp::new("/pthread/pthread_test")?;
res.push(pthread_test);
res
Ok(res)
}
pub fn get_busybox_app() -> UserApp {
pub fn get_busybox_app() -> Result<UserApp> {
// busybox
let mut busybox = UserApp::new("/busybox", read_busybox_content());
let mut busybox = UserApp::new("/busybox/busybox")?;
// -l option means the busybox is running as logging shell
let argv = ["/busybox", "sh", "-l"];
let envp = [
@ -78,23 +92,11 @@ pub fn get_busybox_app() -> UserApp {
"OLDPWD=/",
];
let argv = to_vec_cstring(&argv).unwrap();
let envp = to_vec_cstring(&envp).unwrap();
let argv = to_vec_cstring(&argv)?;
let envp = to_vec_cstring(&envp)?;
busybox.set_argv(argv);
busybox.set_envp(envp);
busybox
}
fn read_hello_world_content() -> &'static [u8] {
include_bytes!("../../../../apps/hello_world/hello_world")
}
fn read_hello_c_content() -> &'static [u8] {
include_bytes!("../../../../apps/hello_c/hello")
}
fn read_fork_content() -> &'static [u8] {
include_bytes!("../../../../apps/fork/fork")
Ok(busybox)
}
fn read_execve_content() -> &'static [u8] {
@ -105,22 +107,6 @@ pub fn read_execve_hello_content() -> &'static [u8] {
include_bytes!("../../../../apps/execve/hello")
}
fn read_fork_c_content() -> &'static [u8] {
include_bytes!("../../../../apps/fork_c/fork")
}
fn read_signal_test_content() -> &'static [u8] {
include_bytes!("../../../../apps/signal_c/signal_test")
}
fn read_pthread_test_content() -> &'static [u8] {
include_bytes!("../../../../apps/pthread/pthread_test")
}
fn read_busybox_content() -> &'static [u8] {
include_bytes!("../../../../apps/busybox/busybox")
}
fn to_vec_cstring(raw_strs: &[&str]) -> Result<Vec<CString>> {
let mut res = Vec::new();
for raw_str in raw_strs {