init: add some initialization stuff

This commit is contained in:
Mark Poliakov 2023-07-27 16:25:26 +03:00
parent 9cb3b744f7
commit 4225806394
19 changed files with 1280 additions and 41 deletions

121
Cargo.lock generated
View File

@ -2,6 +2,127 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "anstyle"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd"
[[package]]
name = "clap"
version = "4.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d"
dependencies = [
"clap_builder",
"clap_derive",
"once_cell",
]
[[package]]
name = "clap_builder"
version = "4.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1"
dependencies = [
"anstyle",
"clap_lex",
]
[[package]]
name = "clap_derive"
version = "4.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]] [[package]]
name = "init" name = "init"
version = "0.1.0" version = "0.1.0"
dependencies = [
"yggdrasil-rt",
]
[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "proc-macro2"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
dependencies = [
"proc-macro2",
]
[[package]]
name = "shell"
version = "0.1.0"
dependencies = [
"yggdrasil-rt",
]
[[package]]
name = "syn"
version = "2.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "sysutils"
version = "0.1.0"
dependencies = [
"clap",
"yggdrasil-rt",
]
[[package]]
name = "unicode-ident"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
[[package]]
name = "yggdrasil-abi"
version = "0.1.0"
[[package]]
name = "yggdrasil-rt"
version = "0.1.0"
dependencies = [
"yggdrasil-abi",
]

View File

@ -1,5 +1,13 @@
[workspace] [workspace]
resolver = "1" resolver = "1"
members = [ members = [
"init" "init",
"shell",
"sysutils"
] ]
[patch.'https://git.alnyan.me/yggdrasil/yggdrasil-abi.git']
yggdrasil-abi = { path = "abi" }
[patch.'https://git.alnyan.me/yggdrasil/yggdrasil-rt.git']
yggdrasil-rt = { path = "rt" }

1
abi Symbolic link
View File

@ -0,0 +1 @@
../abi

View File

@ -34,16 +34,32 @@ check_toolchain() {
} }
pack_initrd() { pack_initrd() {
local workspace_dir=$(pwd)
local build_dir=target/${USER_TARGET}/${PROFILE} local build_dir=target/${USER_TARGET}/${PROFILE}
local root_dir=${build_dir}/rootfs local root_dir=${build_dir}/rootfs
mkdir -p "${root_dir}"
mkdir -p "${root_dir}"
mkdir -p "${root_dir}/sbin"
mkdir -p "${root_dir}/bin"
# init
cp ${build_dir}/init ${root_dir} cp ${build_dir}/init ${root_dir}
cp ${build_dir}/rc ${root_dir}/sbin/
# shell
cp ${build_dir}/shell ${root_dir}/bin/sh
# sysutils
cp ${build_dir}/mount ${root_dir}/sbin/
cp ${build_dir}/login ${root_dir}/sbin/
cp -r ${workspace_dir}/etc ${root_dir}/
cd "${root_dir}" cd "${root_dir}"
mkdir -p dev mkdir -p dev
touch dev/.do_not_remove
tar cf ../initrd.tar `find . -printf "%P\n"` tar cf ../initrd.tar `find . -type f -printf "%P\n"`
cd - cd -
} }
@ -51,6 +67,10 @@ check_toolchain
case "$1" in case "$1" in
build|"") build|"")
if [ "${CLEAN_BUILD}" = 1 ]; then
cargo clean
fi
pstatus "Building userspace programs" pstatus "Building userspace programs"
cargo +ygg-stage1 build ${USER_CARGO_OPTS} cargo +ygg-stage1 build ${USER_CARGO_OPTS}

3
etc/inittab Normal file
View File

@ -0,0 +1,3 @@
init:1:wait:/sbin/rc default
user:1:once:/sbin/login /dev/ttyS0

1
etc/rc.d/00-mount Normal file
View File

@ -0,0 +1 @@
!(~/sbin/mount nil "/dev" "devfs")

1
etc/rc.d/99-motd Normal file
View File

@ -0,0 +1 @@
(echo "TODO: message of the day")

View File

@ -5,4 +5,13 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bin]]
name = "init"
path = "src/main.rs"
[[bin]]
name = "rc"
path = "src/rc.rs"
[dependencies] [dependencies]
yggdrasil-rt = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-rt.git" }

View File

