Readline: Erhalten Sie eine neue Eingabeaufforderung auf SIGINT

Lesezeit: 6 Minuten

Ich habe Code ähnlich dem folgenden, mit readline:

#include <errno.h>
#include <error.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <readline/readline.h>
#include <readline/history.h>

void handle_signals(int signo) {
  if (signo == SIGINT) {
    printf("You pressed Ctrl+C\n");
  }
}

int main (int argc, char **argv)
{
   //printf("path is: %s\n", path_string);
  char * input;
  char * shell_prompt = "i-shell> ";
  if (signal(SIGINT, handle_signals) == SIG_ERR) {
    printf("failed to register interrupts with kernel\n");
  }

  //set up custom completer and associated data strucutres
  setup_readline();

  while (1) 
  {
    input = readline(shell_prompt);
    if (!input)
      break;
    add_history(input);

    //do something with the code
    execute_command(input);

  }  
  return 0;
}

Ich habe es zum Abfangen eingerichtet SIGINT (dh Benutzer drückt Ctrl+C), also kann ich sagen, dass der Signalhandler handle_signals() funktioniert. Allerdings, wenn die Kontrolle zurückkehrt readline(), verwendet es dieselbe Textzeile wie vor der Eingabe. Ich möchte, dass readline die aktuelle Textzeile “abbricht” und mir eine neue Zeile gibt, ähnlich wie die BASH-Shell. Etwas in der Art:

i-shell> bad_command^C
i-shell> _

Gibt es eine Chance, das zum Laufen zu bringen? Etwas auf einer Mailingliste, die ich gelesen habe, erwähnte Verwendung longjmp(2)aber das scheint wirklich keine gute Idee zu sein.

Benutzer-Avatar
Jancheta

Sie sind richtig in Ihrer Denkweise, longjmp zu verwenden. Da sich longjmp jedoch in einem Signalhandler befinden würde, müssen Sie sigsetjmp/siglongjmp verwenden.

Als kurzes Beispiel mit Ihrem Code als Basis:

#include <setjmp.h>
#include <errno.h>
#include <error.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <readline/readline.h>
#include <readline/history.h>

sigjmp_buf ctrlc_buf;

void handle_signals(int signo) {
  if (signo == SIGINT) {
    printf("You pressed Ctrl+C\n");
    siglongjmp(ctrlc_buf, 1);
  }
}

int my_cmd_loop(int argc, char **argv)
{
   //printf("path is: %s\n", path_string);
  char * input;
  char * shell_prompt = "i-shell> ";
  if (signal(SIGINT, handle_signals) == SIG_ERR) {
    printf("failed to register interrupts with kernel\n");
  }

  //set up custom completer and associated data strucutres
  setup_readline();

  while (1) 
  {
    while ( sigsetjmp( ctrlc_buf, 1 ) != 0 );

    input = readline(shell_prompt);
    if (!input)
      break;
    add_history(input);

    //do something with the code
    execute_command(input);

  }  
  return 0;
}

siglongjmp gibt einen anderen Wert als 0 (in diesem Fall eine 1) an sigsetjmp zurück, sodass die While-Schleife sigsetjmp erneut aufruft (ein erfolgreicher Rückgabewert von sigsetjmp ist 0) und dann erneut readline aufruft.

es kann auch hilfreich sein einzustellen rl_catch_signals = 1 und dann anrufen rl_set_signals() damit die readline-Signalverarbeitung alle Variablen bereinigt, die sie benötigt, bevor sie das Signal an Ihr Programm weiterleitet, wo Sie dann zurückspringen, um readline ein zweites Mal aufzurufen.

  • Sie können nicht sicher anrufen printf von einem Signalhandler.

    – pat

    18. April 2014 um 7:16 Uhr


Benutzer-Avatar
Metamorph

Ich war zuerst verwirrt von Janchetas Antwort, bis ich entdeckte, dass der Zweck von siglongjmp ist, das empfangene Signal in der Signalmaske zu entsperren, bevor der Sprung gemacht wird. Das Signal wird am Eingang des Signalhandlers blockiert, damit der Handler sich nicht selbst unterbricht. Wir wollen das Signal nicht blockiert lassen, wenn wir die normale Ausführung wieder aufnehmen, und deshalb verwenden wir siglongjmp Anstatt von longjmp. AIUI, das ist nur eine Abkürzung, könnten wir auch nennen sigprocmask gefolgt von longjmpdas scheint das zu sein, was glibc tut siglongjmp.

