2013-05-30 6 views
10

eine neue Ansage auf SIGINT Werde ich habe Code ähnlich dem folgenden bekam, mit Readline-: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 bis SIGINT (dh Drücken der Tasten Ctrl+C) abzufangen, so dass ich kann sagen, dass der Signalhandler handle_signals() funktioniert. Wenn die Steuerung jedoch zu readline() zurückkehrt, verwendet sie die gleiche Textzeile, die sie vor der Eingabe verwendet hat. Was ich möchte, ist, dass readline die aktuelle Textzeile "abbricht" und mir eine neue Zeile gibt, ähnlich wie die BASH-Shell. Etwas wie so:

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

Jede Chance, dass dies funktioniert? Etwas auf einer Mailingliste, die ich mit longjmp(2) erwähnt habe, aber das scheint wirklich keine gute Idee zu sein.

Antwort

5

Sie sind in Ihrer Linie zu denken, longjmp zu verwenden. Da sich longjmp jedoch in einem Signal-Handler befindet, müssen Sie sigsetjmp/siglongjmp verwenden.

Als kurzes Beispiel der 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 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 (sigsetjmp(ctrlc_buf, 1) != 0); 

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

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

    } 
    return 0; 
} 

siglongjmp gibt einen Wert ungleich 0 (in diesem Fall a 1) wieder sigsetjmp so die while-Schleife Anrufe sigsetjmp (einen erfolgreichen Rückgabewert von sigsetjmp ist 0) und wird dann readline erneut aufrufen.

Es kann auch hilfreich sein, einzustellen und dann rl_set_signals() aufzurufen, damit die Readline-Signalverarbeitung alle Variablen aufräumt, bevor sie das Signal an Ihr Programm weitergibt, wo Sie dann zum zweiten Aufruf von readline zurückspringen.

+1

Sie können 'printf' nicht sicher von einem Signalhandler aufrufen. – pat

4

Zuerst war ich durch die Antwort von Jancheta verwirrt, bis ich entdeckte, dass der Zweck von siglongjmp darin besteht, das empfangene Signal in der Signalmaske zu entsperren, bevor der Sprung ausgeführt wird. Das Signal wird am Eingang des Signal-Handlers blockiert, so dass sich der Handler nicht selbst unterbricht. Wir wollen das Signal nicht blockieren, wenn wir die normale Ausführung fortsetzen, und deshalb verwenden wir siglongjmp anstelle von longjmp. AIUI, das ist nur Kurzschrift, wir könnten auch sigprocmask gefolgt von longjmp aufrufen, was zu sein scheint, was glibc in siglongjmp tut.

Ich dachte, es könnte unsicher sein, einen Sprung zu tun, weil readline() Anrufe malloc und free. Wenn das Signal empfangen wird, während eine asynchrone Signal-unsichere Funktion wie malloc oder free den globalen Zustand verändert, könnte es zu einer gewissen Beschädigung kommen, wenn wir dann aus dem Signal-Handler herausspringen würden. Aber Readline installiert seine eigenen Signalhandler, die darauf achten. Sie haben nur eine Flagge gesetzt und gehen ab; Wenn die Readline-Bibliothek die Kontrolle wieder erlangt (normalerweise nach einem unterbrochenen "read()" -Aufruf), ruft sie RL_CHECK_SIGNALS() auf, die dann ein anstehendes Signal unter Verwendung von kill() an die Client-Anwendung weiterleitet. Es ist also sicher, siglongjmp() zu verwenden, um einen Signalhandler für ein Signal zu verlassen, das einen Anruf zu unterbrochen hat - das Signal wird garantiert nicht während einer async-Signal-unsicheren Funktion empfangen worden sein.

Eigentlich ist das nicht ganz richtig, weil es ein paar Anrufe zu malloc() und free() innerhalb rl_set_prompt() sind, die readline() Anrufe kurz vor rl_set_signals(). Ich frage mich, ob diese Rufreihenfolge geändert werden sollte. In jedem Fall ist die Wahrscheinlichkeit einer Race Condition sehr gering.

Ich schaute auf den Bash-Quellcode und es scheint aus seinem SIGINT-Handler zu springen.

Eine andere 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 z. B. festzustellen, ob die Größe eines Plotfensters geändert wird, während die Befehlszeilenschnittstelle aktiv ist. Sie tun dies in einer select() Schleife.

Hier eine Nachricht von Chet Ramey, die einige Ideen davon, was zu tun Bash-ähnliches Verhalten zu erhalten, nach dem Empfang SIGINT in der Callback-Schnittstelle: schlägt

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

Die Nachrichten, dass Sie etwas tun, wie dies:

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 gesetzt könnte, und versuchen Sie es später die Flagge in Ihrer select() Schleife - da die select() Anruf erhalten unterbrochen wird durch das Signal mit errno==EINTR. Wenn Sie feststellen, dass das Flag gesetzt wurde, führen Sie den obigen Code aus.

Meine Meinung ist, dass Readline etwas wie das obige Fragment in seinem eigenen SIGINT-Handling-Code ausführen sollte. Derzeit führt es mehr oder weniger nur die ersten zwei Zeilen aus, weshalb Dinge wie inkrementelle Such- und Tastaturmakros durch^C abgebrochen werden, aber die Zeile wird nicht gelöscht.

Ein anderes Plakat sagte "Call rl_clear_signals()", die mich immer noch verwirrt. Ich habe es nicht ausprobiert, aber ich sehe nicht, wie es irgendwas erreichen würde, vorausgesetzt, dass (1) die Signalhandler von Readline das Signal sowieso weiterleiten, und (2) readline() installiert die Signalhandler bei der Eingabe (und löscht sie, wenn sie beendet wird)), so dass sie normalerweise außerhalb des Readline-Codes nicht aktiv sind.

1

Einen Sprung zu erstellen scheint hacky und fehleranfällig für mich. Die Shell-Implementierung, zu der ich diese Unterstützung hinzufügte, erlaubte diese Änderung nicht.

Glücklicherweise hat readline eine klarere, alternative Lösung. Meine SIGINT Handler wie folgt aussehen:

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 keinen anderen anderswo zusätzlichen Code nahmen diese Arbeit zu bekommen - keine globalen Variablen, springt keine Einstellung.