gioser

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 188Rust 55Shell 30Markdown 281211 Definiciones

⎇ 6809ef4be28f606aae856890a74667187ff75b05 crates/simi/src/main.rs Rust · 144 líneas · 5.1 KB Historia Quién tocó qué crudo

simi: el /bin/sh propio del producto — 4448 líneas, sólo libc, y CERO divergencias contra el ash 6809ef4be · Sergio ·

1//! simi — el `/bin/sh` del producto.
2//!
3//! «simi» es *boca, lengua* en quechua: es cómo se le habla a la máquina.
4//!
5//! **Por qué existe.** El frente C del ADR 0008 pone a busybox en la raíz de confianza del arranque
6//! (`STAGE1_COMPONENTS`, `ATTEST_PATHS`) y en el `exec` de cada card de arje. Botarlo pedía un `sh`,
7//! y el candidato que se midió —brush 0.4.0— pesa 6,9 MB, arranca 3,7× más lento que el ash de
8//! busybox y trae dos divergencias medidas contra POSIX (reubeno/brush#1394 y #1396).
9//!
10//! **Alcance, y está MEDIDO, no supuesto** (`docs/plan-botar-busybox.md`):
11//!
12//! | consumidor | qué usa |
13//! |---|---|
14//! | las 23 cards de arje con shell | 8 builtins, 13 construcciones, ninguna función, ningún `if` |
15//! | los 21 `/init` empotrados de las imágenes | + `if`, funciones, `case`, aquí-documentos, `${…}`, posicionales |
16//! | `takana-live-install.sh` (corre en la imagen viva) | POSIX entero |
17//!
18//! Lo que NO está y se dice en vez de fingirlo: control de trabajos (`jobs`/`fg`/`bg`), edición de
19//! línea, historial, `alias`, `getopts`, `[[ ]]`, arrays. El shell interactivo de `getty` es otro
20//! consumidor y no está cubierto todavía.
21//!
22//! Uso: `simi [-c ORDEN | FICHERO] [-e] [-u] [-x] [-n] [ARGS…]`, y por stdin si no hay ninguno.
23
24mod arith;
25mod ast;
26mod builtins;
27mod exec;
28mod expand;
29mod glob;
30mod lexer;
31mod parser;
32mod shell;
33
34use std::io::Read;
35
36use shell::{Flujo, Shell};
37
38fn main() -> std::process::ExitCode {
39    let args: Vec<String> = std::env::args().collect();
40    let mut sh = Shell::nuevo();
41    // argv[0] con guión delante = shell de login; hoy sólo cambia el nombre en los mensajes.
42    let argv0 = args.first().cloned().unwrap_or_else(|| "simi".into());
43    sh.nombre = argv0.trim_start_matches('-').rsplit('/').next().unwrap_or("simi").to_string();
44
45    let mut i = 1;
46    let mut orden: Option<String> = None;
47    let mut fichero: Option<String> = None;
48    while i < args.len() {
49        let a = &args[i];
50        match a.as_str() {
51            "-c" => {
52                i += 1;
53                match args.get(i) {
54                    Some(c) => orden = Some(c.clone()),
55                    None => {
56                        eprintln!("{}: -c necesita una orden", sh.nombre);
57                        return std::process::ExitCode::from(2);
58                    }
59                }
60            }
61            "-s" => {}
62            "--" => {
63                i += 1;
64                break;
65            }
66            _ if a.starts_with('-') && a.len() > 1 && !a.starts_with("--") => {
67                for c in a[1..].chars() {
68                    match c {
69                        'e' => sh.opts.errexit = true,
70                        'u' => sh.opts.nounset = true,
71                        'x' => sh.opts.xtrace = true,
72                        'n' => sh.opts.noexec = true,
73                        'f' => sh.opts.noglob = true,
74                        'i' | 'l' => {}
75                        otro => {
76                            eprintln!("{}: -{otro}: opción no soportada", sh.nombre);
77                            return std::process::ExitCode::from(2);
78                        }
79                    }
80                }
81            }
82            _ => {
83                if orden.is_none() {
84                    fichero = Some(a.clone());
85                }
86                break;
87            }
88        }
89        i += 1;
90    }
91
92    // Los argumentos que quedan son los posicionales. Con `-c`, el primero es `$0`.
93    let resto: Vec<String> = args[i..].to_vec();
94    let texto = match (&orden, &fichero) {
95        (Some(c), _) => {
96            if !resto.is_empty() {
97                sh.nombre = resto[0].clone();
98                sh.params = resto[1..].to_vec();
99            }
100            c.clone()
101        }
102        (None, Some(f)) => {
103            sh.nombre = f.clone();
104            sh.params = resto[1..].to_vec();
105            match std::fs::read_to_string(f) {
106                Ok(t) => t,
107                Err(e) => {
108                    eprintln!("{}: {}: {}", argv0, f, e);
109                    return std::process::ExitCode::from(127);
110                }
111            }
112        }
113        (None, None) => {
114            let mut t = String::new();
115            if std::io::stdin().read_to_string(&mut t).is_err() {
116                eprintln!("{}: no pude leer la entrada", sh.nombre);
117                return std::process::ExitCode::from(2);
118            }
119            t
120        }
121    };
122
123    let lista = match parser::parsear(&texto) {
124        Ok(l) => l,
125        Err(e) => {
126            eprintln!("{}: error de sintaxis: {}", sh.nombre, e);
127            return std::process::ExitCode::from(2);
128        }
129    };
130    // `-n`: parsear y no ejecutar. Es lo que usa el banco diferencial, y lo que hace que un guion
131    // roto se detecte sin correrlo.
132    if sh.opts.noexec {
133        return std::process::ExitCode::SUCCESS;
134    }
135
136    let flujo = exec::correr_lista(&mut sh, &lista);
137    let estado = match flujo {
138        Flujo::Exit(c) => c,
139        _ => sh.estado,
140    };
141    exec::correr_trap_exit(&mut sh, estado);
142    let final_ = if sh.trap_exit_corrido { estado } else { sh.estado };
143    std::process::ExitCode::from((final_ & 0xff) as u8)
144}