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 815Rust 56Shell 37Markdown 291229 Definiciones

⎇ main crates/simi/src/exec.rs Rust · 864 líneas · 29.8 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//! Ejecución: redirecciones, tuberías, subshells, funciones y el `fork`/`execve`.
2//!
3//! Dos invariantes que este fichero sostiene a propósito, porque las dos se midieron rotas en el
4//! candidato que se evaluó antes (ver `docs/plan-botar-busybox.md`):
5//!
6//! 1. **El `trap EXIT` de un subshell CORRE.** No sólo el del shell de arriba: también el de
7//!    `( … )`, el de `$( … )` y el de un componente de tubería. Es reubeno/brush#1396, y falla en
8//!    silencio: el script termina con `rc=0` y la limpieza no se hizo.
9//! 2. **`exec` reemplaza el proceso.** Las cards de arje son `sh -c '…; exec <daemon>'`: si el
10//!    shell forkea, arje supervisa al shell y no al daemon, y el reinicio apunta al proceso
11//!    equivocado.
12//!
13//! Sobre `fork` en Rust: este proceso es **de un solo hilo** a propósito (ni tokio ni threads), que
14//! es lo que hace legítimo el `fork` — igual que dash en C. Entre el `fork` y el `execve` el hijo
15//! no toma locks ajenos.
16
17use std::ffi::CString;
18use std::io::{Read, Write};
19use std::os::unix::io::{AsRawFd, FromRawFd};
20
21use crate::ast::*;
22use crate::expand;
23use crate::parser::parsear;
24use crate::shell::{Flujo, Shell, LLEGO_SENAL};
25
26// ───────────────────────────────────────────────────────── listas y enlaces
27
28/// Corre `f` con `set -e` suspendido. Ver `Shell::errexit_suspendido`.
29fn suspendiendo_errexit<T>(sh: &mut Shell, f: impl FnOnce(&mut Shell) -> T) -> T {
30    sh.errexit_suspendido += 1;
31    let r = f(sh);
32    sh.errexit_suspendido -= 1;
33    r
34}
35
36/// Lo que hay que hacer al ENTRAR en un subshell, en los cuatro sitios que forkean.
37///
38/// POSIX §2.12: los traps heredados que no estén ignorados se ponen en su acción por defecto. O
39/// sea que un subshell **no** corre el `trap EXIT` del padre — sólo el que él mismo ponga. Las dos
40/// mitades son distintas y confundirlas es fácil: la otra mitad (que el trap PROPIO del subshell sí
41/// corre) es reubeno/brush#1396. Acá se rompió la primera versión de simi, al revés que brush, y no
42/// lo vio el banco diferencial sino la prueba de regresión — los dos oráculos hacen falta.
43fn entrar_en_subshell(sh: &mut Shell) {
44    sh.es_subshell = true;
45    sh.traps.clear();
46}
47
48fn debe_abortar(sh: &Shell) -> bool {
49    sh.opts.errexit && sh.errexit_suspendido == 0 && sh.estado != 0
50}
51
52pub fn correr_lista(sh: &mut Shell, l: &Lista) -> Flujo {
53    for (yo, fin) in l {
54        atender_senales(sh);
55        match fin {
56            Fin::SegundoPlano => {
57                lanzar_al_fondo(sh, yo);
58            }
59            Fin::Secuencial => {
60                let f = correr_yo(sh, yo, false);
61                if f != Flujo::Normal {
62                    return f;
63                }
64            }
65        }
66    }
67    Flujo::Normal
68}
69
70/// `permite_fallo`: estamos en una condición (`if`, `while`, lado izquierdo de `&&`, `!`), donde
71/// un estado distinto de cero **no** dispara `set -e`.
72pub fn correr_yo(sh: &mut Shell, yo: &YO, permite_fallo: bool) -> Flujo {
73    let cuerpo = |sh: &mut Shell| -> Flujo {
74        let hay_resto = !yo.resto.is_empty();
75        // La primera de una cadena `&&`/`||` es una CONDICIÓN: su fallo no aborta.
76        let f = if hay_resto {
77            suspendiendo_errexit(sh, |sh| correr_tuberia(sh, &yo.primera))
78        } else {
79            correr_tuberia(sh, &yo.primera)
80        };
81        if f != Flujo::Normal {
82            return f;
83        }
84        for (n, (enlace, t)) in yo.resto.iter().enumerate() {
85            let ultima = n + 1 == yo.resto.len();
86            let saltar = match enlace {
87                Enlace::Y => sh.estado != 0,
88                Enlace::O => sh.estado == 0,
89            };
90            if saltar {
91                continue;
92            }
93            let f = if ultima {
94                correr_tuberia(sh, t)
95            } else {
96                suspendiendo_errexit(sh, |sh| correr_tuberia(sh, t))
97            };
98            if f != Flujo::Normal {
99                return f;
100            }
101        }
102        if debe_abortar(sh) {
103            return Flujo::Exit(sh.estado);
104        }
105        Flujo::Normal
106    };
107    if permite_fallo {
108        suspendiendo_errexit(sh, cuerpo)
109    } else {
110        cuerpo(sh)
111    }
112}
113
114fn correr_tuberia(sh: &mut Shell, t: &Tuberia) -> Flujo {
115    if t.ordenes.len() == 1 {
116        // `! cmd` es una condición: el fallo de `cmd` no aborta.
117        let f = if t.negada {
118            suspendiendo_errexit(sh, |sh| correr_orden(sh, &t.ordenes[0]))
119        } else {
120            correr_orden(sh, &t.ordenes[0])
121        };
122        if t.negada {
123            sh.estado = (sh.estado == 0) as i32;
124        }
125        if debe_abortar(sh) && f == Flujo::Normal {
126            return Flujo::Exit(sh.estado);
127        }
128        return f;
129    }
130
131    let n = t.ordenes.len();
132    let mut entrada: Option<i32> = None;
133    let mut pids: Vec<i32> = Vec::new();
134    for (i, orden) in t.ordenes.iter().enumerate() {
135        let (lectura, escritura) = if i + 1 < n {
136            let mut fds = [0i32; 2];
137            if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
138                sh.error("no pude crear la tubería");
139                sh.estado = 1;
140                return Flujo::Normal;
141            }
142            (Some(fds[0]), Some(fds[1]))
143        } else {
144            (None, None)
145        };
146        let pid = unsafe { libc::fork() };
147        if pid == 0 {
148            // hijo
149            if let Some(e) = entrada {
150                unsafe { libc::dup2(e, 0) };
151                unsafe { libc::close(e) };
152            }
153            if let Some(w) = escritura {
154                unsafe { libc::dup2(w, 1) };
155                unsafe { libc::close(w) };
156            }
157            if let Some(r) = lectura {
158                unsafe { libc::close(r) };
159            }
160            entrar_en_subshell(sh);
161            let f = correr_orden(sh, orden);
162            let estado = match f {
163                Flujo::Exit(c) => c,
164                _ => sh.estado,
165            };
166            salir_de_subshell(sh, estado);
167        }
168        if let Some(e) = entrada {
169            unsafe { libc::close(e) };
170        }
171        if let Some(w) = escritura {
172            unsafe { libc::close(w) };
173        }
174        entrada = lectura;
175        pids.push(pid);
176    }
177    let mut ultimo = 0;
178    for pid in pids {
179        ultimo = esperar(pid);
180    }
181    sh.estado = if t.negada { (ultimo == 0) as i32 } else { ultimo };
182    if debe_abortar(sh) {
183        return Flujo::Exit(sh.estado);
184    }
185    Flujo::Normal
186}
187
188fn lanzar_al_fondo(sh: &mut Shell, yo: &YO) {
189    let pid = unsafe { libc::fork() };
190    if pid == 0 {
191        entrar_en_subshell(sh);
192        let f = correr_yo(sh, yo, true);
193        let estado = match f {
194            Flujo::Exit(c) => c,
195            _ => sh.estado,
196        };
197        salir_de_subshell(sh, estado);
198    }
199    sh.ultimo_fondo = Some(pid);
200    sh.estado = 0;
201}
202
203// ───────────────────────────────────────────────────────── órdenes
204
205fn correr_orden(sh: &mut Shell, o: &Orden) -> Flujo {
206    match o {
207        Orden::Funcion { nombre, cuerpo } => {
208            sh.funcs.insert(nombre.clone(), (**cuerpo).clone());
209            sh.estado = 0;
210            Flujo::Normal
211        }
212        Orden::Simple { asignaciones, palabras, redirs } => {
213            correr_simple(sh, asignaciones, palabras, redirs)
214        }
215        Orden::Grupo { cuerpo, redirs } => con_redirs(sh, redirs, |sh| correr_lista(sh, cuerpo)),
216        Orden::Subshell { cuerpo, redirs } => {
217            let pid = unsafe { libc::fork() };
218            if pid == 0 {
219                entrar_en_subshell(sh);
220                let f = match aplicar(sh, redirs) {
221                    Ok(_) => correr_lista(sh, cuerpo),
222                    Err(e) => {
223                        sh.error(&e);
224                        sh.estado = 1;
225                        Flujo::Normal
226                    }
227                };
228                let estado = match f {
229                    Flujo::Exit(c) => c,
230                    _ => sh.estado,
231                };
232                salir_de_subshell(sh, estado);
233            }
234            sh.estado = esperar(pid);
235            Flujo::Normal
236        }
237        Orden::Si { ramas, sino, redirs } => con_redirs(sh, redirs, |sh| {
238            for (cond, cuerpo) in ramas {
239                let f = correr_lista_condicion(sh, cond);
240                if let Some(f) = f {
241                    return f;
242                }
243                if sh.estado == 0 {
244                    return correr_lista(sh, cuerpo);
245                }
246            }
247            match sino {
248                Some(l) => correr_lista(sh, l),
249                None => {
250                    sh.estado = 0;
251                    Flujo::Normal
252                }
253            }
254        }),
255        Orden::Bucle { hasta, cond, cuerpo, redirs } => con_redirs(sh, redirs, |sh| {
256            sh.bucles += 1;
257            let mut ultimo = 0;
258            let salida = loop {
259                if let Some(f) = correr_lista_condicion(sh, cond) {
260                    break f;
261                }
262                let seguir = if *hasta { sh.estado != 0 } else { sh.estado == 0 };
263                if !seguir {
264                    break Flujo::Normal;
265                }
266                match correr_lista(sh, cuerpo) {
267                    Flujo::Break(n) => {
268                        if n > 1 {
269                            break Flujo::Break(n - 1);
270                        }
271                        break Flujo::Normal;
272                    }
273                    Flujo::Continue(n) => {
274                        if n > 1 {
275                            break Flujo::Continue(n - 1);
276                        }
277                    }
278                    Flujo::Normal => {}
279                    f => break f,
280                }
281                ultimo = sh.estado;
282            };
283            sh.bucles -= 1;
284            if salida == Flujo::Normal {
285                sh.estado = ultimo;
286            }
287            salida
288        }),
289        Orden::Para { var, palabras, cuerpo, redirs } => con_redirs(sh, redirs, |sh| {
290            let lista: Vec<String> = match palabras {
291                None => sh.params.clone(),
292                Some(ps) => {
293                    let mut v = Vec::new();
294                    for p in ps {
295                        match expand::campos(sh, p) {
296                            Ok(cs) => v.extend(cs),
297                            Err(e) => {
298                                sh.error(&e);
299                                sh.estado = 1;
300                                return Flujo::Normal;
301                            }
302                        }
303                    }
304                    v
305                }
306            };
307            sh.bucles += 1;
308            sh.estado = 0;
309            let salida = 'bucle: {
310                for v in lista {
311                    sh.asignar(var, &v);
312                    match correr_lista(sh, cuerpo) {
313                        Flujo::Break(n) => {
314                            if n > 1 {
315                                break 'bucle Flujo::Break(n - 1);
316                            }
317                            break 'bucle Flujo::Normal;
318                        }
319                        Flujo::Continue(n) => {
320                            if n > 1 {
321                                break 'bucle Flujo::Continue(n - 1);
322                            }
323                        }
324                        Flujo::Normal => {}
325                        f => break 'bucle f,
326                    }
327                }
328                Flujo::Normal
329            };
330            sh.bucles -= 1;
331            salida
332        }),
333        Orden::Caso { sujeto, ramas, redirs } => con_redirs(sh, redirs, |sh| {
334            let s = match expand::una(sh, sujeto) {
335                Ok(s) => s,
336                Err(e) => {
337                    sh.error(&e);
338                    sh.estado = 2;
339                    return Flujo::Exit(2);
340                }
341            };
342            for (patrones, cuerpo) in ramas {
343                for p in patrones {
344                    let pat = match expand::patron(sh, p) {
345                        Ok(p) => p,
346                        Err(e) => {
347                            sh.error(&e);
348                            sh.estado = 1;
349                            return Flujo::Normal;
350                        }
351                    };
352                    if crate::glob::encaja(&pat, &s) {
353                        if cuerpo.is_empty() {
354                            sh.estado = 0;
355                            return Flujo::Normal;
356                        }
357                        return correr_lista(sh, cuerpo);
358                    }
359                }
360            }
361            sh.estado = 0;
362            Flujo::Normal
363        }),
364    }
365}
366
367/// Corre la lista de una condición (`if`/`while`). Devuelve `Some(flujo)` sólo si hay que abandonar
368/// (un `exit` o un `return` de adentro); si no, deja el estado en `sh.estado`.
369fn correr_lista_condicion(sh: &mut Shell, l: &Lista) -> Option<Flujo> {
370    suspendiendo_errexit(sh, |sh| {
371        for (yo, _) in l {
372            let f = correr_yo(sh, yo, false);
373            if f != Flujo::Normal {
374                return Some(f);
375            }
376        }
377        None
378    })
379}
380
381fn correr_simple(
382    sh: &mut Shell,
383    asignaciones: &[Asignacion],
384    palabras: &[Palabra],
385    redirs: &[Redir],
386) -> Flujo {
387    // 1. expandir las palabras
388    let mut argv: Vec<String> = Vec::new();
389    for p in palabras {
390        match expand::campos(sh, p) {
391            Ok(cs) => argv.extend(cs),
392            Err(e) => {
393                // Un error de expansión (comilla sin cerrar en una sustitución, `${x:?}`, `set -u`)
394                // ABORTA con 2 en un shell no interactivo, no sigue con 1. Medido contra el ash.
395                sh.error(&e);
396                sh.estado = 2;
397                return Flujo::Exit(2);
398            }
399        }
400    }
401
402    // 2. las asignaciones
403    let mut pares: Vec<(String, String)> = Vec::new();
404    for a in asignaciones {
405        match expand::una(sh, &a.valor) {
406            Ok(v) => pares.push((a.nombre.clone(), v)),
407            Err(e) => {
408                // Un error de expansión (comilla sin cerrar en una sustitución, `${x:?}`, `set -u`)
409                // ABORTA con 2 en un shell no interactivo, no sigue con 1. Medido contra el ash.
410                sh.error(&e);
411                sh.estado = 2;
412                return Flujo::Exit(2);
413            }
414        }
415    }
416
417    if argv.is_empty() {
418        // sólo asignaciones (y quizá redirecciones, que se abren y se cierran)
419        match aplicar(sh, redirs) {
420            Ok(g) => restaurar(g),
421            Err(e) => {
422                // Un error de expansión (comilla sin cerrar en una sustitución, `${x:?}`, `set -u`)
423                // ABORTA con 2 en un shell no interactivo, no sigue con 1. Medido contra el ash.
424                sh.error(&e);
425                sh.estado = 2;
426                return Flujo::Exit(2);
427            }
428        }
429        for (k, v) in pares {
430            sh.asignar(&k, &v);
431        }
432        sh.estado = 0;
433        return Flujo::Normal;
434    }
435
436    if sh.opts.xtrace {
437        eprintln!("+ {}", argv.join(" "));
438    }
439
440    // 3. ¿función?
441    if let Some(cuerpo) = sh.funcs.get(&argv[0]).cloned() {
442        return con_redirs(sh, redirs, |sh| {
443            let previos = std::mem::replace(&mut sh.params, argv[1..].to_vec());
444            sh.funcion_honda += 1;
445            sh.locales.push(Vec::new());
446            let f = correr_orden(sh, &cuerpo);
447            if let Some(marco) = sh.locales.pop() {
448                for (nombre, previo) in marco.into_iter().rev() {
449                    match previo {
450                        Some(v) => {
451                            sh.vars.insert(nombre, v);
452                        }
453                        None => {
454                            sh.vars.remove(&nombre);
455                        }
456                    }
457                }
458            }
459            sh.funcion_honda -= 1;
460            sh.params = previos;
461            match f {
462                Flujo::Return => Flujo::Normal,
463                otro => otro,
464            }
465        });
466    }
467
468    // 4. ¿builtin?
469    if crate::builtins::es_builtin(&argv[0]) {
470        return match aplicar(sh, redirs) {
471            Ok(guardados) => {
472                // Las asignaciones de una orden con builtin quedan en el shell (POSIX lo deja
473                // sin especificar para los regulares; hacerlas persistir es lo que hace ash).
474                for (k, v) in &pares {
475                    sh.asignar(k, v);
476                }
477                let f = crate::builtins::correr(sh, &argv);
478                restaurar(guardados);
479                f
480            }
481            Err(e) => {
482                sh.error(&e);
483                sh.estado = 2;
484                Flujo::Exit(2)
485            }
486        };
487    }
488
489    // 5. orden externa
490    lanzar_externo(sh, &argv, redirs, &pares, false)
491}
492
493/// Aplica redirecciones, corre `cuerpo`, restaura. Para grupos y compuestas, que NO forkean.
494fn con_redirs<F: FnOnce(&mut Shell) -> Flujo>(sh: &mut Shell, redirs: &[Redir], cuerpo: F) -> Flujo {
495    match aplicar(sh, redirs) {
496        Ok(guardados) => {
497            let f = cuerpo(sh);
498            restaurar(guardados);
499            f
500        }
501        Err(e) => {
502            sh.error(&e);
503            sh.estado = 1;
504            Flujo::Normal
505        }
506    }
507}
508
509// ───────────────────────────────────────────────────────── redirecciones
510
511pub struct Guardado {
512    fd: i32,
513    copia: i32,
514}
515
516pub fn aplicar(sh: &mut Shell, redirs: &[Redir]) -> Result<Vec<Guardado>, String> {
517    let mut guardados = Vec::new();
518    for r in redirs {
519        let (fd, nuevo) = match r {
520            Redir::Salida { fd, destino, append } => {
521                let ruta = expand::una(sh, destino)?;
522                let banderas = if *append {
523                    libc::O_WRONLY | libc::O_CREAT | libc::O_APPEND
524                } else {
525                    libc::O_WRONLY | libc::O_CREAT | libc::O_TRUNC
526                };
527                (*fd, abrir(&ruta, banderas)?)
528            }
529            Redir::Entrada { fd, origen } => {
530                let ruta = expand::una(sh, origen)?;
531                (*fd, abrir(&ruta, libc::O_RDONLY)?)
532            }
533            Redir::LecturaEscritura { fd, ruta } => {
534                let ruta = expand::una(sh, ruta)?;
535                (*fd, abrir(&ruta, libc::O_RDWR | libc::O_CREAT)?)
536            }
537            Redir::Duplica { fd, otro, .. } => match otro {
538                DupDestino::Cerrar => {
539                    guardados.push(Guardado { fd: *fd, copia: unsafe { libc::dup(*fd) } });
540                    unsafe { libc::close(*fd) };
541                    continue;
542                }
543                DupDestino::Fd(p) => {
544                    let s = expand::una(sh, p)?;
545                    let n: i32 = s.parse().map_err(|_| format!("{s}: no es un descriptor"))?;
546                    let copia = unsafe { libc::dup(n) };
547                    if copia < 0 {
548                        return Err(format!("{n}: descriptor no válido"));
549                    }
550                    (*fd, copia)
551                }
552            },
553            Redir::AquiDoc { fd, cuerpo, expande } => {
554                let texto = if *expande {
555                    expand::una(sh, cuerpo)?
556                } else {
557                    match &cuerpo[0] {
558                        Seg::Lit { texto, .. } => texto.clone(),
559                        _ => String::new(),
560                    }
561                };
562                (*fd, fd_con_texto(&texto)?)
563            }
564        };
565        let copia = unsafe { libc::dup(fd) };
566        guardados.push(Guardado { fd, copia });
567        unsafe { libc::dup2(nuevo, fd) };
568        unsafe { libc::close(nuevo) };
569    }
570    Ok(guardados)
571}
572
573pub fn restaurar(guardados: Vec<Guardado>) {
574    for g in guardados.into_iter().rev() {
575        if g.copia >= 0 {
576            unsafe { libc::dup2(g.copia, g.fd) };
577            unsafe { libc::close(g.copia) };
578        } else {
579            unsafe { libc::close(g.fd) };
580        }
581    }
582}
583
584fn abrir(ruta: &str, banderas: i32) -> Result<i32, String> {
585    let c = CString::new(ruta).map_err(|_| format!("{ruta}: ruta con NUL"))?;
586    let fd = unsafe { libc::open(c.as_ptr(), banderas, 0o644 as libc::c_int) };
587    if fd < 0 {
588        return Err(format!("{ruta}: no pude abrir"));
589    }
590    Ok(fd)
591}
592
593/// Un descriptor de sólo lectura con `texto` adentro, para los aquí-documentos.
594///
595/// Se usa `memfd_create` en vez de una tubería porque una tubería se **bloquea** si el cuerpo pasa
596/// del buffer del kernel (64 K) y no hay nadie leyendo todavía. Si no hay memfd, cae a la tubería,
597/// que para los cuerpos de este árbol (2 en 21 guiones, cortos) alcanza.
598fn fd_con_texto(texto: &str) -> Result<i32, String> {
599    let nombre = CString::new("simi-aquidoc").unwrap();
600    let fd = unsafe { libc::memfd_create(nombre.as_ptr(), 0) };
601    if fd >= 0 {
602        let bytes = texto.as_bytes();
603        let mut escrito = 0;
604        while escrito < bytes.len() {
605            let n = unsafe {
606                libc::write(
607                    fd,
608                    bytes[escrito..].as_ptr() as *const libc::c_void,
609                    bytes.len() - escrito,
610                )
611            };
612            if n <= 0 {
613                unsafe { libc::close(fd) };
614                return Err("aquí-documento: no pude escribir".into());
615            }
616            escrito += n as usize;
617        }
618        unsafe { libc::lseek(fd, 0, libc::SEEK_SET) };
619        return Ok(fd);
620    }
621    let mut fds = [0i32; 2];
622    if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
623        return Err("aquí-documento: no pude crear la tubería".into());
624    }
625    let bytes = texto.as_bytes();
626    unsafe { libc::write(fds[1], bytes.as_ptr() as *const libc::c_void, bytes.len()) };
627    unsafe { libc::close(fds[1]) };
628    Ok(fds[0])
629}
630
631// ───────────────────────────────────────────────────────── procesos
632
633/// `reemplazar` = viene del builtin `exec`: NO se forkea, se sustituye este proceso.
634pub fn lanzar_externo(
635    sh: &mut Shell,
636    argv: &[String],
637    redirs: &[Redir],
638    asignaciones: &[(String, String)],
639    reemplazar: bool,
640) -> Flujo {
641    let ruta = match resolver(sh, &argv[0]) {
642        Some(r) => r,
643        None => {
644            sh.error(&format!("{}: no encontrado", argv[0]));
645            sh.estado = 127;
646            return Flujo::Normal;
647        }
648    };
649    let mut entorno = sh.entorno();
650    for (k, v) in asignaciones {
651        entorno.retain(|e| !e.starts_with(&format!("{k}=")));
652        entorno.push(format!("{k}={v}"));
653    }
654
655    if reemplazar {
656        if let Err(e) = aplicar(sh, redirs) {
657            sh.error(&e);
658            sh.estado = 1;
659            return Flujo::Normal;
660        }
661        ejecutar(&ruta, argv, &entorno);
662        // si volvemos, el execve falló
663        sh.error(&format!("{}: no pude ejecutar", argv[0]));
664        return Flujo::Exit(126);
665    }
666
667    let pid = unsafe { libc::fork() };
668    if pid < 0 {
669        sh.error("no pude forkear");
670        sh.estado = 1;
671        return Flujo::Normal;
672    }
673    if pid == 0 {
674        if let Err(e) = aplicar(sh, redirs) {
675            eprintln!("{}: {}", sh.nombre, e);
676            unsafe { libc::_exit(1) };
677        }
678        // el hijo no hereda los traps atrapados (POSIX §2.11)
679        unsafe {
680            libc::signal(libc::SIGINT, libc::SIG_DFL);
681            libc::signal(libc::SIGQUIT, libc::SIG_DFL);
682        }
683        ejecutar(&ruta, argv, &entorno);
684        eprintln!("{}: {}: no pude ejecutar", sh.nombre, argv[0]);
685        unsafe { libc::_exit(126) };
686    }
687    sh.estado = esperar(pid);
688    Flujo::Normal
689}
690
691fn ejecutar(ruta: &str, argv: &[String], entorno: &[String]) {
692    let c_ruta = match CString::new(ruta) {
693        Ok(c) => c,
694        Err(_) => return,
695    };
696    let c_argv: Vec<CString> = argv.iter().filter_map(|a| CString::new(a.as_str()).ok()).collect();
697    let c_env: Vec<CString> = entorno.iter().filter_map(|e| CString::new(e.as_str()).ok()).collect();
698    let mut p_argv: Vec<*const libc::c_char> = c_argv.iter().map(|c| c.as_ptr()).collect();
699    p_argv.push(std::ptr::null());
700    let mut p_env: Vec<*const libc::c_char> = c_env.iter().map(|c| c.as_ptr()).collect();
701    p_env.push(std::ptr::null());
702    unsafe { libc::execve(c_ruta.as_ptr(), p_argv.as_ptr(), p_env.as_ptr()) };
703}
704
705/// Resolución por `PATH`. Propia y no `execvp`, porque el `PATH` que manda es el del SHELL
706/// (`sh.vars`), que no tiene por qué ser el del `environ` heredado.
707fn resolver(sh: &Shell, nombre: &str) -> Option<String> {
708    if nombre.contains('/') {
709        return Some(nombre.to_string());
710    }
711    let path = sh.leer("PATH").unwrap_or_else(|| "/usr/bin:/bin".into());
712    for dir in path.split(':') {
713        let dir = if dir.is_empty() { "." } else { dir };
714        let cand = format!("{dir}/{nombre}");
715        let c = CString::new(cand.clone()).ok()?;
716        if unsafe { libc::access(c.as_ptr(), libc::X_OK) } == 0 {
717            return Some(cand);
718        }
719    }
720    None
721}
722
723fn esperar(pid: i32) -> i32 {
724    let mut estado = 0i32;
725    loop {
726        let r = unsafe { libc::waitpid(pid, &mut estado, 0) };
727        if r < 0 {
728            let e = std::io::Error::last_os_error();
729            if e.raw_os_error() == Some(libc::EINTR) {
730                continue;
731            }
732            return 1;
733        }
734        break;
735    }
736    if libc::WIFEXITED(estado) {
737        libc::WEXITSTATUS(estado)
738    } else if libc::WIFSIGNALED(estado) {
739        128 + libc::WTERMSIG(estado)
740    } else {
741        1
742    }
743}
744
745/// Salida de un subshell: **corre el `trap EXIT` antes de irse**. Es la invariante 1 del módulo.
746pub fn salir_de_subshell(sh: &mut Shell, estado: i32) -> ! {
747    correr_trap_exit(sh, estado);
748    let _ = std::io::stdout().flush();
749    let _ = std::io::stderr().flush();
750    unsafe { libc::_exit(estado) }
751}
752
753pub fn correr_trap_exit(sh: &mut Shell, estado: i32) {
754    if sh.trap_exit_corrido {
755        return;
756    }
757    sh.trap_exit_corrido = true;
758    if let Some(accion) = sh.traps.get("EXIT").cloned() {
759        sh.estado = estado;
760        if let Ok(l) = parsear(&accion) {
761            correr_lista(sh, &l);
762        }
763        sh.estado = estado;
764    }
765}
766
767/// Entre órdenes: si llegó una señal atrapada, corre su acción. El handler sólo prende una
768/// bandera (es lo único async-signal-safe que se puede hacer).
769pub fn atender_senales(sh: &mut Shell) {
770    for n in 1..32usize {
771        if LLEGO_SENAL[n].swap(false, std::sync::atomic::Ordering::SeqCst) {
772            let nombre = crate::builtins::nombre_de_senal(n as i32);
773            if let Some(accion) = sh.traps.get(&nombre).cloned() {
774                if let Ok(l) = parsear(&accion) {
775                    let previo = sh.estado;
776                    correr_lista(sh, &l);
777                    sh.estado = previo;
778                }
779            }
780        }
781    }
782}
783
784// ───────────────────────────────────────────────────────── sustituciones
785
786/// `$( … )` y `` ` … ` ``: corre el cuerpo en un subshell y devuelve su stdout sin los saltos
787/// finales. El subshell corre su `trap EXIT` como cualquier otro (invariante 1).
788pub fn sustituir_orden(sh: &mut Shell, cuerpo: &str) -> Result<String, String> {
789    let lista = parsear(cuerpo).map_err(|e| format!("sustitución: {e}"))?;
790    let mut fds = [0i32; 2];
791    if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
792        return Err("no pude crear la tubería".into());
793    }
794    let pid = unsafe { libc::fork() };
795    if pid < 0 {
796        unsafe { libc::close(fds[0]) };
797        unsafe { libc::close(fds[1]) };
798        return Err("no pude forkear".into());
799    }
800    if pid == 0 {
801        unsafe { libc::close(fds[0]) };
802        unsafe { libc::dup2(fds[1], 1) };
803        unsafe { libc::close(fds[1]) };
804        entrar_en_subshell(sh);
805        let f = correr_lista(sh, &lista);
806        let estado = match f {
807            Flujo::Exit(c) => c,
808            _ => sh.estado,
809        };
810        salir_de_subshell(sh, estado);
811    }
812    unsafe { libc::close(fds[1]) };
813    let mut salida = String::new();
814    {
815        let mut f = unsafe { std::fs::File::from_raw_fd(fds[0]) };
816        let mut buf = Vec::new();
817        let _ = f.read_to_end(&mut buf);
818        salida = String::from_utf8_lossy(&buf).into_owned();
819        let _ = f.as_raw_fd();
820    }
821    sh.estado = esperar(pid);
822    while salida.ends_with('\n') {
823        salida.pop();
824    }
825    Ok(salida)
826}
827
828pub fn evaluar_aritmetica(sh: &mut Shell, expr: &str) -> Result<String, String> {
829    // Las variables de la expresión pueden traer expansiones: `$(( $x + 1 ))`.
830    let expr = expandir_texto(sh, expr)?;
831    let vars: std::cell::RefCell<Vec<(String, String)>> = std::cell::RefCell::new(Vec::new());
832    let leer_sh: Vec<(String, String)> = sh
833        .vars
834        .iter()
835        .map(|(k, (v, _))| (k.clone(), v.clone()))
836        .collect();
837    let leer = |n: &str| {
838        leer_sh
839            .iter()
840            .find(|(k, _)| k == n)
841            .map(|(_, v)| v.clone())
842            .unwrap_or_default()
843    };
844    let mut asignar = |n: &str, v: &str| vars.borrow_mut().push((n.to_string(), v.to_string()));
845    let mut ctx = crate::arith::Contexto { leer: &leer, asignar: &mut asignar };
846    let r = crate::arith::evaluar(&expr, &mut ctx);
847    for (k, v) in vars.into_inner() {
848        sh.asignar(&k, &v);
849    }
850    r.map(|n| n.to_string())
851}
852
853/// Expande las sustituciones de un texto suelto (el interior de `$(( ))`).
854fn expandir_texto(sh: &mut Shell, texto: &str) -> Result<String, String> {
855    if !texto.contains('$') && !texto.contains('`') {
856        return Ok(texto.to_string());
857    }
858    let escapado = texto.replace('"', "\\\"");
859    let l = parsear(&format!(": \"{escapado}\"")).map_err(|e| format!("aritmética: {e}"))?;
860    let Orden::Simple { palabras, .. } = &l[0].0.primera.ordenes[0] else {
861        return Ok(texto.to_string());
862    };
863    expand::una(sh, &palabras[1])
864}