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…
simi: el /bin/sh propio del producto — 4448 líneas, sólo libc, y CERO divergencias contra el ash
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}