Add support for executing a script that begins with a shebang
This commit is contained in:
parent
ec970f0e76
commit
92207d5535
85
src/libos/src/process/do_spawn/exec_loader.rs
Normal file
85
src/libos/src/process/do_spawn/exec_loader.rs
Normal file
@ -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<String>, Vec<u8>)> {
|
||||||
|
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<u8>) -> 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<u8>) -> Result<String> {
|
||||||
|
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<Vec<u8>> {
|
||||||
|
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"))
|
||||||
|
}
|
@ -2,18 +2,20 @@ use std::ffi::{CStr, CString};
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use self::aux_vec::{AuxKey, AuxVec};
|
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::elf_file::{ElfFile, ElfHeader, ProgramHeader, ProgramHeaderExt};
|
||||||
use super::process::ProcessBuilder;
|
use super::process::ProcessBuilder;
|
||||||
use super::task::Task;
|
use super::task::Task;
|
||||||
use super::{table, task, ProcessRef, ThreadRef};
|
use super::{table, task, ProcessRef, ThreadRef};
|
||||||
use crate::fs::{
|
use crate::fs::{
|
||||||
CreationFlags, File, FileDesc, FileMode, FileTable, FsView, HostStdioFds, INodeExt, StdinFile,
|
CreationFlags, File, FileDesc, FileTable, FsView, HostStdioFds, StdinFile, StdoutFile,
|
||||||
StdoutFile, ROOT_INODE,
|
ROOT_INODE,
|
||||||
};
|
};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::vm::ProcessVM;
|
use crate::vm::ProcessVM;
|
||||||
|
|
||||||
mod aux_vec;
|
mod aux_vec;
|
||||||
|
mod exec_loader;
|
||||||
mod init_stack;
|
mod init_stack;
|
||||||
mod init_vm;
|
mod init_vm;
|
||||||
|
|
||||||
@ -91,17 +93,31 @@ fn do_spawn_common(
|
|||||||
|
|
||||||
/// Create a new process and its main thread.
|
/// Create a new process and its main thread.
|
||||||
fn new_process(
|
fn new_process(
|
||||||
elf_path: &str,
|
file_path: &str,
|
||||||
argv: &[CString],
|
argv: &[CString],
|
||||||
envp: &[CString],
|
envp: &[CString],
|
||||||
file_actions: &[FileAction],
|
file_actions: &[FileAction],
|
||||||
host_stdio_fds: Option<&HostStdioFds>,
|
host_stdio_fds: Option<&HostStdioFds>,
|
||||||
current_ref: &ThreadRef,
|
current_ref: &ThreadRef,
|
||||||
) -> Result<ProcessRef> {
|
) -> Result<ProcessRef> {
|
||||||
let elf_buf = load_elf_to_vec(elf_path, current_ref)
|
let mut argv = argv.clone().to_vec();
|
||||||
.cause_err(|e| errno!(e.errno(), "cannot load the executable"))?;
|
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_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"))?;
|
.cause_err(|e| errno!(e.errno(), "cannot load ld.so"))?;
|
||||||
|
|
||||||
let exec_elf_file =
|
let exec_elf_file =
|
||||||
@ -145,7 +161,7 @@ fn new_process(
|
|||||||
};
|
};
|
||||||
let user_stack_base = vm.get_stack_base();
|
let user_stack_base = vm.get_stack_base();
|
||||||
let user_stack_limit = vm.get_stack_limit();
|
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 {
|
unsafe {
|
||||||
Task::new(
|
Task::new(
|
||||||
ldso_entry,
|
ldso_entry,
|
||||||
@ -166,7 +182,7 @@ fn new_process(
|
|||||||
|
|
||||||
ProcessBuilder::new()
|
ProcessBuilder::new()
|
||||||
.vm(vm_ref)
|
.vm(vm_ref)
|
||||||
.exec_path(elf_path)
|
.exec_path(&elf_path)
|
||||||
.parent(process_ref)
|
.parent(process_ref)
|
||||||
.task(task)
|
.task(task)
|
||||||
.sched(sched_ref)
|
.sched(sched_ref)
|
||||||
@ -201,31 +217,6 @@ pub enum FileAction {
|
|||||||
Close(FileDesc),
|
Close(FileDesc),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_elf_to_vec(elf_path: &str, current_ref: &ThreadRef) -> Result<Vec<u8>> {
|
|
||||||
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(
|
fn init_files(
|
||||||
current_ref: &ThreadRef,
|
current_ref: &ThreadRef,
|
||||||
file_actions: &[FileAction],
|
file_actions: &[FileAction],
|
||||||
|
Loading…
Reference in New Issue
Block a user