diff --git a/src/libos/src/process/do_spawn/exec_loader.rs b/src/libos/src/process/do_spawn/exec_loader.rs new file mode 100644 index 00000000..b0a937fc --- /dev/null +++ b/src/libos/src/process/do_spawn/exec_loader.rs @@ -0,0 +1,85 @@ +use super::ThreadRef; +use crate::fs::{FileMode, INodeExt}; +use crate::prelude::*; +use rcore_fs::vfs::INode; +use std::ffi::CString; + +/// Load an ELF file itself or a script's interpreter into a vector. +/// +/// If the file is an executable binary, then just load this file. +/// If the file is an script text, then parse the shebang and load +/// the interpreter. +pub fn load_exec_file_to_vec( + file_path: &str, + current_ref: &ThreadRef, +) -> Result<(Option, Vec)> { + let file_buf = load_file_to_vec(&file_path, current_ref)?; + let is_script = is_script_file(&file_buf); + if is_script { + // load interpreter + let interpreter_path = parse_script_interpreter(&file_buf)?; + if interpreter_path.starts_with("/host/") { + return_errno!( + EACCES, + "libos doesn't support executing binaries from \"/host\" directory" + ); + } + let elf_buf = load_file_to_vec(&interpreter_path, current_ref)?; + Ok((Some(interpreter_path), elf_buf)) + } else { + Ok((None, file_buf)) + } +} + +fn is_script_file(file_buf: &Vec) -> bool { + file_buf.starts_with(&[b'#', b'!']) +} + +// TODO: Support parsing interpreter arguments. e.g. `/usr/bin/python -u` +fn parse_script_interpreter(file_buf: &Vec) -> Result { + let mut start = 2; // after '#', '!' + const MAX_LEN: usize = 127; + + // skip whitespaced between shebang and interpreter + while (start < file_buf.len()) + && (file_buf[start] == ' ' as u8 || file_buf[start] == '\t' as u8) + { + start += 1; + } + + let end = file_buf + .iter() + .take(MAX_LEN) + .position(|&c| c == '\n' as u8) + .ok_or_else(|| errno!(EINVAL, "script parsing error"))?; + + let interpreter = std::str::from_utf8(&file_buf[start..end]) + .map_err(|e| errno!(ENOEXEC, "failed to get the script interpreter"))?; + trace!("script file using interpreter: {:?}", interpreter); + Ok(interpreter.to_owned()) +} + +pub fn load_file_to_vec(file_path: &str, current_ref: &ThreadRef) -> Result> { + let inode = current_ref + .fs() + .lock() + .unwrap() + .lookup_inode_follow(file_path) + .map_err(|e| errno!(e.errno(), "cannot find the file"))?; + let file_mode = { + let info = inode.metadata()?; + FileMode::from_bits_truncate(info.mode) + }; + if !file_mode.is_executable() { + return_errno!(EACCES, "file is not executable"); + } + if file_mode.has_set_uid() || file_mode.has_set_gid() { + warn!( + "set-user-ID and set-group-ID are not supportted, FileMode:{:?}", + file_mode + ); + } + inode + .read_as_vec() + .map_err(|e| errno!(e.errno(), "failed to read the file")) +} diff --git a/src/libos/src/process/do_spawn/mod.rs b/src/libos/src/process/do_spawn/mod.rs index da06487c..aeca3eb3 100644 --- a/src/libos/src/process/do_spawn/mod.rs +++ b/src/libos/src/process/do_spawn/mod.rs @@ -2,18 +2,20 @@ use std::ffi::{CStr, CString}; use std::path::Path; use self::aux_vec::{AuxKey, AuxVec}; +use self::exec_loader::{load_exec_file_to_vec, load_file_to_vec}; use super::elf_file::{ElfFile, ElfHeader, ProgramHeader, ProgramHeaderExt}; use super::process::ProcessBuilder; use super::task::Task; use super::{table, task, ProcessRef, ThreadRef}; use crate::fs::{ - CreationFlags, File, FileDesc, FileMode, FileTable, FsView, HostStdioFds, INodeExt, StdinFile, - StdoutFile, ROOT_INODE, + CreationFlags, File, FileDesc, FileTable, FsView, HostStdioFds, StdinFile, StdoutFile, + ROOT_INODE, }; use crate::prelude::*; use crate::vm::ProcessVM; mod aux_vec; +mod exec_loader; mod init_stack; mod init_vm; @@ -91,17 +93,31 @@ fn do_spawn_common( /// Create a new process and its main thread. fn new_process( - elf_path: &str, + file_path: &str, argv: &[CString], envp: &[CString], file_actions: &[FileAction], host_stdio_fds: Option<&HostStdioFds>, current_ref: &ThreadRef, ) -> Result { - let elf_buf = load_elf_to_vec(elf_path, current_ref) - .cause_err(|e| errno!(e.errno(), "cannot load the executable"))?; + let mut argv = argv.clone().to_vec(); + let (is_script, elf_buf) = load_exec_file_to_vec(file_path, current_ref)?; + + // elf_path might be different from file_path because file_path could lead to a script text file. + // And intepreter will be the loaded ELF. + let elf_path = if let Some(interpreter_path) = is_script { + if argv.len() == 0 { + return_errno!(EINVAL, "argv[0] not found"); + } + argv.insert(0, CString::new(interpreter_path.as_str())?); + argv[1] = CString::new(file_path)?; // script file needs to be the full path + interpreter_path + } else { + file_path.to_string() + }; + let ldso_path = "/lib/ld-musl-x86_64.so.1"; - let ldso_elf_buf = load_elf_to_vec(ldso_path, current_ref) + let ldso_elf_buf = load_file_to_vec(ldso_path, current_ref) .cause_err(|e| errno!(e.errno(), "cannot load ld.so"))?; let exec_elf_file = @@ -145,7 +161,7 @@ fn new_process( }; let user_stack_base = vm.get_stack_base(); let user_stack_limit = vm.get_stack_limit(); - let user_rsp = init_stack::do_init(user_stack_base, 4096, argv, envp, &auxvec)?; + let user_rsp = init_stack::do_init(user_stack_base, 4096, &argv, envp, &auxvec)?; unsafe { Task::new( ldso_entry, @@ -166,7 +182,7 @@ fn new_process( ProcessBuilder::new() .vm(vm_ref) - .exec_path(elf_path) + .exec_path(&elf_path) .parent(process_ref) .task(task) .sched(sched_ref) @@ -201,31 +217,6 @@ pub enum FileAction { Close(FileDesc), } -fn load_elf_to_vec(elf_path: &str, current_ref: &ThreadRef) -> Result> { - let inode = current_ref - .fs() - .lock() - .unwrap() - .lookup_inode_follow(elf_path) - .map_err(|e| errno!(e.errno(), "cannot find the ELF"))?; - let file_mode = { - let info = inode.metadata()?; - FileMode::from_bits_truncate(info.mode) - }; - if !file_mode.is_executable() { - return_errno!(EACCES, "elf file is not executable"); - } - if file_mode.has_set_uid() || file_mode.has_set_gid() { - warn!( - "set-user-ID and set-group-ID are not supportted, FileMode:{:?}", - file_mode - ); - } - inode - .read_as_vec() - .map_err(|e| errno!(e.errno(), "failed to read the executable ELF")) -} - fn init_files( current_ref: &ThreadRef, file_actions: &[FileAction],