diff --git a/.gitignore b/.gitignore index 5ecbcc3fb..a9e70705c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,7 @@ target/ **/*.rs.bk **/.DS_Store + +# Ramdisk file +src/ramdisk/initramfs/ +src/ramdisk/build/ diff --git a/Makefile b/Makefile index 5f583fe97..426a2f8d4 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/src/ramdisk/Makefile b/src/ramdisk/Makefile new file mode 100644 index 000000000..f2e12bcba --- /dev/null +++ b/src/ramdisk/Makefile @@ -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) diff --git a/src/ramdisk/mkinitramfs b/src/ramdisk/mkinitramfs new file mode 100755 index 000000000..52b8fc549 --- /dev/null +++ b/src/ramdisk/mkinitramfs @@ -0,0 +1,15 @@ +#!/bin/bash +set -e + +if [ $# -ne 2 ]; then + echo "Usage: mkinitramfs " + 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 diff --git a/src/services/libs/jinux-std/Cargo.toml b/src/services/libs/jinux-std/Cargo.toml index 31202f4ef..5655a2e06 100644 --- a/src/services/libs/jinux-std/Cargo.toml +++ b/src/services/libs/jinux-std/Cargo.toml @@ -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" diff --git a/src/services/libs/jinux-std/src/error.rs b/src/services/libs/jinux-std/src/error.rs index 15bf0a26e..06516b5cc 100644 --- a/src/services/libs/jinux-std/src/error.rs +++ b/src/services/libs/jinux-std/src/error.rs @@ -211,6 +211,31 @@ impl From for Error { } } +impl From 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 for jinux_frame::Error { fn from(error: Error) -> Self { match error.errno { diff --git a/src/services/libs/jinux-std/src/fs/file_handle/inode_handle/mod.rs b/src/services/libs/jinux-std/src/fs/file_handle/inode_handle/mod.rs index 61b287a3d..a5132b6c8 100644 --- a/src/services/libs/jinux-std/src/fs/file_handle/inode_handle/mod.rs +++ b/src/services/libs/jinux-std/src/fs/file_handle/inode_handle/mod.rs @@ -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 InodeHandle { self.0.offset() } + pub fn len(&self) -> usize { + self.0.len() + } + pub fn access_mode(&self) -> AccessMode { self.0.access_mode() } diff --git a/src/services/libs/jinux-std/src/fs/initramfs.rs b/src/services/libs/jinux-std/src/fs/initramfs.rs new file mode 100644 index 000000000..430a41889 --- /dev/null +++ b/src/services/libs/jinux-std/src/fs/initramfs.rs @@ -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(()) +} diff --git a/src/services/libs/jinux-std/src/fs/mod.rs b/src/services/libs/jinux-std/src/fs/mod.rs index 77018bc58..ed7305f6a 100644 --- a/src/services/libs/jinux-std/src/fs/mod.rs +++ b/src/services/libs/jinux-std/src/fs/mod.rs @@ -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; diff --git a/src/services/libs/jinux-std/src/lib.rs b/src/services/libs/jinux-std/src/lib.rs index 6088d441f..cb046e549 100644 --- a/src/services/libs/jinux-std/src/lib.rs +++ b/src/services/libs/jinux-std/src/lib.rs @@ -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() -> ! { diff --git a/src/services/libs/jinux-std/src/user_apps.rs b/src/services/libs/jinux-std/src/user_apps.rs index bba25e2c7..bd17b5eee 100644 --- a/src/services/libs/jinux-std/src/user_apps.rs +++ b/src/services/libs/jinux-std/src/user_apps.rs @@ -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, pub argv: Vec, pub envp: Vec, } impl UserApp { - pub fn new(elf_path: &str, app_content: &'static [u8]) -> Self { + pub fn new(elf_path: &str) -> Result { 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) { @@ -27,45 +41,45 @@ impl UserApp { } } -pub fn get_all_apps() -> Vec { +pub fn get_all_apps() -> Result> { 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 { // 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> { let mut res = Vec::new(); for raw_str in raw_strs {