1#![allow(dead_code)]
13#![allow(unused_variables)]
16
17pub(crate) mod aliases;
18pub(crate) mod ansi;
19mod commands;
20pub(crate) mod completion;
21pub(crate) mod expand;
22pub(crate) mod functions;
23pub(crate) mod glob;
24pub(crate) mod jobs;
25pub(crate) mod line_editor;
26pub(crate) mod redirect;
27pub(crate) mod script;
28mod state;
29
30use alloc::{
34 boxed::Box,
35 collections::BTreeMap,
36 format,
37 string::{String, ToString},
38 vec,
39 vec::Vec,
40};
41
42use commands::{
43 AcpiCommand, AliasCommand, ArpCommand, AtCommand, AuditCommand, BgCommand, Blake3sumCommand,
44 BlkidCommand, BondCommand, BracketTestCommand, BrowserCommand, BtCommand, CapCommand,
45 CatCommand, CdCommand, ChmodCommand, CiCommand, ClearCommand, CloudInitCommand,
46 ContainerCommand, CoredumpCommand, CpCommand, CrontabCommand, CurlCommand, CutCommand,
47 DateCommand, DfCommand, DhcpCommand, DmesgCommand, DnsCommand, DotCommand, EchoCommand,
48 EnvCommand, ExitCommand, ExportCommand, FalseCommand, FgCommand, FirewallCommand, FreeCommand,
49 FsckCommand, GdbCommand, GitCommand, GrepCommand, GroupsCommand, HeadCommand, HelpCommand,
50 HibernateCommand, HistoryCommand, HostnameCommand, HttpServerCommand, HwinfoCommand, IdCommand,
51 IfconfigCommand, IpcsCommand, IscsiadmCommand, JobsCommand, KillCommand, KinitCommand,
52 KlistCommand, KptiCommand, KubectlCommand, LdapsearchCommand, LsCommand, LsblkCommand,
53 LscpuCommand, LsmodCommand, LsnsCommand, LspciCommand, LsusbCommand, MacCommand, MakeCommand,
54 MdadmCommand, MkdirCommand, MkfsCommand, MountCommand, MvCommand, NatCommand, NdpCommand,
55 NetstatCommand, NfsmountCommand, NotifyCommand, NtpCommand, NumaCommand, PasswdCommand,
56 PerfCommand, Ping6Command, PingCommand, PkgCommand, PlayCommand, PoweroffCommand,
57 PrintfCommand, ProfilerCommand, PsCommand, PwdCommand, ReadCommand, RebootCommand, RmCommand,
58 RouteCommand, SchedCommand, ScreenshotCommand, ServiceCommand, SetCommand, Sha256sumCommand,
59 ShutdownCommand, SlabCommand, SmbclientCommand, SortCommand, SourceCommand, SsCommand,
60 SshCommand, SshdCommand, StartGuiCommand, StraceCommand, SuCommand, SudoCommand,
61 SuspendCommand, SyncCommand, SysctlCommand, TailCommand, TarCommand, TeeCommand, TestCommand,
62 ThemeCommand, TopCommand, TouchCommand, TpmCommand, TrCommand, TraceCommand, TrueCommand,
63 TypeCommand, UnaliasCommand, UnameCommand, UniqCommand, UnsetCommand, UptimeCommand,
64 UseraddCommand, UserdelCommand, VlanCommand, VmstatCommand, VmxCommand, VolumeCommand,
65 VpnCommand, WcCommand, WgCommand, WhichCommand, WhoamiCommand, WifiCommand, WinfoCommand,
66 XattrCommand,
67};
68use spin::RwLock;
69pub use state::{get_shell, init, run_shell, try_get_shell};
70
71#[derive(Debug)]
73pub enum CommandResult {
74 Success(i32),
75 Error(String),
76 NotFound,
77 Exit(i32),
78}
79
80pub trait BuiltinCommand: Send + Sync {
82 fn name(&self) -> &str;
84
85 fn description(&self) -> &str;
87
88 fn execute(&self, args: &[String], shell: &Shell) -> CommandResult;
90}
91
92#[derive(Debug, Clone)]
94pub struct EnvVar {
95 pub name: String,
96 pub value: String,
97}
98
99#[derive(Debug, Clone)]
101pub struct ShellConfig {
102 pub prompt: String,
103 pub history_size: usize,
104 pub path: Vec<String>,
105 pub editor: String,
106 pub pager: String,
107}
108
109impl Default for ShellConfig {
110 fn default() -> Self {
111 Self {
112 prompt: String::from("\\u@\\h:\\w\\$ "),
113 history_size: 1000,
114 path: vec![
115 String::from("/bin"),
116 String::from("/usr/bin"),
117 String::from("/sbin"),
118 String::from("/usr/sbin"),
119 ],
120 editor: String::from("vi"),
121 pager: String::from("less"),
122 }
123 }
124}
125
126pub struct Shell {
128 config: ShellConfig,
130
131 pub(crate) environment: RwLock<BTreeMap<String, String>>,
133
134 pub(crate) history: RwLock<Vec<String>>,
136
137 pub(crate) builtins: RwLock<BTreeMap<String, Box<dyn BuiltinCommand>>>,
139
140 cwd: RwLock<String>,
142
143 pub(crate) last_exit_code: RwLock<i32>,
145
146 running: RwLock<bool>,
148
149 line_editor: RwLock<line_editor::LineEditor>,
151
152 pub(crate) job_table: RwLock<jobs::JobTable>,
154
155 pub(crate) function_registry: RwLock<functions::FunctionRegistry>,
157
158 pub(crate) alias_registry: RwLock<aliases::AliasRegistry>,
160}
161
162impl Default for Shell {
163 fn default() -> Self {
164 Self::new()
165 }
166}
167
168impl Shell {
169 pub fn new() -> Self {
171 let shell = Self {
172 config: ShellConfig::default(),
173 environment: RwLock::new(BTreeMap::new()),
174 history: RwLock::new(Vec::new()),
175 builtins: RwLock::new(BTreeMap::new()),
176 cwd: RwLock::new(String::from("/")),
177 last_exit_code: RwLock::new(0),
178 running: RwLock::new(true),
179 line_editor: RwLock::new(line_editor::LineEditor::new()),
180 job_table: RwLock::new(jobs::JobTable::new()),
181 function_registry: RwLock::new(functions::FunctionRegistry::new()),
182 alias_registry: RwLock::new(aliases::AliasRegistry::new()),
183 };
184
185 shell.init_environment();
187
188 shell.register_builtins();
190
191 shell
192 }
193
194 pub fn run(&self) -> ! {
196 crate::println!("VeridianOS Shell v1.0");
197 crate::println!("Type 'help' for available commands");
198 crate::graphics::fbcon::flush();
199
200 *self.running.write() = true;
201
202 loop {
203 if !*self.running.read() {
204 break;
205 }
206
207 let _prompt = self.expand_prompt();
209 crate::print!("{}", _prompt);
210 crate::graphics::fbcon::flush();
211 crate::graphics::fbcon::update_cursor();
212
213 let command_line = self.read_line();
215
216 if !command_line.trim().is_empty() {
218 self.add_to_history(command_line.clone());
219 }
220
221 let result = self.execute_command(&command_line);
223
224 match result {
226 CommandResult::Success(code) => {
227 *self.last_exit_code.write() = code;
228 }
229 CommandResult::Error(_msg) => {
230 crate::println!("vsh: {}", _msg);
231 *self.last_exit_code.write() = 1;
232 }
233 CommandResult::NotFound => {
234 if !command_line.trim().is_empty() {
235 crate::println!("vsh: command not found");
236 *self.last_exit_code.write() = 127;
237 }
238 }
239 CommandResult::Exit(code) => {
240 crate::println!("exit");
241 *self.last_exit_code.write() = code;
242 break;
243 }
244 }
245
246 crate::graphics::fbcon::flush();
247
248 self.notify_completed_jobs();
250 }
251
252 crate::process::lifecycle::exit_process(*self.last_exit_code.read());
254
255 loop {
257 #[cfg(target_arch = "x86_64")]
261 unsafe {
262 core::arch::asm!("hlt")
263 }
264
265 #[cfg(target_arch = "aarch64")]
266 unsafe {
267 core::arch::asm!("wfi")
268 }
269
270 #[cfg(target_arch = "riscv64")]
271 unsafe {
272 core::arch::asm!("wfi")
273 }
274
275 #[cfg(not(any(
276 target_arch = "x86_64",
277 target_arch = "aarch64",
278 target_arch = "riscv64"
279 )))]
280 core::hint::spin_loop();
281 }
282 }
283
284 pub fn execute_command(&self, command_line: &str) -> CommandResult {
291 let trimmed = command_line.trim();
292 crate::println!("[SHELL-EXEC] command_line='{}'", trimmed);
293 if trimmed.is_empty() {
294 return CommandResult::Success(0);
295 }
296
297 if let Some(result) = self.try_execute_subshell(trimmed) {
299 return result;
300 }
301
302 if let Some(result) = self.try_execute_list(trimmed) {
304 return result;
305 }
306
307 let expanded_alias = aliases::expand_aliases(trimmed, &self.alias_registry.read());
309
310 let exit_code = *self.last_exit_code.read();
312 let env = self.environment.read().clone();
313 let expanded = expand::expand_variables(&expanded_alias, &env, exit_code);
314
315 let (command_str, _is_background) =
317 if expanded.ends_with('&') && !expanded.ends_with("&&") && !expanded.ends_with(">&") {
318 (expanded[..expanded.len() - 1].trim(), true)
319 } else {
320 (expanded.as_str(), false)
321 };
322
323 if command_str.is_empty() {
324 return CommandResult::Success(0);
325 }
326
327 let pipe_segments: Vec<&str> = command_str.split('|').collect();
329 if pipe_segments.len() > 1 {
330 return self.execute_pipeline(&pipe_segments);
331 }
332
333 let tokens = self.tokenize(command_str);
335 if tokens.is_empty() {
336 return CommandResult::Success(0);
337 }
338
339 let cwd = self.get_cwd();
340 let tokens = glob::expand_globs(tokens, &cwd);
341
342 let (cmd_tokens, redirections) = redirect::parse_redirections(&tokens);
343 if cmd_tokens.is_empty() {
344 return CommandResult::Success(0);
345 }
346
347 let command = &cmd_tokens[0];
348 let args = &cmd_tokens[1..];
349
350 {
352 let func_reg = self.function_registry.read();
353 if let Some(func) = func_reg.get(command) {
354 let body = func.body.clone();
355 drop(func_reg);
356 let mut last_result = CommandResult::Success(0);
357 for line in &body {
358 last_result = self.execute_command(line);
359 }
360 return last_result;
361 }
362 }
363
364 if let Some(builtin) = self.builtins.read().get(command) {
366 crate::println!("[SHELL-EXEC] Found builtin: {}", command);
367 let result = builtin.execute(args, self);
368
369 if !redirections.is_empty() {
371 for redir in &redirections {
372 if let redirect::Redirection::StdoutTo(path)
373 | redirect::Redirection::StdoutAppend(path) = redir
374 {
375 let _ = crate::fs::write_file(path, b"");
376 }
377 }
378 }
379
380 return result;
381 }
382
383 crate::println!("[SHELL-EXEC] Trying external command: {}", command);
385 let result = self.execute_external_command(command, args);
386 match &result {
387 CommandResult::Success(code) => {
388 crate::println!("[SHELL-EXEC] Result: Success({})", code)
389 }
390 CommandResult::Error(msg) => crate::println!("[SHELL-EXEC] Result: Error({})", msg),
391 CommandResult::NotFound => crate::println!("[SHELL-EXEC] Result: NotFound"),
392 CommandResult::Exit(code) => crate::println!("[SHELL-EXEC] Result: Exit({})", code),
393 }
394 result
395 }
396
397 fn try_execute_list(&self, command_line: &str) -> Option<CommandResult> {
401 if command_line.contains(';') {
403 let segments: Vec<&str> = command_line.split(';').collect();
404 if segments.len() > 1 {
405 let mut last_result = CommandResult::Success(0);
406 for seg in segments {
407 let seg = seg.trim();
408 if !seg.is_empty() {
409 last_result = self.execute_command(seg);
410 }
411 }
412 return Some(last_result);
413 }
414 }
415
416 if command_line.contains("&&") {
418 let parts: Vec<&str> = command_line.splitn(2, "&&").collect();
419 if parts.len() == 2 {
420 let left_result = self.execute_command(parts[0].trim());
421 match &left_result {
422 CommandResult::Success(0) => {
423 return Some(self.execute_command(parts[1].trim()));
424 }
425 _ => return Some(left_result),
426 }
427 }
428 }
429
430 if command_line.contains("||") {
432 let parts: Vec<&str> = command_line.splitn(2, "||").collect();
433 if parts.len() == 2 {
434 let left_result = self.execute_command(parts[0].trim());
435 match &left_result {
436 CommandResult::Success(0) => return Some(left_result),
437 _ => return Some(self.execute_command(parts[1].trim())),
438 }
439 }
440 }
441
442 None
443 }
444
445 fn try_execute_subshell(&self, command_line: &str) -> Option<CommandResult> {
455 let trimmed = command_line.trim();
456
457 if !trimmed.starts_with('(') || !trimmed.ends_with(')') {
459 return None;
460 }
461
462 let inner = &trimmed[1..trimmed.len() - 1];
464
465 let mut depth = 1i32;
468 for (i, ch) in inner.chars().enumerate() {
469 match ch {
470 '(' => depth += 1,
471 ')' => {
472 depth -= 1;
473 if depth == 0 && i < inner.len() - 1 {
474 return None;
477 }
478 }
479 _ => {}
480 }
481 }
482
483 if depth != 1 {
484 return None;
486 }
487
488 let inner = inner.trim();
490 if inner.is_empty() {
491 return Some(CommandResult::Success(0));
492 }
493
494 Some(self.execute_command(inner))
497 }
498
499 pub fn init_console(&self) -> Result<(u32, u32), crate::error::KernelError> {
507 let (master_id, slave_id) = crate::fs::pty::with_pty_manager(|mgr| mgr.create_pty())
508 .ok_or(crate::error::KernelError::InvalidState {
509 expected: "pty_manager_initialized",
510 actual: "pty_manager_not_available",
511 })??;
512
513 crate::fs::pty::with_pty_manager(|mgr| {
515 if let Some(master) = mgr.get_master(master_id) {
516 master.set_winsize(crate::fs::pty::Winsize {
517 rows: 24,
518 cols: 80,
519 xpixel: 0,
520 ypixel: 0,
521 });
522 }
523 });
524
525 self.set_env(String::from("TTY"), format!("/dev/pts/{}", slave_id));
527
528 crate::println!(
529 "[shell] Console initialized: PTY master={}, slave={}",
530 master_id,
531 slave_id
532 );
533 Ok((master_id, slave_id))
534 }
535
536 fn execute_pipeline(&self, segments: &[&str]) -> CommandResult {
538 if segments.len() < 2 {
539 return self.execute_command(segments[0]);
540 }
541
542 let mut input_data: Option<Vec<u8>> = None;
545
546 for (i, segment) in segments.iter().enumerate() {
547 let segment = segment.trim();
548 if segment.is_empty() {
549 continue;
550 }
551
552 let is_last = i == segments.len() - 1;
553
554 if is_last {
555 let result = self.execute_command(segment);
559 return result;
560 }
561
562 match crate::fs::pipe::create_pipe() {
564 Ok((reader, writer)) => {
565 let _result = self.execute_command(segment);
568
569 writer.close();
571
572 input_data = Some(crate::fs::pipe::drain_pipe(&reader));
574 }
575 Err(_) => {
576 return CommandResult::Error("vsh: pipe creation failed".to_string());
577 }
578 }
579 }
580
581 CommandResult::Success(0)
582 }
583
584 pub fn get_cwd(&self) -> String {
586 self.cwd.read().clone()
587 }
588
589 pub fn set_cwd(&self, path: String) -> Result<(), crate::error::KernelError> {
591 let vfs = crate::fs::get_vfs().read();
593 let node = vfs.resolve_path(&path)?;
594 let metadata = node.metadata()?;
595
596 if metadata.node_type != crate::fs::NodeType::Directory {
597 return Err(crate::error::KernelError::FsError(
598 crate::error::FsError::NotADirectory,
599 ));
600 }
601
602 *self.cwd.write() = path;
603 Ok(())
604 }
605
606 pub fn get_env(&self, name: &str) -> Option<String> {
608 self.environment.read().get(name).cloned()
609 }
610
611 pub fn set_env(&self, name: String, value: String) {
613 self.environment.write().insert(name, value);
614 }
615
616 pub fn get_all_env(&self) -> Vec<String> {
618 self.environment
619 .read()
620 .iter()
621 .map(|(k, v)| format!("{}={}", k, v))
622 .collect()
623 }
624
625 pub fn stop(&self) {
627 *self.running.write() = false;
628 }
629
630 pub fn register_builtin(&self, command: Box<dyn BuiltinCommand>) {
632 let mut builtins = self.builtins.write();
633 builtins.insert(command.name().to_string(), command);
634 }
635
636 pub fn register_builtins_batch(&self, commands: Vec<Box<dyn BuiltinCommand>>) {
638 let mut builtins = self.builtins.write();
639 for command in commands {
640 builtins.insert(command.name().to_string(), command);
641 }
642 }
643
644 fn init_environment(&self) {
649 let mut env = self.environment.write();
650 env.insert(
651 String::from("PATH"),
652 String::from("/bin:/usr/bin:/sbin:/usr/sbin"),
653 );
654 env.insert(String::from("HOME"), String::from("/"));
655 env.insert(String::from("SHELL"), String::from("/bin/vsh"));
656 env.insert(String::from("TERM"), String::from("veridian"));
657 env.insert(String::from("USER"), String::from("root"));
658 env.insert(String::from("PWD"), String::from("/"));
659 }
660
661 fn register_builtins(&self) {
662 let mut builtins = self.builtins.write();
663
664 builtins.insert("help".into(), Box::new(HelpCommand));
666 builtins.insert("?".into(), Box::new(HelpCommand));
667
668 builtins.insert("cd".into(), Box::new(CdCommand));
670 builtins.insert("pwd".into(), Box::new(PwdCommand));
671 builtins.insert("ls".into(), Box::new(LsCommand));
672 builtins.insert("mkdir".into(), Box::new(MkdirCommand));
673
674 builtins.insert("cat".into(), Box::new(CatCommand));
676 builtins.insert("echo".into(), Box::new(EchoCommand));
677 builtins.insert("touch".into(), Box::new(TouchCommand));
678 builtins.insert("rm".into(), Box::new(RmCommand));
679
680 builtins.insert("ps".into(), Box::new(PsCommand));
682 builtins.insert("kill".into(), Box::new(KillCommand));
683 builtins.insert("uptime".into(), Box::new(UptimeCommand));
684 builtins.insert("mount".into(), Box::new(MountCommand));
685 builtins.insert("lsmod".into(), Box::new(LsmodCommand));
686
687 builtins.insert("env".into(), Box::new(EnvCommand));
689 builtins.insert("export".into(), Box::new(ExportCommand));
690 builtins.insert("unset".into(), Box::new(UnsetCommand));
691
692 builtins.insert("pkg".into(), Box::new(PkgCommand));
694
695 builtins.insert("history".into(), Box::new(HistoryCommand));
697 builtins.insert("clear".into(), Box::new(ClearCommand));
698 builtins.insert("exit".into(), Box::new(ExitCommand));
699 builtins.insert("logout".into(), Box::new(ExitCommand));
700
701 builtins.insert("true".into(), Box::new(TrueCommand));
703 builtins.insert("false".into(), Box::new(FalseCommand));
704 builtins.insert("test".into(), Box::new(TestCommand));
705 builtins.insert("[".into(), Box::new(BracketTestCommand));
706
707 builtins.insert("wc".into(), Box::new(WcCommand));
709 builtins.insert("head".into(), Box::new(HeadCommand));
710 builtins.insert("tail".into(), Box::new(TailCommand));
711 builtins.insert("grep".into(), Box::new(GrepCommand));
712 builtins.insert("sort".into(), Box::new(SortCommand));
713 builtins.insert("uniq".into(), Box::new(UniqCommand));
714 builtins.insert("cut".into(), Box::new(CutCommand));
715 builtins.insert("tr".into(), Box::new(TrCommand));
716 builtins.insert("tee".into(), Box::new(TeeCommand));
717 builtins.insert("printf".into(), Box::new(PrintfCommand));
718
719 builtins.insert("read".into(), Box::new(ReadCommand));
721
722 builtins.insert("cp".into(), Box::new(CpCommand));
724 builtins.insert("mv".into(), Box::new(MvCommand));
725 builtins.insert("chmod".into(), Box::new(ChmodCommand));
726
727 builtins.insert("date".into(), Box::new(DateCommand));
729 builtins.insert("uname".into(), Box::new(UnameCommand));
730 builtins.insert("free".into(), Box::new(FreeCommand));
731 builtins.insert("dmesg".into(), Box::new(DmesgCommand));
732 builtins.insert("df".into(), Box::new(DfCommand));
733 builtins.insert("sync".into(), Box::new(SyncCommand));
734
735 builtins.insert("set".into(), Box::new(SetCommand));
737 builtins.insert("source".into(), Box::new(SourceCommand));
738 builtins.insert(".".into(), Box::new(DotCommand));
739 builtins.insert("alias".into(), Box::new(AliasCommand));
740 builtins.insert("unalias".into(), Box::new(UnaliasCommand));
741 builtins.insert("type".into(), Box::new(TypeCommand));
742 builtins.insert("which".into(), Box::new(WhichCommand));
743
744 builtins.insert("fg".into(), Box::new(FgCommand));
746 builtins.insert("bg".into(), Box::new(BgCommand));
747 builtins.insert("jobs".into(), Box::new(JobsCommand));
748
749 builtins.insert("perf".into(), Box::new(PerfCommand));
751 builtins.insert("trace".into(), Box::new(TraceCommand));
752
753 builtins.insert("acpi".into(), Box::new(AcpiCommand));
755
756 builtins.insert("ifconfig".into(), Box::new(IfconfigCommand));
758 builtins.insert("dhcp".into(), Box::new(DhcpCommand));
759 builtins.insert("netstat".into(), Box::new(NetstatCommand));
760 builtins.insert("arp".into(), Box::new(ArpCommand));
761 builtins.insert("ping6".into(), Box::new(Ping6Command));
762 builtins.insert("ndp".into(), Box::new(NdpCommand));
763
764 builtins.insert("startgui".into(), Box::new(StartGuiCommand));
766
767 builtins.insert("play".into(), Box::new(PlayCommand));
769 builtins.insert("volume".into(), Box::new(VolumeCommand));
770
771 builtins.insert("vmx".into(), Box::new(VmxCommand));
773 builtins.insert("container".into(), Box::new(ContainerCommand));
774
775 builtins.insert("numa".into(), Box::new(NumaCommand));
777 builtins.insert("kpti".into(), Box::new(KptiCommand));
778
779 builtins.insert("lsblk".into(), Box::new(LsblkCommand));
781 builtins.insert("lspci".into(), Box::new(LspciCommand));
782 builtins.insert("lsusb".into(), Box::new(LsusbCommand));
783
784 builtins.insert("sched".into(), Box::new(SchedCommand));
786 builtins.insert("slab".into(), Box::new(SlabCommand));
787 builtins.insert("vmstat".into(), Box::new(VmstatCommand));
788
789 builtins.insert("audit".into(), Box::new(AuditCommand));
791 builtins.insert("cap".into(), Box::new(CapCommand));
792 builtins.insert("mac".into(), Box::new(MacCommand));
793 builtins.insert("tpm".into(), Box::new(TpmCommand));
794
795 builtins.insert("blake3sum".into(), Box::new(Blake3sumCommand));
797 builtins.insert("sha256sum".into(), Box::new(Sha256sumCommand));
798
799 builtins.insert("ipcs".into(), Box::new(IpcsCommand));
801
802 builtins.insert("route".into(), Box::new(RouteCommand));
804 builtins.insert("ss".into(), Box::new(SsCommand));
805
806 builtins.insert("firewall".into(), Box::new(FirewallCommand));
808 builtins.insert("nat".into(), Box::new(NatCommand));
809 builtins.insert("dns".into(), Box::new(DnsCommand));
810 builtins.insert("ntp".into(), Box::new(NtpCommand));
811 builtins.insert("vpn".into(), Box::new(VpnCommand));
812 builtins.insert("wg".into(), Box::new(WgCommand));
813 builtins.insert("wifi".into(), Box::new(WifiCommand));
814 builtins.insert("bt".into(), Box::new(BtCommand));
815 builtins.insert("ssh".into(), Box::new(SshCommand));
816 builtins.insert("curl".into(), Box::new(CurlCommand));
817 builtins.insert("ping".into(), Box::new(PingCommand));
818 builtins.insert("vlan".into(), Box::new(VlanCommand));
819 builtins.insert("bond".into(), Box::new(BondCommand));
820 builtins.insert("ldapsearch".into(), Box::new(LdapsearchCommand));
821 builtins.insert("kinit".into(), Box::new(KinitCommand));
822 builtins.insert("klist".into(), Box::new(KlistCommand));
823
824 builtins.insert("winfo".into(), Box::new(WinfoCommand));
826
827 builtins.insert("lsns".into(), Box::new(LsnsCommand));
829
830 builtins.insert("git".into(), Box::new(GitCommand));
832 builtins.insert("make".into(), Box::new(MakeCommand));
833 builtins.insert("gdb".into(), Box::new(GdbCommand));
834 builtins.insert("profiler".into(), Box::new(ProfilerCommand));
835 builtins.insert("ci".into(), Box::new(CiCommand));
836
837 builtins.insert("top".into(), Box::new(TopCommand));
839 builtins.insert("strace".into(), Box::new(StraceCommand));
840 builtins.insert("coredump".into(), Box::new(CoredumpCommand));
841 builtins.insert("lscpu".into(), Box::new(LscpuCommand));
842 builtins.insert("hostname".into(), Box::new(HostnameCommand));
843 builtins.insert("sysctl".into(), Box::new(SysctlCommand));
844
845 builtins.insert("hwinfo".into(), Box::new(HwinfoCommand));
847
848 builtins.insert("cloud-init".into(), Box::new(CloudInitCommand));
850 builtins.insert("kubectl".into(), Box::new(KubectlCommand));
851
852 builtins.insert("http-server".into(), Box::new(HttpServerCommand));
854 builtins.insert("sshd".into(), Box::new(SshdCommand));
855
856 builtins.insert("screenshot".into(), Box::new(ScreenshotCommand));
858 builtins.insert("notify".into(), Box::new(NotifyCommand));
859 builtins.insert("theme".into(), Box::new(ThemeCommand));
860 builtins.insert("browser".into(), Box::new(BrowserCommand));
861
862 builtins.insert("whoami".into(), Box::new(WhoamiCommand));
864 builtins.insert("id".into(), Box::new(IdCommand));
865 builtins.insert("groups".into(), Box::new(GroupsCommand));
866 builtins.insert("useradd".into(), Box::new(UseraddCommand));
867 builtins.insert("userdel".into(), Box::new(UserdelCommand));
868 builtins.insert("passwd".into(), Box::new(PasswdCommand));
869 builtins.insert("su".into(), Box::new(SuCommand));
870 builtins.insert("sudo".into(), Box::new(SudoCommand));
871
872 builtins.insert("service".into(), Box::new(ServiceCommand));
874 builtins.insert("reboot".into(), Box::new(RebootCommand));
875 builtins.insert("shutdown".into(), Box::new(ShutdownCommand));
876 builtins.insert("poweroff".into(), Box::new(PoweroffCommand));
877 builtins.insert("suspend".into(), Box::new(SuspendCommand));
878 builtins.insert("hibernate".into(), Box::new(HibernateCommand));
879
880 builtins.insert("crontab".into(), Box::new(CrontabCommand));
882 builtins.insert("at".into(), Box::new(AtCommand));
883
884 builtins.insert("tar".into(), Box::new(TarCommand));
886 builtins.insert("xattr".into(), Box::new(XattrCommand));
887 builtins.insert("mkfs".into(), Box::new(MkfsCommand));
888 builtins.insert("fsck".into(), Box::new(FsckCommand));
889 builtins.insert("blkid".into(), Box::new(BlkidCommand));
890 builtins.insert("nfsmount".into(), Box::new(NfsmountCommand));
891 builtins.insert("smbclient".into(), Box::new(SmbclientCommand));
892
893 builtins.insert("mdadm".into(), Box::new(MdadmCommand));
895 builtins.insert("iscsiadm".into(), Box::new(IscsiadmCommand));
896 }
897
898 fn tokenize(&self, command_line: &str) -> Vec<String> {
899 let mut tokens = Vec::new();
900 let mut current_token = String::new();
901 let mut in_quotes = false;
902 let mut escape_next = false;
903
904 for ch in command_line.chars() {
905 if escape_next {
906 current_token.push(ch);
907 escape_next = false;
908 } else if ch == '\\' {
909 escape_next = true;
910 } else if ch == '"' {
911 in_quotes = !in_quotes;
912 } else if ch.is_whitespace() && !in_quotes {
913 if !current_token.is_empty() {
914 tokens.push(current_token.clone());
915 current_token.clear();
916 }
917 } else {
918 current_token.push(ch);
919 }
920 }
921
922 if !current_token.is_empty() {
923 tokens.push(current_token);
924 }
925
926 tokens
927 }
928
929 fn execute_external_command(&self, command: &str, args: &[String]) -> CommandResult {
930 if command.starts_with('/') || command.starts_with("./") || command.starts_with("../") {
932 if let Ok(_node) = crate::fs::get_vfs().read().resolve_path(command) {
933 crate::println!("[SHELL] Found executable: {}", command);
934 match crate::userspace::load_user_program(
935 command,
936 &args.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
937 &self
938 .get_all_env()
939 .iter()
940 .map(|s| s.as_str())
941 .collect::<Vec<_>>(),
942 ) {
943 Ok(pid) => {
944 crate::println!("[SHELL] Process {} created, about to run", pid.0);
945 let exit_code = run_user_process_from_shell(pid);
946 crate::println!("[SHELL] Process {} exited with code {}", pid.0, exit_code);
947 return CommandResult::Success(exit_code);
948 }
949 Err(e) => {
950 return CommandResult::Error(format!(
951 "Failed to execute {}: {}",
952 command, e
953 ));
954 }
955 }
956 }
957 return CommandResult::NotFound;
958 }
959
960 let path_env = self.get_env("PATH").unwrap_or_default();
962 let paths: Vec<&str> = path_env.split(':').collect();
963
964 for path_dir in paths {
965 let full_path = if path_dir.ends_with('/') {
966 format!("{}{}", path_dir, command)
967 } else {
968 format!("{}/{}", path_dir, command)
969 };
970
971 if let Ok(_node) = crate::fs::get_vfs().read().resolve_path(&full_path) {
973 crate::println!("[SHELL] Found executable: {}", full_path);
974 match crate::userspace::load_user_program(
976 &full_path,
977 &args.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
978 &self
979 .get_all_env()
980 .iter()
981 .map(|s| s.as_str())
982 .collect::<Vec<_>>(),
983 ) {
984 Ok(pid) => {
985 crate::println!("[SHELL] Process {} created, about to run", pid.0);
986 let exit_code = run_user_process_from_shell(pid);
990 crate::println!("[SHELL] Process {} exited with code {}", pid.0, exit_code);
991 return CommandResult::Success(exit_code);
992 }
993 Err(e) => {
994 return CommandResult::Error(format!(
995 "Failed to execute {}: {}",
996 command, e
997 ));
998 }
999 }
1000 }
1001 }
1002
1003 CommandResult::NotFound
1004 }
1005
1006 fn expand_prompt(&self) -> String {
1007 let mut prompt = self.config.prompt.clone();
1008
1009 prompt = prompt.replace("\\w", &self.get_cwd());
1011 prompt = prompt.replace("\\u", &self.get_env("USER").unwrap_or_default());
1012 prompt = prompt.replace("\\h", "veridian");
1013 prompt = prompt.replace(
1014 "\\$",
1015 if self.get_env("USER").as_deref() == Some("root") {
1016 "#"
1017 } else {
1018 "$"
1019 },
1020 );
1021
1022 prompt
1023 }
1024
1025 fn read_line(&self) -> String {
1026 let mut editor = self.line_editor.write();
1027 editor.reset();
1028
1029 loop {
1030 let ch = Self::read_char();
1031
1032 match ch {
1033 Some(byte) => {
1034 let history = self.history.read();
1035 let result = editor.feed(byte, &history);
1036 drop(history);
1037
1038 match result {
1039 Some(line_editor::EditResult::Done) => {
1040 crate::print!("\n");
1041 crate::graphics::fbcon::flush();
1042 return editor.line();
1043 }
1044 Some(line_editor::EditResult::Cancel) => {
1045 crate::print!("^C\n");
1046 crate::graphics::fbcon::flush();
1047 return String::new();
1048 }
1049 Some(line_editor::EditResult::Eof) => {
1050 crate::print!("\n");
1051 crate::graphics::fbcon::flush();
1052 return String::from("exit");
1053 }
1054 Some(line_editor::EditResult::ClearScreen) => {
1055 crate::print!("\x1b[2J\x1b[H");
1057 let _prompt = self.expand_prompt();
1058 crate::print!("{}", _prompt);
1059 let line = editor.line();
1060 crate::print!("{}", line);
1061 let pos = editor.cursor_pos();
1063 let len = editor.len();
1064 if pos < len {
1065 crate::print!("\x1b[{}D", len - pos);
1066 }
1067 crate::graphics::fbcon::flush();
1068 }
1069 Some(line_editor::EditResult::TabComplete) => {
1070 let line = editor.line();
1071 let cursor = editor.cursor_pos();
1072
1073 let builtins_guard = self.builtins.read();
1075 let builtin_names: Vec<&str> =
1076 builtins_guard.keys().map(|s| s.as_str()).collect();
1077
1078 let env_guard = self.environment.read();
1080 let env_names: Vec<&str> =
1081 env_guard.keys().map(|s| s.as_str()).collect();
1082
1083 let cwd = self.get_cwd();
1084
1085 let candidates = completion::complete(
1086 &line,
1087 cursor,
1088 &builtin_names,
1089 &env_names,
1090 &cwd,
1091 );
1092
1093 drop(builtins_guard);
1094 drop(env_guard);
1095
1096 if candidates.is_empty() {
1097 crate::print!("\x07");
1099 } else {
1100 let before = &line[..cursor.min(line.len())];
1102 let word_start = if before.ends_with(' ') || before.is_empty() {
1103 cursor
1104 } else {
1105 before.rfind(' ').map_or(0, |p| p + 1)
1106 };
1107
1108 if candidates.len() == 1 {
1109 let mut replacement = candidates[0].clone();
1111 if !replacement.ends_with('/') {
1112 replacement.push(' ');
1113 }
1114 editor.replace_word(word_start, &replacement);
1115 } else {
1116 let lcp = completion::longest_common_prefix(&candidates);
1119
1120 let current_word_len = cursor - word_start;
1122 if lcp.len() > current_word_len {
1123 editor.replace_word_silent(word_start, &lcp);
1124 }
1125
1126 crate::print!("\n");
1128 for candidate in &candidates {
1129 crate::print!("{} ", candidate);
1130 }
1131 crate::print!("\n");
1132
1133 let prompt = self.expand_prompt();
1135 crate::print!("{}", prompt);
1136 let updated_line = editor.line();
1137 crate::print!("{}", updated_line);
1138 let pos = editor.cursor_pos();
1139 let len = editor.len();
1140 if pos < len {
1141 crate::print!("\x1b[{}D", len - pos);
1142 }
1143 }
1144 }
1145 crate::graphics::fbcon::flush();
1146 }
1147 Some(line_editor::EditResult::Suspend) => {
1148 crate::print!("^Z\n");
1149 crate::graphics::fbcon::flush();
1150 self.suspend_foreground_job();
1156 return String::new();
1159 }
1160 Some(line_editor::EditResult::Continue) => {
1161 let row = crate::graphics::fbcon::cursor_row();
1162 crate::graphics::fbcon::flush_row(row);
1163 crate::graphics::fbcon::update_cursor();
1164 }
1165 None => {
1166 }
1170 }
1171 }
1172 None => {
1173 for _ in 0..256 {
1181 core::hint::spin_loop();
1182 }
1183 }
1184 }
1185 }
1186 }
1187
1188 fn read_char() -> Option<u8> {
1190 crate::drivers::input::read_char()
1191 }
1192
1193 #[allow(dead_code)] fn handle_signal(&self, signum: i32) {
1199 use crate::process::exit::signals;
1200
1201 match signum {
1202 signals::SIGINT => {
1203 if let Some(job) = self.job_table.read().current_job() {
1207 if job.is_running() {
1208 for &pid in &job.pids {
1209 let _ = crate::process::exit::kill_process(
1210 crate::process::ProcessId(pid),
1211 signals::SIGINT,
1212 );
1213 }
1214 }
1215 }
1216 }
1217 signals::SIGTSTP => {
1218 self.suspend_foreground_job();
1220 }
1221 signals::SIGCHLD => {
1222 self.notify_completed_jobs();
1224 }
1225 signals::SIGCONT => {
1226 }
1228 _ => {}
1229 }
1230 }
1231
1232 fn suspend_foreground_job(&self) {
1235 use crate::process::exit::signals;
1236
1237 let mut job_table = self.job_table.write();
1238 if let Some(job) = job_table.current_job() {
1240 if job.is_running() {
1241 let job_id = job.job_id;
1242 let _cmd = job.command_line.clone();
1243
1244 for &pid in &job.pids {
1246 let _ = crate::process::exit::kill_process(
1247 crate::process::ProcessId(pid),
1248 signals::SIGTSTP,
1249 );
1250 }
1251
1252 job_table.update_status(job_id, jobs::JobStatus::Stopped);
1254 crate::println!("[{}]+ Stopped {}", job_id, _cmd);
1255 }
1256 }
1257 }
1260
1261 fn notify_completed_jobs(&self) {
1266 let reaped = self.job_table.write().reap_done();
1267 for job in &reaped {
1268 crate::println!(
1269 "[{}]+ Done {}",
1270 job.job_id,
1271 job.command_line
1272 );
1273 }
1274 }
1275
1276 fn add_to_history(&self, command: String) {
1277 let mut history = self.history.write();
1278 history.push(command);
1279
1280 while history.len() > self.config.history_size {
1282 history.remove(0);
1283 }
1284 }
1285}
1286
1287fn run_user_process_from_shell(pid: crate::process::ProcessId) -> i32 {
1295 use crate::process::get_process;
1296
1297 crate::println!("[SHELL] run_user_process_from_shell: pid={}", pid.0);
1298
1299 let process = match get_process(pid) {
1300 Some(p) => p,
1301 None => {
1302 crate::println!("[SHELL] Error: Process {} not found", pid.0);
1303 return 1;
1304 }
1305 };
1306
1307 #[cfg(target_arch = "x86_64")]
1309 {
1310 let vas = process.memory_space.lock();
1312 let pt_root = vas.get_page_table();
1313 if pt_root == 0 {
1314 drop(vas);
1315 crate::println!("[SHELL] Error: Process {} has no page table", pid.0);
1316 return 1;
1317 }
1318
1319 let threads = process.threads.lock();
1321 let thread = match threads.values().next() {
1322 Some(t) => t,
1323 None => {
1324 drop(threads);
1325 drop(vas);
1326 crate::println!("[SHELL] Error: Process {} has no threads", pid.0);
1327 return 1;
1328 }
1329 };
1330
1331 let tid = thread.tid;
1332
1333 let (entry_point, user_stack_ptr) = {
1334 use crate::arch::context::ThreadContext;
1335 let ctx = thread.context.lock();
1336 (
1337 ctx.get_instruction_pointer() as u64,
1338 ctx.get_stack_pointer() as u64,
1339 )
1340 };
1341
1342 drop(threads);
1344 drop(vas);
1345
1346 crate::println!(
1347 "[SHELL] Entering Ring 3: entry={:#x} stack={:#x}",
1348 entry_point,
1349 user_stack_ptr
1350 );
1351
1352 crate::process::set_boot_current(pid, tid);
1356
1357 let user_cs: u64 = 0x33; let user_ss: u64 = 0x2B; let kernel_rsp_ptr = crate::arch::x86_64::syscall::per_cpu_data_ptr() as u64;
1372 unsafe {
1373 crate::arch::x86_64::usermode::enter_usermode_returnable(
1374 entry_point,
1375 user_stack_ptr,
1376 user_cs,
1377 user_ss,
1378 pt_root,
1379 kernel_rsp_ptr,
1380 );
1381 }
1382
1383 crate::process::clear_boot_current();
1385
1386 crate::println!("[SHELL] Returned from Ring 3");
1387
1388 let current_pt_root = if let Some(proc) = get_process(pid) {
1392 proc.memory_space.lock().get_page_table()
1393 } else {
1394 0
1395 };
1396
1397 if current_pt_root != 0 && current_pt_root != pt_root {
1399 crate::mm::vas::free_user_page_table_frames(current_pt_root);
1400 }
1401 if pt_root != 0 {
1403 crate::mm::vas::free_user_page_table_frames(pt_root);
1404 }
1405 if let Some(proc) = get_process(pid) {
1407 proc.memory_space.lock().set_page_table(0);
1408 }
1409
1410 let exit_code = match get_process(pid) {
1412 Some(p) => p.get_exit_code(),
1413 None => 0,
1414 };
1415 if let Some(proc) = get_process(pid) {
1416 let state = proc.get_state();
1417 if state == crate::process::ProcessState::Zombie
1418 || state == crate::process::ProcessState::Dead
1419 {
1420 crate::process::table::remove_process(pid);
1421 }
1422 }
1423
1424 exit_code
1425 }
1426
1427 #[cfg(not(target_arch = "x86_64"))]
1428 {
1429 crate::println!(
1433 "[SHELL] Warning: Direct user-mode entry not implemented on this architecture"
1434 );
1435 crate::println!("[SHELL] Attempting scheduler-based wait (may freeze)...");
1436
1437 crate::sched::yield_cpu();
1438
1439 match crate::process::wait_for_child(Some(pid)) {
1440 Ok((_child_pid, exit_code)) => exit_code,
1441 Err(e) => {
1442 crate::println!("[SHELL] Error waiting for process: {:?}", e);
1443 1
1444 }
1445 }
1446 }
1447}