@ -1,46 +1,161 @@
#![feature(rustc_private)] use std::{
fmt,
fs::File,
io::{BufRead, BufReader},
path::Path,
process::{Command, ExitCode},
str::FromStr,
};
use std::os::yggdrasil::{mount, open, OpenOptions, FileMode, MountOptions}; use yggdrasil_rt::debug_trace;
use std::borrow::Cow;
use std::path::{Path, PathBuf};
fn ls_tree<P: AsRef<Path>>(path: P, depth: usize) -> std::io::Result<()> { const INITTAB_PATH: &str = "/etc/inittab";
for entry in std::fs::read_dir(path)? {
let entry = entry?;
let os_filename = entry.file_name();
let filename = os_filename.to_str().unwrap();
let path = entry.path();
if filename.starts_with('.') { pub enum InitError {
IoError(std::io::Error),
OsError(yggdrasil_rt::Error),
CustomError(String),
}
impl From<std::io::Error> for InitError {
fn from(value: std::io::Error) -> Self {
Self::IoError(value)
}
}
impl From<yggdrasil_rt::Error> for InitError {
fn from(value: yggdrasil_rt::Error) -> Self {
Self::OsError(value)
}
}
impl fmt::Debug for InitError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::IoError(e) => fmt::Debug::fmt(e, f),
Self::OsError(e) => fmt::Debug::fmt(e, f),
Self::CustomError(e) => fmt::Debug::fmt(e, f),
}
}
}
#[derive(Debug, PartialEq)]
pub enum RuleAction {
Wait,
Once,
Boot,
}
#[derive(Debug)]
pub struct Rule {
#[allow(unused)]
id: String,
// runlevels ignored here
action: RuleAction,
program: String,
arguments: Vec<String>,
}
impl Rule {
pub fn run(&self) -> Result<(), InitError> {
let arguments: Vec<_> = self.arguments.iter().map(String::as_str).collect();
let mut child = match self.action {
RuleAction::Wait | RuleAction::Once => {
Command::new(&self.program).args(&arguments).spawn()?
}
RuleAction::Boot => todo!(),
};
if self.action == RuleAction::Wait {
child.wait()?;
}
Ok(())
}
}
impl FromStr for RuleAction {
type Err = InitError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"wait" => Ok(Self::Wait),
"once" => Ok(Self::Once),
"boot" => Ok(Self::Boot),
_ => Err(InitError::CustomError(format!(
"Unrecognized rule action: {:?}",
s
))),
}
}
}
fn parse_rule(line: &str) -> Result<Rule, InitError> {
let terms: Vec<_> = line.split(':').collect();
if terms.len() != 4 {
return Err(InitError::CustomError(format!(
"Expected 4 terms in init rule, got {}",
terms.len()
)));
}
let id = terms[0].to_owned();
// Runlevel (term 1) ignored
let action = RuleAction::from_str(&terms[2])?;
let command_terms: Vec<_> = terms[3].split(' ').collect();
let program = command_terms[0].to_owned();
let arguments = command_terms
.into_iter()
.skip(1)
.map(str::to_owned)
.collect();
Ok(Rule {
id,
action,
program,
arguments,
})
}
fn load_rules<P: AsRef<Path>>(path: P) -> Result<Vec<Rule>, InitError> {
let file = BufReader::new(File::open(path)?);
let mut rules = vec![];
for line in file.lines() {
let line = line?;
let line = line.trim();
let (line, _) = line.split_once('#').unwrap_or((line, ""));
if line.is_empty() {
continue; continue;
} }
for _ in 0..depth { rules.push(parse_rule(&line)?);
print!(" ");
}
println!("{:?}", filename);
if entry.file_type().unwrap().is_dir() {
ls_tree(path, depth + 1);
}
}
Ok(())
}
fn main() {
unsafe {
mount(MountOptions {
source: None,
filesystem: Some("devfs"),
target: "/dev",
})
.unwrap();
// Open stdin/stdout/stderr
open("/dev/ttyS0", OpenOptions::READ, FileMode::empty()).unwrap();
open("/dev/ttyS0", OpenOptions::WRITE, FileMode::empty()).unwrap();
open("/dev/ttyS0", OpenOptions::WRITE, FileMode::empty()).unwrap();
} }
ls_tree("/", 0).ok(); Ok(rules)
}
fn main() -> ExitCode {
debug_trace!("Userspace init starting");
let rules = match load_rules(INITTAB_PATH) {
Ok(s) => s,
Err(e) => {
debug_trace!("init: failed to load rules: {:?}", e);
return ExitCode::FAILURE;
}
};
debug_trace!("Rules loaded");
for rule in rules {
if let Err(err) = rule.run() {
debug_trace!("rc: failed to execute rule {:?}: {:?}", rule, err);
}
}
loop {}
} }

