//! simi — el `/bin/sh` del producto. //! //! «simi» es *boca, lengua* en quechua: es cómo se le habla a la máquina. //! //! **Por qué existe.** El frente C del ADR 0008 pone a busybox en la raíz de confianza del arranque //! (`STAGE1_COMPONENTS`, `ATTEST_PATHS`) y en el `exec` de cada card de arje. Botarlo pedía un `sh`, //! y el candidato que se midió —brush 0.4.0— pesa 6,9 MB, arranca 3,7× más lento que el ash de //! busybox y trae dos divergencias medidas contra POSIX (reubeno/brush#1394 y #1396). //! //! **Alcance, y está MEDIDO, no supuesto** (`docs/plan-botar-busybox.md`): //! //! | consumidor | qué usa | //! |---|---| //! | las 23 cards de arje con shell | 8 builtins, 13 construcciones, ninguna función, ningún `if` | //! | los 21 `/init` empotrados de las imágenes | + `if`, funciones, `case`, aquí-documentos, `${…}`, posicionales | //! | `takana-live-install.sh` (corre en la imagen viva) | POSIX entero | //! //! Lo que NO está y se dice en vez de fingirlo: control de trabajos (`jobs`/`fg`/`bg`), edición de //! línea, historial, `alias`, `getopts`, `[[ ]]`, arrays. El shell interactivo de `getty` es otro //! consumidor y no está cubierto todavía. //! //! Uso: `simi [-c ORDEN | FICHERO] [-e] [-u] [-x] [-n] [ARGS…]`, y por stdin si no hay ninguno. mod arith; mod ast; mod builtins; mod exec; mod expand; mod glob; mod lexer; mod parser; mod shell; use std::io::Read; use shell::{Flujo, Shell}; fn main() -> std::process::ExitCode { let args: Vec = std::env::args().collect(); let mut sh = Shell::nuevo(); // argv[0] con guión delante = shell de login; hoy sólo cambia el nombre en los mensajes. let argv0 = args.first().cloned().unwrap_or_else(|| "simi".into()); sh.nombre = argv0.trim_start_matches('-').rsplit('/').next().unwrap_or("simi").to_string(); let mut i = 1; let mut orden: Option = None; let mut fichero: Option = None; while i < args.len() { let a = &args[i]; match a.as_str() { "-c" => { i += 1; match args.get(i) { Some(c) => orden = Some(c.clone()), None => { eprintln!("{}: -c necesita una orden", sh.nombre); return std::process::ExitCode::from(2); } } } "-s" => {} "--" => { i += 1; break; } _ if a.starts_with('-') && a.len() > 1 && !a.starts_with("--") => { for c in a[1..].chars() { match c { 'e' => sh.opts.errexit = true, 'u' => sh.opts.nounset = true, 'x' => sh.opts.xtrace = true, 'n' => sh.opts.noexec = true, 'f' => sh.opts.noglob = true, 'i' | 'l' => {} otro => { eprintln!("{}: -{otro}: opción no soportada", sh.nombre); return std::process::ExitCode::from(2); } } } } _ => { if orden.is_none() { fichero = Some(a.clone()); } break; } } i += 1; } // Los argumentos que quedan son los posicionales. Con `-c`, el primero es `$0`. let resto: Vec = args[i..].to_vec(); let texto = match (&orden, &fichero) { (Some(c), _) => { if !resto.is_empty() { sh.nombre = resto[0].clone(); sh.params = resto[1..].to_vec(); } c.clone() } (None, Some(f)) => { sh.nombre = f.clone(); sh.params = resto[1..].to_vec(); match std::fs::read_to_string(f) { Ok(t) => t, Err(e) => { eprintln!("{}: {}: {}", argv0, f, e); return std::process::ExitCode::from(127); } } } (None, None) => { let mut t = String::new(); if std::io::stdin().read_to_string(&mut t).is_err() { eprintln!("{}: no pude leer la entrada", sh.nombre); return std::process::ExitCode::from(2); } t } }; let lista = match parser::parsear(&texto) { Ok(l) => l, Err(e) => { eprintln!("{}: error de sintaxis: {}", sh.nombre, e); return std::process::ExitCode::from(2); } }; // `-n`: parsear y no ejecutar. Es lo que usa el banco diferencial, y lo que hace que un guion // roto se detecte sin correrlo. if sh.opts.noexec { return std::process::ExitCode::SUCCESS; } let flujo = exec::correr_lista(&mut sh, &lista); let estado = match flujo { Flujo::Exit(c) => c, _ => sh.estado, }; exec::correr_trap_exit(&mut sh, estado); let final_ = if sh.trap_exit_corrido { estado } else { sh.estado }; std::process::ExitCode::from((final_ & 0xff) as u8) }