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 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 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 s = if true {
741 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}