shell/
main.rs

1#![feature(iterator_try_collect)]
2#![feature(unix_send_signal)]
3
4use std::{
5    collections::BTreeMap,
6    ffi::c_void,
7    fmt::Debug,
8    fs::{File, OpenOptions},
9    io::{Read, Write, stderr, stdin, stdout},
10    os::{
11        fd::{AsFd, FromRawFd, IntoRawFd, OwnedFd, RawFd},
12        twizzler::process::ChildExt,
13    },
14    path::PathBuf,
15    process::{Child, Command, ExitStatus, Stdio},
16    sync::{Arc, Mutex},
17    time::Instant,
18};
19
20use clap::Parser;
21use colored::Colorize;
22use embedded_io::ErrorType;
23use miette::IntoDiagnostic;
24use tracing::Level;
25use twizzler_abi::{
26    syscall::sys_thread_exit,
27    upcall::{UpcallData, UpcallFrame},
28};
29use twizzler_io::pty::{DEFAULT_TERMIOS, DEFAULT_TERMIOS_RAW};
30use twizzler_rt_abi::bindings::{IO_REGISTER_TERMIOS, twz_rt_set_upcall_handler};
31
32#[derive(Debug, Clone, Copy)]
33enum NextHop {
34    Seq,
35    Pipe,
36    Background,
37}
38
39#[derive(Debug)]
40struct Redirect {
41    fd: u32,
42    append: bool,
43    path: String,
44}
45
46impl Redirect {
47    pub fn build_stdio(&self, ctx: &mut InvokeCtx) -> miette::Result<Stdio> {
48        let raw = self.build_rawfd(ctx)?;
49        let stdio = unsafe { Stdio::from_raw_fd(raw) };
50        Ok(stdio)
51    }
52
53    pub fn build_rawfd(&self, _ctx: &mut InvokeCtx) -> miette::Result<RawFd> {
54        let mut open = OpenOptions::new();
55        let create = self.fd != 0;
56        let file = open
57            .create(create)
58            .append(self.append)
59            .truncate(!self.append && create)
60            .write(self.fd != 0)
61            .read(self.fd == 0)
62            .open(&self.path)
63            .into_diagnostic()?;
64        let file: OwnedFd = file.into();
65        let child_file = file.try_clone().into_diagnostic()?;
66        let stdio = child_file.into_raw_fd();
67
68        Ok(stdio)
69    }
70}
71
72#[derive(Debug)]
73struct ShellInvoke {
74    redirect: Vec<Redirect>,
75    next: NextHop,
76    command: Vec<String>,
77    env: Vec<(String, String)>,
78}
79
80mod builtins {
81    use std::{
82        fs::File,
83        io::{BufReader, BufWriter, Write},
84    };
85
86    use hhmmss::Hhmmss;
87    use miette::IntoDiagnostic;
88
89    use crate::{InvokeCtx, ShellInvoke, WaitChild};
90
91    pub struct BuiltinCtx<'a> {
92        pub stdin: File,
93        pub stdout: File,
94        pub stderr: File,
95        pub cmd: &'a ShellInvoke,
96    }
97
98    impl BuiltinCtx<'_> {
99        fn _stdin(&self) -> BufReader<&File> {
100            BufReader::new(&self.stdin)
101        }
102
103        fn stdout(&self) -> BufWriter<&File> {
104            BufWriter::new(&self.stdout)
105        }
106
107        fn stderr(&self) -> &File {
108            &self.stderr
109        }
110    }
111
112    pub fn jobs(ctx: &mut BuiltinCtx, invoke: &InvokeCtx) -> miette::Result<()> {
113        writeln!(ctx.stdout(), "JOB  ELAPSED TYPE NAME",).into_diagnostic()?;
114        for job in invoke.jobs.jobs.values() {
115            let dur = job.start_time.elapsed();
116            let ty = match job.next {
117                crate::NextHop::Seq => "    ",
118                crate::NextHop::Pipe => "pipe",
119                crate::NextHop::Background => "bgnd",
120            };
121            writeln!(
122                ctx.stdout(),
123                "{:3.3} {} {} {}",
124                job.id,
125                dur.hhmmss(),
126                ty,
127                job.name
128            )
129            .into_diagnostic()?;
130        }
131        Ok(())
132    }
133
134    pub fn kill(ctx: &mut BuiltinCtx, invoke: &InvokeCtx) -> miette::Result<()> {
135        if ctx.cmd.command.len() < 2 {
136            writeln!(ctx.stderr(), "usage: kill <job_id>").into_diagnostic()?;
137            return Ok(());
138        }
139        let Ok(job_id) = ctx.cmd.command[1].parse::<u64>() else {
140            writeln!(ctx.stderr(), "invalid job id `{}'", ctx.cmd.command[1]).into_diagnostic()?;
141            return Ok(());
142        };
143        let Some(job) = invoke.jobs.jobs.get(&job_id) else {
144            writeln!(ctx.stderr(), "job {} not found", job_id).into_diagnostic()?;
145            return Ok(());
146        };
147        let Ok(ch) = &mut *job.child.lock().unwrap() else {
148            writeln!(ctx.stderr(), "job {} not running", job_id).into_diagnostic()?;
149            return Ok(());
150        };
151        if let WaitChild::Child(ch) = ch {
152            if let Err(e) = ch.kill() {
153                writeln!(ctx.stderr(), "failed to kill job {}: {}", job_id, e).into_diagnostic()?;
154            }
155        } else {
156            writeln!(ctx.stderr(), "job {} not running", job_id).into_diagnostic()?;
157        }
158        Ok(())
159    }
160
161    pub fn wait(ctx: &mut BuiltinCtx, invoke: &InvokeCtx) -> miette::Result<()> {
162        if ctx.cmd.command.len() < 2 {
163            writeln!(ctx.stderr(), "usage: wait <job_id>").into_diagnostic()?;
164            return Ok(());
165        }
166        let Ok(job_id) = ctx.cmd.command[1].parse::<u64>() else {
167            writeln!(ctx.stderr(), "invalid job id `{}'", ctx.cmd.command[1]).into_diagnostic()?;
168            return Ok(());
169        };
170        let Some(job) = invoke.jobs.jobs.get(&job_id) else {
171            writeln!(ctx.stderr(), "job {} not found", job_id).into_diagnostic()?;
172            return Ok(());
173        };
174        let Ok(ch) = &mut *job.child.lock().unwrap() else {
175            writeln!(ctx.stderr(), "job {} not running", job_id).into_diagnostic()?;
176            return Ok(());
177        };
178        if let WaitChild::Child(ch) = ch {
179            if let Err(e) = ch.wait() {
180                writeln!(ctx.stderr(), "failed to wait for job {}: {}", job_id, e)
181                    .into_diagnostic()?;
182            }
183        } else {
184            writeln!(ctx.stderr(), "job {} not running", job_id).into_diagnostic()?;
185        }
186        Ok(())
187    }
188
189    pub fn cd(ctx: &mut BuiltinCtx, _invoke: &InvokeCtx) -> miette::Result<()> {
190        if ctx.cmd.command.len() != 2 {
191            writeln!(ctx.stderr(), "usage: cd <directory>").into_diagnostic()?;
192            return Ok(());
193        }
194        if let Err(e) = std::env::set_current_dir(&ctx.cmd.command[1]) {
195            writeln!(ctx.stderr(), "failed to change directory: {}", e).into_diagnostic()?;
196        }
197        Ok(())
198    }
199
200    pub fn pwd(ctx: &mut BuiltinCtx, _invoke: &InvokeCtx) -> miette::Result<()> {
201        let Ok(current) = std::env::current_dir() else {
202            writeln!(ctx.stderr(), "failed to get current directory").into_diagnostic()?;
203            return Ok(());
204        };
205        writeln!(ctx.stdout(), "{}", current.display()).into_diagnostic()?;
206        Ok(())
207    }
208
209    pub fn echo(ctx: &mut BuiltinCtx, _invoke: &InvokeCtx) -> miette::Result<()> {
210        for arg in &ctx.cmd.command[1..] {
211            write!(ctx.stdout(), "{} ", arg).into_diagnostic()?;
212        }
213        writeln!(ctx.stdout()).into_diagnostic()?;
214        Ok(())
215    }
216
217    pub fn set(ctx: &mut BuiltinCtx, _invoke: &InvokeCtx) -> miette::Result<()> {
218        if ctx.cmd.command.len() < 2 {
219            writeln!(ctx.stderr(), "usage: set VAR [val]").into_diagnostic()?;
220            return Ok(());
221        }
222        let var = &ctx.cmd.command[1];
223        let val = ctx.cmd.command.get(2).map(|s| s.as_str()).unwrap_or("");
224        unsafe { std::env::set_var(var, val) };
225        Ok(())
226    }
227
228    pub fn unset(ctx: &mut BuiltinCtx, _invoke: &InvokeCtx) -> miette::Result<()> {
229        if ctx.cmd.command.len() < 2 {
230            writeln!(ctx.stderr(), "usage: unset VAR").into_diagnostic()?;
231            return Ok(());
232        }
233        let var = &ctx.cmd.command[1];
234        unsafe { std::env::remove_var(var) };
235        Ok(())
236    }
237
238    pub fn env(ctx: &mut BuiltinCtx, _invoke: &InvokeCtx) -> miette::Result<()> {
239        for (key, value) in std::env::vars() {
240            writeln!(ctx.stdout(), "{}={}", key, value).into_diagnostic()?;
241        }
242        Ok(())
243    }
244
245    pub fn help(ctx: &mut BuiltinCtx, _invoke: &InvokeCtx) -> miette::Result<()> {
246        writeln!(ctx.stdout(), "Twizzler Shell 0.1").into_diagnostic()?;
247        writeln!(ctx.stdout(), "This shell has basic line-editing, env vars, pipes and I/O redirection, backgrounding and jobs, and system command execution.").into_diagnostic()?;
248        Ok(())
249    }
250}
251
252impl ShellInvoke {
253    fn invoke_builtin(
254        &self,
255        f: impl FnOnce(&mut builtins::BuiltinCtx, &InvokeCtx) -> miette::Result<()>,
256        ctx: &mut InvokeCtx,
257    ) -> miette::Result<Job> {
258        let mut bctx = unsafe {
259            builtins::BuiltinCtx {
260                stdin: File::from_raw_fd(
261                    stdin()
262                        .as_fd()
263                        .try_clone_to_owned()
264                        .into_diagnostic()?
265                        .into_raw_fd(),
266                ),
267                stdout: File::from_raw_fd(
268                    stdout()
269                        .as_fd()
270                        .try_clone_to_owned()
271                        .into_diagnostic()?
272                        .into_raw_fd(),
273                ),
274                stderr: File::from_raw_fd(
275                    stderr()
276                        .as_fd()
277                        .try_clone_to_owned()
278                        .into_diagnostic()?
279                        .into_raw_fd(),
280                ),
281                cmd: self,
282            }
283        };
284        if let Some(stdin) = ctx.pipe.take() {
285            bctx.stdin = unsafe { File::from_raw_fd(stdin.into_raw_fd()) };
286        }
287
288        if let Some(r) = self.get_stdin_redir() {
289            bctx.stdin = unsafe { File::from_raw_fd(r.build_rawfd(ctx)?) };
290        }
291
292        if let Some(r) = self.get_stdout_redir() {
293            bctx.stdout = unsafe { File::from_raw_fd(r.build_rawfd(ctx)?) };
294        }
295
296        if let Some(r) = self.get_stderr_redir() {
297            bctx.stderr = unsafe { File::from_raw_fd(r.build_rawfd(ctx)?) };
298        }
299
300        let (wait, pipe) = match &self.next {
301            NextHop::Seq => (true, None),
302            NextHop::Background => (false, None),
303            NextHop::Pipe => {
304                let (reader, writer) = std::io::pipe().into_diagnostic()?;
305                let reader: OwnedFd = reader.into();
306                let writer: OwnedFd = writer.into();
307
308                bctx.stdout = unsafe { File::from_raw_fd(writer.into_raw_fd()) };
309
310                (false, Some(reader))
311            }
312        };
313        let mut job = Job::new(
314            Arc::new(Mutex::new(Ok(WaitChild::Builtin))),
315            ctx.jobs.next_id(),
316            &self.command[0],
317            self.next,
318        );
319        f(&mut bctx, ctx)?;
320
321        ctx.pipe = pipe;
322        if wait {
323            job.wait()?;
324        }
325        Ok(job)
326    }
327
328    fn try_builtin(&self, ctx: &mut InvokeCtx) -> miette::Result<Option<Job>> {
329        match self.command[0].as_str() {
330            "jobs" => self.invoke_builtin(builtins::jobs, ctx).map(|j| Some(j)),
331            "kill" => self.invoke_builtin(builtins::kill, ctx).map(|j| Some(j)),
332            "cd" => self.invoke_builtin(builtins::cd, ctx).map(|j| Some(j)),
333            "pwd" => self.invoke_builtin(builtins::pwd, ctx).map(|j| Some(j)),
334            "echo" => self.invoke_builtin(builtins::echo, ctx).map(|j| Some(j)),
335            "wait" => self.invoke_builtin(builtins::wait, ctx).map(|j| Some(j)),
336            "set" => self.invoke_builtin(builtins::set, ctx).map(|j| Some(j)),
337            "unset" => self.invoke_builtin(builtins::unset, ctx).map(|j| Some(j)),
338            "env" => self.invoke_builtin(builtins::env, ctx).map(|j| Some(j)),
339            "help" => self.invoke_builtin(builtins::help, ctx).map(|j| Some(j)),
340            _ => Ok(None),
341        }
342    }
343
344    pub fn invoke(&self, ctx: &mut InvokeCtx) -> miette::Result<Job> {
345        if let Some(job) = self.try_builtin(ctx)? {
346            return Ok(job);
347        }
348
349        let path = if std::fs::exists(&self.command[0]).into_diagnostic()? {
350            self.command[0].clone()
351        } else {
352            let r = std::fs::read_dir("/pkg").into_diagnostic()?;
353            let mut found = None;
354            for entry in r {
355                if let Ok(entry) = entry {
356                    let path = format!(
357                        "/pkg/{}/bin/{}",
358                        entry.file_name().display(),
359                        &self.command[0]
360                    );
361                    if std::fs::exists(&path).into_diagnostic()? {
362                        found = Some(path);
363                        break;
364                    }
365                }
366            }
367
368            found.unwrap_or(self.command[0].clone())
369        };
370
371        let mut cmd = Command::new(path);
372        cmd.args(&self.command[1..]);
373        cmd.envs(std::env::vars());
374        cmd.envs(self.env.iter().map(|x| (&x.0, &x.1)));
375        cmd.current_dir(std::env::current_dir().into_diagnostic()?);
376
377        if let Some(stdin) = ctx.pipe.take() {
378            cmd.stdin(unsafe { Stdio::from_raw_fd(stdin.into_raw_fd()) });
379        }
380
381        if let Some(r) = self.get_stdin_redir() {
382            cmd.stdin(r.build_stdio(ctx)?);
383        }
384
385        if let Some(r) = self.get_stdout_redir() {
386            cmd.stdout(r.build_stdio(ctx)?);
387        }
388
389        if let Some(r) = self.get_stderr_redir() {
390            cmd.stderr(r.build_stdio(ctx)?);
391        }
392
393        let (wait, pipe) = match &self.next {
394            NextHop::Seq => (true, None),
395            NextHop::Background => (false, None),
396            NextHop::Pipe => {
397                let (reader, writer) = std::io::pipe().into_diagnostic()?;
398                let reader: OwnedFd = reader.into();
399                let writer: OwnedFd = writer.into();
400
401                cmd.stdout(unsafe { Stdio::from_raw_fd(writer.into_raw_fd()) });
402
403                (false, Some(reader))
404            }
405        };
406
407        ctx.pipe = pipe;
408
409        let child = if wait {
410            let mut c = cmd.spawn().into_diagnostic()?;
411            drop(cmd);
412            c.wait().into_diagnostic()?;
413            Arc::new(Mutex::new(Ok(WaitChild::Child(c))))
414        } else {
415            let cell = Arc::new(Mutex::new(Ok(WaitChild::Waiting)));
416            let tcell = cell.clone();
417            std::thread::spawn(move || {
418                let child = cmd.spawn();
419                match child {
420                    Ok(mut child) => {
421                        let _ = child.wait_ready();
422                        *tcell.lock().unwrap() = Ok(WaitChild::Child(child));
423                    }
424                    Err(err) => {
425                        *tcell.lock().unwrap() = Err(err);
426                    }
427                }
428                drop(cmd);
429                // TODO: this shouldn't be needed
430                sys_thread_exit(0);
431            });
432            cell
433        };
434
435        let job = Job::new(child, ctx.jobs.next_id(), &self.command[0], self.next);
436        Ok(job)
437    }
438
439    fn get_stdin_redir(&self) -> Option<&Redirect> {
440        self.redirect.iter().find(|r| r.fd == 0)
441    }
442
443    fn get_stdout_redir(&self) -> Option<&Redirect> {
444        self.redirect.iter().find(|r| r.fd == 1)
445    }
446
447    fn get_stderr_redir(&self) -> Option<&Redirect> {
448        self.redirect.iter().find(|r| r.fd == 2)
449    }
450}
451
452#[derive(Debug)]
453struct ShellCommand {
454    invokes: Vec<ShellInvoke>,
455}
456
457struct InvokeCtx<'a> {
458    pipe: Option<OwnedFd>,
459    jobs: &'a mut Jobs,
460}
461
462impl<'a> InvokeCtx<'a> {
463    pub fn new(jobs: &'a mut Jobs) -> Self {
464        InvokeCtx { pipe: None, jobs }
465    }
466}
467
468enum WaitChild {
469    Builtin,
470    Waiting,
471    Child(Child),
472}
473
474struct Job {
475    child: Arc<Mutex<std::io::Result<WaitChild>>>,
476    id: u64,
477    start_time: Instant,
478    name: String,
479    next: NextHop,
480}
481
482impl Debug for Job {
483    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
484        f.debug_struct("Job")
485            .field("id", &self.id)
486            .field("start_time", &self.start_time)
487            .field("name", &self.name)
488            .finish()
489    }
490}
491
492impl Job {
493    pub fn new(
494        child: Arc<Mutex<std::io::Result<WaitChild>>>,
495        id: u64,
496        name: impl ToString,
497        next: NextHop,
498    ) -> Self {
499        Job {
500            child,
501            id,
502            start_time: Instant::now(),
503            name: name.to_string(),
504            next,
505        }
506    }
507
508    pub fn try_wait(&mut self) -> miette::Result<Option<ExitStatus>> {
509        match &mut *self.child.lock().unwrap() {
510            Ok(WaitChild::Child(child)) => child.try_wait().into_diagnostic(),
511            Ok(WaitChild::Builtin) => Ok(Some(ExitStatus::default())),
512            Ok(WaitChild::Waiting) => Ok(None),
513            _ => Ok(Some(ExitStatus::default())),
514        }
515    }
516
517    pub fn wait(&mut self) -> miette::Result<ExitStatus> {
518        match &mut *self.child.lock().unwrap() {
519            Ok(WaitChild::Child(child)) => child.wait().into_diagnostic(),
520            Ok(WaitChild::Waiting) => panic!("job is still waiting"),
521            _ => Ok(ExitStatus::default()),
522        }
523    }
524}
525
526#[derive(Default)]
527struct Jobs {
528    jobs: BTreeMap<u64, Job>,
529    next_job_id: u64,
530    id_stack: Vec<u64>,
531}
532
533impl Jobs {
534    pub fn next_id(&mut self) -> u64 {
535        if self.id_stack.is_empty() {
536            self.next_job_id += 1;
537            self.next_job_id
538        } else {
539            self.id_stack.pop().unwrap()
540        }
541    }
542
543    pub fn release_id(&mut self, id: u64) {
544        if self.next_job_id == id {
545            self.next_job_id -= 1;
546            return;
547        }
548        self.id_stack.push(id);
549    }
550
551    pub fn scan(&mut self) {
552        let finished = self
553            .jobs
554            .extract_if(.., |_, j| j.try_wait().is_ok_and(|s| s.is_some()))
555            .collect::<Vec<_>>();
556        for (_, job) in finished {
557            if matches!(job.next, NextHop::Background) {
558                println!("job {} finished", job.id);
559            }
560            self.jobs.remove(&job.id);
561            self.release_id(job.id);
562        }
563    }
564}
565
566impl ShellCommand {
567    pub fn invoke(&self, ctx: &mut InvokeCtx) -> miette::Result<Vec<Job>> {
568        self.invokes.iter().map(|i| i.invoke(ctx)).try_collect()
569    }
570}
571
572impl ShellInvoke {
573    fn parse(input: &str, next: NextHop) -> miette::Result<Option<Self>> {
574        let mut redirect = Vec::new();
575        let mut command = Vec::new();
576        let mut env = Vec::new();
577        let mut parse_env = true;
578
579        let parts: Vec<&str> = input.split_whitespace().collect();
580        for part in parts {
581            if parse_env && part.contains('=') {
582                let split = part.split_once('=').unwrap();
583                env.push((split.0.to_string(), split.1.to_string()));
584                continue;
585            }
586            parse_env = false;
587
588            let (fd, append, prefix_len) = if part.starts_with("2>>") {
589                (2, true, 3)
590            } else if part.starts_with("2>") {
591                (2, false, 2)
592            } else if part.starts_with(">>") {
593                (1, true, 2)
594            } else if part.starts_with(">") {
595                (1, false, 1)
596            } else if part.starts_with("<") {
597                (0, false, 1)
598            } else {
599                command.push(part.to_string());
600                continue;
601            };
602
603            if prefix_len >= part.len() {
604                return Err(miette::miette!("invalid redirect: `{}'", part));
605            }
606
607            redirect.push(Redirect {
608                fd,
609                append,
610                path: part[prefix_len..].to_string(),
611            });
612        }
613
614        if command.is_empty() {
615            return Ok(None);
616        }
617
618        Ok(Some(Self {
619            redirect,
620            next,
621            command,
622            env,
623        }))
624    }
625}
626
627impl ShellCommand {
628    fn parse(line: &str) -> miette::Result<Self> {
629        let split = line.split_inclusive([';', '|', '\n', '&']);
630
631        let mut invokes = Vec::new();
632        for item in split {
633            if item.len() == 0 {
634                continue;
635            }
636
637            let last = item.chars().last().unwrap();
638            let next = match last {
639                '|' => NextHop::Pipe,
640                '&' => NextHop::Background,
641                _ => NextHop::Seq,
642            };
643
644            let si = ShellInvoke::parse(item.trim_end_matches([';', '|', '&', '\n']), next)?;
645            if let Some(si) = si {
646                invokes.push(si);
647            }
648        }
649
650        Ok(Self { invokes })
651    }
652}
653
654struct TwzIo;
655
656impl ErrorType for TwzIo {
657    type Error = std::io::Error;
658}
659
660impl embedded_io::Read for TwzIo {
661    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
662        let len = std::io::stdin().read(buf)?;
663
664        Ok(len)
665    }
666}
667
668impl embedded_io::Write for TwzIo {
669    fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
670        let len = std::io::stdout().write(buf)?;
671        std::io::stdout().flush()?;
672        Ok(len)
673    }
674
675    fn flush(&mut self) -> Result<(), Self::Error> {
676        std::io::stdout().flush()
677    }
678}
679
680unsafe extern "C-unwind" fn upcall_handler(frame: *mut c_void, data: *const c_void) {
681    let data = unsafe { data.cast::<UpcallData>().as_ref().unwrap() };
682    let _frame = unsafe { frame.cast::<UpcallFrame>().as_ref().unwrap() };
683    match data.info {
684        twizzler_abi::upcall::UpcallInfo::Mailbox(val) => {
685            if val == libc::SIGWINCH as u64 {
686                // Ignores window change signals from other PTY applications
687                return;
688            }
689            twizzler_abi::klog_println!("shell: signal {}", val);
690            if val == libc::SIGINFO as u64 {
691                let mstats = monitor_api::stats().unwrap();
692                twizzler_abi::klog_println!("{:?}", mstats);
693            }
694        }
695        _ => {
696            panic!("fatal error: {:?}", data);
697        }
698    }
699}
700
701#[derive(Debug, Clone, clap::Parser)]
702struct Args {
703    #[clap(short)]
704    cmd: Option<String>,
705}
706
707fn main() {
708    unsafe { twz_rt_set_upcall_handler(Some(upcall_handler)) };
709
710    let sub = tracing_subscriber::fmt()
711        .with_max_level(Level::INFO)
712        .finish();
713    tracing::subscriber::set_global_default(sub).unwrap();
714
715    let mut io = TwzIo;
716    let mut buffer = [0; 1024];
717    let mut history = [0; 1024];
718    let mut editor = noline::builder::EditorBuilder::from_slice(&mut buffer)
719        .with_slice_history(&mut history)
720        .build_sync(&mut io)
721        .unwrap();
722    colored::control::set_override(true);
723    let mut jobs = Jobs::default();
724
725    let args = Args::parse();
726
727    if let Some(cmd) = args.cmd.as_ref() {
728        run_cmd(jobs, cmd);
729        return;
730    }
731
732    loop {
733        jobs.scan();
734
735        let cd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/"));
736        let user = "root".red();
737        let host = "twizzler".bright_blue();
738        //let cd = cd.to_str().unwrap().bright_cyan();
739
740        let s = if true {
741            // TODO: color seems to break noline
742            let prompt = format!("root@twizzler [{}]# ", cd.display());
743            twizzler_rt_abi::io::twz_rt_fd_set_config(0, IO_REGISTER_TERMIOS, DEFAULT_TERMIOS_RAW)
744                .unwrap();
745            let line = match editor.readline(prompt.as_str(), &mut io) {
746                Ok(line) => line,
747                Err(_) => break,
748            };
749            twizzler_rt_abi::io::twz_rt_fd_set_config(0, IO_REGISTER_TERMIOS, DEFAULT_TERMIOS)
750                .unwrap();
751            line.to_string()
752        } else {
753            print!("{}@{} [{}]> ", user, host, cd.display());
754            stdout().flush().unwrap();
755            let mut s = String::new();
756            let _ = stdin().read_line(&mut s).unwrap();
757            s
758        };
759
760        if s.is_empty() {
761            continue;
762        }
763
764        let cmd = ShellCommand::parse(&s).unwrap();
765
766        let mut ctx = InvokeCtx::new(&mut jobs);
767        let res = cmd.invoke(&mut ctx);
768        drop(ctx);
769
770        if let Ok(new_jobs) = res {
771            for new_job in new_jobs {
772                if matches!(new_job.next, NextHop::Background) {
773                    println!("job {} backgrounding ({})", new_job.id, new_job.name);
774                }
775                jobs.jobs.insert(new_job.id, new_job);
776            }
777        } else {
778            eprintln!("shell: {}", res.unwrap_err());
779        }
780    }
781}
782
783fn run_cmd(mut jobs: Jobs, cmd: &str) {
784    if !cmd.is_empty() {
785        let cmd = ShellCommand::parse(cmd).unwrap();
786        let mut ctx = InvokeCtx::new(&mut jobs);
787        let res = cmd.invoke(&mut ctx);
788        if let Err(e) = res {
789            eprintln!("shell: {}", e);
790        }
791    }
792}