shell: add a simple shell
This commit is contained in:
parent
17cf067d10
commit
7e42999539
41
Cargo.lock
generated
41
Cargo.lock
generated
@ -53,6 +53,15 @@ version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "humansize"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
|
||||
dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "init"
|
||||
version = "0.1.0"
|
||||
@ -60,6 +69,34 @@ dependencies = [
|
||||
"yggdrasil-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.18.0"
|
||||
@ -88,6 +125,9 @@ dependencies = [
|
||||
name = "shell"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"nom",
|
||||
"yggdrasil-abi",
|
||||
"yggdrasil-rt",
|
||||
]
|
||||
|
||||
@ -107,6 +147,7 @@ name = "sysutils"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"humansize",
|
||||
"yggdrasil-rt",
|
||||
]
|
||||
|
||||
|
1
build.sh
1
build.sh
@ -52,6 +52,7 @@ pack_initrd() {
|
||||
# sysutils
|
||||
cp ${build_dir}/mount ${root_dir}/sbin/
|
||||
cp ${build_dir}/login ${root_dir}/sbin/
|
||||
cp ${build_dir}/ls ${root_dir}/bin/
|
||||
|
||||
cp -r ${workspace_dir}/etc ${root_dir}/
|
||||
|
||||
|
1
etc/profile
Normal file
1
etc/profile
Normal file
@ -0,0 +1 @@
|
||||
set PATH /bin:/sbin
|
@ -1 +1 @@
|
||||
!(~/sbin/mount nil "/dev" "devfs")
|
||||
/sbin/mount -t devfs /dev
|
||||
|
@ -1 +1 @@
|
||||
(echo "TODO: message of the day")
|
||||
echo TODO: message of the day
|
||||
|
@ -7,3 +7,7 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
yggdrasil-rt = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-rt.git" }
|
||||
yggdrasil-abi = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-abi.git" }
|
||||
|
||||
clap = { version = "4.3.19", features = ["std", "derive"], default-features = false }
|
||||
nom = "7.1.3"
|
||||
|
75
shell/src/builtins.rs
Normal file
75
shell/src/builtins.rs
Normal file
@ -0,0 +1,75 @@
|
||||
use std::{collections::HashMap, path::{Path, PathBuf}, env};
|
||||
|
||||
use crate::{Error, Outcome};
|
||||
|
||||
pub type BuiltinCommand = fn(&[String], &mut HashMap<String, String>) -> Result<Outcome, Error>;
|
||||
|
||||
static BUILTINS: &[(&str, BuiltinCommand)] = &[("echo", b_echo), ("set", b_set), ("which", b_which)];
|
||||
|
||||
pub fn get_builtin(name: &str) -> Option<BuiltinCommand> {
|
||||
BUILTINS
|
||||
.iter()
|
||||
.find_map(|&(key, value)| if key == name { Some(value) } else { None })
|
||||
}
|
||||
|
||||
fn b_which(args: &[String], _envs: &mut HashMap<String, String>) -> Result<Outcome, Error> {
|
||||
fn find_in_path(path: &str, program: &str) -> Option<String> {
|
||||
for entry in path.split(':') {
|
||||
let full_path = PathBuf::from(entry).join(program);
|
||||
|
||||
if full_path.exists() {
|
||||
return Some(full_path.to_str().unwrap().to_owned());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
if args.len() != 1 {
|
||||
eprintln!("which usage: which PROGRAM");
|
||||
return Ok(Outcome::Exited(1));
|
||||
}
|
||||
|
||||
let program = args[0].as_str();
|
||||
|
||||
let resolution = if program.starts_with('/') || program.starts_with('.') {
|
||||
if Path::new(program).exists() {
|
||||
Some(program.to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if let Ok(path) = env::var("PATH") {
|
||||
find_in_path(&path, program)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match resolution {
|
||||
Some(path) => {
|
||||
println!("{}: {}", program, path);
|
||||
Ok(Outcome::Exited(0))
|
||||
}
|
||||
_ => {
|
||||
Ok(Outcome::Exited(1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn b_set(args: &[String], envs: &mut HashMap<String, String>) -> Result<Outcome, Error> {
|
||||
if args.len() != 2 {
|
||||
eprintln!("set usage: set VAR VALUE");
|
||||
return Ok(Outcome::Exited(1));
|
||||
}
|
||||
envs.insert(args[0].clone(), args[1].clone());
|
||||
Ok(Outcome::ok())
|
||||
}
|
||||
|
||||
fn b_echo(args: &[String], _envs: &mut HashMap<String, String>) -> Result<Outcome, Error> {
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
if i != 0 {
|
||||
print!(" ");
|
||||
}
|
||||
print!("{}", arg);
|
||||
}
|
||||
println!();
|
||||
Ok(Outcome::ok())
|
||||
}
|
@ -1,204 +0,0 @@
|
||||
use std::{collections::LinkedList, fmt, rc::Rc};
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
pub struct Position {
|
||||
pub line: u64,
|
||||
pub column: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ExprValue {
|
||||
List(LinkedList<Rc<Expr>>),
|
||||
Nil,
|
||||
Ident(String),
|
||||
StringLiteral(String),
|
||||
IntLiteral(i64),
|
||||
Quote(Rc<Expr>),
|
||||
Meta(char, Rc<Expr>),
|
||||
Error(String),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Expr {
|
||||
pub value: ExprValue,
|
||||
pub position: Option<Position>,
|
||||
}
|
||||
|
||||
pub struct ExprPrinter<'a> {
|
||||
expr: &'a Expr,
|
||||
ellipsisize_lists: bool,
|
||||
pretty: bool,
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
pub fn nil() -> Rc<Self> {
|
||||
Rc::new(Self {
|
||||
value: ExprValue::Nil,
|
||||
position: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ident<S: Into<String>>(s: S) -> Rc<Self> {
|
||||
Rc::new(Self {
|
||||
value: ExprValue::Ident(s.into()),
|
||||
position: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn str<S: Into<String>>(s: S) -> Rc<Self> {
|
||||
Rc::new(Self {
|
||||
value: ExprValue::StringLiteral(s.into()),
|
||||
position: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn t() -> Rc<Self> {
|
||||
Rc::new(Self {
|
||||
value: ExprValue::IntLiteral(1),
|
||||
position: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn quote(expr: Rc<Self>) -> Rc<Self> {
|
||||
Rc::new(Self {
|
||||
value: ExprValue::Quote(expr),
|
||||
position: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn meta(m: char, expr: Rc<Self>) -> Rc<Self> {
|
||||
Rc::new(Self {
|
||||
value: ExprValue::Meta(m, expr),
|
||||
position: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn list(list: LinkedList<Rc<Self>>) -> Rc<Self> {
|
||||
Rc::new(Self {
|
||||
value: ExprValue::List(list),
|
||||
position: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn error<S: Into<String>>(s: S) -> Rc<Self> {
|
||||
Rc::new(Self {
|
||||
value: ExprValue::Error(s.into()),
|
||||
position: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_nil(&self) -> bool {
|
||||
match &self.value {
|
||||
ExprValue::Nil => true,
|
||||
ExprValue::List(e) if e.is_empty() => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_list(&self) -> bool {
|
||||
match &self.value {
|
||||
ExprValue::Nil => true,
|
||||
ExprValue::List(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_string(&self) -> String {
|
||||
match &self.value {
|
||||
ExprValue::Nil => "".to_owned(),
|
||||
ExprValue::List(items) => items.iter().fold(String::new(), |acc, i| {
|
||||
if acc.is_empty() {
|
||||
i.as_string()
|
||||
} else {
|
||||
acc + " " + &i.as_string()
|
||||
}
|
||||
}),
|
||||
ExprValue::Meta(_, s) => s.as_string(),
|
||||
ExprValue::IntLiteral(s) => format!("{}", s),
|
||||
ExprValue::StringLiteral(s) | ExprValue::Ident(s) => s.clone(),
|
||||
ExprValue::Error(e) => format!("(error {:?})", e),
|
||||
ExprValue::Quote(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_error(&self) -> Option<&str> {
|
||||
match &self.value {
|
||||
ExprValue::Error(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ExprPrinter<'a> {
|
||||
pub fn new(expr: &'a Expr) -> Self {
|
||||
Self {
|
||||
expr,
|
||||
ellipsisize_lists: false,
|
||||
pretty: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ellipsisize_lists(&mut self) -> &mut Self {
|
||||
self.ellipsisize_lists = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn pretty(&mut self) -> &mut Self {
|
||||
self.pretty = true;
|
||||
self
|
||||
}
|
||||
|
||||
fn print_node(&self, f: &mut fmt::Formatter<'_>, node: &Expr, depth: usize) -> fmt::Result {
|
||||
if node.is_nil() {
|
||||
f.write_str("nil")
|
||||
} else if node.is_list() {
|
||||
f.write_str("(")?;
|
||||
|
||||
if depth >= 2 && self.ellipsisize_lists {
|
||||
f.write_str("...")?;
|
||||
} else {
|
||||
// Non-empty list
|
||||
if let ExprValue::List(items) = &node.value {
|
||||
for (i, item) in items.iter().enumerate() {
|
||||
if i != 0 {
|
||||
f.write_str(" ")?;
|
||||
}
|
||||
self.print_node(f, item, depth + 1)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f.write_str(")")
|
||||
} else {
|
||||
match &node.value {
|
||||
ExprValue::Ident(name) => f.write_str(&name),
|
||||
ExprValue::Meta(q, expr) => {
|
||||
write!(f, "{}", q)?;
|
||||
self.print_node(f, expr, depth)
|
||||
}
|
||||
ExprValue::IntLiteral(value) => write!(f, "{}", value),
|
||||
ExprValue::StringLiteral(value) => {
|
||||
if self.pretty {
|
||||
write!(f, "{}", value)
|
||||
} else {
|
||||
write!(f, "{:?}", value)
|
||||
}
|
||||
},
|
||||
ExprValue::Quote(expr) => {
|
||||
f.write_str("'")?;
|
||||
self.print_node(f, expr, depth)
|
||||
}
|
||||
ExprValue::Error(s) => {
|
||||
write!(f, "(error {:?})", s)
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for ExprPrinter<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.print_node(f, self.expr, 0)
|
||||
}
|
||||
}
|
@ -1,243 +1,181 @@
|
||||
#![feature(linked_list_cursors)]
|
||||
use std::{
|
||||
collections::{linked_list::Cursor, HashMap, LinkedList},
|
||||
collections::HashMap,
|
||||
env,
|
||||
fs::File,
|
||||
io::BufReader,
|
||||
process::{Command, ExitCode},
|
||||
rc::Rc,
|
||||
io::{self, stdin, stdout, BufRead, BufReader, Stdin, Write},
|
||||
path::Path,
|
||||
process::{Command, ExitCode, ExitStatus},
|
||||
};
|
||||
|
||||
use expr::{Expr, ExprPrinter, ExprValue};
|
||||
use parse::{Input, InputError, Parser};
|
||||
use clap::Parser;
|
||||
|
||||
use yggdrasil_rt::{
|
||||
io::{DeviceRequest, RawFd},
|
||||
sys,
|
||||
};
|
||||
|
||||
pub mod expr;
|
||||
pub mod parse;
|
||||
mod builtins;
|
||||
mod parser;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EvalError {
|
||||
UndefinedIdent(String),
|
||||
MetaError(Rc<Expr>, String),
|
||||
pub enum Error {
|
||||
IoError(io::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ScriptError {
|
||||
Input(InputError),
|
||||
Eval(EvalError),
|
||||
#[derive(Parser)]
|
||||
pub struct Args {
|
||||
#[arg(short)]
|
||||
login: bool,
|
||||
script: Option<String>,
|
||||
args: Vec<String>
|
||||
}
|
||||
|
||||
impl From<InputError> for ScriptError {
|
||||
fn from(e: InputError) -> Self {
|
||||
Self::Input(e)
|
||||
pub enum Outcome {
|
||||
Exited(i32),
|
||||
Killed(i32),
|
||||
ExitShell(ExitCode),
|
||||
}
|
||||
|
||||
pub enum Input {
|
||||
Interactive(Stdin),
|
||||
File(BufReader<File>),
|
||||
}
|
||||
|
||||
impl Outcome {
|
||||
pub const fn ok() -> Self {
|
||||
Self::Exited(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EvalError> for ScriptError {
|
||||
fn from(e: EvalError) -> Self {
|
||||
Self::Eval(e)
|
||||
}
|
||||
}
|
||||
impl Input {
|
||||
pub fn getline(&mut self, buf: &mut String) -> io::Result<usize> {
|
||||
match self {
|
||||
Self::Interactive(input) => {
|
||||
let mut stdout = stdout();
|
||||
print!("$ ");
|
||||
stdout.flush().ok();
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Function {
|
||||
Script(LinkedList<String>, Rc<Expr>),
|
||||
Native(Rc<dyn Fn(&mut Shell, Cursor<'_, Rc<Expr>>) -> Result<Rc<Expr>, EvalError>>),
|
||||
}
|
||||
|
||||
pub struct Shell {
|
||||
parser: Parser,
|
||||
// TODO variable substitution
|
||||
#[allow(unused)]
|
||||
variables: HashMap<String, String>,
|
||||
functions: HashMap<String, Function>,
|
||||
}
|
||||
|
||||
impl Shell {
|
||||
pub fn new<R: Into<Input>, I: IntoIterator<Item = String>>(input: R, args: I) -> Self {
|
||||
let parser = Parser::new(input.into());
|
||||
let variables = Self::init_variables(args);
|
||||
let functions = Self::init_functions();
|
||||
Self {
|
||||
parser,
|
||||
variables,
|
||||
functions,
|
||||
}
|
||||
}
|
||||
|
||||
fn init_variables<I: IntoIterator<Item = String>>(args: I) -> HashMap<String, String> {
|
||||
let mut vars = HashMap::new();
|
||||
for (i, arg) in args.into_iter().enumerate() {
|
||||
vars.insert(i.to_string(), arg);
|
||||
}
|
||||
vars
|
||||
}
|
||||
|
||||
fn init_functions() -> HashMap<String, Function> {
|
||||
// Add builtins
|
||||
let mut funcs = HashMap::new();
|
||||
funcs.insert("echo".to_string(), Function::Native(Rc::new(builtin_echo)));
|
||||
funcs
|
||||
}
|
||||
|
||||
fn eval_command(
|
||||
&mut self,
|
||||
program: &str,
|
||||
mut args: Cursor<'_, Rc<Expr>>,
|
||||
_capture_output: bool,
|
||||
) -> Result<Rc<Expr>, EvalError> {
|
||||
let mut str_args = vec![];
|
||||
|
||||
while let Some(arg) = args.current() {
|
||||
let arg = self.eval(arg)?;
|
||||
str_args.push(arg.as_string());
|
||||
args.move_next();
|
||||
}
|
||||
|
||||
let mut process = match Command::new(program).args(str_args).spawn() {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
return Ok(Expr::error(format!("{}: {}", program, e.to_string())));
|
||||
input.read_line(buf)
|
||||
}
|
||||
};
|
||||
Self::File(input) => input.read_line(buf),
|
||||
}
|
||||
}
|
||||
|
||||
let status = process.wait().unwrap();
|
||||
pub fn is_interactive(&self) -> bool {
|
||||
matches!(self, Self::Interactive(_))
|
||||
}
|
||||
}
|
||||
|
||||
if status.success() {
|
||||
Ok(Expr::t())
|
||||
impl From<io::Error> for Error {
|
||||
fn from(value: io::Error) -> Self {
|
||||
Self::IoError(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "unix", target_os = "linux"))]
|
||||
impl From<ExitStatus> for Outcome {
|
||||
fn from(value: ExitStatus) -> Self {
|
||||
use std::os::unix::process::ExitStatusExt;
|
||||
|
||||
if let Some(code) = value.code() {
|
||||
Self::Exited(code)
|
||||
} else if let Some(sig) = value.signal() {
|
||||
Self::Killed(sig)
|
||||
} else {
|
||||
Ok(Expr::nil())
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_call(&mut self, list: &LinkedList<Rc<Expr>>) -> Result<Rc<Expr>, EvalError> {
|
||||
let Some(head) = list.front() else {
|
||||
todo!();
|
||||
};
|
||||
|
||||
let ExprValue::Ident(head) = &head.as_ref().value else {
|
||||
todo!();
|
||||
};
|
||||
|
||||
let mut cursor = list.cursor_front();
|
||||
cursor.move_next();
|
||||
|
||||
if head.starts_with('~') {
|
||||
// Execute external command
|
||||
let head = head.trim_start_matches('~');
|
||||
self.eval_command(head, cursor, false)
|
||||
#[cfg(target_os = "yggdrasil")]
|
||||
impl From<ExitStatus> for Outcome {
|
||||
fn from(value: ExitStatus) -> Self {
|
||||
if let Some(code) = value.code() {
|
||||
Self::Exited(code)
|
||||
} else {
|
||||
let Some(func) = self.functions.get(head).cloned() else {
|
||||
return Err(EvalError::UndefinedIdent(head.clone()));
|
||||
};
|
||||
// func.eval(self, cursor)
|
||||
match func {
|
||||
Function::Native(f) => f(self, cursor),
|
||||
_ => todo!(),
|
||||
}
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn eval(&mut self, expr: &Rc<Expr>) -> Result<Rc<Expr>, EvalError> {
|
||||
match &expr.as_ref().value {
|
||||
ExprValue::List(list) => self.eval_call(list),
|
||||
ExprValue::Ident(name) => todo!("Ident: {:?}", name),
|
||||
ExprValue::StringLiteral(_) | ExprValue::IntLiteral(_) | ExprValue::Nil => {
|
||||
Ok(expr.clone())
|
||||
}
|
||||
ExprValue::Quote(q) => Ok(q.clone()),
|
||||
ExprValue::Error(_) => todo!(),
|
||||
ExprValue::Meta('!', q) => {
|
||||
let expr = self.eval(q)?;
|
||||
if expr.is_nil() {
|
||||
Err(EvalError::MetaError(q.clone(), "unexpected nil".to_owned()))
|
||||
} else if let Some(err) = expr.as_error() {
|
||||
Err(EvalError::MetaError(q.clone(), err.to_owned()))
|
||||
} else {
|
||||
Ok(expr)
|
||||
// TODO this has one flaw: it needs to somehow fork() (?) to set the created process' process group
|
||||
pub fn exec(cmd: &[String], env: &mut HashMap<String, String>) -> Result<Outcome, Error> {
|
||||
let Some((cmd, args)) = cmd.split_first() else {
|
||||
return Ok(Outcome::ok());
|
||||
};
|
||||
|
||||
if let Some(builtin) = builtins::get_builtin(cmd) {
|
||||
builtin(args, env)
|
||||
} else {
|
||||
let status = Command::new(&cmd).args(args).envs(env.iter()).status()?;
|
||||
|
||||
Ok(Outcome::from(status))
|
||||
}
|
||||
}
|
||||
|
||||
fn run(mut input: Input, vars: &mut HashMap<String, String>) -> io::Result<ExitCode> {
|
||||
let mut line = String::new();
|
||||
|
||||
loop {
|
||||
line.clear();
|
||||
|
||||
let len = input.getline(&mut line)?;
|
||||
|
||||
if len == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
let line = line.trim();
|
||||
let cmd = parser::parse_line(vars, line).unwrap();
|
||||
|
||||
match exec(&cmd, vars) {
|
||||
Ok(status) => {
|
||||
if input.is_interactive() {
|
||||
match status {
|
||||
Outcome::Killed(signal) => {
|
||||
eprintln!("Killed: {}", signal);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprValue::Meta(_, _) => todo!(),
|
||||
Err(e) => eprintln!("{:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(mut self) -> Result<ExitCode, ScriptError> {
|
||||
loop {
|
||||
let Some(expr) = self.parser.parse()? else {
|
||||
break;
|
||||
};
|
||||
|
||||
self.eval(&expr)?;
|
||||
}
|
||||
|
||||
Ok(ExitCode::SUCCESS)
|
||||
}
|
||||
Ok(ExitCode::SUCCESS)
|
||||
}
|
||||
|
||||
fn builtin_echo(shell: &mut Shell, mut args: Cursor<'_, Rc<Expr>>) -> Result<Rc<Expr>, EvalError> {
|
||||
while let Some(arg) = args.current() {
|
||||
let arg = shell.eval(arg)?;
|
||||
print!("{}", ExprPrinter::new(&arg).pretty());
|
||||
|
||||
if args.peek_next().is_some() {
|
||||
print!(" ");
|
||||
}
|
||||
args.move_next();
|
||||
}
|
||||
println!();
|
||||
Ok(Expr::nil())
|
||||
fn run_file<P: AsRef<Path>>(path: P, env: &mut HashMap<String, String>) -> io::Result<ExitCode> {
|
||||
let input = BufReader::new(File::open(path)?);
|
||||
run(Input::File(input), env)
|
||||
}
|
||||
|
||||
fn print_eval_error(e: &EvalError) {
|
||||
match e {
|
||||
EvalError::MetaError(e, msg) => {
|
||||
let pos_string = if let Some(pos) = e.position {
|
||||
format!("{},{}", pos.line + 1, pos.column)
|
||||
} else {
|
||||
"[...]".to_owned()
|
||||
};
|
||||
eprintln!("{}: expression failed in:", pos_string);
|
||||
let mut p = ExprPrinter::new(e);
|
||||
p.ellipsisize_lists();
|
||||
eprintln!(" {}", p);
|
||||
eprintln!(": {}", msg);
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
fn run_stdin(env: &mut HashMap<String, String>) -> io::Result<ExitCode> {
|
||||
run(Input::Interactive(stdin()), env)
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let script_name = env::args().skip(1).next();
|
||||
let args = Args::parse();
|
||||
let mut vars = HashMap::new();
|
||||
|
||||
let input = if let Some(script_name) = script_name {
|
||||
let file = File::open(&script_name).unwrap();
|
||||
Input::from(BufReader::new(file))
|
||||
for (key, value) in env::vars() {
|
||||
vars.insert(key, value);
|
||||
}
|
||||
|
||||
if args.login {
|
||||
run_file("/etc/profile", &mut vars).ok();
|
||||
}
|
||||
|
||||
// Insert PATH to current process env
|
||||
if let Some(path) = vars.get("PATH") {
|
||||
env::set_var("PATH", path);
|
||||
}
|
||||
|
||||
let result = if let Some(script) = &args.script {
|
||||
run_file(script, &mut vars)
|
||||
} else {
|
||||
// Interactive
|
||||
// Gain control of the terminal signals
|
||||
let pid = std::process::id();
|
||||
unsafe {
|
||||
sys::set_process_group_id(pid, pid).unwrap();
|
||||
let mut req = DeviceRequest::SetTerminalGroup(pid);
|
||||
sys::device_request(RawFd::STDIN, &mut req).unwrap();
|
||||
}
|
||||
|
||||
Input::from(std::io::stdin())
|
||||
run_stdin(&mut vars)
|
||||
};
|
||||
|
||||
match Shell::new(input, env::args()).run() {
|
||||
Ok(res) => res,
|
||||
Err(ScriptError::Eval(e)) => {
|
||||
eprintln!("Script error:");
|
||||
print_eval_error(&e);
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
Err(ScriptError::Input(e)) => {
|
||||
eprintln!("Could not load the script:");
|
||||
match result {
|
||||
Ok(_) => ExitCode::SUCCESS,
|
||||
Err(e) => {
|
||||
eprintln!("{:?}", e);
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
|
@ -1,241 +0,0 @@
|
||||
use std::{fs::File, io::{Read, BufReader, Stdin}, rc::Rc, collections::LinkedList};
|
||||
|
||||
use crate::expr::{Position, Expr, ExprValue};
|
||||
|
||||
trait CharExt {
|
||||
fn is_ident0(self) -> bool;
|
||||
fn is_ident1(self) -> bool;
|
||||
}
|
||||
|
||||
const IDENT_CHARS: &str = ":+-*/%$=!_-~";
|
||||
|
||||
impl CharExt for char {
|
||||
fn is_ident0(self) -> bool {
|
||||
self.is_ascii_alphabetic() || IDENT_CHARS.contains(self)
|
||||
}
|
||||
|
||||
fn is_ident1(self) -> bool {
|
||||
self.is_ascii_alphanumeric() || IDENT_CHARS.contains(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InputError {
|
||||
IoError(std::io::Error),
|
||||
Eof,
|
||||
ParseError(String)
|
||||
}
|
||||
|
||||
enum InputInner {
|
||||
File(BufReader<File>),
|
||||
Stdin(Stdin)
|
||||
}
|
||||
|
||||
pub struct Input {
|
||||
position: Position,
|
||||
inner: InputInner,
|
||||
buffer: Option<char>,
|
||||
}
|
||||
|
||||
pub struct Parser {
|
||||
input: Input
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub fn new(input: Input) -> Self {
|
||||
Self { input }
|
||||
}
|
||||
|
||||
fn skip_whitespace(&mut self) -> Result<(), InputError> {
|
||||
while let Some(c) = self.input.peek()? {
|
||||
if c.is_ascii_whitespace() {
|
||||
self.input.next()?;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_list(&mut self, p: Position) -> Result<Rc<Expr>, InputError> {
|
||||
let mut buf = LinkedList::new();
|
||||
|
||||
loop {
|
||||
self.skip_whitespace()?;
|
||||
|
||||
let Some(c) = self.input.peek()? else {
|
||||
return Err(InputError::Eof);
|
||||
};
|
||||
|
||||
if c == ')' {
|
||||
self.input.next()?.unwrap();
|
||||
break;
|
||||
}
|
||||
|
||||
let Some(item) = self.parse()? else {
|
||||
return Err(InputError::Eof);
|
||||
};
|
||||
|
||||
buf.push_back(item);
|
||||
}
|
||||
|
||||
Ok(Rc::new(Expr {
|
||||
value: ExprValue::List(buf),
|
||||
position: Some(p),
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_ident(&mut self, p: Position) -> Result<Rc<Expr>, InputError> {
|
||||
let mut buf = String::new();
|
||||
|
||||
while let Some(c) = self.input.peek()? {
|
||||
if !c.is_ident1() {
|
||||
break;
|
||||
}
|
||||
|
||||
self.input.next()?.unwrap();
|
||||
buf.push(c);
|
||||
}
|
||||
|
||||
Ok(Rc::new(Expr{
|
||||
value: if buf == "nil" {
|
||||
ExprValue::Nil
|
||||
} else {
|
||||
ExprValue::Ident(buf)
|
||||
},
|
||||
position: Some(p)
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_string(&mut self, p: Position) -> Result<Rc<Expr>, InputError> {
|
||||
let mut buf = String::new();
|
||||
|
||||
loop {
|
||||
let Some(c) = self.input.peek()? else {
|
||||
return Err(InputError::Eof);
|
||||
};
|
||||
|
||||
self.input.next()?.unwrap();
|
||||
|
||||
if c == '"' {
|
||||
break;
|
||||
}
|
||||
|
||||
buf.push(c);
|
||||
}
|
||||
|
||||
Ok(Rc::new(Expr {
|
||||
value: ExprValue::StringLiteral(buf),
|
||||
position: Some(p)
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn parse(&mut self) -> Result<Option<Rc<Expr>>, InputError> {
|
||||
self.skip_whitespace()?;
|
||||
let Some(c) = self.input.peek()? else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let p = self.input.position;
|
||||
|
||||
// TODO omit '(' for toplevels
|
||||
match c {
|
||||
'(' => {
|
||||
self.input.next()?.unwrap();
|
||||
self.parse_list(p)
|
||||
},
|
||||
'"' => {
|
||||
self.input.next()?.unwrap();
|
||||
self.parse_string(p)
|
||||
},
|
||||
'\'' => {
|
||||
self.input.next()?.unwrap();
|
||||
let Some(expr) = self.parse()? else {
|
||||
todo!();
|
||||
};
|
||||
Ok(Rc::new(Expr {
|
||||
value: ExprValue::Quote(expr),
|
||||
position: Some(p)
|
||||
}))
|
||||
}
|
||||
'!' => {
|
||||
self.input.next()?.unwrap();
|
||||
let Some(expr) = self.parse()? else {
|
||||
todo!();
|
||||
};
|
||||
Ok(Rc::new(Expr {
|
||||
value: ExprValue::Meta('!', expr),
|
||||
position: Some(p)
|
||||
}))
|
||||
}
|
||||
_ if c.is_ident0() => self.parse_ident(p),
|
||||
_ => todo!("Undefined char: {:?}", c),
|
||||
}.map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
impl Input {
|
||||
pub fn peek(&mut self) -> Result<Option<char>, InputError> {
|
||||
if self.buffer.is_none() {
|
||||
self.next()?;
|
||||
}
|
||||
Ok(self.buffer)
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> Result<Option<char>, InputError> {
|
||||
if let Some(old) = self.buffer {
|
||||
self.buffer = self.getc()?;
|
||||
Ok(Some(old))
|
||||
} else {
|
||||
self.buffer = self.getc()?;
|
||||
Ok(self.buffer)
|
||||
}
|
||||
}
|
||||
|
||||
fn getc(&mut self) -> Result<Option<char>, InputError> {
|
||||
let mut buf = [0u8; 1];
|
||||
let count = match &mut self.inner {
|
||||
InputInner::File(f) => f.read(&mut buf),
|
||||
InputInner::Stdin(f) => f.read(&mut buf)
|
||||
}?;
|
||||
|
||||
Ok(if count == 1 {
|
||||
self.position.column += 1;
|
||||
if buf[0] == b'\n' {
|
||||
self.position.line += 1;
|
||||
self.position.column = 0;
|
||||
}
|
||||
|
||||
Some(buf[0] as char)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Stdin> for Input {
|
||||
fn from(value: Stdin) -> Self {
|
||||
Self {
|
||||
position: Position::default(),
|
||||
inner: InputInner::Stdin(value),
|
||||
buffer: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BufReader<File>> for Input {
|
||||
fn from(value: BufReader<File>) -> Self {
|
||||
Self {
|
||||
position: Position::default(),
|
||||
inner: InputInner::File(value),
|
||||
buffer: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl From<std::io::Error> for InputError {
|
||||
fn from(e: std::io::Error) -> Self {
|
||||
Self::IoError(e)
|
||||
}
|
||||
}
|
168
shell/src/parser.rs
Normal file
168
shell/src/parser.rs
Normal file
@ -0,0 +1,168 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use nom::{
|
||||
branch::alt,
|
||||
bytes::complete::{is_not, tag},
|
||||
character::complete::{space0, space1},
|
||||
combinator::{map, value, verify},
|
||||
multi::{many0, many1, separated_list0},
|
||||
sequence::{delimited, pair, preceded, terminated},
|
||||
IResult, Parser,
|
||||
};
|
||||
|
||||
fn text(input: &str) -> IResult<&str, &str> {
|
||||
let not_delimiter = is_not("\"\\ $\t");
|
||||
|
||||
verify(not_delimiter, |s: &str| !s.is_empty())(input)
|
||||
}
|
||||
|
||||
fn token_text(input: &str) -> IResult<&str, &str> {
|
||||
let not_delimiter = is_not(" \\$\t");
|
||||
|
||||
verify(not_delimiter, |s: &str| !s.is_empty())(input)
|
||||
}
|
||||
|
||||
fn dquoted(input: &str) -> IResult<&str, &str> {
|
||||
let not_delimiter = is_not("\\\"$");
|
||||
|
||||
alt((
|
||||
delimited(tag("\""), not_delimiter, tag("\"")),
|
||||
value("", tag("\"\"")),
|
||||
))(input)
|
||||
}
|
||||
|
||||
fn parse_word(input: &str) -> IResult<&str, String> {
|
||||
map(many1(alt((text, dquoted))), |items| items.join(""))(input)
|
||||
}
|
||||
|
||||
pub fn parse_line_raw(input: &str) -> IResult<&str, Vec<String>> {
|
||||
delimited(space0, separated_list0(space1, parse_word), space0)(input)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Token<'a> {
|
||||
Text(&'a str),
|
||||
Var(&'a str),
|
||||
Space,
|
||||
}
|
||||
|
||||
impl Token<'_> {
|
||||
pub fn substitute(self, env: &HashMap<String, String>) -> String {
|
||||
match self {
|
||||
Self::Text(text) => text.to_owned(),
|
||||
Self::Space => " ".to_owned(),
|
||||
Self::Var(name) => env.get(name).cloned().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn token(input: &str) -> IResult<&str, Token> {
|
||||
alt((
|
||||
map(preceded(tag("$"), text), Token::Var),
|
||||
map(token_text, Token::Text),
|
||||
map(space1, |_| Token::Space),
|
||||
))(input)
|
||||
}
|
||||
fn tokens(input: &str) -> IResult<&str, Vec<Token>> {
|
||||
many0(token)(input)
|
||||
}
|
||||
|
||||
fn subst_vars<'e, 's>(
|
||||
env: &'e HashMap<String, String>,
|
||||
input: &'s str,
|
||||
) -> IResult<&'s str, String> {
|
||||
map(tokens, |items| {
|
||||
items
|
||||
.into_iter()
|
||||
.map(|token| token.substitute(env))
|
||||
.collect()
|
||||
})(input)
|
||||
}
|
||||
|
||||
pub fn parse_line(
|
||||
env: &HashMap<String, String>,
|
||||
input: &str,
|
||||
) -> Result<Vec<String>, ()> {
|
||||
let (_, substituted) = subst_vars(env, input).map_err(|_| ())?;
|
||||
let (rem, words) = parse_line_raw(&substituted).map_err(|_| ())?;
|
||||
if !rem.is_empty() {
|
||||
return Err(());
|
||||
}
|
||||
Ok(words)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn simple_cases() {
|
||||
let input = "a b c d";
|
||||
let (rem, output) = parse_line_raw(input).unwrap();
|
||||
assert_eq!(rem, "");
|
||||
assert_eq!(output, vec!["a", "b", "c", "d"]);
|
||||
|
||||
let input = " a b c d ";
|
||||
let (rem, output) = parse_line_raw(input).unwrap();
|
||||
assert_eq!(rem, "");
|
||||
assert_eq!(output, vec!["a", "b", "c", "d"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strings() {
|
||||
let input = r#" a b "c" d "#;
|
||||
let (rem, output) = parse_line_raw(input).unwrap();
|
||||
assert_eq!(rem, "");
|
||||
assert_eq!(output, vec!["a", "b", "c", "d"]);
|
||||
|
||||
// dq adjacent to a word
|
||||
let input = r#" a b"c" "e"d "#;
|
||||
let (rem, output) = parse_line_raw(input).unwrap();
|
||||
assert_eq!(rem, "");
|
||||
assert_eq!(output, vec!["a", "bc", "ed"]);
|
||||
|
||||
// Empty dq
|
||||
let input = r#" a "" c "#;
|
||||
let (rem, output) = parse_line_raw(input).unwrap();
|
||||
assert_eq!(rem, "");
|
||||
assert_eq!(output, vec!["a", "", "c"]);
|
||||
|
||||
// Empty dq adjacent to a word
|
||||
let input = r#" a ""c "#;
|
||||
let (rem, output) = parse_line_raw(input).unwrap();
|
||||
assert_eq!(rem, "");
|
||||
assert_eq!(output, vec!["a", "c"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_tokens() {
|
||||
let input = r#"a"b$c" d$e"f""#;
|
||||
let (rem, output) = tokens(input).unwrap();
|
||||
assert_eq!(rem, "");
|
||||
assert_eq!(
|
||||
output,
|
||||
vec![
|
||||
Token::Text("a\"b"),
|
||||
Token::Var("c"),
|
||||
Token::Text("\""),
|
||||
Token::Space,
|
||||
Token::Text("d"),
|
||||
Token::Var("e"),
|
||||
Token::Text("\"f\"")
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_subst() {
|
||||
let env = HashMap::from_iter([
|
||||
("a".to_owned(), "VAR?".to_owned()),
|
||||
("c".to_owned(), "VAR0".to_owned()),
|
||||
("e".to_owned(), "VAR1".to_owned()),
|
||||
]);
|
||||
let input = r#"ab$c d$e"f"$g h"#;
|
||||
let (rem, output) = subst_vars(&env, input).unwrap();
|
||||
assert_eq!(rem, "");
|
||||
assert_eq!(output, "abVAR0 dVAR1\"f\" h");
|
||||
}
|
||||
}
|
@ -7,8 +7,13 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.3.19", features = ["std", "derive"], default-features = false }
|
||||
# TODO own impl
|
||||
humansize = { version = "2.1.3", features = ["impl_style"] }
|
||||
yggdrasil-rt = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-rt.git" }
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "mount"
|
||||
path = "src/mount.rs"
|
||||
@ -16,3 +21,7 @@ path = "src/mount.rs"
|
||||
[[bin]]
|
||||
name = "login"
|
||||
path = "src/login.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "ls"
|
||||
path = "src/ls.rs"
|
||||
|
0
sysutils/src/display.rs
Normal file
0
sysutils/src/display.rs
Normal file
1
sysutils/src/lib.rs
Normal file
1
sysutils/src/lib.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod display;
|
@ -27,7 +27,7 @@ fn login_readline<R: BufRead + AsRawFd>(
|
||||
}
|
||||
|
||||
fn login_as(username: &str, _password: &str) -> Result<(), std::io::Error> {
|
||||
let mut shell = Command::new("/bin/sh").spawn()?;
|
||||
let mut shell = Command::new("/bin/sh").arg("-l").spawn()?;
|
||||
println!("Hello {:?}", username);
|
||||
shell.wait()?;
|
||||
Ok(())
|
||||
@ -53,6 +53,20 @@ fn login_attempt(erase: bool) -> Result<(), std::io::Error> {
|
||||
login_as(username.trim(), "")
|
||||
}
|
||||
|
||||
fn setup_session(terminal: &str) -> Result<(), yggdrasil_rt::Error> {
|
||||
// This will close the file descriptors associated with the old terminal
|
||||
unsafe { sys::start_session()? };
|
||||
|
||||
unsafe {
|
||||
// File descriptors 0, 1, 2 are now free, need to reopen them with a new terminal
|
||||
sys::open(None, terminal, OpenOptions::READ, FileMode::empty())?;
|
||||
sys::open(None, terminal, OpenOptions::WRITE, FileMode::empty())?;
|
||||
sys::open(None, terminal, OpenOptions::WRITE, FileMode::empty())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let args: Vec<_> = env::args().skip(1).collect();
|
||||
if args.len() != 1 {
|
||||
@ -63,26 +77,16 @@ fn main() -> ExitCode {
|
||||
|
||||
// TODO check that `terminal` is a terminal
|
||||
|
||||
// Start a new session
|
||||
if let Err(err) = unsafe { sys::start_session() } {
|
||||
eprintln!("setsid(): {:?}", err);
|
||||
debug_trace!("Starting a session at {}", terminal);
|
||||
if let Err(err) = setup_session(terminal) {
|
||||
debug_trace!("Failed: {:?}", err);
|
||||
eprintln!("Could not setup session at {}: {:?}", terminal, err);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
|
||||
debug_trace!("Opening terminal: {}", terminal);
|
||||
|
||||
// Open the target terminal
|
||||
unsafe {
|
||||
// File descriptors 0, 1, 2 are now free, need to reopen them with a new terminal
|
||||
sys::open(None, terminal, OpenOptions::READ, FileMode::empty()).unwrap();
|
||||
sys::open(None, terminal, OpenOptions::WRITE, FileMode::empty()).unwrap();
|
||||
sys::open(None, terminal, OpenOptions::WRITE, FileMode::empty()).unwrap();
|
||||
}
|
||||
|
||||
set_signal_handler(Signal::Interrupted, SignalHandler::Function(handler));
|
||||
|
||||
let mut attempt_number = 0;
|
||||
loop {
|
||||
debug_trace!("Login attempt {}", attempt_number);
|
||||
// "Attach" the terminal
|
||||
unsafe {
|
||||
let mut req = DeviceRequest::SetTerminalGroup(sys::get_pid());
|
||||
|
202
sysutils/src/ls.rs
Normal file
202
sysutils/src/ls.rs
Normal file
@ -0,0 +1,202 @@
|
||||
use std::{
|
||||
fmt,
|
||||
fs::{read_dir, FileType},
|
||||
io,
|
||||
mem::MaybeUninit,
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use clap::Parser;
|
||||
use humansize::{FormatSize, BINARY};
|
||||
use yggdrasil_rt::io::FileAttr;
|
||||
|
||||
#[derive(Parser)]
|
||||
pub struct Args {
|
||||
#[arg(short)]
|
||||
long: bool,
|
||||
#[arg(short, long)]
|
||||
human_readable: bool,
|
||||
|
||||
paths: Vec<String>,
|
||||
}
|
||||
|
||||
trait DisplayBit {
|
||||
fn display_bit(&self, opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result;
|
||||
|
||||
fn display_with<'a, 'b>(&'a self, opts: &'b Args) -> DisplayWith<'a, 'b, Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
DisplayWith { item: self, opts }
|
||||
}
|
||||
}
|
||||
|
||||
trait DisplaySizeBit: Sized {
|
||||
fn display_size_bit(self, opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result;
|
||||
|
||||
fn display_size_with<'a>(self, opts: &'a Args) -> DisplaySizeWith<'a, Self> {
|
||||
DisplaySizeWith { size: self, opts }
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplaySizeBit for u64 {
|
||||
fn display_size_bit(self, opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if opts.human_readable {
|
||||
fmt::Display::fmt(&self.format_size(BINARY), f)
|
||||
} else {
|
||||
fmt::Display::fmt(&self, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DisplaySizeWith<'a, T> {
|
||||
size: T,
|
||||
opts: &'a Args
|
||||
}
|
||||
|
||||
struct DisplayWith<'a, 'b, T: DisplayBit> {
|
||||
item: &'a T,
|
||||
opts: &'b Args,
|
||||
}
|
||||
|
||||
impl<T: DisplayBit> fmt::Display for DisplayWith<'_, '_, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.item.display_bit(self.opts, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: DisplaySizeBit + Copy> fmt::Display for DisplaySizeWith<'_, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.size.display_size_bit(self.opts, f)
|
||||
}
|
||||
}
|
||||
|
||||
struct Entry {
|
||||
name: String,
|
||||
ty: Option<FileType>,
|
||||
attrs: Option<FileAttr>,
|
||||
}
|
||||
|
||||
impl DisplayBit for Option<FileType> {
|
||||
fn display_bit(&self, _opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if let Some(ty) = self {
|
||||
if ty.is_dir() {
|
||||
f.write_str("d")
|
||||
} else if ty.is_symlink() {
|
||||
f.write_str("l")
|
||||
} else {
|
||||
f.write_str("-")
|
||||
}
|
||||
} else {
|
||||
f.write_str("?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayBit for Option<FileAttr> {
|
||||
fn display_bit(&self, opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let Some(attrs) = self else {
|
||||
return write!(f, "--------- {:<8}", "???");
|
||||
};
|
||||
|
||||
write!(f, "{} {:>8}", attrs.mode, attrs.size.display_size_with(opts))
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayBit for Entry {
|
||||
fn display_bit(&self, opts: &Args, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if opts.long {
|
||||
write!(
|
||||
f,
|
||||
"{}{} {}",
|
||||
self.ty.display_with(opts),
|
||||
self.attrs.display_with(opts),
|
||||
self.name
|
||||
)
|
||||
} else {
|
||||
f.write_str(&self.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_entry<P: AsRef<Path>>(path: P) -> Option<FileAttr> {
|
||||
let mut attrs = MaybeUninit::uninit();
|
||||
let attrs = unsafe {
|
||||
yggdrasil_rt::sys::get_metadata(None, path.as_ref().to_str()?, &mut attrs, false).ok()?;
|
||||
attrs.assume_init()
|
||||
};
|
||||
Some(attrs)
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
fn invalid() -> Self {
|
||||
Self {
|
||||
name: "???".to_owned(),
|
||||
ty: None,
|
||||
attrs: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn list_directory(path: &Path) -> io::Result<Vec<Entry>> {
|
||||
let mut entries = vec![];
|
||||
for entry in read_dir(path)? {
|
||||
let Ok(entry) = entry else {
|
||||
entries.push(Entry::invalid());
|
||||
continue;
|
||||
};
|
||||
|
||||
let os_filename = entry.file_name();
|
||||
let ty = entry.file_type().ok();
|
||||
let attrs = fetch_entry(entry.path());
|
||||
|
||||
entries.push(Entry {
|
||||
name: os_filename.to_string_lossy().to_string(),
|
||||
ty,
|
||||
attrs,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
fn list(opts: &Args, path: &Path) -> io::Result<()> {
|
||||
if path.is_dir() {
|
||||
let entries = list_directory(path)?;
|
||||
|
||||
for entry in entries {
|
||||
println!("{}", entry.display_with(opts));
|
||||
}
|
||||
} else {
|
||||
// TODO fetch info
|
||||
println!("{}", path.display());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn list_wrap<P: AsRef<Path>>(opts: &Args, path: P) {
|
||||
let path = path.as_ref();
|
||||
|
||||
match list(opts, path) {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
eprintln!("{}: {}", path.display(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let args = Args::parse();
|
||||
|
||||
if args.paths.is_empty() {
|
||||
list_wrap(&args, ".");
|
||||
} else {
|
||||
for path in args.paths.iter() {
|
||||
if args.paths.len() > 1 {
|
||||
println!("{}:", path);
|
||||
}
|
||||
list_wrap(&args, path);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,31 +5,26 @@ use yggdrasil_rt::{io::MountOptions, sys::mount};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Args {
|
||||
#[arg(short)]
|
||||
ty: Option<String>,
|
||||
source: String,
|
||||
target: String,
|
||||
filesystem: Option<String>,
|
||||
target: Option<String>,
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let args = Args::parse();
|
||||
|
||||
let source = if args.source.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(args.source.as_str())
|
||||
};
|
||||
let filesystem = args.filesystem.as_deref();
|
||||
let target = args.target.as_str();
|
||||
let source = if args.target.is_some() { Some(args.source.as_str()) } else { None };
|
||||
let target = args.target.as_deref().unwrap_or(args.source.as_str());
|
||||
let filesystem = args.ty.as_deref();
|
||||
|
||||
println!("Mount {:?}, {:?}, {:?}", source, target, filesystem);
|
||||
|
||||
// Permissions are not yet implemented, lol
|
||||
let result = unsafe {
|
||||
let options = MountOptions {
|
||||
source,
|
||||
filesystem,
|
||||
target
|
||||
};
|
||||
// Permissions are not yet implemented, lol
|
||||
let result = unsafe {
|
||||
let options = MountOptions {
|
||||
source,
|
||||
filesystem,
|
||||
target
|
||||
};
|
||||
|
||||
mount(&options)
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user