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…
feat(simi): `alias` no existía, y con eso ningún atajo de la consola funcionaba
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}