É possível forçar um programa que ignora sinais a sair em ctrl-C?

É possível forçar um programa que ignora sinais a sair em ctrl-C?

Tenho um programa que ignora o SIGINT, mas que quero executar em primeiro plano. Eu gostaria de encontrar uma maneira de forçá-lo a fechar com Ctrl-C. Existe alguma maneira de escrever um wrapper (que você chamaria ./wrapper.sh my_program) que forçaria o encerramento do programa com comportamento incorreto, potencialmente detectando o SIGINT ignorado e gerando um SIGKILL?

Esta respostaé exatamente o oposto do que estou procurando - gostaria de forçar um programa que ignora um sinal para encerrar no SIGINT.

Responder1

Vamos construir um wrapper que "converte" SIGINT em SIGKILL.

Precisamos de um processo que seja executado my_programe obtenha SIGINT em Ctrl+ c. Ao receber SIGINT, ele deverá enviar SIGKILL para my_program. Observe que não precisamos my_programreceber SIGINT causado por Ctrl+ c; basta que seja SIGKILL causado pelo processo extra.

Fatos relevantes:

  • Uma maneira padrão de executar um processo junto com outro é executar um deles de forma assíncrona em segundo plano (ou seja, com terminação &).
  • Quando você pressiona Ctrl+ c, é o seu emulador de terminal quem envia SIGINT para processos no grupo de processos em primeiro plano.
  • Alguns shells (simples) executam tudo no mesmo grupo de processos. Se eles forem instruídos a executar um comando em segundo plano, eles redirecionarão seu stdin para /dev/nullou arquivo equivalente para evitar que o comando roube entrada.
  • Outros shells podem executar cada comando em um grupo de processos separado. Se eles forem instruídos a executar um comando em segundo plano, eles deixarão seu stdin como está; ainda assim, o comando em segundo plano não será capaz de roubar a entrada do terminal de controle por causa deSIGTTIN. Isso permite que o shell mova um trabalho do segundo plano para o primeiro plano, informando ao terminal o novo grupo de processos em primeiro plano. O mecanismo é chamado de controle de trabalho e pode ser desabilitado. Nos scripts está desabilitado por padrão.
  • De qualquer forma, um processo em segundo plano não pode ler no terminal. Isso significa que não devemos executar my_programem segundo plano caso o stdin seja o terminal e my_programprecise lê-lo.
  • Infelizmente, em alguns shells também não devemos executar o outro processo em segundo plano. Alguns shells usam grupos de processos separados mesmo quando o controle de tarefas está desabilitado. O outro processo que não estiver no grupo de processos em primeiro plano não receberá SIGINT em Ctrl+ c, portanto não poderá "convertê-lo" em SIGKILL.
  • No meu Debian 10 poshexiste um shell que executa tudo no mesmo grupo de processos.

Isso leva ao seguinte wrapper:

#!/usr/bin/env posh

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

exec "$@"é executado my_program(ou o que você especificar), possivelmente com argumentos. Graças a exec my_programsubstituirá o invólucro. Não só será capaz de ler o stdin que pode ser um terminal; seu PID será o do wrapper substituído. Nesse sentido, o invólucro é transparente. Além disso, isso tem um recurso interessante: o PID de my_programé conhecido antes my_programdo início e podemos usá-lo facilmente em outro processo (neste caso, em um subshell em segundo plano) para detectar quando my_programrealmente termina.

O trap"converte" SIGINT em SIGKILL. Nota kill -s KILL 0envia SIGKILL para todo o grupo de processos, incluindo filhos de my_programse eles estiverem no grupo (se você quiser matar my_programapenas, use kill -s KILL "$$"). No entanto kill -s 0 "$$"testa a existência de my_programapenas.

Existe uma alternativa que não requer um shell que execute tudo no mesmo grupo de processos. O truque é: os processos em um pipeline devem ser executados em um grupo de processos; e com um redirecionamento inteligente você pode construir um pipeline onde as peças não estão conectadas umas às outras.

#!/bin/sh -

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

Nesta variante my_programnão substituirá o invólucro. A segunda killé "converter" SIGINT em SIGKILL. A primeira killé encerrar o loop caso my_programsaia em circunstâncias em que o loop sobreviveria.

Existem alguns cenários que podem não funcionar conforme o esperado (com qualquer wrapper). Entre eles:

  • Se my_programbifurca e sai, deixando o verdadeiro trabalho para a criança. O programa problemático em questão provavelmente não se comporta assim, já que você tentou Ctrl+ c-lo. Mas, em geral, pode.
  • Se my_programgerar processos filhos em outro(s) grupo(s) de processos e você quiser eliminá-los junto com seus pais.
  • Se my_programconfigura o terminal para não enviar SIGINT em Ctrl+ c. Se você suspeitar que isso acontece, melhore o wrapper colocando stty -F /dev/tty intr ^Centre sleep 1e done. Neste caso o programa pode até não ignorar o SIGINT, ele pode simplesmente garantir que não o obterá do terminal. Então talvez SIGKILL seja um exagero; talvez seja o suficiente para restaurar esta funcionalidade do terminal e Ctrl+ ccomeçará a funcionar.

De um comentário:

Os programas que ignoram o SIGINT geralmente fazem isso por um bom motivo.

Verdadeiro. Experimente SIGTERM ou SIGHUP em vez de SIGKILL. Talvez o programa em questão não ignore pelo menos um deles e saia normalmente com a limpeza adequada.

informação relacionada