Ich dachte, es wäre unsicher, einen Sprung zu machen, weil readline() Anrufe malloc und free. Wenn das Signal empfangen wird, während einige asynchrone Signal-unsichere Funktionen wie z malloc oder free den globalen Zustand modifiziert, könnte es zu einer Beschädigung kommen, wenn wir dann aus dem Signal-Handler herausspringen würden. Aber Readline installiert seine eigenen Signal-Handler, die darauf achten. Sie setzen einfach eine Flagge und gehen; Wenn die Readline-Bibliothek wieder die Kontrolle erhält (normalerweise nach einem unterbrochenen ‘read()’-Aufruf), ruft sie auf RL_CHECK_SIGNALS() die dann jedes anstehende Signal an die Client-Anwendung weiterleitet kill(). Es ist also sicher zu verwenden siglongjmp() um einen Signalhandler für ein Signal zu verlassen, das einen Anruf unterbrochen hat readline() – Das Signal wurde garantiert nicht während einer Async-Signal-unsafe-Funktion empfangen.

Eigentlich stimmt das nicht ganz, denn es kommen ein paar Anrufe dazu malloc() und free() innerhalb rl_set_prompt()die readline() ruft kurz vorher an rl_set_signals(). Ich frage mich, ob diese Anrufreihenfolge geändert werden sollte. In jedem Fall ist die Wahrscheinlichkeit einer Rennbedingung sehr gering.

Ich habe mir den Bash-Quellcode angesehen und er scheint aus seinem SIGINT-Handler zu springen.

Eine weitere Readline-Schnittstelle, die Sie verwenden können, ist die Callback-Schnittstelle. Dies wird von Anwendungen wie Python oder R verwendet, die mehrere Dateideskriptoren gleichzeitig abhören müssen, um beispielsweise festzustellen, ob die Größe eines Plotfensters geändert wird, während die Befehlszeilenschnittstelle aktiv ist. Sie werden dies in a tun select() Schleife.

Hier ist eine Nachricht von Chet Ramey, die einige Ideen gibt, was zu tun ist, um ein Bash-ähnliches Verhalten zu erhalten, wenn SIGINT in der Callback-Schnittstelle empfangen wird:

https://lists.gnu.org/archive/html/bug-readline/2016-04/msg00071.html

Die Meldungen schlagen vor, dass Sie so etwas tun:

    rl_free_line_state ();
    rl_cleanup_after_signal ();
    RL_UNSETSTATE(RL_STATE_ISEARCH|RL_STATE_NSEARCH|RL_STATE_VIMOTION|RL_STATE_NUMERICARG|RL_STATE_MULTIKEY);
    rl_line_buffer[rl_point = rl_end = rl_mark = 0] = 0;
    printf("\n");

Wenn Ihr SIGINT empfangen wird, können Sie ein Flag setzen und später das Flag in Ihrem überprüfen select() Schleife – seit dem select() Der Anruf wird durch das Signal mit unterbrochen errno==EINTR. Wenn Sie feststellen, dass das Flag gesetzt wurde, führen Sie den obigen Code aus.

Meiner Meinung nach sollte Readline so etwas wie das obige Fragment in seinem eigenen SIGINT-Behandlungscode ausführen. Derzeit führt es mehr oder weniger nur die ersten beiden Zeilen aus, weshalb Dinge wie inkrementelle Suche und Tastaturmakros von ^C abgebrochen werden, aber die Zeile nicht gelöscht wird.

Ein anderer Poster sagte “Call rl_clear_signals()”, was mich immer noch verwirrt. Ich habe es nicht ausprobiert, aber ich sehe nicht, wie es irgendetwas bewirken würde, da (1) die Signal-Handler von Readline das Signal trotzdem an Sie weiterleiten und (2) readline() installiert die Signal-Handler beim Eintritt (und löscht sie beim Beenden), sodass sie normalerweise außerhalb des Readline-Codes nicht aktiv sind.

Das Erstellen eines Sprungs erscheint mir hacky und fehleranfällig. Die Shell-Implementierung, zu der ich diese Unterstützung hinzufügte, ließ diese Änderung nicht zu.

Glücklicherweise, readlinehat eine klarere, alternative Lösung. Mein SIGINT Handler sieht so aus:

static void
int_handler(int status) {
    printf("\n"); // Move to a new line
    rl_on_new_line(); // Regenerate the prompt on a newline
    rl_replace_line("", 0); // Clear the previous text
    rl_redisplay();
}

Dies erforderte keinen weiteren zusätzlichen Code an anderer Stelle, um dies zum Laufen zu bringen – keine globalen Variablen, keine Einstellungssprünge.

Benutzer-Avatar
alk

Anruf rl_clear_signals().

Dadurch werden die Signalhandler deaktiviert libreadline Eingerichtet. Der, der anfasst SIGINT ist für das beobachtete Verhalten beim Wiederherstellen der Eingabeaufforderung verantwortlich.

Weitere Details zur Verwaltung readline()s Signalbehandlung kann hier nachgelesen werden.

1131190cookie-checkReadline: Erhalten Sie eine neue Eingabeaufforderung auf SIGINT

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy