Skip to content

Commit

Permalink
Merge pull request sunriseos#544 from roblabla/env-variables
Browse files Browse the repository at this point in the history
Implement environment variables
  • Loading branch information
roblabla authored Nov 25, 2019
2 parents 8c53c3d + 81433e6 commit cb0e403
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 60 deletions.
8 changes: 6 additions & 2 deletions ipcdefs/loader.id
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
#
# Responsible for creating, loading, starting and waiting on processes.
interface sunrise_libuser::ldr::ILoaderInterface is ldr:shel {
# Create and load the process `title_name` with the given args.
# Create and load the process `title_name` with the given args and env.
# Returns the process' pid. The process will not be started yet, use
# `launch_title` to start it.
[0] create_title(array<u8, 9> title_name, array<u8, 9> args) -> u64 pid;
#
# The args given is a cmdline string that will be passed verbatim to the
# subprocess. The environment should be a \0-delimited array of environment
# variables.
[0] create_title(array<u8, 9> title_name, array<u8, 9> args, array<u8, 9> env) -> u64 pid;
# Starts a process created with create_title.
[2] launch_title(u64 pid);
# Wait for the process with the given pid, returning the exit status.
Expand Down
147 changes: 117 additions & 30 deletions libuser/src/argv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,62 @@
//! | Argument Storage |
//! +------------------------+ < allocated_size
//! ```
//!
//! ## SunriseOS Extension: Envp
//!
//! Passing environment variables to a subprogram is necessary for proper
//! execution of the SunriseOS userspace. For instance, it is necessary to pass
//! the current working directory to the subprocess so that executing the
//! following script executes the ls in the right directory.
//!
//! ```bash
//! cd /etc
//! uutils ls
//! ```
//!
//! To achieve this, the same mechanism to pass arguments is used to pass
//! environment variables. The environment variables are passed as a string,
//! after the cmdline, as a `\0`-separated list of VAR_NAME=VAR_VALUE. A
//! variable name may not have an = or a \0 in its name.
//!
//! To separate the cmdline from the environment variables, a `\0` is used. As
//! such, the region allocated by Loader will look like this:
//!
//! ```txt
//! +------------------------+ < Always page-aligned.
//! | ProgramArguments |
//! | u32 allocated_size |
//! | u32 arguments_size |
//! +------------------------+
//! | 0x18 Reserved bytes |
//! +------------------------+
//! | Raw CmdLine |
//! | arguments_size bytes |
//! | |
//! | +--------------------+ |
//! | | Arguments | |
//! | +--------------------+ |
//! | | \0 | |
//! | +--------------------+ |
//! | | Environment vars | |
//! | +--------------------+ |
//! | |
//! +------------------------+
//! | Argument Storage |
//! | arguments_size bytes |
//! +------------------------+
//! | Alignment bytes. Force |
//! | align to size_of(usize)|
//! +------------------------+
//! | System Argv |
//! | Array of pointers to |
//! | Argument Storage |
//! +------------------------+
//! | System Envp |
//! | Array of pointers to |
//! | environment vars |
//! +------------------------+ < allocated_size
//! ```

#[cfg(not(feature = "build-for-std-app"))]
use core::mem::{size_of, align_of};
Expand Down Expand Up @@ -60,6 +116,9 @@ extern {
/// elements.
#[link_name = "__libuser_get_argv"]
pub fn argv() -> *const *const u8;
/// Get the environ array. It is guaranteed to end with a NULL element.
#[link_name = "__libuser_get_envp"]
pub fn envp() -> *const *const u8;
}

/// Get the number of arguments in argv.
Expand All @@ -77,22 +136,32 @@ pub extern fn argv() -> *const *const u8 {
__libuser_get_args().0 as *const *const u8
}

/// Get the arguments. This will parse and setup the arguments the first time it
/// is called - modifying the __argdata__ section in the process. This function
/// is safe to call from multiple threads - accesses are synchronized.
/// Get the environ array. It is guaranteed to end with a NULL element.
#[cfg(not(feature = "build-for-std-app"))]
#[export_name = "__libuser_get_envp"]
pub extern fn envp() -> *const *const u8 {
__libuser_get_args().2 as *const *const u8
}

/// Get the arguments and environment. This will parse and setup the arguments
/// and environment the first time it is called - modifying the __argdata__
/// section in the process. This function is safe to call from multiple threads
/// - accesses are synchronized.
///
/// First returned value is the argv, second value is the argc.
/// First returned value is the argv, second value is the argc, third value is
/// the envp.
#[cfg(not(feature = "build-for-std-app"))]
#[allow(clippy::cognitive_complexity)]
fn __libuser_get_args() -> (usize, isize) {
fn __libuser_get_args() -> (usize, isize, usize) {
use sunrise_libkern::MemoryPermissions;

/// Once argdata is parsed, this static contains the pointer to the argument
/// vector and the size of that vector.
static ARGS: Once<(usize, isize)> = Once::new();
/// vector, the size of that vector, and a pointer to the environment
/// vector.
static ARGS: Once<(usize, isize, usize)> = Once::new();

/// Data returned when reading the args fails.
const NO_ARGS: (usize, isize) = (0, 0);
const NO_ARGS: (usize, isize, usize) = (0, 0, 0);

extern {
/// Location where the loader will put the argument data. This symbol is
Expand Down Expand Up @@ -209,33 +278,33 @@ fn __libuser_get_args() -> (usize, isize) {
let mut arg_len = 0;
let mut quote_flag = false;
let mut argstorage_idx = 0;
let mut env_start = None;

for argi in 0..argdata_strsize {
if arg_start.is_none() && args[argi].is_ascii_whitespace() {
// Skip over whitespace when we're not currently dealing with an arg.
for (argi, item) in args.iter().copied().enumerate() {
if arg_start.is_none() && item.is_ascii_whitespace() {
// Skip over ascii whitespace
continue;
}

if item == 0 {
// We found a '\0', time to start handling environment
// variables.
env_start = Some(argi + 1);
break;
}

if let Some(arg_start_idx) = arg_start {
// We're currently handling an arg.
let mut end_flag = false;

// Check if we have reached the end of an argument.
if quote_flag {
if args[argi] == b'"' {
end_flag = true;
}
} else if args[argi].is_ascii_whitespace() {
end_flag = true;
}
let end_flag = (quote_flag && item == b'"') || item.is_ascii_whitespace();

// If we didn't, include the character being processed in the
// current arg.
if !end_flag && args[argi] != 0 {
if !end_flag && item != 0 {
arg_len += 1;
}

if (args[argi] == 0 || end_flag) && arg_len != 0 {
if (item == 0 || end_flag) && arg_len != 0 {
// If we've reached the end of an argument we copy it to the
// argstorage region, and put it in argv.
argstorage[argstorage_idx..argstorage_idx + arg_len]
Expand All @@ -254,15 +323,14 @@ fn __libuser_get_args() -> (usize, isize) {
break;
}
}
} else if item == b'"' {
// Found a new quoted argument.
arg_start = Some(argi + 1);
quote_flag = true;
} else {
// Found a new argument.
if args[argi] == b'"' {
arg_start = Some(argi + 1);
quote_flag = true;
} else if args[argi] != 0 {
arg_start = Some(argi);
arg_len += 1;
}
arg_start = Some(argi);
arg_len += 1;
}
}

Expand All @@ -279,7 +347,26 @@ fn __libuser_get_args() -> (usize, isize) {

__system_argv[__system_argc] = 0;

let (__system_argv, __system_envp) = __system_argv.split_at_mut(__system_argc + 1);
let mut __system_envc = 0;

if let Some(env_start) = env_start {
for split in args[env_start..].split(|&v| v == 0) {
if split.contains(&b'=') {
// Handle environment variable.
__system_envp[__system_envc] = split.as_ptr() as usize;
__system_envc += 1;
} else {
log::error!("Invalid env variable: Does not contain = sign.");
}
}
} else {
log::debug!("No env variables found");
}

__system_envp[__system_envc] = 0;

#[allow(clippy::cast_possible_wrap)]
(__system_argv.as_ptr() as usize, __system_argc as isize)
(__system_argv.as_ptr() as usize, __system_argc as isize, __system_envp.as_ptr() as usize)
})
}
53 changes: 35 additions & 18 deletions loader/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ extern crate alloc;

use core::str;
use core::slice;
use core::mem::size_of;
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::collections::BTreeMap;
Expand Down Expand Up @@ -70,7 +69,7 @@ lazy_static! {
}

/// Start the given titleid by loading its content from the provided filesystem.
fn boot(fs: &IFileSystemProxy, titlename: &str, args: &[u8], start: bool) -> Result<Pid, Error> {
fn boot(fs: &IFileSystemProxy, titlename: &str, args: &[u8], env: &[u8], start: bool) -> Result<Pid, Error> {
info!("Booting titleid {}", titlename);

let val = format!("/bin/{}/main", titlename);
Expand Down Expand Up @@ -136,13 +135,26 @@ fn boot(fs: &IFileSystemProxy, titlename: &str, args: &[u8], start: bool) -> Res
//
// Like, wtf is this. So we do our own, based on our usage. See
// libuser::argv for more info.
let args_size = args.len() * 2 + 0x20;
let args_size = align_up(args_size, size_of::<usize>());
// Add a whole page for the vector of ptrs.
let args_size = args_size + 0x1000 / size_of::<usize>();
let args_size = align_up(args_size, PAGE_SIZE);

let total_size = elf_size + align_up(args_size, PAGE_SIZE);
// Get the size of the args + environ combined
let args_size = args.len() + 1 + env.len();
// For prealloc size, get the 0x20 byte header
let mut prealloc_size = 0x20;
// Add the args_size twice, once for the actual args, and one for the
// argument storage location.
prealloc_size += args_size * 2;
// Align the size to the max pointer size supported by the OS (8).
prealloc_size = align_up(prealloc_size, 8);
// Add a whole page for the vector of ptrs for the System Argv. This allows
// 512 args.
prealloc_size += 0x1000;
// And another page for the vector of ptrs for the System Envp. This allows
// 512 environment variables.
prealloc_size += 0x1000;
// Finally, align the size up to the nearest page.
let prealloc_size = align_up(prealloc_size, PAGE_SIZE);

let total_size = elf_size + prealloc_size;

let process = sunrise_libuser::syscalls::create_process(&ProcInfo {
name: titlename_bytes,
Expand All @@ -159,31 +171,34 @@ fn boot(fs: &IFileSystemProxy, titlename: &str, args: &[u8], start: bool) -> Res
elf_loader::load_file(&process, &elf, aslr_base)?;

debug!("Handling args");
let addr = find_free_address(args_size, 0x1000)?;
map_process_memory(addr, &process, aslr_base + elf_size, args_size)?;
let addr = find_free_address(prealloc_size, 0x1000)?;
map_process_memory(addr, &process, aslr_base + elf_size, prealloc_size)?;

{
// Copy the ELF data in the remote process.
let dest_ptr = addr as *mut u8;
let dest = unsafe {
// Safety: Guaranteed to be OK if the syscall returns successfully.
slice::from_raw_parts_mut(dest_ptr, args_size)
slice::from_raw_parts_mut(dest_ptr, prealloc_size)
};
// Copy header
dest[0..4].copy_from_slice(&args_size.to_le_bytes());
dest[4..8].copy_from_slice(&args.len().to_le_bytes());
dest[0..4].copy_from_slice(&prealloc_size.to_le_bytes());
dest[4..8].copy_from_slice(&args_size.to_le_bytes());
// Copy raw cmdline.
dest[0x20..0x20 + args.len()].copy_from_slice(args);
// Copy raw env.
let curpos = 0x20 + args.len() + 1;
dest[curpos..curpos + env.len()].copy_from_slice(env);
}

// Maybe I should panic if this fails, cuz that'd be really bad.
unsafe {
// Safety: this memory was previously mapped and all pointers to it
// should have been dropped already.
syscalls::unmap_process_memory(addr, &process, aslr_base + elf_size, args_size)?;
syscalls::unmap_process_memory(addr, &process, aslr_base + elf_size, prealloc_size)?;
}

syscalls::set_process_memory_permission(&process, aslr_base + elf_size, args_size, MemoryPermissions::RW)?;
syscalls::set_process_memory_permission(&process, aslr_base + elf_size, prealloc_size, MemoryPermissions::RW)?;

if start {
debug!("Starting process.");
Expand Down Expand Up @@ -212,10 +227,10 @@ lazy_static! {
struct LoaderIface;

impl ILoaderInterfaceAsync for LoaderIface {
fn create_title(&mut self, _workqueue: WorkQueue<'static>, title_name: &[u8], args: &[u8]) -> FutureObj<'_, Result<u64, Error>> {
fn create_title(&mut self, _workqueue: WorkQueue<'static>, title_name: &[u8], args: &[u8], env: &[u8]) -> FutureObj<'_, Result<u64, Error>> {
let res = (|| -> Result<u64, Error> {
let title_name = str::from_utf8(title_name).or(Err(LoaderError::ProgramNotFound))?;
let Pid(pid) = boot(&*BOOT_FROM_FS, title_name, args, false)?;
let Pid(pid) = boot(&*BOOT_FROM_FS, title_name, args, env, false)?;
Ok(pid)
})();
FutureObj::new(Box::new(async move {
Expand Down Expand Up @@ -404,7 +419,9 @@ fn main() {
.find(|(_, v)| **v == b'/' || **v == b'\0')
.map(|(idx, _)| idx).unwrap_or_else(|| entry.path.len());
if let Ok(titleid) = str::from_utf8(&entry.path[5..endpos]) {
let _ = boot(&fs, titleid, &[], true);
if let Err(err) = boot(&fs, titleid, &[], &[], true) {
error!("Failed to boot {}: {:?}.", titleid, err);
}
} else {
error!("Non-ASCII titleid found in /boot.");
continue;
Expand Down
2 changes: 1 addition & 1 deletion rust/src/libstd/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1895,7 +1895,7 @@ impl Path {
#[stable(feature = "rust1", since = "1.0.0")]
#[allow(deprecated)]
pub fn is_absolute(&self) -> bool {
self.has_root() && (cfg!(all(unix, not(target_os = "redox"), not(target_os = "sunrise"))) || self.prefix().is_some())
self.has_root() && (cfg!(all(unix, not(target_os = "redox"))) || self.prefix().is_some())
}

/// Returns `true` if the `Path` is relative, i.e., not absolute.
Expand Down
Loading

0 comments on commit cb0e403

Please sign in to comment.