You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

199 lines
6.0 KiB
Rust

use async_std::path::Path;
use async_std::prelude::*;
use std::cell::Cell;
use crate::utils::asyn::IntoResults;
use std::io::{ErrorKind, Error};
use async_std::fs::{read_to_string, read_dir, ReadDir, DirEntry};
use async_std::task;
use crate::config::Config;
use std::collections::HashSet;
use crate::unit::Unit;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct LoaderConfig {
pub locations: Vec<String>,
}
impl LoaderConfig {
pub fn default() -> Self {
LoaderConfig {
locations: vec![
"etc/systemd/system/".to_string(),
"run/systemd/system/".to_string(),
"usr/lib/systemd/system/".to_string(),
]
}
}
}
#[derive(Debug, Clone)]
pub struct Loader {
root: String,
config: LoaderConfig,
}
impl Default for Loader {
fn default() -> Self {
Self::new("/", LoaderConfig::default())
}
}
impl Loader {
pub fn with_root(root: &str) -> Loader {
Self::new(root, LoaderConfig::default())
}
pub fn new(root: &str, config: LoaderConfig) -> Loader {
Loader { root: root.to_string(), config }
}
pub async fn load_unit(&self, unit_name: &str) -> Option<Unit> {
let config = self.load_config(unit_name).await;
let mut unit = config.get_unit()?;
let read_items = |postfix: String| {
async {
let futures = self.config
.locations
.iter()
.map(move |loc| {
let path = Path::new(&self.root).join(loc).join(format!("{}.{}", unit_name, postfix));
read_dir(path)
})
.collect::<Vec<_>>()
.into_results()
.await
.into_iter()
.filter_map(|result: Result<ReadDir, Error>| {
result.ok()
})
.collect::<Vec<_>>();
let mut results = vec![];
for future in futures {
let mut reader = future;
while let Some(item) = reader.next().await {
if let Ok(item) = item {
if let Some(name) = item.file_name().to_str() {
results.push(String::from(name));
}
}
}
}
results
}
};
unit.requires.append(&mut read_items("requires".to_string()).await);
unit.wants.append(&mut read_items("wants".to_string()).await);
Some(unit)
}
pub async fn load_config(&self, unit_name: &str) -> Config {
let mut futures = Vec::new();
let mut paths = HashSet::new();
let mut config_dirs = HashSet::new();
for location in &self.config.locations {
let current_loc = Path::new(&self.root).join(&location);
paths.insert(current_loc.join(unit_name));
let (unit_type, name) = {
let mut items = unit_name.rsplitn(2, ".");
(items.next().unwrap(), items.next().unwrap())
};
let parts: Vec<&str> = name.split("-").collect();
let mut builder = String::new();
for part_index in 0..parts.len() - 1 {
let part = parts[part_index];
builder += part;
builder += "-";
config_dirs.insert(current_loc.join(format!("{}.{}.d", builder, unit_type)));
}
// for unit test@nice.service it should also check test@.service.d
if name.contains('@') && !name.ends_with('@') {
let mut parts = name.splitn(2, '@');
config_dirs.insert(current_loc.join(format!("{}@.{}.d", parts.next().unwrap(), unit_type)));
}
config_dirs.insert(current_loc.join(unit_name.to_string() + ".d"));
}
for config_dir in config_dirs {
let dir_res: Result<ReadDir, std::io::Error> = read_dir(config_dir).await;
match dir_res {
Ok(mut dir) => {
while let Some(item) = dir.next().await {
if item.is_err() {
continue;
}
let item: DirEntry = item.unwrap();
if item.path().extension().map(|x| x == "conf").unwrap_or(false) {
paths.insert(item.path());
}
}
}
Err(err) => {
if err.kind() != ErrorKind::NotFound {
// Log?
}
}
}
}
for location in paths {
let path = location.to_str().unwrap().to_string();
futures.push(async {
let result: Result<String, std::io::Error> = read_to_string(&path).await;
match result {
Ok(contents) => {
Some((path, contents))
}
Err(err) => {
if err.kind() != ErrorKind::NotFound {
// Log?
}
None
}
}
});
}
let mut results: Vec<(String, String)> = futures
.into_results().await
.into_iter()
.filter(|o| o.is_some())
.map(|x| x.unwrap())
.collect();
let mut config = Config::new(unit_name);
let mut configs = Vec::new();
results.sort();
for (path, contents) in results {
if path.ends_with(".conf") {
configs.push((path, contents));
continue;
}
config.load_file(&path, &contents).unwrap();
}
for (path, contents) in configs {
config.load_file(&path, &contents).unwrap();
}
return config;
}
}