Commit f2c9980b authored by SamwiseFilmore's avatar SamwiseFilmore

Refactor

I basically pushed some of the legacy stuff to a new module and added a
nicer abstraction for the dependency graph which should make working
with that part of the lib a lot nicer. Theoretically that part will get
pulled into it's own crate when it's more mature.
parent 5985e1fc
use std::collections::{HashMap, HashSet};
use std::collections::HashMap;
use failure::{Error, err_msg};
use generational_arena::{Arena, Index};
use generational_arena::Index;
use service::Service;
use dependency_graph::DepGraph;
use service::{Service, State};
#[derive(Debug)]
pub struct ServiceNode {
pub service: Service,
pub dependencies: Vec<Index>
}
impl ServiceNode {
// Empty dependencies
fn from_service(service: Service) -> ServiceNode {
ServiceNode {
service,
dependencies: vec![]
}
}
}
pub struct DepGraph {
pub graph: Arena<ServiceNode>,
on_provides: HashMap<String, fn()>
}
impl DepGraph {
pub fn on_provided(mut self, provide: impl AsRef<str>, callback: fn()) -> DepGraph {
self.on_provides.insert(provide.as_ref().to_string(), callback);
self
}
pub fn graph_from_services(mut services: Vec<Service>) -> DepGraph<Service> {
let mut graph = DepGraph::with_capacity(services.len());
pub fn from_services(mut services: Vec<Service>) -> DepGraph {
let mut graph = Arena::with_capacity(services.len());
let services: HashMap<String, Index> = services.drain(..)
.map(|service| (service.name.clone(), graph.insert(ServiceNode::from_service(service))) )
.collect();
let services: HashMap<String, Index> = services.drain(..)
.map(|service| (service.name.clone(), graph.insert(service)) )
.collect();
for parent in services.values() {
let dependencies = graph.get(*parent)
.expect("services were just added")
.dependencies.clone();
for index in services.values() {
let node = graph.get_mut(*index)
.expect("services were just added");
if let Some(ref dependencies) = node.service.dependencies {
for dependency in dependencies.iter() {
match services.get(dependency) {
Some(index) => node.dependencies.push(*index),
// It's not a super big deal if a dependency doesn't exist
None => warn!("dependency not found: {}", dependency)
}
if let Some(ref dependencies) = dependencies {
for dependency in dependencies.iter() {
match services.get(dependency) {
Some(child) => graph.dependency(*parent, *child)
.unwrap_or_else(|_| warn!("failed to add dependency") ),
// 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
None => warn!("dependency not found: {}", dependency)
}
}
}
DepGraph {
graph,
on_provides: HashMap::new()
}
}
graph
}
/// Naive linear dependency resolution algorithm. The _should_
/// be a solution to the dependency graph. Note that the solution
/// is probably not deterministic.
///
/// # Limitations
/// Not currently detecting dependency cycles
/// Can't figure out
fn resolve_linear(&self) -> Vec<Index> {
let arena_len = self.graph.len();
let mut resolved = Vec::with_capacity(arena_len);
let mut seen = HashSet::with_capacity(arena_len);
pub fn start_services(mut graph: DepGraph<Service>, provide_hooks: HashMap<String, impl Fn()>) -> Result<(), Error> {
let resolved = graph.linear_resolve();
for index in resolved.iter() {
let service = graph.get_mut(*index)
// These should all exist, we just got them out
.expect("resolved service index did not exist");
while resolved.len() < arena_len {
for (index, service_node) in self.graph.iter() {
// formatting?
if !seen.contains(&index) &&
(service_node.dependencies.is_empty() ||
service_node.dependencies.iter().all(|index| resolved.contains(index)))
{
seen.insert(index);
resolved.push(index);
}
}
if let Some(method) = service.methods.get("start") {
method.wait();
} else {
let msg = format!("service {} missing 'start' method", service.name);
return Err(err_msg(msg));
}
resolved
}
/// Resolve dependencies and start them. This is not multi-threaded right now,
/// although that is the goal eventually
pub fn start(&self) -> Result<(), Error> {
let resolved = self.resolve_linear();
for index in resolved.iter() {
// These should all exist, we just got them out
let node = self.graph.get(*index).unwrap();
if let Some(method) = node.service.methods.get("start") {
method.wait();
} else {
let msg = format!("service {} missing 'start' method", node.service.name);
return Err(err_msg(msg));
}
if let Some(ref provides) = node.service.provides {
for provide in provides.iter() {
if let Some(on_provided) = self.on_provides.get(provide) {
on_provided();
}
//TODO: Better solution to this
// Should be able to get rid of the mutable borrow here I hope
service.state = State::Online;
if let Some(ref provides) = service.provides {
for provide in provides.iter() {
if let Some(on_provided) = provide_hooks.get(provide) {
on_provided();
}
}
}
Ok(())
}
Ok(())
}
use std::collections::HashSet;
use generational_arena::{Arena, Index};
struct Node<T> {
inner: T,
dependencies: Vec<Index>
}
impl<T> Node<T> {
fn new(inner: T) -> Node<T> {
Node {
inner,
dependencies: vec![]
}
}
fn get_inner(&self) -> &T {
&self.inner
}
fn get_mut_inner(&mut self) -> &mut T {
&mut self.inner
}
fn unwrap(self) -> T {
self.inner
}
}
/// A sorta thin wrapper over a Generational arena that includes
/// dependency resolution and traversal methods
pub struct DepGraph<T> {
graph: Arena<Node<T>>
}
impl<T> DepGraph<T> {
pub fn with_capacity(n: usize) -> DepGraph<T> {
DepGraph {
graph: Arena::with_capacity(n)
}
}
pub fn insert(&mut self, inner: T) -> Index {
self.graph.insert(Node::new(inner))
}
pub fn get(&self, indx: Index) -> Option<&T> {
self.graph.get(indx)
.map(|node| node.get_inner() )
}
pub fn get_mut(&mut self, indx: Index) -> Option<&mut T> {
self.graph.get_mut(indx)
.map(|node| node.get_mut_inner() )
}
pub fn remove(&mut self, indx: Index) -> Option<T> {
self.graph.remove(indx)
.map(|node| node.unwrap() )
}
/// Add a dependent relationship between a parent and a child
///
/// Returns Err() if either of the indecies do not exist in the graph
pub fn dependency(&mut self, parent: Index, child: Index) -> Result<(), ()> {
if self.graph.contains(parent) && self.graph.contains(child) {
self.graph.get_mut(parent)
.unwrap() // Cannot be None
.dependencies.push(child);
Ok(())
} else {
Err(())
}
}
pub fn linear_resolve(&self) -> Vec<Index> {
let arena_len = self.graph.len();
let mut resolved = Vec::with_capacity(arena_len);
let mut seen = HashSet::with_capacity(arena_len);
while resolved.len() < arena_len {
for (index, service_node) in self.graph.iter() {
// formatting?
if !seen.contains(&index) &&
(service_node.dependencies.is_empty() ||
service_node.dependencies.iter().all(|index| resolved.contains(index)))
{
seen.insert(index);
resolved.push(index);
}
}
}
resolved
}
}
//! Just a hiding place for the code that made up the old
//! init system. It's still here mostly because service files
//! for most packages don't exist yet.
use std::env;
use std::fs::{File, read_dir};
use std::io::{Read, Result};
use std::path::Path;
use std::process::Command;
use switch_stdio;
pub fn run(file: &Path) -> Result<()> {
let mut data = String::new();
File::open(file)?.read_to_string(&mut data)?;
for line in data.lines() {
let line = line.trim();
if ! line.is_empty() && ! line.starts_with('#') {
let mut args = line.split(' ').map(|arg| if arg.starts_with('$') {
env::var(&arg[1..]).unwrap_or(String::new())
} else {
arg.to_string()
});
if let Some(cmd) = args.next() {
match cmd.as_str() {
"cd" => if let Some(dir) = args.next() {
if let Err(err) = env::set_current_dir(&dir) {
println!("init: failed to cd to '{}': {}", dir, err);
}
} else {
println!("init: failed to cd: no argument");
},
"echo" => {
if let Some(arg) = args.next() {
print!("{}", arg);
}
for arg in args {
print!(" {}", arg);
}
print!("\n");
},
"export" => if let Some(var) = args.next() {
let mut value = String::new();
if let Some(arg) = args.next() {
value.push_str(&arg);
}
for arg in args {
value.push(' ');
value.push_str(&arg);
}
env::set_var(var, value);
} else {
println!("init: failed to export: no argument");
},
"run" => if let Some(new_file) = args.next() {
if let Err(err) = run(&Path::new(&new_file)) {
println!("init: failed to run '{}': {}", new_file, err);
}
} else {
println!("init: failed to run: no argument");
},
"run.d" => if let Some(new_dir) = args.next() {
let mut entries = vec![];
match read_dir(&new_dir) {
Ok(list) => for entry_res in list {
match entry_res {
Ok(entry) => {
entries.push(entry.path());
},
Err(err) => {
println!("init: failed to run.d: '{}': {}", new_dir, err);
}
}
},
Err(err) => {
println!("init: failed to run.d: '{}': {}", new_dir, err);
}
}
entries.sort();
for entry in entries {
if let Err(err) = run(&entry) {
println!("init: failed to run '{}': {}", entry.display(), err);
}
}
} else {
println!("init: failed to run.d: no argument");
},
"stdio" => if let Some(stdio) = args.next() {
if let Err(err) = switch_stdio(&stdio) {
println!("init: failed to switch stdio to '{}': {}", stdio, err);
}
} else {
println!("init: failed to set stdio: no argument");
},
_ => {
let mut command = Command::new(cmd);
for arg in args {
command.arg(arg);
}
match command.spawn() {
Ok(mut child) => match child.wait() {
Ok(_status) => (), //println!("init: waited for {}: {:?}", line, status.code()),
Err(err) => println!("init: failed to wait for '{}': {}", line, err)
},
Err(err) => println!("init: failed to execute '{}': {}", line, err)
}
}
}
}
}
}
Ok(())
}
......@@ -11,18 +11,20 @@ extern crate syscall;
extern crate toml;
mod dependency;
mod dependency_graph;
mod legacy;
mod service;
use std::collections::HashMap;
use std::env;
use std::fs::{File, read_dir};
use std::io::{Read, Error, Result};
use std::fs::File;
use std::io::{Error, Result};
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::path::Path;
use std::process::Command;
use syscall::flag::{O_RDONLY, O_WRONLY};
use dependency::DepGraph;
use dependency::{graph_from_services, start_services};
use service::services;
fn switch_stdio(stdio: &str) -> Result<()> {
......@@ -43,114 +45,6 @@ fn switch_stdio(stdio: &str) -> Result<()> {
Ok(())
}
pub fn run(file: &Path) -> Result<()> {
let mut data = String::new();
File::open(file)?.read_to_string(&mut data)?;
for line in data.lines() {
let line = line.trim();
if ! line.is_empty() && ! line.starts_with('#') {
let mut args = line.split(' ').map(|arg| if arg.starts_with('$') {
env::var(&arg[1..]).unwrap_or(String::new())
} else {
arg.to_string()
});
if let Some(cmd) = args.next() {
match cmd.as_str() {
"cd" => if let Some(dir) = args.next() {
if let Err(err) = env::set_current_dir(&dir) {
println!("init: failed to cd to '{}': {}", dir, err);
}
} else {
println!("init: failed to cd: no argument");
},
"echo" => {
if let Some(arg) = args.next() {
print!("{}", arg);
}
for arg in args {
print!(" {}", arg);
}
print!("\n");
},
"export" => if let Some(var) = args.next() {
let mut value = String::new();
if let Some(arg) = args.next() {
value.push_str(&arg);
}
for arg in args {
value.push(' ');
value.push_str(&arg);
}
env::set_var(var, value);
} else {
println!("init: failed to export: no argument");
},
"run" => if let Some(new_file) = args.next() {
if let Err(err) = run(&Path::new(&new_file)) {
println!("init: failed to run '{}': {}", new_file, err);
}
} else {
println!("init: failed to run: no argument");
},
"run.d" => if let Some(new_dir) = args.next() {
let mut entries = vec![];
match read_dir(&new_dir) {
Ok(list) => for entry_res in list {
match entry_res {
Ok(entry) => {
entries.push(entry.path());
},
Err(err) => {
println!("init: failed to run.d: '{}': {}", new_dir, err);
}
}
},
Err(err) => {
println!("init: failed to run.d: '{}': {}", new_dir, err);
}
}
entries.sort();
for entry in entries {
if let Err(err) = run(&entry) {
println!("init: failed to run '{}': {}", entry.display(), err);
}
}
} else {
println!("init: failed to run.d: no argument");
},
"stdio" => if let Some(stdio) = args.next() {
if let Err(err) = switch_stdio(&stdio) {
println!("init: failed to switch stdio to '{}': {}", stdio, err);
}
} else {
println!("init: failed to set stdio: no argument");
},
_ => {
let mut command = Command::new(cmd);
for arg in args {
command.arg(arg);
}
match command.spawn() {
Ok(mut child) => match child.wait() {
Ok(_status) => (), //println!("init: waited for {}: {:?}", line, status.code()),
Err(err) => println!("init: failed to wait for '{}': {}", line, err)
},
Err(err) => println!("init: failed to execute '{}': {}", line, err)
}
}
}
}
}
}
Ok(())
}
pub fn main() {
simple_logger::init()
.unwrap_or_else(|err| {
......@@ -163,8 +57,9 @@ pub fn main() {
vec![]
});
let services = DepGraph::from_services(services)
.on_provided("file:", || {
let services = graph_from_services(services);
let mut provide_hooks: HashMap<_, fn()> = HashMap::with_capacity(2);
provide_hooks.insert("file:".into(), || {
info!("setting cwd to file:");
if let Err(err) = env::set_current_dir("file:") {
error!("failed to set cwd: {}", err);
......@@ -174,11 +69,11 @@ pub fn main() {
env::set_var("PATH", "file:/bin");
// This file has had the services removed now
if let Err(err) = run(&Path::new("initfs:etc/init.rc")) {
if let Err(err) = legacy::run(&Path::new("initfs:etc/init.rc")) {
error!("failed to run initfs:etc/init.rc: {}", err);
}
})
.on_provided("display:", || {
});
provide_hooks.insert("display:".into(), || {
switch_stdio("display:1")
.unwrap_or_else(|err| {
warn!("{}", err);
......@@ -188,7 +83,7 @@ pub fn main() {
info!("setting PATH=initfs:/bin");
env::set_var("PATH", "initfs:/bin");
services.start().expect("failed to start services");
start_services(services, provide_hooks).expect("failed to start services");
syscall::setrens(0, 0).expect("init: failed to enter null namespace");
......
use std::collections::HashMap;
use std::default::Default;
use std::env;
use std::ffi::OsStr;
use std::fs::{File, read_dir};
......@@ -9,6 +10,17 @@ use std::process::Command;
use failure::Error;
use toml;
#[derive(Debug)]
pub enum State {
Offline,
Online,
Failed
}
impl Default for State {
fn default() -> State { State::Offline }
}
#[derive(Debug, Deserialize)]
pub struct Method {
pub cmd: Vec<String>
......@@ -55,7 +67,10 @@ pub struct Service {
pub dependencies: Option<Vec<String>>,
pub provides: Option<Vec<String>>,
pub methods: HashMap<String, Method>
pub methods: HashMap<String, Method>,
#[serde(skip)]
pub state: State
}
impl Service {
......
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