¿Es posible forzar el cierre de un programa que ignora las señales con Ctrl-C?

¿Es posible forzar el cierre de un programa que ignora las señales con Ctrl-C?

Tengo un programa que ignora SIGINT pero quiero ejecutarlo en primer plano. Me gustaría encontrar una manera de forzar su cierre con Ctrl-C. ¿Hay alguna forma de escribir un contenedor (al que usted llamaría ./wrapper.sh my_program) que obligaría a cerrar el programa que se comporta mal, potencialmente detectando el SIGINT ignorado y generando un SIGKILL?

esta respuestaes exactamente lo contrario de lo que estoy buscando: me gustaría forzar a un programa que ignora una señal a salir de SIGINT.

Respuesta1

Construyamos un contenedor que "convierta" SIGINT en SIGKILL.

Necesitamos un proceso que se ejecute my_programy obtenga SIGINT en Ctrl+ c. Cuando recibe SIGINT debe enviar SIGKILL a my_program. Tenga en cuenta que no necesitamos my_programrecibir SIGINT causado por Ctrl+ c; es suficiente que obtenga SIGKILL causado por el proceso adicional.

Hechos relevantes:

  • Una forma estándar de ejecutar un proceso junto con otro es ejecutar uno de ellos de forma asincrónica en segundo plano (es decir, terminando &).
  • Cuando presionas Ctrl+ c, es tu emulador de terminal quien envía SIGINT a los procesos en el grupo de procesos de primer plano.
  • Algunos shells (simples) ejecutan todo en el mismo grupo de procesos. Si se les dice que ejecuten un comando en segundo plano, redirigirán su entrada estándar a /dev/nullun archivo equivalente para evitar que el comando robe la entrada.
  • Otros shells pueden ejecutar cada comando en un grupo de procesos separado. Si se les dice que ejecuten un comando en segundo plano, dejarán su entrada estándar como está; Aún así, el comando en segundo plano no podrá robar la entrada del terminal de control debido aSIGTTIN. Esto permite que el shell mueva un trabajo del fondo al primer plano informando al terminal del nuevo grupo de procesos en primer plano. El mecanismo se llama control de trabajo y se puede desactivar. En los scripts está deshabilitado de forma predeterminada.
  • De cualquier manera, un proceso en segundo plano no puede leer desde la terminal. Esto significa que no debemos ejecutar my_programen segundo plano en caso de que la entrada estándar sea la terminal y my_programnecesite leer desde ella.
  • Desafortunadamente, en algunos shells tampoco deberíamos ejecutar el otro proceso en segundo plano. Algunos shells utilizan grupos de procesos separados incluso cuando el control de trabajos está deshabilitado. El otro proceso que no esté en el grupo de procesos de primer plano no recibirá SIGINT en Ctrl+ c, por lo que no podrá "convertirlo" a SIGKILL.
  • En mi Debian 10 poshhay un shell que ejecuta todo en el mismo grupo de procesos.

Esto lleva al siguiente contenedor:

#!/usr/bin/env posh

( trap 'kill -s KILL 0' INT
  while kill -s 0 "$$" 2>/dev/null; do sleep 1; done
) &
exec "$@"

exec "$@"se ejecuta my_program(o lo que usted especifique), posiblemente con argumentos. Gracias a exec my_programreemplazará el envoltorio. No solo podrá leer desde la entrada estándar que puede ser una terminal; su PID será el del contenedor reemplazado. En este sentido el envoltorio es transparente. Además, esto tiene una característica interesante: el PID my_programse conoce antes de my_programque comience y podemos usarlo fácilmente en el otro proceso (en este caso en un subshell en segundo plano) para detectar cuándo my_programtermina realmente.

El trap"convierte" SIGINT en SIGKILL. Note kill -s KILL 0envía SIGKILL a todo el grupo de procesos, incluidos los hijos de my_programsi están en el grupo (si desea eliminar my_programsolo, utilícelo kill -s KILL "$$"en su lugar). Sin embargo kill -s 0 "$$"prueba la existencia de my_programsólo.

Existe una alternativa que no requiere un shell que ejecute todo en el mismo grupo de procesos. El truco es: los procesos de una canalización deben ejecutarse en un grupo de procesos; y mediante una redirección inteligente se puede construir un canal donde las piezas no estén conectadas entre sí.

#!/bin/sh -

exec 9>&1
( "$@"; kill -s TERM 0 ) >&9 9>&- | (
  trap 'kill -s KILL 0' INT
  while :; do sleep 1; done
)

En esta variante my_programno se sustituirá el envoltorio. El segundo killes "convertir" SIGINT a SIGKILL. La primera killes terminar el bucle en caso de my_programsalidas en circunstancias en las que, de otro modo, el bucle sobreviviría.

Hay algunos escenarios que pueden no funcionar como se espera (con cualquier contenedor). Entre ellos:

  • Si my_programse bifurca y sale, dejando el verdadero trabajo al niño. Lo más probable es que el programa problemático en cuestión no se comporte así, ya que intentaste Ctrl+ c. Pero en general podría ser así.
  • Si my_programgenera procesos secundarios en otros grupos de procesos y desea eliminarlos junto con sus padres.
  • Si my_programconfigura el terminal para no enviar SIGINT en Ctrl+ c. Si sospecha que esto sucede, mejore el contenedor poniendo stty -F /dev/tty intr ^Centre sleep 1y done. En este caso, es posible que el programa ni siquiera ignore SIGINT, simplemente puede asegurarse de que no lo obtendrá del terminal. Entonces tal vez SIGKILL sea excesivo; tal vez sea suficiente con restaurar esta funcionalidad del terminal y Ctrl+ ccomenzará a funcionar.

De un comentario:

Los programas que ignoran SIGINT normalmente lo hacen por una muy buena razón.

Verdadero. Pruebe SIGTERM o SIGHUP en lugar de SIGKILL. Quizás el programa en cuestión no ignore al menos uno de estos y salga correctamente con una limpieza adecuada.

información relacionada