Commit b95e039a authored by SamwiseFilmore's avatar SamwiseFilmore

Support Namespaces; Use syscalls to impl Command

While attempting to implement namespaces for services, I found that
std::process::Command was inadequate for the task, so this commit
includes a reimplementation of it with redox syscalls. It's not as good,
but it does what I need, and hopefully will be improved upon as I
continue to work on this.

Namespaces work now!
parent f9ab21b2
Pipeline #2772 passed with stage
in 2 minutes and 22 seconds
use std::collections::HashMap;
use std::env;
use std::fmt::{Display, Formatter, self};
use std::fs::File;
use std::io::{Error, Result};
use std::os::unix::io::AsRawFd;
use syscall::{
self,
error::Error as SyscallError
};
/// An alternative to `std::process::Command` for
/// just redox, which allows for more flexibility
/// in extending the API.
#[derive(Debug)]
pub struct Command {
bin: String,
args: Vec<String>,
env: HashMap<String, String>,
clear_env: bool,
cwd: Option<String>,
uid: Option<usize>,
gid: Option<usize>,
ns: Option<Vec<String>>,
}
impl Command {
pub fn new(bin: String) -> Command {
Command {
bin,
args: vec![],
env: HashMap::new(),
clear_env: false,
cwd: None,
uid: None,
gid: None,
ns: None,
}
}
pub fn arg(&mut self, arg: String) -> &mut Command {
self.args.push(arg);
self
}
pub fn args(&mut self, mut args: Vec<String>) -> &mut Command {
self.args.append(&mut args);
self
}
pub fn env(&mut self, var: String, val: String) -> &mut Command {
self.env.insert(var, val);
self
}
pub fn env_clear(&mut self) -> &mut Command {
self.clear_env = true;
self
}
pub fn cwd(&mut self, cwd: String) -> &mut Command {
self.cwd = Some(cwd);
self
}
pub fn uid(&mut self, uid: usize) -> &mut Command {
self.uid = Some(uid);
self
}
pub fn gid(&mut self, gid: usize) -> &mut Command {
self.gid = Some(gid);
self
}
pub fn ns(&mut self, ns: Vec<String>) -> &mut Command {
self.ns = Some(ns);
self
}
pub fn spawn(&self) -> Result<()> {
const CLOEXEC_MSG_FOOTER: &[u8] = b"NOEX";
let bin = File::open(&self.bin)?;
// This is ust copied from the std redox impl
let pid = unsafe {
match syscall::clone(0).map_err(|e| Error::from_raw_os_error(e.errno) )? {
0 => {
let err = self.do_exec(bin);
let bytes = [
(err.errno >> 24) as u8,
(err.errno >> 16) as u8,
(err.errno >> 8) as u8,
(err.errno >> 0) as u8,
CLOEXEC_MSG_FOOTER[0], CLOEXEC_MSG_FOOTER[1],
CLOEXEC_MSG_FOOTER[2], CLOEXEC_MSG_FOOTER[3]
];
// pipe I/O up to PIPE_BUF bytes should be atomic, and then
// we want to be sure we *don't* run at_exit destructors as
// we're being torn down regardless
//assert!(output.write(&bytes).is_ok());
let _ = syscall::exit(1);
panic!("failed to exit");
}
n => n,
}
};
let mut status = 0;
syscall::waitpid(pid, &mut status, 0)
.map_err(|e| Error::from_raw_os_error(e.errno) )?;
Ok(())
}
/// This puppy sets env vars, user/group ids, cwd, namespaces,
/// etc, and actually calls fexec.
// Currently not parsing shebangs or $PATH for bin locations
// Open files and things at the top here, so that it
// doesn't interfere with namespace setting
fn do_exec(&self, bin: File) -> SyscallError {
macro_rules! t {
($err:expr) => {
match $err {
Ok(val) => val,
Err(e) => return e
}
}
}
if let Some(g) = self.gid {
t!(syscall::setregid(g, g));
}
if let Some(u) = self.uid {
t!(syscall::setreuid(u, u));
}
if let Some(ref cwd) = self.cwd {
t!(syscall::chdir(cwd));
}
if let Some(ref ns) = self.ns {
let ns = t!(syscall::mkns(&raw_ns(ns)));
t!(syscall::setrens(ns, ns));
}
if self.clear_env {
for (k, _) in env::vars_os() {
env::remove_var(k);
}
}
for (key, val) in self.env.iter() {
env::set_var(key, val);
}
let vars: Vec<[usize; 2]> = env::vars_os()
.map(|(var, val)| format!("{}={}", var.to_string_lossy(), val.to_string_lossy()) )
.map(|var| [var.as_ptr() as usize, var.len()] )
.collect();
let mut args: Vec<[usize; 2]> = Vec::with_capacity(1 + self.args.len());
args.push([self.bin.as_ptr() as usize, self.bin.len()]);
args.extend(self.args.iter().map(|arg| [arg.as_ptr() as usize, arg.len()]));
if let Err(err) = syscall::fexec(bin.as_raw_fd(), &args, &vars) {
err
} else {
panic!("return from exec without err");
}
}
}
impl Display for Command {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.bin)?;
for arg in self.args.iter() {
write!(f, " {}", arg)?;
}
Ok(())
}
}
//TODO: Feels like this could be done better
fn raw_ns(schemes: &Vec<String>) -> Vec<[usize; 2]> {
let mut ptrs = vec![];
for scheme in schemes.iter() {
ptrs.push([scheme.as_ptr() as usize, scheme.len()]);
}
ptrs
}
//#![deny(warnings)]
#![feature(dbg_macro)]
mod command;
mod dep_graph;
mod legacy;
mod service;
mod service_tree;
use std::env;
use std::fs::{self, File};
use std::io::{Error, Result};
use std::os::unix::io::{AsRawFd, FromRawFd};
......@@ -54,6 +56,8 @@ impl PathExt for Path {
}
pub fn main() {
env::set_var("RUST_BACKTRACE", "1");
simple_logger::init()
.unwrap_or_else(|err| {
println!("init: failed to start logger: {}", err);
......@@ -65,26 +69,32 @@ pub fn main() {
error!("failed to run initfs:/etc/init.rc: {}", err);
}
} else {
let service_graph = ServiceGraph::new();
let initfs_services = Service::from_dir(INITFS_SERVICE_DIR)
.unwrap_or_else(|err| {
error!("error parsing service directory '{}': {}", INITFS_SERVICE_DIR, err);
error!("failed to parse service directory '{}': {}", INITFS_SERVICE_DIR, err);
vec![]
});
let service_graph = ServiceGraph::new();
service_graph.push_services(initfs_services);
service_graph.start_services();
//*
/*
crate::switch_stdio("display:1")
.unwrap_or_else(|err| {
error!("error switching stdio: {}", err);
});
// */
env::set_current_dir("file:")
.unwrap_or_else(|err| {
error!("failed to set cwd: {}", err);
});
let fs_services = Service::from_dir(FS_SERVICE_DIR)
.unwrap_or_else(|err| {
error!("error parsing service directory '{}': {}", FS_SERVICE_DIR, err);
error!("failed to parse service directory '{}': {}", FS_SERVICE_DIR, err);
vec![]
});
......
......@@ -4,9 +4,7 @@ use std::env;
use std::ffi::OsStr;
use std::fs::{File, read_dir};
use std::io::Read;
use std::os::unix::process::CommandExt;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::path::Path;
use failure::{err_msg, Error};
use log::{debug, error, info, trace};
......@@ -15,6 +13,7 @@ use serde_derive::Deserialize;
use toml;
use crate::PathExt;
use crate::command::Command;
use self::ServiceState::*;
#[derive(Clone, Copy, Debug)]
......@@ -49,7 +48,7 @@ pub struct Method {
pub vars: Option<HashMap<String, String>>,
/// The current working directory for the process executed
/// by this method. Overrides service-level cwd.
pub cwd: Option<PathBuf>,
pub cwd: Option<String>,
/// Username to run this method's process as. Overrides
/// service-level username. Must be present in order to use
......@@ -60,6 +59,12 @@ pub struct Method {
/// is not, `user`'s primary group id is used. Defaults to
/// `root`.
pub group: Option<String>,
/// A process's namespace is the set of schemes which it may
/// access during it's entire lifetime. This field maps directly
/// to the Redox kernel functionality. Overrides service-level
/// namespace declarations.
pub namespace: Option<Vec<String>>,
}
impl Method {
......@@ -85,38 +90,34 @@ impl Method {
}
pub fn wait(&self, vars: Option<&HashMap<String, String>>,
cwd: Option<&PathBuf>,
cwd: Option<&String>,
user: Option<&String>,
group: Option<&String>,
namespace: Option<&Vec<String>>,
) -> Result<(), Error> {
let mut cmd = Command::new(&self.cmd[0]);
cmd.args(self.cmd[1..].iter())
let mut cmd = Command::new(self.cmd[0].clone());
cmd.args(self.cmd[1..].to_vec())
.env_clear();
//TODO: Some mechanic that allows use of service-level
// vars. Is that a good idea?
if let Some(vars) = self.vars.as_ref().or(vars) {
// Typechecker hell if you try Command::envs
// This is the verbatim impl
for (var, val) in vars.iter() {
cmd.env(var, val);
cmd.env(var.to_string(), val.to_string());
}
}
// Is inheriting cwd from `init` OK? Should it use the root of
// the filesystem the service was parsed from?
if let Some(cwd) = self.cwd.as_ref().or(cwd) {
cmd.current_dir(cwd);
cmd.cwd(cwd.to_string());
}
// Same as above goes for user and group
// Any reason to default to `root` instead of
// inheriting from init?
// Tbh rewrite this control flow
if let Some(user) = self.user.as_ref().or(user) {
let users = AllUsers::new(false)?;
if let Some(user) = users.get_by_name(user) {
//BUG
cmd.uid(user.uid as u32);
cmd.uid(user.uid);
// Once we know the the user exists, then we can check
// for group stuff.
......@@ -124,24 +125,32 @@ impl Method {
let groups = AllGroups::new()?;
if let Some(group) = groups.get_by_name(group) {
//BUG
cmd.gid(group.gid as u32);
cmd.gid(group.gid);
} else {
error!("group does not exist: {}", group);
cmd.gid(user.gid as u32);
cmd.gid(user.gid);
}
} else {
cmd.gid(user.gid as u32);
cmd.gid(user.gid);
}
} else {
error!("user does not exist: {}", user);
}
} else {
if let Some(group) = self.group.as_ref().or(group) {
error!("group provided without user, ignoring: '{}'", group);
}
}
debug!("waiting on {:?}", cmd);
if let Some(namespace) = self.namespace.as_ref().or(namespace) {
cmd.ns(namespace.to_vec());
}
debug!("waiting on '{}'", cmd);
dbg!(&cmd);
cmd.spawn()?
.wait()?;
cmd.spawn()?;
//.wait()?;
Ok(())
}
}
......@@ -178,13 +187,19 @@ pub struct Service {
/// part of this service.
pub vars: Option<HashMap<String, String>>,
/// The current working directory for all methods that are
/// a part of this service/
pub cwd: Option<PathBuf>,
/// a part of this service. This defaults to the root of the
/// scheme that this service was parsed from.
pub cwd: Option<String>,
/// Username to run all service methods as
pub user: Option<String>,
/// Groupname to run all service methods as
pub group: Option<String>,
/// The default namespace used for every method on this service.
/// This is a list of schemes that a process has access to over
/// its entire lifetime.
pub namespace: Option<Vec<String>>,
}
impl Service {
......@@ -210,7 +225,7 @@ impl Service {
// that the service should be started in
if let None = service.cwd {
if let Some(scheme) = file_path.scheme() {
service.cwd = Some(scheme);
service.cwd = Some(scheme.to_string_lossy().to_string());
}
}
Ok(service)
......@@ -260,7 +275,13 @@ impl Service {
Some(method) => {
info!("running method '{}' for service '{}'", method_name, self.name);
method.wait(self.vars.as_ref(), self.cwd.as_ref(), self.user.as_ref(), self.group.as_ref())?;
method.wait(
self.vars.as_ref(),
self.cwd.as_ref(),
self.user.as_ref(),
self.group.as_ref(),
self.namespace.as_ref(),
)?;
Ok(())
},
None => {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment