⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/services/shell/
mod.rs

1//! VeridianOS Shell Implementation
2//!
3//! Basic shell with command parsing and built-in commands.
4//!
5//! # Module structure
6//!
7//! - [`mod.rs`](self) - Shell struct, main loop, command dispatch, and public
8//!   types
9//! - [`commands`] - All built-in command implementations
10//! - [`state`] - Global singleton management (init, get_shell, try_get_shell)
11
12#![allow(dead_code)]
13// Many variables in this module are only used in println! calls which are
14// no-ops on some architectures (like AArch64), causing unused variable warnings.
15#![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
30// Re-export the public API from the state module so that callers using
31// `services::shell::init()`, `services::shell::get_shell()`, etc. continue
32// to work without any path changes.
33use 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/// Command execution result
72#[derive(Debug)]
73pub enum CommandResult {
74    Success(i32),
75    Error(String),
76    NotFound,
77    Exit(i32),
78}
79
80/// Shell built-in command
81pub trait BuiltinCommand: Send + Sync {
82    /// Get command name
83    fn name(&self) -> &str;
84
85    /// Get command description
86    fn description(&self) -> &str;
87
88    /// Execute the command
89    fn execute(&self, args: &[String], shell: &Shell) -> CommandResult;
90}
91
92/// Shell environment variable
93#[derive(Debug, Clone)]
94pub struct EnvVar {
95    pub name: String,
96    pub value: String,
97}
98
99/// Shell configuration
100#[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
126/// VeridianOS Shell
127pub struct Shell {
128    /// Shell configuration
129    config: ShellConfig,
130
131    /// Environment variables
132    pub(crate) environment: RwLock<BTreeMap<String, String>>,
133
134    /// Command history
135    pub(crate) history: RwLock<Vec<String>>,
136
137    /// Built-in commands
138    pub(crate) builtins: RwLock<BTreeMap<String, Box<dyn BuiltinCommand>>>,
139
140    /// Current working directory
141    cwd: RwLock<String>,
142
143    /// Last exit code
144    pub(crate) last_exit_code: RwLock<i32>,
145
146    /// Shell is running
147    running: RwLock<bool>,
148
149    /// Line editor for interactive input
150    line_editor: RwLock<line_editor::LineEditor>,
151
152    /// Job table for background process tracking
153    pub(crate) job_table: RwLock<jobs::JobTable>,
154
155    /// User-defined function registry
156    pub(crate) function_registry: RwLock<functions::FunctionRegistry>,
157
158    /// Command alias registry
159    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    /// Create a new shell
170    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        // Initialize environment
186        shell.init_environment();
187
188        // Register built-in commands
189        shell.register_builtins();
190
191        shell
192    }
193
194    /// Run the shell
195    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            // Display prompt
208            let _prompt = self.expand_prompt();
209            crate::print!("{}", _prompt);
210            crate::graphics::fbcon::flush();
211            crate::graphics::fbcon::update_cursor();
212
213            // Read command
214            let command_line = self.read_line();
215
216            // Add to history
217            if !command_line.trim().is_empty() {
218                self.add_to_history(command_line.clone());
219            }
220
221            // Execute command
222            let result = self.execute_command(&command_line);
223
224            // Handle result
225            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            // Notify the user about completed background jobs
249            self.notify_completed_jobs();
250        }
251
252        // Exit the shell process
253        crate::process::lifecycle::exit_process(*self.last_exit_code.read());
254
255        // Should never reach here after exit_process
256        loop {
257            // SAFETY: These halt/wait-for-interrupt instructions are the
258            // standard low-power idle mechanism for each architecture. They
259            // are safe in this unreachable context after process exit.
260            #[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    /// Execute a command line.
285    ///
286    /// Supports `&&`, `||`, `;` operators, pipes (`|`), I/O redirections
287    /// (`>`, `>>`, `<`, `<<<`, `2>`, `2>&1`), variable expansion, alias
288    /// expansion, glob pattern expansion, command substitution (`$(...)`),
289    /// and subshell grouping (`(cmd1; cmd2)`).
290    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        // --- Phase 0: Subshell grouping `(cmd1; cmd2)` ---
298        if let Some(result) = self.try_execute_subshell(trimmed) {
299            return result;
300        }
301
302        // --- Phase 1: Handle && / || / ; command lists ---
303        if let Some(result) = self.try_execute_list(trimmed) {
304            return result;
305        }
306
307        // --- Phase 2: Expand aliases ---
308        let expanded_alias = aliases::expand_aliases(trimmed, &self.alias_registry.read());
309
310        // --- Phase 3: Expand variables ---
311        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        // --- Phase 4: Check for background execution (`&` suffix) ---
316        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        // --- Phase 5: Handle pipes ---
328        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        // --- Phase 6: Tokenize, expand globs, parse redirections ---
334        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        // --- Phase 7: Check user-defined functions ---
351        {
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        // --- Phase 8: Check built-in commands ---
365        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            // Apply output redirections if any
370            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        // --- Phase 9: Try external command ---
384        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    /// Try to split and execute a command list using `;`, `&&`, or `||`.
398    ///
399    /// Returns `None` if the command contains no list operators.
400    fn try_execute_list(&self, command_line: &str) -> Option<CommandResult> {
401        // Split on `;` (sequential execution, lowest precedence)
402        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        // Split on `&&` (AND list — right only if left succeeds)
417        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        // Split on `||` (OR list — right only if left fails)
431        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    /// Try to execute a subshell grouping: `(cmd1; cmd2; ...)`.
446    ///
447    /// If the entire command line is wrapped in parentheses, the inner
448    /// commands are executed sequentially in a nested scope. The subshell
449    /// inherits the current environment but does not propagate changes back
450    /// (since we have no fork semantics yet, environment isolation is
451    /// noted but not enforced — matching the kernel-space limitation).
452    ///
453    /// Returns `None` if the command is not a subshell grouping.
454    fn try_execute_subshell(&self, command_line: &str) -> Option<CommandResult> {
455        let trimmed = command_line.trim();
456
457        // Must start with '(' and end with ')'
458        if !trimmed.starts_with('(') || !trimmed.ends_with(')') {
459            return None;
460        }
461
462        // Verify balanced parentheses — the outer parens must match
463        let inner = &trimmed[1..trimmed.len() - 1];
464
465        // Check that the opening '(' at position 0 matches the closing ')' at
466        // the end, not some intermediate grouping like `(a) && (b)`.
467        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                        // The opening paren closed before the end, so this is
475                        // not a single subshell grouping.
476                        return None;
477                    }
478                }
479                _ => {}
480            }
481        }
482
483        if depth != 1 {
484            // Unbalanced parens inside — not a valid subshell
485            return None;
486        }
487
488        // Execute the inner commands (semicolon-separated) sequentially
489        let inner = inner.trim();
490        if inner.is_empty() {
491            return Some(CommandResult::Success(0));
492        }
493
494        // The inner content is executed as a regular command line, which
495        // handles `;`, `&&`, `||`, nested subshells, etc.
496        Some(self.execute_command(inner))
497    }
498
499    /// Initialize the console device by creating a PTY pair.
500    ///
501    /// Creates a PTY master/slave pair via the PTY manager and optionally
502    /// sets the terminal size. Shell output can then be routed through the
503    /// PTY slave while falling back to serial when PTY is unavailable.
504    ///
505    /// Returns `Ok((master_id, slave_id))` on success.
506    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        // Set default terminal size (80x24)
514        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        // Store the PTY IDs in the environment for child processes
526        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    /// Execute a pipeline of commands connected by pipes.
537    fn execute_pipeline(&self, segments: &[&str]) -> CommandResult {
538        if segments.len() < 2 {
539            return self.execute_command(segments[0]);
540        }
541
542        // For kernel-space shell, we execute each segment and pipe data
543        // through kernel pipe objects.
544        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                // Last command in pipeline: if we have piped input, provide it
556                // via stdin redirection (for builtins that support it).
557                // For now, just execute the command normally.
558                let result = self.execute_command(segment);
559                return result;
560            }
561
562            // Create a pipe for this stage
563            match crate::fs::pipe::create_pipe() {
564                Ok((reader, writer)) => {
565                    // Execute the command — for builtins, output goes to serial.
566                    // We capture what we can via the pipe.
567                    let _result = self.execute_command(segment);
568
569                    // Close the writer end
570                    writer.close();
571
572                    // Drain the pipe for the next stage
573                    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    /// Get current working directory
585    pub fn get_cwd(&self) -> String {
586        self.cwd.read().clone()
587    }
588
589    /// Set current working directory
590    pub fn set_cwd(&self, path: String) -> Result<(), crate::error::KernelError> {
591        // Verify directory exists using VFS
592        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    /// Get environment variable
607    pub fn get_env(&self, name: &str) -> Option<String> {
608        self.environment.read().get(name).cloned()
609    }
610
611    /// Set environment variable
612    pub fn set_env(&self, name: String, value: String) {
613        self.environment.write().insert(name, value);
614    }
615
616    /// Get all environment variables
617    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    /// Stop the shell
626    pub fn stop(&self) {
627        *self.running.write() = false;
628    }
629
630    /// Register a builtin command (public API for external modules)
631    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    /// Register multiple builtin commands at once
637    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    // ========================================================================
645    // Private methods
646    // ========================================================================
647
648    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        // Help command
665        builtins.insert("help".into(), Box::new(HelpCommand));
666        builtins.insert("?".into(), Box::new(HelpCommand));
667
668        // Directory commands
669        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        // File commands
675        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        // System commands
681        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        // Environment commands
688        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        // Package management
693        builtins.insert("pkg".into(), Box::new(PkgCommand));
694
695        // Shell commands
696        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        // Utility commands
702        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        // Text processing commands
708        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        // I/O commands
720        builtins.insert("read".into(), Box::new(ReadCommand));
721
722        // File management commands
723        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        // System information commands
728        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        // Shell control commands
736        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        // Job control commands
745        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        // Performance commands
750        builtins.insert("perf".into(), Box::new(PerfCommand));
751        builtins.insert("trace".into(), Box::new(TraceCommand));
752
753        // Hardware diagnostics commands
754        builtins.insert("acpi".into(), Box::new(AcpiCommand));
755
756        // Network commands
757        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        // Desktop / GUI commands
765        builtins.insert("startgui".into(), Box::new(StartGuiCommand));
766
767        // Audio commands
768        builtins.insert("play".into(), Box::new(PlayCommand));
769        builtins.insert("volume".into(), Box::new(VolumeCommand));
770
771        // Virtualization commands
772        builtins.insert("vmx".into(), Box::new(VmxCommand));
773        builtins.insert("container".into(), Box::new(ContainerCommand));
774
775        // Diagnostics commands
776        builtins.insert("numa".into(), Box::new(NumaCommand));
777        builtins.insert("kpti".into(), Box::new(KptiCommand));
778
779        // Hardware discovery commands
780        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        // Memory & performance commands
785        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        // Security commands
790        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        // Crypto commands
796        builtins.insert("blake3sum".into(), Box::new(Blake3sumCommand));
797        builtins.insert("sha256sum".into(), Box::new(Sha256sumCommand));
798
799        // IPC commands
800        builtins.insert("ipcs".into(), Box::new(IpcsCommand));
801
802        // Networking commands
803        builtins.insert("route".into(), Box::new(RouteCommand));
804        builtins.insert("ss".into(), Box::new(SsCommand));
805
806        // Extended network commands
807        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        // Desktop commands
825        builtins.insert("winfo".into(), Box::new(WinfoCommand));
826
827        // Virtualization / namespace commands
828        builtins.insert("lsns".into(), Box::new(LsnsCommand));
829
830        // Development tools
831        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        // System diagnostics
838        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        // Hardware info
846        builtins.insert("hwinfo".into(), Box::new(HwinfoCommand));
847
848        // Cloud/container commands
849        builtins.insert("cloud-init".into(), Box::new(CloudInitCommand));
850        builtins.insert("kubectl".into(), Box::new(KubectlCommand));
851
852        // Server commands
853        builtins.insert("http-server".into(), Box::new(HttpServerCommand));
854        builtins.insert("sshd".into(), Box::new(SshdCommand));
855
856        // Desktop commands (extended)
857        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        // User/group management
863        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        // Service and power management
873        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        // Scheduling
881        builtins.insert("crontab".into(), Box::new(CrontabCommand));
882        builtins.insert("at".into(), Box::new(AtCommand));
883
884        // Filesystem tools
885        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        // Hardware/storage
894        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 is an absolute or relative path, try it directly first
931        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        // Try to find the command in PATH
961        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            // Check if file exists using VFS
972            if let Ok(_node) = crate::fs::get_vfs().read().resolve_path(&full_path) {
973                crate::println!("[SHELL] Found executable: {}", full_path);
974                // Load and execute the program
975                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                        // Run the user process directly like bootstrap does.
987                        // This transfers control to Ring 3 via iretq and returns
988                        // when the process exits via sys_exit.
989                        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        // Replace prompt variables
1010        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                            // Clear screen and redraw prompt + current line
1056                            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                            // Reposition cursor if not at end
1062                            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                            // Collect builtin names
1074                            let builtins_guard = self.builtins.read();
1075                            let builtin_names: Vec<&str> =
1076                                builtins_guard.keys().map(|s| s.as_str()).collect();
1077
1078                            // Collect environment variable names
1079                            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                                // No matches — audible bell
1098                                crate::print!("\x07");
1099                            } else {
1100                                // Find start of the word being completed
1101                                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                                    // Single match — replace word directly
1110                                    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                                    // Multiple matches — show candidates, insert
1117                                    // longest common prefix
1118                                    let lcp = completion::longest_common_prefix(&candidates);
1119
1120                                    // Extend word to lcp if it's longer
1121                                    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                                    // Show candidates
1127                                    crate::print!("\n");
1128                                    for candidate in &candidates {
1129                                        crate::print!("{}  ", candidate);
1130                                    }
1131                                    crate::print!("\n");
1132
1133                                    // Redraw prompt + current line
1134                                    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                            // Attempt to suspend the foreground job. In the
1151                            // kernel shell there is no true foreground process
1152                            // to SIGTSTP, so we print the indicator and return
1153                            // an empty line (matching bash behavior when there
1154                            // is nothing to suspend).
1155                            self.suspend_foreground_job();
1156                            // Redraw prompt — the caller will see an empty
1157                            // command and re-prompt.
1158                            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                            // Incomplete escape sequence (e.g., first byte of
1167                            // arrow key).
1168                            // No visible change yet — skip MMIO update.
1169                        }
1170                    }
1171                }
1172                None => {
1173                    // No input available — yield CPU briefly.
1174                    // We use spin_loop() rather than hlt/wfi because the APIC
1175                    // is initialized and takes over interrupt routing from the
1176                    // PIC, so PIC-based timer/keyboard IRQs may not fire.
1177                    // Input is polled from serial + keyboard ring buffer.
1178                    // Multiple iterations (~1us delay) reduces idle CPU usage
1179                    // and gives QEMU's display thread more time to render.
1180                    for _ in 0..256 {
1181                        core::hint::spin_loop();
1182                    }
1183                }
1184            }
1185        }
1186    }
1187
1188    /// Read a single byte from any available input source (keyboard + serial).
1189    fn read_char() -> Option<u8> {
1190        crate::drivers::input::read_char()
1191    }
1192
1193    /// Handle a signal delivered to the shell or its foreground job.
1194    ///
1195    /// Dispatches to the appropriate handler based on the signal number.
1196    /// Uses POSIX signal constants from `crate::process::exit::signals`.
1197    #[allow(dead_code)] // Signal handling -- wired when job control is fully active
1198    fn handle_signal(&self, signum: i32) {
1199        use crate::process::exit::signals;
1200
1201        match signum {
1202            signals::SIGINT => {
1203                // Interrupt: if there is a foreground job, send SIGINT to it.
1204                // Otherwise, just cancel the current input line (handled in
1205                // read_line via EditResult::Cancel).
1206                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                // Terminal stop: suspend the foreground job.
1219                self.suspend_foreground_job();
1220            }
1221            signals::SIGCHLD => {
1222                // Child status changed: reap completed jobs and notify.
1223                self.notify_completed_jobs();
1224            }
1225            signals::SIGCONT => {
1226                // Continue: nothing special for the shell itself.
1227            }
1228            _ => {}
1229        }
1230    }
1231
1232    /// Suspend the current foreground job (if any) by sending SIGTSTP to
1233    /// all of its processes and marking it as Stopped in the job table.
1234    fn suspend_foreground_job(&self) {
1235        use crate::process::exit::signals;
1236
1237        let mut job_table = self.job_table.write();
1238        // The "current" job is the most recently added one.
1239        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                // Send SIGTSTP to every process in the job's pipeline.
1245                for &pid in &job.pids {
1246                    let _ = crate::process::exit::kill_process(
1247                        crate::process::ProcessId(pid),
1248                        signals::SIGTSTP,
1249                    );
1250                }
1251
1252                // Update the job table entry.
1253                job_table.update_status(job_id, jobs::JobStatus::Stopped);
1254                crate::println!("[{}]+  Stopped                 {}", job_id, _cmd);
1255            }
1256        }
1257        // If there is no running foreground job, Ctrl-Z is a no-op (bash
1258        // prints nothing and returns to the prompt).
1259    }
1260
1261    /// Reap completed background jobs and print notifications.
1262    ///
1263    /// Called once per REPL iteration so the user sees "[N]+ Done ..." lines
1264    /// immediately before the next prompt, matching bash/zsh behavior.
1265    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        // Limit history size
1281        while history.len() > self.config.history_size {
1282            history.remove(0);
1283        }
1284    }
1285}
1286
1287/// Run a user process directly from the shell, similar to bootstrap's approach.
1288///
1289/// This function transfers control to Ring 3 via iretq and returns when the
1290/// process exits. Unlike wait_for_child(), this doesn't block the shell's
1291/// process context or depend on scheduler wakeup semantics.
1292///
1293/// Returns the process's exit code.
1294fn 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    // Architecture-specific user-mode entry
1308    #[cfg(target_arch = "x86_64")]
1309    {
1310        // Get the process's page table root (physical address for CR3)
1311        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        // Get entry point, user stack, and thread ID from the process's first thread
1320        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 locks before entering user mode
1343        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        // Register as the current process so that current_process() /
1353        // current_thread() return the correct values during syscalls
1354        // (required for arch_prctl, brk, mmap, etc.).
1355        crate::process::set_boot_current(pid, tid);
1356
1357        // User CS and SS selectors (Ring 3)
1358        let user_cs: u64 = 0x33; // GDT index 6, RPL 3
1359        let user_ss: u64 = 0x2B; // GDT index 5, RPL 3
1360
1361        // Enter Ring 3 via iretq with returnable context.
1362        // When the user process calls sys_exit, this call returns normally
1363        // with the exit code.
1364        //
1365        // SAFETY: All preconditions for enter_usermode_returnable are met:
1366        // - entry_point is in the process's user-space page tables
1367        // - user_stack_ptr points to the top of the user stack
1368        // - CS/SS are valid Ring 3 selectors from the GDT
1369        // - pt_root is a valid L4 page table with kernel mappings preserved
1370        // - kernel_rsp_ptr points to the per-CPU kernel_rsp field
1371        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        // Clear boot current tracking after user process exits
1384        crate::process::clear_boot_current();
1385
1386        crate::println!("[SHELL] Returned from Ring 3");
1387
1388        // Free the process's page table hierarchy frames. Deferred from
1389        // cleanup_process() because the process's CR3 was still active
1390        // at that point. Now that boot CR3 is restored, it is safe.
1391        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        // Free post-exec page table if exec changed it
1398        if current_pt_root != 0 && current_pt_root != pt_root {
1399            crate::mm::vas::free_user_page_table_frames(current_pt_root);
1400        }
1401        // Free the original page table
1402        if pt_root != 0 {
1403            crate::mm::vas::free_user_page_table_frames(pt_root);
1404        }
1405        // Clear so no double-free
1406        if let Some(proc) = get_process(pid) {
1407            proc.memory_space.lock().set_page_table(0);
1408        }
1409
1410        // Get exit code and reap zombie
1411        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        // For AArch64 and RISC-V, user-mode entry is not yet implemented in the shell.
1430        // Fall back to yielding and waiting (this will still freeze, but at least
1431        // it compiles and shows what needs to be done).
1432        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}