¿Cuáles son las razones por las que un exec (execl,execlp, etc.) puede fallar? Si realiza una llamada a exec y regresa, ¿existen mejores prácticas además de entrar en pánico y llamar a exit?
El problema con el manejo exec
el fracaso es que por lo general exec
se realiza en un proceso secundario y desea realizar el manejo de errores en el proceso principal. Pero no puedes simplemente exit(errno)
porque (1) no sabe si los códigos de error caben en un código de salida y (2), no puede distinguir entre errores exec
y códigos de salida de falla del nuevo programa que exec
.
La mejor solución que conozco es usar tuberías para comunicar el éxito o el fracaso de exec
:
- Antes de bifurcar, abra una tubería en el proceso principal.
- Después de bifurcar, el padre cierra el extremo de escritura de la tubería y lee desde el extremo de lectura.
- El niño cierra el final de lectura y establece el indicador de cierre en ejecución para el final de escritura.
- El niño llama a exec.
- Si exec falla, el hijo escribe el código de error en el padre utilizando la canalización y luego sale.
- El padre lee eof (una lectura de longitud cero) si el hijo realizó con éxito
exec
ya que close-on-exec tuvo éxitoexec
cierre el extremo de escritura de la tubería. O siexec
falló, el padre lee el código de error y puede proceder en consecuencia. De cualquier manera, el padre bloquea hasta que el niño llamaexec
. - El padre cierra el extremo de lectura de la tubería.
-
Tengo algunos problemas cuando se redireccionan las secuencias estándar. El proceso principal se bloquea cuando espera que se cierre el final de escritura. Todo funciona si no hay redirección en curso.
– usuario877329
1 de julio de 2016 a las 16:32
-
Normalmente, esto significa que el padre tiene su propio fd para el extremo de escritura de la canalización debido a que no se cerró después de la bifurcación.
– R.. GitHub DEJA DE AYUDAR A ICE
1 de julio de 2016 a las 16:41
-
Parece que
read
no regresa hasta que el proceso hijo haya terminado.– usuario877329
1 de julio de 2016 a las 17:07
-
No. Está configurado
fcntl(exec_error_write_end,FD_CLOEXEC)
. La llamada tiene éxito, pero no tiene ningún efecto.– usuario877329
1 de julio de 2016 a las 17:17
-
Lea la documentación para
fcntl
. No es así como lo usas, y desafortunadamente la API variable evita que el compilador pueda decirte que lo estás usando mal…– R.. GitHub DEJA DE AYUDAR A ICE
1 de julio de 2016 a las 17:59
carl norum
Desde el exec(3)
página man:
los
execl()
,execle()
,execlp()
,execvp()
yexecvP()
las funciones pueden fallar y establecer errno para cualquiera de los errores especificados para las funciones de la bibliotecaexecve(2)
ymalloc(3)
.los
execv()
la función puede fallar y establecer errno para cualquiera de los errores especificados para la función de bibliotecaexecve(2)
.
Y luego desde el execve(2)
página man:
ERRORES
Execve()
fallará y volverá al proceso de llamada si:
[E2BIG]
– El número de bytes en la lista de argumentos del nuevo proceso es mayor que el límite impuesto por el sistema. Este límite está especificado por elsysctl(3)
variable MIBKERN_ARGMAX
.[EACCES]
– Se deniega el permiso de búsqueda para un componente del prefijo de la ruta.[EACCES]
– El nuevo archivo de proceso no es un archivo ordinario.[EACCES]
– El nuevo modo de archivo de proceso niega el permiso de ejecución.[EACCES]
– El nuevo archivo de proceso está en un sistema de archivos montado con la ejecución deshabilitada (MNT_NOEXEC
en<sys/mount.h>
).[EFAULT]
– El nuevo archivo de proceso no es tan largo como lo indican los valores de tamaño en su encabezado.[EFAULT]
– Path, argv o envp apuntan a una dirección ilegal.[EIO]
– Se produjo un error de E/S al leer del sistema de archivos.[ELOOP]
– Se encontraron demasiados enlaces simbólicos al traducir el nombre de la ruta. Esto se considera indicativo de un enlace simbólico en bucle.[ENAMETOOLONG]
– Se excedió un componente de un nombre de ruta{NAME_MAX}
caracteres o un nombre de ruta completo excedido{PATH_MAX}
caracteres.[ENOENT]
– El archivo del nuevo proceso no existe.[ENOEXEC]
– El nuevo archivo de proceso tiene el permiso de acceso adecuado, pero tiene un formato no reconocido (por ejemplo, un número mágico no válido en su encabezado).[ENOMEM]
– El nuevo proceso requiere más memoria virtual de la permitida por el máximo impuesto (getrlimit(2)
).[ENOTDIR]
– Un componente del prefijo de ruta no es un directorio.[ETXTBSY]
– El nuevo archivo de proceso es un archivo de procedimiento puro (texto compartido) que actualmente está abierto para escritura o lectura por parte de algún proceso.
malloc()
es mucho menos complicado, y utiliza sólo ENOMEM
. Desde el malloc(3) man page
:
Si tiene éxito,
calloc()
,malloc()
,realloc()
,reallocf()
yvalloc()
Las funciones devuelven un puntero a la memoria asignada. Si hay un error, devuelven unNULL
puntero y conjuntoerrno
aENOMEM
.
-
Además, consideraré agrupar los ERRNO mediante algún esquema para que no tenga que manejarlos todos individualmente. También puede ser útil hacer un seguimiento de los códigos que surgen durante las pruebas y la implementación para saber si el alcance de su manejo de errores es razonable.
– Dana la Cuerda
13 de septiembre de 2010 a las 17:58
jonathan leffler
¿Qué haces después de la exec()
las devoluciones de llamadas dependen del contexto: lo que se supone que debe hacer el programa, cuál es el error y qué podría hacer para solucionar el problema.
Una fuente de problemas podría ser que haya especificado un nombre de programa simple en lugar de un nombre de ruta; tal vez podrías volver a intentarlo con execvp()
o convertir el comando en una invocación de sh -c 'what you originally specified'
. Si alguno de estos es razonable depende de la aplicación. Si hay problemas importantes de seguridad involucrados, probablemente no vuelva a intentarlo.
Si especificó un nombre de ruta y hay un problema con eso (ENOTDIR, ENOENT, EPERM), es posible que no tenga ninguna alternativa sensata, pero puede informar el error de manera significativa.
En los viejos tiempos (hace más de 10 años), algunos sistemas no admitían el ‘#!’ notación shebang, y si no estaba seguro de si estaba ejecutando un ejecutable o un script de shell, lo probó como un ejecutable y luego lo volvió a intentar como un script de shell. Eso podría o no funcionar si estaba ejecutando un script de Perl, pero en esos días, escribió sus scripts de Perl para detectar que estaban siendo ejecutados por un shell y volver a ejecutarse con Perl. Afortunadamente, esos días en su mayoría han terminado.
En la medida de lo posible, es importante asegurarse de que el proceso informe el problema para que pueda rastrearse, escribiendo su mensaje en un archivo de registro o simplemente en stderr (o tal vez incluso syslog()
), para que aquellos que tienen que averiguar qué salió mal tengan más información que les ayude, aparte del desafortunado informe del usuario final “Probé X y no funcionó”. Es crucial que si nada funciona, entonces el estado de salida no es 0, ya que eso indica éxito. Incluso eso podría ser ignorado, pero hiciste lo que pudiste.
usuario446568
Además de entrar en pánico, podría tomar una decisión basada en el valor de errno.
sam watkins
Exec siempre debe tener éxito (excepto en los shells, por ejemplo, si el usuario ingresó un comando falso).
Si exec falla, indica:
- una “falla” con el programa (componente faltante o defectuoso, nombre de ruta incorrecto, mala memoria, …), o
- un error grave del sistema (memoria insuficiente, demasiados procesos, fallo de disco, …)
Para cualquier error grave, el enfoque normal es escribir el mensaje de error en stderr y luego salir con un código de error. Casi todas las herramientas estándar hacen esto. Para ejecutivo:
execl("bork", "bork", NULL);
perror("failed: exec");
exit(127);
El caparazón también hace eso (más o menos).
Normalmente, si un proceso hijo falla, el padre también ha fallado y debe salir. No importa si el niño falló en ejecución o mientras ejecutaba el programa. Si exec falló, no importa por qué exec falló. Si el proceso secundario falla por algún motivo, el proceso de llamada tiene problemas y debe detenerse.
No pierda mucho tiempo tratando de anticipar todas las posibles condiciones de error. No escriba código que intente manejar cada código de error de la mejor manera posible. Simplemente inflará el código e introducirá muchos errores nuevos. Si su programa está roto o se está abusando de él, simplemente debería fallar. Si lo obligas a continuar, vendrán peores problemas.
Por ejemplo, si el sistema se queda sin memoria y el intercambio de hiperactividad, no queremos realizar ciclos una y otra vez para intentar ejecutar un proceso; solo empeoraría la situación. Si recibimos un error en el sistema de archivos, no queremos continuar ejecutándonos en ese sistema de archivos; podría empeorar la corrupción. Si el programa se instaló incorrectamente, tiene un error o la memoria está dañada, queremos detenerlo lo antes posible, antes de que el programa dañado cause un daño real (como enviar un informe dañado a un cliente, destrozar una base de datos, . ..).
Una alternativa posible: un proceso fallido podría solicitar ayuda, pausarse (SIGSTOP) y luego volver a intentar la operación si se le indica que continúe. Esto podría ayudar cuando el sistema se queda sin memoria, o los discos están llenos, o incluso si hay una falla en el programa. Pocas operaciones son tan costosas e importantes que esto valdría la pena.
Si está creando un programa GUI interactivo, intente hacerlo como un envoltorio delgado sobre herramientas de línea de comandos reutilizables (que se cierran si algo sale mal). Cada función en su programa debe ser accesible a través de la GUI, a través de la línea de comandos y como una llamada de función. Escribe tus funciones. Escriba algunas herramientas para crear envoltorios de línea de comandos y GUI para cualquier función. Utilice subprocesos también.
Si está creando un sistema verdaderamente crítico, como un controlador para una central nuclear o un programa para predecir tsunamis, entonces, ¿qué está haciendo al leer mi tonto consejo? Los sistemas críticos no deben depender completamente de computadoras o software. Tiene que haber una ‘anulación manual’, con alguien que la conduzca. Especialmente, no intente construir un sistema crítico en MS Windows; eso es como construir castillos de arena bajo el agua.
-
Esta es una respuesta bastante obstinada.
– Frankster
13 de septiembre de 2018 a las 17:23
-
Esta es una respuesta bastante obstinada.
– Frankster
13 de septiembre de 2018 a las 17:23
Entrar en pánico rara vez es la mejor práctica.
– bta
13 de septiembre de 2010 a las 17:58
@bta: pero un buen pánico puede ser notablemente terapéutico, si no catártico.
–Jonathan Leffler
13 de septiembre de 2010 a las 18:09
Pregunta sincera, y no pretendo que se tome a mal… ¿Cómo aprendiste a programar Linux? Cualquiera que sea el mecanismo que utilizó, debería haberle enseñado a buscar en las páginas man para preguntas como esta…
– Aidan Cully
13 de septiembre de 2010 a las 18:11
@Aidan: las páginas man que he visto realmente no cubren las mejores prácticas sobre qué hacer si el
exec()
falla–Jonathan Leffler
13 de septiembre de 2010 a las 18:58
Las páginas del manual no solo no cubren las mejores prácticas, sino que realmente no sirven para enumerar los posibles valores de errno. Cotizar:
may fail and set errno for any of the errors specified for the library functions execve(2) and malloc(3)
. Sí, está bien… así que estoy leyendo la página del manual paraexecve
y se refiere a sí mismo sin ninguna especificación. Realmente útil.– spartygw
25 de agosto de 2020 a las 19:40