init: add some initialization stuff
This commit is contained in:
parent
9cb3b744f7
commit
4225806394
121
Cargo.lock
generated
121
Cargo.lock
generated
@ -2,6 +2,127 @@
|
||||
# It is not intended for manual editing.
|
||||
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]]
|
||||
name = "init"
|
||||
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",
|
||||
]
|
||||
|
10
Cargo.toml
10
Cargo.toml
@ -1,5 +1,13 @@
|
||||
[workspace]
|
||||
resolver = "1"
|
||||
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" }
|
||||
|
24
build.sh
24
build.sh
@ -34,16 +34,32 @@ check_toolchain() {
|
||||
}
|
||||
|
||||
pack_initrd() {
|
||||
local workspace_dir=$(pwd)
|
||||
local build_dir=target/${USER_TARGET}/${PROFILE}
|
||||
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}/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}"
|
||||
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 -
|
||||
}
|
||||
|
||||
@ -51,6 +67,10 @@ check_toolchain
|
||||
|
||||
case "$1" in
|
||||
build|"")
|
||||
if [ "${CLEAN_BUILD}" = 1 ]; then
|
||||
cargo clean
|
||||
fi
|
||||
|
||||
pstatus "Building userspace programs"
|
||||
cargo +ygg-stage1 build ${USER_CARGO_OPTS}
|
||||
|
||||
|
3
etc/inittab
Normal file
3
etc/inittab
Normal 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
1
etc/rc.d/00-mount
Normal file
@ -0,0 +1 @@
|
||||
!(~/sbin/mount nil "/dev" "devfs")
|
1
etc/rc.d/99-motd
Normal file
1
etc/rc.d/99-motd
Normal file
@ -0,0 +1 @@
|
||||
(echo "TODO: message of the day")
|
@ -5,4 +5,13 @@ edition = "2021"
|
||||
|
||||
# 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]
|
||||
yggdrasil-rt = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-rt.git" }
|
||||
|
185
init/src/main.rs
185
init/src/main.rs
@ -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 std::borrow::Cow;
|
||||
use std::path::{Path, PathBuf};
|
||||
use yggdrasil_rt::debug_trace;
|
||||
|
||||
fn ls_tree<P: AsRef<Path>>(path: P, depth: usize) -> std::io::Result<()> {
|
||||
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();
|
||||
const INITTAB_PATH: &str = "/etc/inittab";
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
for _ in 0..depth {
|
||||
print!(" ");
|
||||
}
|
||||
println!("{:?}", filename);
|
||||
|
||||
if entry.file_type().unwrap().is_dir() {
|
||||
ls_tree(path, depth + 1);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
rules.push(parse_rule(&line)?);
|
||||
}
|
||||
|
||||
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();
|
||||
Ok(rules)
|
||||
}
|
||||
|
||||
ls_tree("/", 0).ok();
|
||||
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
103
init/src/rc.rs
Normal 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,3 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "ygg-stage1"
|
||||
targets = ["aarch64-unknown-yggdrasil"]
|
||||
|
9
shell/Cargo.toml
Normal file
9
shell/Cargo.toml
Normal 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
204
shell/src/expr.rs
Normal 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
245
shell/src/main.rs
Normal 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
241
shell/src/parse.rs
Normal 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
18
sysutils/Cargo.toml
Normal 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
96
sysutils/src/login.rs
Normal 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
44
sysutils/src/mount.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user