103
init/src/rc.rs Normal file
View File

@ -0,0 +1,103 @@
use std::{
env,
fs::read_dir,
path::{Path, PathBuf},
process::{Command, ExitCode},
str::FromStr,
};
const RC_DIR: &str = "/etc/rc.d";
#[derive(Debug)]
enum Error {
IncorrectUsage(String),
IoError(std::io::Error),
}
impl From<std::io::Error> for Error {
fn from(value: std::io::Error) -> Self {
Self::IoError(value)
}
}
#[derive(Debug)]
enum Mode {
Default,
}
impl FromStr for Mode {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"default" => Ok(Self::Default),
_ => Err(Error::IncorrectUsage(format!("Incorrect mode: {:?}", s))),
}
}
}
fn exec_script<P: AsRef<Path>>(path: P, arg: &str) -> Result<(), Error> {
let path = path.as_ref();
yggdrasil_rt::debug_trace!("rc: {:?} {}", path, arg);
// TODO run those in parallel, if allowed
// TODO binfmt guessing
let mut process = Command::new("/bin/sh").arg(path).arg(arg).spawn()?;
if !process.wait()?.success() {
eprintln!("{:?}: Failed", path);
}
Ok(())
}
fn get_scripts<P: AsRef<Path>>(dir: P) -> Result<Vec<String>, Error> {
let mut items = read_dir(dir)?
.map(|item| item.map(|item| item.file_name().to_str().unwrap().to_owned()))
.collect::<Result<Vec<_>, _>>()?;
items.sort();
Ok(items)
}
fn _main(mode: &str) -> Result<(), Error> {
// TODO only 1 mode supported, so ignore it
let _mode = Mode::from_str(mode)?;
let path = PathBuf::from(RC_DIR);
let scripts = get_scripts(&path)?;
// Execute scripts in order
for script in scripts {
if let Err(err) = exec_script(path.join(&script), "start") {
eprintln!("{}: {:?}", script, err);
}
}
Ok(())
}
fn usage(_cmd: &str) {
eprintln!("Usage: ...");
}
fn main() -> ExitCode {
let args: Vec<_> = env::args().collect();
if args.len() != 2 {
eprintln!("Incorrect rc usage, expected 2 args, got {}", args.len());
usage(&args[0]);
return ExitCode::FAILURE;
}
match _main(&args[1]) {
Ok(_) => ExitCode::SUCCESS,
Err(Error::IncorrectUsage(e)) => {
eprintln!("rc: incorrect usage: {:?}", e);
usage(&args[0]);
ExitCode::FAILURE
}
Err(e) => {
eprintln!("rc: {:?}", e);
ExitCode::FAILURE
}
}
}

1
rt Symbolic link
View File

@ -0,0 +1 @@
../../sandbox/yggdrasil-rust/yggdrasil-rt

View File

@ -1,3 +1,2 @@
[toolchain] [toolchain]
channel = "ygg-stage1" channel = "ygg-stage1"
targets = ["aarch64-unknown-yggdrasil"]

9
shell/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "shell"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
yggdrasil-rt = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-rt.git" }

204
shell/src/expr.rs Normal file
View File

@ -0,0 +1,204 @@
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)
}
}

245
shell/src/main.rs Normal file
View File

