Commit ef1371ed authored by SamwiseFilmore's avatar SamwiseFilmore

Handle Environment Better

This is a pretty big step towards having a functional init. Services
have their environment's cleared before they are started, and their
CWD's default to the scheme from which they were parsed. Both of these
things are now configurable from the config file.

This commit also has a little refactoring and other helpful fixes. Still
has bugs, but it does work.
parent b951f4e9
Pipeline #2697 failed with stage
in 2 minutes and 10 seconds
//#![deny(warnings)]
#![feature(dbg_macro)]
mod dep_graph;
mod legacy;
......@@ -8,14 +9,16 @@ mod service_tree;
use std::fs::{self, File};
use std::io::{Error, Result};
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::path::Path;
use std::path::{Path, PathBuf};
use log::{error, warn};
use log::error;
use syscall::flag::{O_RDONLY, O_WRONLY};
use crate::service::services;
use crate::service_tree::ServiceTree;
const INITFS_SERVICE_DIR: &str = "initfs:/etc/init.d";
fn switch_stdio(stdio: &str) -> Result<()> {
let stdin = unsafe { File::from_raw_fd(
syscall::open(stdio, O_RDONLY).map_err(|err| Error::from_raw_os_error(err.errno))?
......@@ -34,6 +37,41 @@ fn switch_stdio(stdio: &str) -> Result<()> {
Ok(())
}
trait PathExt {
fn scheme(&self) -> Option<PathBuf>;
}
impl PathExt for Path {
//TODO: Could be better written, gross indexing
fn scheme(&self) -> Option<PathBuf> {
/*
let last = self//.as_ref()
.ancestors()
.filter(|element| element != &Path::new("") )
.last();
// lossy is fine 'cause Redox
let last = String::from(last?.to_string_lossy());
let last_len: usize = last.len();
// Redox returns `file:/` as the last, not `file:`
if last.get(last_len - 1usize)? == ":" {
Some(Path::new(&last))
} else if (last.get(last_len - 1usize)? == "/") && (last.get(last_len - 2usize)? == ":") {
Some(Path::new(last.get(0usize..last_len - 1usize)?))
} else {
None
}*/
let path = self.as_os_str()
.to_string_lossy();
if let Some(indx) = path.find(':') {
Some(PathBuf::from(&path[..indx + 1]))
} else {
None
}
}
}
pub fn main() {
simple_logger::init()
.unwrap_or_else(|err| {
......@@ -46,9 +84,9 @@ pub fn main() {
error!("failed to run initfs:/etc/init.rc: {}", err);
}
} else {
let service_list = services("initfs:/etc/init.d")
let service_list = services(INITFS_SERVICE_DIR)
.unwrap_or_else(|err| {
warn!("{}", err);
error!("error parsing service directory '{}': {}", INITFS_SERVICE_DIR, err);
vec![]
});
......
......@@ -4,36 +4,52 @@ use std::env;
use std::ffi::OsStr;
use std::fs::{File, read_dir};
use std::io::Read;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::process::Command;
//use std::sync::mpsc::Sender;
//use std::thread;
use failure::Error;
use failure::{err_msg, Error};
use log::{error, info};
//use generational_arena::Index;
use serde_derive::Deserialize;
use toml;
use crate::PathExt;
use self::ServiceState::*;
#[derive(Debug)]
pub enum State {
pub enum ServiceState {
Offline,
// This might be surperfluous, I included it for
// a dumb debugging reason, might take it out later.
Starting,
Online,
Failed
}
impl State {
pub fn is_running(&self) -> bool {
impl ServiceState {
pub fn is_starting(&self) -> bool {
match self {
Offline => false,
Starting => true,
Online => false,
Failed => false
}
}
pub fn is_online(&self) -> bool {
match self {
State::Offline => false,
State::Online => true,
State::Failed => false
Offline => false,
Starting => false,
Online => true,
Failed => false
}
}
}
impl Default for State {
fn default() -> State { State::Offline }
impl Default for ServiceState {
fn default() -> ServiceState { ServiceState::Offline }
}
#[derive(Debug, Deserialize)]
......@@ -62,25 +78,31 @@ impl Method {
.collect();
self.cmd = modified_cmd;
}
/* WIP
pub fn spawn(&self, channel: Sender<(Index, State)>) {
thread::spawn(move || {
});
}*/
pub fn wait(&self) {
pub fn wait(&self, vars: &Option<HashMap<String, String>>, cwd: &Option<impl AsRef<Path>>) -> Result<(), Error> {
let mut cmd = Command::new(&self.cmd[0]);
cmd.args(self.cmd[1..].iter());
info!("waiting on service method start: {:?}", cmd);
cmd.args(self.cmd[1..].iter())
.env_clear();
match cmd.spawn() {
Ok(mut child) => match child.wait() {
Ok(_status) => {},
Err(err) => error!("failed to wait for: {:?}: {}", cmd, err)
},
Err(err) => error!("failed to spawn: {:?}: {}", cmd, err)
if let Some(vars) = vars {
// Typechecker hell if you try Command::envs
// This is literally the same
for (var, val) in vars.iter() {
cmd.env(var, val);
}
}
// Is inheriting cwd from `init` OK? Should it use the root of
// the filesystem the service was parsed from?
if let Some(cwd) = cwd {
cmd.current_dir(cwd);
}
info!("waiting on {:?}", cmd);
cmd.spawn()?
.wait()?;
Ok(())
}
}
......@@ -93,13 +115,18 @@ pub struct Service {
pub provides: Option<Vec<String>>,
pub methods: HashMap<String, Method>,
pub vars: Option<HashMap<String, String>>,
pub cwd: Option<PathBuf>,
#[serde(skip)]
pub state: State
pub state: ServiceState
}
impl Service {
/// Parse a service file
pub fn from_file(file_path: impl AsRef<Path>) -> Result<Service, Error> {
let file_path = file_path.as_ref();
let mut data = String::new();
File::open(&file_path)?
.read_to_string(&mut data)?;
......@@ -107,11 +134,21 @@ impl Service {
let mut service = toml::from_str::<Service>(&data)?;
//BUG: Only removes the portion after the last '.'
service.name = file_path.as_ref().file_stem()
service.name = file_path.file_stem()
.expect("file name empty") // shouldn't be able to happen
.to_string_lossy() // Redox uses unicode, this should never fail
.into();
service.sub_env();
// Assume that the scheme this service came from is the one
// that the service should be started in.
if let None = service.cwd {
// Only if it's a canonical path though
if let Some(scheme) = file_path.scheme() {
info!("setting service '{}' cwd to {}", service.name, scheme);
service.cwd = Some(scheme);
}
}
Ok(service)
}
......@@ -122,13 +159,31 @@ impl Service {
method.sub_env();
}
}
/// Spawn the process indicated by a method on this service and `wait()` on it.
pub fn wait_method(&mut self, method_name: &String) -> Result<(), Error> {
match self.methods.get(method_name) {
Some(method) => {
info!("running method '{}' for service '{}'", method_name, self.name);
self.state = ServiceState::Starting;
method.wait(&self.vars, &self.cwd)?;
self.state = ServiceState::Online; //TODO: Transition statemap out of metadata graph
Ok(())
},
None => {
let msg = format!("service '{}' missing method '{}'", self.name, method_name);
Err(err_msg(msg))
}
}
}
}
/// Parse all the toml files in a directory as services
pub fn services(dir: impl AsRef<Path>) -> Result<Vec<Service>, Error> {
let mut services = vec![];
for file in read_dir(dir)? {
for file in read_dir(&dir)? {
let file_path = match file {
Ok(file) => file,
Err(err) => {
......@@ -145,7 +200,7 @@ pub fn services(dir: impl AsRef<Path>) -> Result<Vec<Service>, Error> {
if is_toml {
match Service::from_file(file_path) {
Ok(service) => services.push(service),
Err(err) => error!("{}", err)
Err(err) => error!("error parsing service file '{:#?}': {}", dir.as_ref(), err)
}
}
}
......
......@@ -4,35 +4,22 @@ use generational_arena::Index;
use log::{error, warn};
use crate::dep_graph::DepGraph;
use crate::service::{Service, services, State};
use crate::service::{Service, services};
const FS_SERVICE_DIR: &str = "file:/etc/init.d";
/// Main data structure for init, containing the main interface
/// for dealing with services
pub struct ServiceTree {
graph: DepGraph<Service>,
//provide_hooks: HashMap<String, Box<FnMut(&mut ServiceTree)>>
graph: DepGraph<Service>
}
impl ServiceTree {
pub fn new() -> ServiceTree {
ServiceTree {
graph: DepGraph::new(),
//provide_hooks: HashMap::new()
graph: DepGraph::new()
}
}
/*
* There are a couple of places where code like this is commented out,
* the code that exists is horribly broken. Looking for a better solution.
*/
/// Add a hook to be called after a dependency has been provided.
/// The dep can be a service's name, or anything listed in the 'provides'
/// field in a service.toml. Currently this is backed by a hashmap, so
/// it will silently overwrite an existing entry if called multiple
/// times with the same dep.
/*
pub fn provide_hook(&mut self, dep: String, hook: Box<FnMut(&mut ServiceTree)>) {
self.provide_hooks.insert(dep, hook);
}*/
/// Push some services into the graph, and add their dependency nodes.
/// Note that this does not start any services, only their metadata
......@@ -57,6 +44,7 @@ impl ServiceTree {
// It's not a super big deal if a dependency doesn't exist
// I mean, it is, but IDK what to do in that situation
// It's really a pkg problem at that point
//TODO: The dep really needs to be invalidated in some way
None => warn!("dependency not found: {}", dependency)
}
}
......@@ -65,44 +53,48 @@ impl ServiceTree {
}
/// WIP: This function attempts to run the start method on each service in the graph
/// if it is not already running.
/// if it is not already running or starting.
pub fn start_services(&mut self) {
let resolved = self.graph.linear_resolve();
for index /*group*/ in resolved.iter() {
//for index in group.iter() {
let mut service = self.graph.get_mut(*index)
let service = self.graph.get_mut(*index)
// These should all exist, the resolver can only
// return indexes that are in the graph anyway
.expect("resolved service index did not exist");
if let Some(method) = service.methods.get("start") {
if !service.state.is_running() {
method.wait();
service.state = State::Online;
}
} else {
error!("service {} missing 'start' method", service.name);
}
if let Some(provides) = &service.provides {
if provides.contains(&"display:".to_string()) {
crate::switch_stdio("display:1")
.unwrap_or_else(|err| {
warn!("{}", err);
});
}
if !(service.state.is_starting() || service.state.is_online()) {
service.wait_method(&"start".to_string())
.unwrap_or_else(|err| { error!("error starting service '{}': {}", service.name, err) });
if provides.contains(&"file:".to_string()) {
let fs_services = services("/etc/init.d")
.unwrap_or_else(|err| {
warn!("{}", err);
vec![]
});
if let Some(provides) = &service.provides {
/*
if provides.contains(&"display:".to_string()) {
crate::switch_stdio("display:1")
.unwrap_or_else(|err| {
warn!("{}", err);
});
}*/
self.push_services(fs_services);
self.start_services();
if provides.contains(&"file:".to_string()) {
let fs_services = services(FS_SERVICE_DIR)
.unwrap_or_else(|err| {
error!("error parsing service directory '{}': {}", FS_SERVICE_DIR, err);
vec![]
});
// This is surely a poor descision, should probably be
// made on a per-service basis
/*
info!("setting cwd to 'file:'");
if let Err(err) = env::set_current_dir("file:") {
error!("failed to set cwd: {}", err);
}*/
self.push_services(fs_services);
self.start_services();
}
}
}
//}
......
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