Rust: Finalize implementation requirements
This commit is contained in:
parent
c5bbdec1a7
commit
4509d5e6ed
4 changed files with 142 additions and 61 deletions
|
|
@ -10,3 +10,8 @@ documentation = "https://tori.jutty.dev/docs/"
|
|||
|
||||
edition = "2024"
|
||||
rust-version = "1.94.0"
|
||||
|
||||
[lints.clippy]
|
||||
zombie_processes = "deny"
|
||||
suspicious_command_arg_space = "warn"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::{collections::HashMap, fs::{self, DirEntry}, path::PathBuf};
|
||||
|
||||
use crate::log::{self, elog};
|
||||
use crate::{run::Command, log::{self, elog}};
|
||||
|
||||
pub fn load() -> Configuration {
|
||||
log::elog("Loading configuration");
|
||||
|
|
@ -14,7 +14,10 @@ pub fn load() -> Configuration {
|
|||
// provided or obtained from $PATH and filesystem permission to execute
|
||||
|
||||
let mut conf = Configuration {
|
||||
su_command: vec!["su".into(), "-c".into(), "{% command %}".into()],
|
||||
su_command: Command {
|
||||
base: "su".into(),
|
||||
args: vec!["-c".into(), "{% command %}".into()],
|
||||
}
|
||||
};
|
||||
|
||||
let root = get_root();
|
||||
|
|
@ -38,13 +41,13 @@ pub fn load() -> Configuration {
|
|||
elog(&format!("{lines:#?}"));
|
||||
|
||||
if let Some(su_command) = map.get("su_command") {
|
||||
let split = su_command.split(' ')
|
||||
let split: Vec<String> = su_command.split(' ')
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.to_string()).collect();
|
||||
|
||||
if let Some(first) = conf.su_command.first() && let Ok(path_match) = resolve_from_path(first) {
|
||||
elog(&format!("Succesfully resolved 'su_command' configuration value {su_command} to {}", path_match.to_string_lossy()));
|
||||
conf.su_command = split;
|
||||
if let Some((base, args)) = split.split_first() && let Ok(resolved_path) = resolve_from_path(base) {
|
||||
elog(&format!("Succesfully resolved 'su_command' configuration value {su_command} through PATH to {resolved_path:?}, with base {base} and args {args:?}"));
|
||||
conf.su_command = Command { base: base.clone(), args: args.to_vec() }
|
||||
} else {
|
||||
eprintln!("Failed validation of 'su_command' configuration value");
|
||||
}
|
||||
|
|
@ -119,7 +122,9 @@ fn resolve_from_path(command: &str) -> Result<PathBuf, String> {
|
|||
Err("{command} not found in any of the directories in PATH".to_string())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Configuration {
|
||||
su_command: Vec<String>,
|
||||
pub su_command: Command,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ fn main() -> std::process::ExitCode {
|
|||
log::elog(&format!("tori {}", env!("CARGO_PKG_VERSION")));
|
||||
let configuration = conf::load();
|
||||
log::elog(&format!("Configuration: {configuration:#?}"));
|
||||
let mut order = run::teller::parse(std::env::args());
|
||||
let mut order = run::teller::parse(std::env::args(), &configuration);
|
||||
log::elog(&format!("Order: {order:#?}"));
|
||||
order.fill();
|
||||
log::elog(&format!("Filled Order: {order:#?}"));
|
||||
|
|
|
|||
191
rust/src/run.rs
191
rust/src/run.rs
|
|
@ -2,24 +2,20 @@
|
|||
// B2.2. DONE help | -h | --help -> MUST print '<long help>'
|
||||
// B2.3. DONE os -> MUST print the OS name and MUST log contents of /etc/os-release
|
||||
// B2.4. DONE user -> MUST print the output of the 'whoami' command
|
||||
// B2.5. TODO pkg p -> MUST call the system package manager using the su_command
|
||||
// to install and then uninstall package p. The user MUST be able to
|
||||
// freely input to these commands' interactive inputs before control
|
||||
// is returned. When done, it MUST log 'Done:', a newline, and the
|
||||
// system commands executed, one per line. If no p is provided, it
|
||||
// MUST NOT run any system commands and print a message
|
||||
// B2.6. DONE echo x y z -> MUST print x y z
|
||||
// B2.7. DONE echo -> MUST NOT print any output and exit with status code 0
|
||||
// B2.8. DONE [no input] -> MUST NOT print any output and exit with status code 0
|
||||
// B2.9. DONE [any other input] -> MUST print 'Unrecognized command: [command]',
|
||||
// a newline, '<short help>' and exit with status code 1
|
||||
|
||||
use crate::conf::Configuration;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Order {
|
||||
tasks: Vec<Task>,
|
||||
pub struct Order<'o> {
|
||||
tasks: Vec<Task<'o>>,
|
||||
}
|
||||
|
||||
impl Order {
|
||||
impl Order<'_> {
|
||||
|
||||
pub fn fill(&mut self) {
|
||||
for task in self.tasks.iter_mut() {
|
||||
|
|
@ -38,16 +34,17 @@ impl Order {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Task {
|
||||
pub struct Task<'t> {
|
||||
kind: TaskKind,
|
||||
done: bool,
|
||||
argument: String,
|
||||
parameters: Vec<String>,
|
||||
configuration: &'t Configuration,
|
||||
}
|
||||
|
||||
impl Task {
|
||||
impl Task<'_> {
|
||||
pub fn complete(&mut self) {
|
||||
use crate::{run::exec::{meta, os, shell, pkg}};
|
||||
use crate::{run::exec::{meta, os, pkg}};
|
||||
use TaskKind::*;
|
||||
|
||||
self.done = match self.kind {
|
||||
|
|
@ -57,16 +54,22 @@ impl Task {
|
|||
User => { os::print_user() },
|
||||
Echo => { meta::echo(self) },
|
||||
Unrecognized => { meta::unrecognized(self) },
|
||||
_ => false, // TODO
|
||||
Package => { pkg::install_uninstall(self) },
|
||||
}
|
||||
}
|
||||
|
||||
fn new(kind: TaskKind, argument: String, parameters: Vec<String>) -> Task {
|
||||
fn new<'t>(
|
||||
kind: TaskKind,
|
||||
argument: &str,
|
||||
parameters: Vec<String>,
|
||||
configuration: &'t Configuration,
|
||||
) -> Task<'t> {
|
||||
Task {
|
||||
kind,
|
||||
done: false,
|
||||
argument,
|
||||
argument: String::from(argument),
|
||||
parameters,
|
||||
configuration,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -82,11 +85,17 @@ pub enum TaskKind {
|
|||
Unrecognized,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Command {
|
||||
pub base: String,
|
||||
pub args: Vec<String>,
|
||||
}
|
||||
|
||||
pub mod teller {
|
||||
use crate::{log::elog, run::{Order, Task, TaskKind}};
|
||||
use crate::{conf::Configuration, log::elog, run::{Order, Task, TaskKind}};
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
pub fn parse(mut raw_args: env::Args) -> Order {
|
||||
pub fn parse(mut raw_args: env::Args, configuration: &Configuration) -> Order<'_> {
|
||||
let (argument, parameters): (String, Vec<String>) = if let Some(first) = raw_args.next() {
|
||||
if is_executable_path(&first) {
|
||||
elog("First argument is the executable path");
|
||||
|
|
@ -111,28 +120,30 @@ pub mod teller {
|
|||
return Order::default();
|
||||
};
|
||||
|
||||
use TaskKind::*;
|
||||
let make_order = |kind: TaskKind| -> Order {
|
||||
Order { tasks: vec![Task::new(kind, &argument, parameters, configuration)] }
|
||||
};
|
||||
|
||||
if argument == "version" || argument == "-v" || argument == "--version" {
|
||||
elog("Command is 'version'");
|
||||
Order { tasks: vec![Task::new(Version, argument, parameters)] }
|
||||
} else if argument == "help" {
|
||||
make_order(TaskKind::Version)
|
||||
} else if argument == "help" || argument == "-h" || argument == "--help" {
|
||||
elog("Command is 'help'");
|
||||
Order { tasks: vec![Task::new(Help, argument, parameters)] }
|
||||
make_order(TaskKind::Help)
|
||||
} else if argument == "os" {
|
||||
elog("Command is 'os'");
|
||||
Order { tasks: vec![Task::new(OsInfo, argument, parameters)] }
|
||||
make_order(TaskKind::OsInfo)
|
||||
} else if argument == "pkg" {
|
||||
elog("Command is 'pkg'");
|
||||
Order { tasks: vec![Task::new(Package, argument, parameters)] }
|
||||
make_order(TaskKind::Package)
|
||||
} else if argument == "user" {
|
||||
elog("Command is 'user'");
|
||||
Order { tasks: vec![Task::new(User, argument, parameters)] }
|
||||
make_order(TaskKind::User)
|
||||
} else if argument == "echo" {
|
||||
elog("Command is 'echo'");
|
||||
Order { tasks: vec![Task::new(Echo, argument, parameters)] }
|
||||
make_order(TaskKind::Echo)
|
||||
} else {
|
||||
Order { tasks: vec![Task::new(Unrecognized, argument, parameters)] }
|
||||
make_order(TaskKind::Unrecognized)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -209,27 +220,14 @@ pub mod exec {
|
|||
}
|
||||
|
||||
pub mod os {
|
||||
use crate::log::elog;
|
||||
use crate::{ log::elog,run::exec::shell::run};
|
||||
|
||||
pub fn print_info() -> bool {
|
||||
use std::process::Command;
|
||||
|
||||
let uname_success = if let Ok(output) = Command::new("uname")
|
||||
.arg("--operating-system")
|
||||
.output() {
|
||||
if let Ok(utf8) = String::from_utf8(output.stdout) {
|
||||
print!("{utf8}");
|
||||
true
|
||||
} else {
|
||||
elog("Failed UTF8 coversion of uname output");
|
||||
false
|
||||
}
|
||||
} else {
|
||||
elog("Failed executing or reading output of uname");
|
||||
false
|
||||
};
|
||||
let uname_result = run("uname", &["--operating-system"]);
|
||||
|
||||
let os_release_success = if let Ok(os_release) = std::fs::read_to_string("/etc/os-release") {
|
||||
let os_release_result = if let Ok(os_release) =
|
||||
std::fs::read_to_string("/etc/os-release") {
|
||||
elog(&os_release);
|
||||
true
|
||||
} else {
|
||||
|
|
@ -237,27 +235,100 @@ pub mod exec {
|
|||
false
|
||||
};
|
||||
|
||||
uname_success && os_release_success
|
||||
uname_result.is_ok() && os_release_result
|
||||
}
|
||||
|
||||
pub fn print_user() -> bool {
|
||||
use std::process::Command;
|
||||
|
||||
if let Ok(output) = Command::new("whoami").output() {
|
||||
if let Ok(utf8) = String::from_utf8(output.stdout) {
|
||||
print!("{utf8}");
|
||||
true
|
||||
} else {
|
||||
elog("Failed UTF8 coversion of whoami output");
|
||||
false
|
||||
}
|
||||
} else {
|
||||
elog("Failed executing or reading output of whoami");
|
||||
false
|
||||
}
|
||||
run("whoami", &[]).is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
pub mod shell {}
|
||||
pub mod pkg {}
|
||||
pub mod pkg {
|
||||
use crate::run::Task;
|
||||
|
||||
// B2.5. DONE pkg p -> MUST call the system package manager using the su_command
|
||||
// to install and then uninstall package p. The user MUST be able to
|
||||
// freely input to these commands' interactive inputs before control
|
||||
// is returned. When done, it MUST log 'Done:', a newline, and the
|
||||
// system commands executed, one per line. If no p is provided, it
|
||||
// MUST NOT run any system commands and print a message
|
||||
pub fn install_uninstall(task: &Task) -> bool {
|
||||
let su_base: String = task.configuration.su_command.base.clone();
|
||||
let su_args: Vec<String> = task.configuration.su_command.args.clone();
|
||||
let command_base: Vec<String> = vec!["apt".into(), "install".into()];
|
||||
let command_args: Vec<String> = task.parameters.clone();
|
||||
|
||||
let su_args_str: Vec<&str> = su_args.iter().map(|s| s.as_str()).collect();
|
||||
let command_base_str: Vec<&str> = command_base.iter().map(|s| s.as_str()).collect();
|
||||
let command_args_str: Vec<&str> = command_args.iter().map(|s| s.as_str()).collect();
|
||||
|
||||
if command_args.is_empty() {
|
||||
println!("Parameters are empty: Nothing to install or uninstall");
|
||||
return false
|
||||
}
|
||||
|
||||
let args: Vec<&str> = [
|
||||
su_args_str,
|
||||
command_base_str,
|
||||
command_args_str,
|
||||
].iter().flatten().copied().collect();
|
||||
|
||||
crate::run::exec::shell::spawn(&su_base, &args);
|
||||
|
||||
crate::run::exec::shell::spawn("sudo", vec!["apt", "remove"]
|
||||
.into_iter()
|
||||
.chain(task.parameters.iter().map(|s| s.as_str()))
|
||||
.collect::<Vec<&str>>().as_slice());
|
||||
|
||||
println!(
|
||||
"Done:\n{su_base} {} {} {}",
|
||||
su_args.join(" "),
|
||||
command_base.join(" "),
|
||||
command_args.join(" "),
|
||||
);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub mod shell {
|
||||
use std::process::Command;
|
||||
|
||||
pub fn spawn(command: &str, args: &[&str]) -> bool {
|
||||
if let Ok(mut child) = Command::new(command)
|
||||
.args(args)
|
||||
.spawn() {
|
||||
let Ok(exit_status) = child.wait() else { return false };
|
||||
exit_status.success()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(command: &str, args: &[&str]) -> Result<String, String> {
|
||||
use std::process::Command;
|
||||
|
||||
if let Ok(output) = Command::new(command)
|
||||
.args(args)
|
||||
.output()
|
||||
|
||||
{
|
||||
if let Ok(utf8) = String::from_utf8(output.stdout) {
|
||||
print!("{utf8}");
|
||||
Ok(utf8)
|
||||
} else {
|
||||
let message = format!("Failed UTF8 coversion of {command} output");
|
||||
eprintln!("{message}");
|
||||
Err(message)
|
||||
}
|
||||
} else {
|
||||
let message = format!("Failed executing or reading output of {command}");
|
||||
eprintln!("{message}");
|
||||
Err(message)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue