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