tawasuyu

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 1435Shell 145Markdown 88Rust 852973 Definiciones

⎇ main crates/simi/src/exec.rs Rust · 941 líneas · 33.1 KB Historia Quién tocó qué crudo

feat(simi): `alias` no existía, y con eso ningún atajo de la consola funcionaba 569e40a23 · 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
381/// Sustitución de alias. Es LÉXICA y va ANTES de expandir: si se hiciera después, el `$HOME` de
382/// `alias donde='echo $HOME'` quedaría literal.
383///
384/// Las reglas finas de POSIX, que son las que hacen que los alias de siempre funcionen:
385///
386///   1. Sólo se mira la PRIMERA palabra de una orden simple, y sólo si es un literal SIN comillas
387///      (`\ls` y `'ls'` esquivan el alias a propósito — es cómo se invoca el original).
388///   2. Si el valor del alias TERMINA EN BLANCO, la palabra siguiente también se mira. Eso es lo
389///      que hace que `alias sudo='sudo '` deje funcionar `sudo ll`.
390///   3. Recursión acotada: un alias cuyo nombre ya se expandió no se vuelve a expandir, así que
391///      `alias ls='ls --color'` no se cuelga.
392///
393/// El valor se RE-PARSEA con el parser entero en vez de partirse por espacios: así
394/// `alias ll='eza -l --git --time-style=long-iso'` y uno con comillas dentro salen bien.
395fn expandir_alias(sh: &Shell, palabras: &[Palabra]) -> Option<Vec<Palabra>> {
396    if sh.alias.is_empty() {
397        return None;
398    }
399    let mut salida: Vec<Palabra> = palabras.to_vec();
400    let mut vistos: Vec<String> = Vec::new();
401    let mut i = 0usize;
402    // Cota dura: un alias patológico (mutuamente recursivo con blanco final) no debe colgar la
403    // shell. Diez vueltas es más de lo que cualquier cadena real necesita.
404    for _ in 0..10 {
405        let Some(nombre) = literal_desnudo(salida.get(i)?) else { break };
406        if vistos.contains(&nombre) {
407            break;
408        }
409        let Some(valor) = sh.alias.get(&nombre).cloned() else { break };
410        let Ok(mut nuevas) = palabras_de_texto(&valor) else { break };
411        if nuevas.is_empty() {
412            // `alias x=''` borra la orden; se deja la lista sin esa palabra.
413            salida.remove(i);
414            return Some(salida);
415        }
416        vistos.push(nombre);
417        let sigue_con_la_proxima = valor.ends_with([' ', '\t']);
418        let resto = salida.split_off(i + 1);
419        salida.pop();
420        let puesto = nuevas.len();
421        salida.append(&mut nuevas);
422        salida.extend(resto);
423        if !sigue_con_la_proxima {
424            break;
425        }
426        i += puesto;
427    }
428    Some(salida)
429}
430
431/// El texto de una palabra que es UN literal sin comillas, o `None`. Lo segundo cubre `"ls"`,
432/// `'ls'`, `\ls` y cualquier palabra con `$`: ninguna dispara el alias.
433fn literal_desnudo(p: &Palabra) -> Option<String> {
434    match p.as_slice() {
435        [crate::ast::Seg::Lit { texto, comillado: false }] => Some(texto.clone()),
436        _ => None,
437    }
438}
439
440/// Parte el valor de un alias en palabras usando el parser de verdad (comillas incluidas).
441fn palabras_de_texto(t: &str) -> Result<Vec<Palabra>, String> {
442    let l = parsear(t)?;
443    let Some(p) = l.first() else { return Ok(Vec::new()) };
444    match p.0.primera.ordenes.first() {
445        Some(Orden::Simple { palabras, .. }) => Ok(palabras.clone()),
446        _ => Err("el alias no es una orden simple".into()),
447    }
448}
449
450fn correr_simple(
451    sh: &mut Shell,
452    asignaciones: &[Asignacion],
453    palabras: &[Palabra],
454    redirs: &[Redir],
455) -> Flujo {
456    // 1. expandir las palabras
457    // 0. los alias, que son sustitución LÉXICA y van antes de todo lo demás.
458    let sustituidas = expandir_alias(sh, palabras);
459    let palabras: &[Palabra] = match &sustituidas {
460        Some(v) => v,
461        None => palabras,
462    };
463    let mut argv: Vec<String> = Vec::new();
464    for p in palabras {
465        match expand::campos(sh, p) {
466            Ok(cs) => argv.extend(cs),
467            Err(e) => {
468                // Un error de expansión (comilla sin cerrar en una sustitución, `${x:?}`, `set -u`)
469                // ABORTA con 2 en un shell no interactivo, no sigue con 1. Medido contra el ash.
470                sh.error(&e);
471                sh.estado = 2;
472                return Flujo::Exit(2);
473            }
474        }
475    }
476
477    // 2. las asignaciones
478    let mut pares: Vec<(String, String)> = Vec::new();
479    for a in asignaciones {
480        match expand::una(sh, &a.valor) {
481            Ok(v) => pares.push((a.nombre.clone(), v)),
482            Err(e) => {
483                // Un error de expansión (comilla sin cerrar en una sustitución, `${x:?}`, `set -u`)
484                // ABORTA con 2 en un shell no interactivo, no sigue con 1. Medido contra el ash.
485                sh.error(&e);
486                sh.estado = 2;
487                return Flujo::Exit(2);
488            }
489        }
490    }
491
492    if argv.is_empty() {
493        // sólo asignaciones (y quizá redirecciones, que se abren y se cierran)
494        match aplicar(sh, redirs) {
495            Ok(g) => restaurar(g),
496            Err(e) => {
497                // Un error de expansión (comilla sin cerrar en una sustitución, `${x:?}`, `set -u`)
498                // ABORTA con 2 en un shell no interactivo, no sigue con 1. Medido contra el ash.
499                sh.error(&e);
500                sh.estado = 2;
501                return Flujo::Exit(2);
502            }
503        }
504        for (k, v) in pares {
505            sh.asignar(&k, &v);
506        }
507        sh.estado = 0;
508        return Flujo::Normal;
509    }
510
511    if sh.opts.xtrace {
512        eprintln!("+ {}", argv.join(" "));
513    }
514
515    // 3. ¿función?
516    if let Some(cuerpo) = sh.funcs.get(&argv[0]).cloned() {
517        return con_redirs(sh, redirs, |sh| {
518            let previos = std::mem::replace(&mut sh.params, argv[1..].to_vec());
519            sh.funcion_honda += 1;
520            sh.locales.push(Vec::new());
521            let f = correr_orden(sh, &cuerpo);
522            if let Some(marco) = sh.locales.pop() {
523                for (nombre, previo) in marco.into_iter().rev() {
524                    match previo {
525                        Some(v) => {
526                            sh.vars.insert(nombre, v);
527                        }
528                        None => {
529                            sh.vars.remove(&nombre);
530                        }
531                    }
532                }
533            }
534            sh.funcion_honda -= 1;
535            sh.params = previos;
536            match f {
537                Flujo::Return => Flujo::Normal,
538                otro => otro,
539            }
540        });
541    }
542
543    // 4. ¿builtin?
544    if crate::builtins::es_builtin(&argv[0]) {
545        return match aplicar(sh, redirs) {
546            Ok(guardados) => {
547                // Las asignaciones de una orden con builtin quedan en el shell (POSIX lo deja
548                // sin especificar para los regulares; hacerlas persistir es lo que hace ash).
549                for (k, v) in &pares {
550                    sh.asignar(k, v);
551                }
552                let f = crate::builtins::correr(sh, &argv);
553                restaurar(guardados);
554                f
555            }
556            Err(e) => {
557                sh.error(&e);
558                sh.estado = 2;
559                Flujo::Exit(2)
560            }
561        };
562    }
563
564    // 5. orden externa
565    lanzar_externo(sh, &argv, redirs, &pares, false)
566}
567
568/// Aplica redirecciones, corre `cuerpo`, restaura. Para grupos y compuestas, que NO forkean.
569fn con_redirs<F: FnOnce(&mut Shell) -> Flujo>(sh: &mut Shell, redirs: &[Redir], cuerpo: F) -> Flujo {
570    match aplicar(sh, redirs) {
571        Ok(guardados) => {
572            let f = cuerpo(sh);
573            restaurar(guardados);
574            f
575        }
576        Err(e) => {
577            sh.error(&e);
578            sh.estado = 1;
579            Flujo::Normal
580        }
581    }
582}
583
584// ───────────────────────────────────────────────────────── redirecciones
585
586pub struct Guardado {
587    fd: i32,
588    copia: i32,
589}
590
591pub fn aplicar(sh: &mut Shell, redirs: &[Redir]) -> Result<Vec<Guardado>, String> {
592    let mut guardados = Vec::new();
593    for r in redirs {
594        let (fd, nuevo) = match r {
595            Redir::Salida { fd, destino, append } => {
596                let ruta = expand::una(sh, destino)?;
597                let banderas = if *append {
598                    libc::O_WRONLY | libc::O_CREAT | libc::O_APPEND
599                } else {
600                    libc::O_WRONLY | libc::O_CREAT | libc::O_TRUNC
601                };
602                (*fd, abrir(&ruta, banderas)?)
603            }
604            Redir::Entrada { fd, origen } => {
605                let ruta = expand::una(sh, origen)?;
606                (*fd, abrir(&ruta, libc::O_RDONLY)?)
607            }
608            Redir::LecturaEscritura { fd, ruta } => {
609                let ruta = expand::una(sh, ruta)?;
610                (*fd, abrir(&ruta, libc::O_RDWR | libc::O_CREAT)?)
611            }
612            Redir::Duplica { fd, otro, .. } => match otro {
613                DupDestino::Cerrar => {
614                    guardados.push(Guardado { fd: *fd, copia: unsafe { libc::dup(*fd) } });
615                    unsafe { libc::close(*fd) };
616                    continue;
617                }
618                DupDestino::Fd(p) => {
619                    let s = expand::una(sh, p)?;
620                    let n: i32 = s.parse().map_err(|_| format!("{s}: no es un descriptor"))?;
621                    let copia = unsafe { libc::dup(n) };
622                    if copia < 0 {
623                        return Err(format!("{n}: descriptor no válido"));
624                    }
625                    (*fd, copia)
626                }
627            },
628            Redir::AquiDoc { fd, cuerpo, expande } => {
629                let texto = if *expande {
630                    expand::una(sh, cuerpo)?
631                } else {
632                    match &cuerpo[0] {
633                        Seg::Lit { texto, .. } => texto.clone(),
634                        _ => String::new(),
635                    }
636                };
637                (*fd, fd_con_texto(&texto)?)
638            }
639        };
640        let copia = unsafe { libc::dup(fd) };
641        guardados.push(Guardado { fd, copia });
642        unsafe { libc::dup2(nuevo, fd) };
643        unsafe { libc::close(nuevo) };
644    }
645    Ok(guardados)
646}
647
648pub fn restaurar(guardados: Vec<Guardado>) {
649    for g in guardados.into_iter().rev() {
650        if g.copia >= 0 {
651            unsafe { libc::dup2(g.copia, g.fd) };
652            unsafe { libc::close(g.copia) };
653        } else {
654            unsafe { libc::close(g.fd) };
655        }
656    }
657}
658
659fn abrir(ruta: &str, banderas: i32) -> Result<i32, String> {
660    let c = CString::new(ruta).map_err(|_| format!("{ruta}: ruta con NUL"))?;
661    let fd = unsafe { libc::open(c.as_ptr(), banderas, 0o644 as libc::c_int) };
662    if fd < 0 {
663        return Err(format!("{ruta}: no pude abrir"));
664    }
665    Ok(fd)
666}
667
668/// Un descriptor de sólo lectura con `texto` adentro, para los aquí-documentos.
669///
670/// Se usa `memfd_create` en vez de una tubería porque una tubería se **bloquea** si el cuerpo pasa
671/// del buffer del kernel (64 K) y no hay nadie leyendo todavía. Si no hay memfd, cae a la tubería,
672/// que para los cuerpos de este árbol (2 en 21 guiones, cortos) alcanza.
673fn fd_con_texto(texto: &str) -> Result<i32, String> {
674    let nombre = CString::new("simi-aquidoc").unwrap();
675    let fd = unsafe { libc::memfd_create(nombre.as_ptr(), 0) };
676    if fd >= 0 {
677        let bytes = texto.as_bytes();
678        let mut escrito = 0;
679        while escrito < bytes.len() {
680            let n = unsafe {
681                libc::write(
682                    fd,
683                    bytes[escrito..].as_ptr() as *const libc::c_void,
684                    bytes.len() - escrito,
685                )
686            };
687            if n <= 0 {
688                unsafe { libc::close(fd) };
689                return Err("aquí-documento: no pude escribir".into());
690            }
691            escrito += n as usize;
692        }
693        unsafe { libc::lseek(fd, 0, libc::SEEK_SET) };
694        return Ok(fd);
695    }
696    let mut fds = [0i32; 2];
697    if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
698        return Err("aquí-documento: no pude crear la tubería".into());
699    }
700    let bytes = texto.as_bytes();
701    unsafe { libc::write(fds[1], bytes.as_ptr() as *const libc::c_void, bytes.len()) };
702    unsafe { libc::close(fds[1]) };
703    Ok(fds[0])
704}
705
706// ───────────────────────────────────────────────────────── procesos
707
708/// `reemplazar` = viene del builtin `exec`: NO se forkea, se sustituye este proceso.
709pub fn lanzar_externo(
710    sh: &mut Shell,
711    argv: &[String],
712    redirs: &[Redir],
713    asignaciones: &[(String, String)],
714    reemplazar: bool,
715) -> Flujo {
716    let ruta = match resolver(sh, &argv[0]) {
717        Some(r) => r,
718        None => {
719            sh.error(&format!("{}: no encontrado", argv[0]));
720            sh.estado = 127;
721            return Flujo::Normal;
722        }
723    };
724    let mut entorno = sh.entorno();
725    for (k, v) in asignaciones {
726        entorno.retain(|e| !e.starts_with(&format!("{k}=")));
727        entorno.push(format!("{k}={v}"));
728    }
729
730    if reemplazar {
731        if let Err(e) = aplicar(sh, redirs) {
732            sh.error(&e);
733            sh.estado = 1;
734            return Flujo::Normal;
735        }
736        ejecutar(&ruta, argv, &entorno);
737        // si volvemos, el execve falló
738        sh.error(&format!("{}: no pude ejecutar", argv[0]));
739        return Flujo::Exit(126);
740    }
741
742    let pid = unsafe { libc::fork() };
743    if pid < 0 {
744        sh.error("no pude forkear");
745        sh.estado = 1;
746        return Flujo::Normal;
747    }
748    if pid == 0 {
749        if let Err(e) = aplicar(sh, redirs) {
750            eprintln!("{}: {}", sh.nombre, e);
751            unsafe { libc::_exit(1) };
752        }
753        // el hijo no hereda los traps atrapados (POSIX §2.11)
754        unsafe {
755            libc::signal(libc::SIGINT, libc::SIG_DFL);
756            libc::signal(libc::SIGQUIT, libc::SIG_DFL);
757        }
758        ejecutar(&ruta, argv, &entorno);
759        eprintln!("{}: {}: no pude ejecutar", sh.nombre, argv[0]);
760        unsafe { libc::_exit(126) };
761    }
762    sh.estado = esperar(pid);
763    Flujo::Normal
764}
765
766fn ejecutar(ruta: &str, argv: &[String], entorno: &[String]) {
767    let c_ruta = match CString::new(ruta) {
768        Ok(c) => c,
769        Err(_) => return,
770    };
771    let c_argv: Vec<CString> = argv.iter().filter_map(|a| CString::new(a.as_str()).ok()).collect();
772    let c_env: Vec<CString> = entorno.iter().filter_map(|e| CString::new(e.as_str()).ok()).collect();
773    let mut p_argv: Vec<*const libc::c_char> = c_argv.iter().map(|c| c.as_ptr()).collect();
774    p_argv.push(std::ptr::null());
775    let mut p_env: Vec<*const libc::c_char> = c_env.iter().map(|c| c.as_ptr()).collect();
776    p_env.push(std::ptr::null());
777    unsafe { libc::execve(c_ruta.as_ptr(), p_argv.as_ptr(), p_env.as_ptr()) };
778}
779
780/// Resolución por `PATH`. Propia y no `execvp`, porque el `PATH` que manda es el del SHELL
781/// (`sh.vars`), que no tiene por qué ser el del `environ` heredado.
782fn resolver(sh: &Shell, nombre: &str) -> Option<String> {
783    if nombre.contains('/') {
784        return Some(nombre.to_string());
785    }
786    let path = sh.leer("PATH").unwrap_or_else(|| "/usr/bin:/bin".into());
787    for dir in path.split(':') {
788        let dir = if dir.is_empty() { "." } else { dir };
789        let cand = format!("{dir}/{nombre}");
790        let c = CString::new(cand.clone()).ok()?;
791        if unsafe { libc::access(c.as_ptr(), libc::X_OK) } == 0 {
792            return Some(cand);
793        }
794    }
795    None
796}
797
798fn esperar(pid: i32) -> i32 {
799    let mut estado = 0i32;
800    loop {
801        let r = unsafe { libc::waitpid(pid, &mut estado, 0) };
802        if r < 0 {
803            let e = std::io::Error::last_os_error();
804            if e.raw_os_error() == Some(libc::EINTR) {
805                continue;
806            }
807            return 1;
808        }
809        break;
810    }
811    if libc::WIFEXITED(estado) {
812        libc::WEXITSTATUS(estado)
813    } else if libc::WIFSIGNALED(estado) {
814        128 + libc::WTERMSIG(estado)
815    } else {
816        1
817    }
818}
819
820/// Salida de un subshell: **corre el `trap EXIT` antes de irse**. Es la invariante 1 del módulo.
821pub fn salir_de_subshell(sh: &mut Shell, estado: i32) -> ! {
822    correr_trap_exit(sh, estado);
823    let _ = std::io::stdout().flush();
824    let _ = std::io::stderr().flush();
825    unsafe { libc::_exit(estado) }
826}
827
828pub fn correr_trap_exit(sh: &mut Shell, estado: i32) {
829    if sh.trap_exit_corrido {
830        return;
831    }
832    sh.trap_exit_corrido = true;
833    if let Some(accion) = sh.traps.get("EXIT").cloned() {
834        sh.estado = estado;
835        if let Ok(l) = parsear(&accion) {
836            correr_lista(sh, &l);
837        }
838        sh.estado = estado;
839    }
840}
841
842/// Entre órdenes: si llegó una señal atrapada, corre su acción. El handler sólo prende una
843/// bandera (es lo único async-signal-safe que se puede hacer).
844pub fn atender_senales(sh: &mut Shell) {
845    for n in 1..32usize {
846        if LLEGO_SENAL[n].swap(false, std::sync::atomic::Ordering::SeqCst) {
847            let nombre = crate::builtins::nombre_de_senal(n as i32);
848            if let Some(accion) = sh.traps.get(&nombre).cloned() {
849                if let Ok(l) = parsear(&accion) {
850                    let previo = sh.estado;
851                    correr_lista(sh, &l);
852                    sh.estado = previo;
853                }
854            }
855        }
856    }
857}
858
859// ───────────────────────────────────────────────────────── sustituciones
860
861/// `$( … )` y `` ` … ` ``: corre el cuerpo en un subshell y devuelve su stdout sin los saltos
862/// finales. El subshell corre su `trap EXIT` como cualquier otro (invariante 1).
863pub fn sustituir_orden(sh: &mut Shell, cuerpo: &str) -> Result<String, String> {
864    let lista = parsear(cuerpo).map_err(|e| format!("sustitución: {e}"))?;
865    let mut fds = [0i32; 2];
866    if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
867        return Err("no pude crear la tubería".into());
868    }
869    let pid = unsafe { libc::fork() };
870    if pid < 0 {
871        unsafe { libc::close(fds[0]) };
872        unsafe { libc::close(fds[1]) };
873        return Err("no pude forkear".into());
874    }
875    if pid == 0 {
876        unsafe { libc::close(fds[0]) };
877        unsafe { libc::dup2(fds[1], 1) };
878        unsafe { libc::close(fds[1]) };
879        entrar_en_subshell(sh);
880        let f = correr_lista(sh, &lista);
881        let estado = match f {
882            Flujo::Exit(c) => c,
883            _ => sh.estado,
884        };
885        salir_de_subshell(sh, estado);
886    }
887    unsafe { libc::close(fds[1]) };
888    let mut salida = String::new();
889    {
890        let mut f = unsafe { std::fs::File::from_raw_fd(fds[0]) };
891        let mut buf = Vec::new();
892        let _ = f.read_to_end(&mut buf);
893        salida = String::from_utf8_lossy(&buf).into_owned();
894        let _ = f.as_raw_fd();
895    }
896    sh.estado = esperar(pid);
897    while salida.ends_with('\n') {
898        salida.pop();
899    }
900    Ok(salida)
901}
902
903pub fn evaluar_aritmetica(sh: &mut Shell, expr: &str) -> Result<String, String> {
904    // Las variables de la expresión pueden traer expansiones: `$(( $x + 1 ))`.
905    let expr = expandir_texto(sh, expr)?;
906    let vars: std::cell::RefCell<Vec<(String, String)>> = std::cell::RefCell::new(Vec::new());
907    let leer_sh: Vec<(String, String)> = sh
908        .vars
909        .iter()
910        .map(|(k, (v, _))| (k.clone(), v.clone()))
911        .collect();
912    let leer = |n: &str| {
913        leer_sh
914            .iter()
915            .find(|(k, _)| k == n)
916            .map(|(_, v)| v.clone())
917            .unwrap_or_default()
918    };
919    let mut asignar = |n: &str, v: &str| vars.borrow_mut().push((n.to_string(), v.to_string()));
920    let mut ctx = crate::arith::Contexto { leer: &leer, asignar: &mut asignar };
921    let r = crate::arith::evaluar(&expr, &mut ctx);
922    for (k, v) in vars.into_inner() {
923        sh.asignar(&k, &v);
924    }
925    r.map(|n| n.to_string())
926}
927
928/// Expande las sustituciones de un texto suelto (el interior de `$(( ))`).
929/// Público desde el 2026-10-23: `main` lo necesita para expandir `$ENV` antes de leerlo, que es
930/// lo que POSIX manda y lo que permite el idioma `ENV=$HOME/.shrc`.
931pub fn expandir_texto(sh: &mut Shell, texto: &str) -> Result<String, String> {
932    if !texto.contains('$') && !texto.contains('`') {
933        return Ok(texto.to_string());
934    }
935    let escapado = texto.replace('"', "\\\"");
936    let l = parsear(&format!(": \"{escapado}\"")).map_err(|e| format!("aritmética: {e}"))?;
937    let Orden::Simple { palabras, .. } = &l[0].0.primera.ordenes[0] else {
938        return Ok(texto.to_string());
939    };
940    expand::una(sh, &palabras[1])
941}