@ -0,0 +1,245 @@
#![feature(linked_list_cursors)]
use std::{
collections::{linked_list::Cursor, HashMap, LinkedList},
env,
fs::File,
io::BufReader,
process::{Command, ExitCode},
rc::Rc,
};
use expr::{Expr, ExprPrinter, ExprValue};
use parse::{Input, InputError, Parser};
use yggdrasil_rt::{
io::{DeviceRequest, RawFd},
sys,
};
pub mod expr;
pub mod parse;
#[derive(Debug)]
pub enum EvalError {
UndefinedIdent(String),
MetaError(Rc<Expr>, String),
}
#[derive(Debug)]
pub enum ScriptError {
Input(InputError),
Eval(EvalError),
}
impl From<InputError> for ScriptError {
fn from(e: InputError) -> Self {
Self::Input(e)
}
}
impl From<EvalError> for ScriptError {
fn from(e: EvalError) -> Self {
Self::Eval(e)
}
}
#[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())));
}
};
let status = process.wait().unwrap();
if status.success() {
Ok(Expr::t())
} else {
Ok(Expr::nil())
}
}
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)
} 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!(),
}
}
}
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)
}
}
ExprValue::Meta(_, _) => todo!(),
}
}
pub fn run(mut self) -> Result<ExitCode, ScriptError> {
loop {
let Some(expr) = self.parser.parse()? else {
break;
};
self.eval(&expr)?;
}
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 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 main() -> ExitCode {
let script_name = env::args().skip(1).next();
let input = if let Some(script_name) = script_name {
let file = File::open(&script_name).unwrap();
Input::from(BufReader::new(file))
} 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())
};
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:");
eprintln!("{:?}", e);
ExitCode::FAILURE
}
}
}

241
shell/src/parse.rs Normal file
View File

@ -0,0 +1,241 @@
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)
}
}

18
sysutils/Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "sysutils"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "4.3.19", features = ["std", "derive"], default-features = false }
yggdrasil-rt = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-rt.git" }
[[bin]]
name = "mount"
path = "src/mount.rs"
[[bin]]
name = "login"
path = "src/login.rs"

96
sysutils/src/login.rs Normal file
View File

@ -0,0 +1,96 @@
#![feature(yggdrasil_os, yggdrasil_raw_fd, rustc_private)]
use std::{
env,
io::{stdin, stdout, BufRead, Write},
os::{
fd::AsRawFd,
yggdrasil::signal::{set_signal_handler, Signal, SignalHandler},
},
process::{ExitCode, Command},
};
use yggdrasil_rt::{
debug_trace,
io::{DeviceRequest, FileMode, OpenOptions, RawFd},
sys,
};
fn handler(_signal: Signal) {}
fn login_readline<R: BufRead + AsRawFd>(
reader: &mut R,
buf: &mut String,
_secret: bool,
) -> Result<usize, std::io::Error> {
reader.read_line(buf)
}
fn login_as(_username: &str, _password: &str) -> Result<(), std::io::Error> {
let mut shell = Command::new("/bin/sh").spawn()?;
shell.wait()?;
Ok(())
}
fn login_attempt(erase: bool) -> Result<(), std::io::Error> {
let mut stdin = stdin().lock();
let mut stdout = stdout();
if erase {
print!("\x1b[0;0H\x1b[2J");
stdout.flush().ok();
}
let mut username = String::new();
print!("Username: ");
stdout.flush().ok();
if login_readline(&mut stdin, &mut username, false)? == 0 {
return Ok(());
}
login_as(username.trim(), "")
}
fn main() -> ExitCode {
let args: Vec<_> = env::args().skip(1).collect();
if args.len() != 1 {
eprintln!("Usage: /sbin/login TTY");
return ExitCode::FAILURE;
}
let terminal = args[0].as_str();
// TODO check that `terminal` is a terminal
// Start a new session
if let Err(err) = unsafe { sys::start_session() } {
eprintln!("setsid(): {:?}", 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 {
// "Attach" the terminal
unsafe {
let mut req = DeviceRequest::SetTerminalGroup(sys::get_pid());
sys::device_request(RawFd::STDIN, &mut req).unwrap();
}
if let Err(err) = login_attempt(attempt_number % 3 == 0) {
eprintln!("login: {}", err.to_string());
}
attempt_number += 1;
}
}

44
sysutils/src/mount.rs Normal file
View File

@ -0,0 +1,44 @@
use std::process::ExitCode;
use clap::Parser;
use yggdrasil_rt::{io::MountOptions, sys::mount};
#[derive(Parser, Debug)]
struct Args {
source: String,
target: String,
filesystem: 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();
println!("Mount {:?}, {:?}, {:?}", source, target, filesystem);
// Permissions are not yet implemented, lol
let result = unsafe {
let options = MountOptions {
source,
filesystem,
target
};
mount(&options)
};
match result {
Ok(()) => ExitCode::SUCCESS,
Err(err) => {
eprintln!("mount: {:?}", err);
ExitCode::FAILURE
}
}
}