2013-05-30 2 views
10

У меня есть код, подобный следующему, с помощью Readline:Readline: Получить новый запрос на SIGINT

#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; 
} 

Я получил его настроить для перехвата SIGINT (т.е. пользователь может нажать Ctrl+C), так что я может сказать, что обработчик сигнала handle_signals() работает. Однако, когда управление возвращается к readline(), оно использует ту же строку текста, которую использовала до ввода. Я бы хотел, чтобы readline «отменил» текущую строку текста и дал мне новую строку, похожую на оболочку BASH. Что-то вроде этого:

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

Есть ли шанс получить это, чтобы это сработало? Что-то в списке рассылки, о котором я читал, упоминается с использованием longjmp(2), но это действительно не похоже на хорошую идею.

ответ

5

Вы правы в своей линии мышления, чтобы использовать longjmp. Но поскольку longjmp будет в обработчике сигнала, вам нужно использовать sigsetjmp/siglongjmp.

Как простой пример, используя свой код в качестве основы:

#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 возвращает значение, отличное от 0 (в данном случае 1) в sigsetjmp поэтому цикл вызовы во время sigsetjmp снова (успешного возвращаемого значения sigsetjmp равно 0), а затем снова вызовет readline.

также может быть полезно установить rl_catch_signals = 1, а затем позвонить rl_set_signals(), чтобы обработка сигнала readline очищала любые переменные, необходимые для передачи сигнала в вашу программу, после чего вы снова переходите к вызову readline во второй раз.

+1

Вы не можете безопасно вызывать 'printf' из обработчика сигналов. – pat

4

Сначала я был смущен ответом Джанчета, пока не обнаружил, что цель siglongjmp состоит в том, чтобы разблокировать принятый сигнал в маске сигнала, прежде чем совершать прыжок. Сигнал блокируется при вводе обработчика сигнала, так что обработчик не прерывает себя. Мы не хотим оставлять сигнал заблокированным, когда мы возобновляем нормальное выполнение, поэтому мы используем siglongjmp вместо longjmp. AIUI, это просто сокращение, мы могли бы также позвонить sigprocmask, а затем longjmp, что, кажется, то, что glibc делает в siglongjmp.

Я думал, что это может быть опасно делать прыжок, потому что readline() звонки malloc и free. Если сигнал принят, в то время как некоторая неактивная функция асинхронного сигнала, такая как malloc или free, модифицирует глобальное состояние, может произойти некоторое повреждение, если мы должны были выскочить из обработчика сигнала. Но Readline устанавливает собственные обработчики сигналов, которые осторожно относятся к этому. Они просто устанавливают флаг и выходят; когда библиотека Readline снова получает контроль (обычно после прерывания вызова read()), он вызывает RL_CHECK_SIGNALS(), который затем пересылает любой ожидающий сигнал в клиентское приложение с использованием kill(). Таким образом, можно безопасно использовать siglongjmp() для выхода из обработчика сигнала для сигнала, который прервал вызов readline() - сигнал гарантированно не был получен во время небезопасной функции асинхронного сигнала.

На самом деле, это не совсем верно, потому что есть несколько вызовов malloc() и free() в пределах rl_set_prompt(), которые readline() вызовов непосредственно перед rl_set_signals(). Интересно, должен ли этот порядок вызова быть изменен. В любом случае вероятность состояния гонки очень тонкая.

Я посмотрел исходный код Bash и, похоже, выпрыгнул из своего обработчика SIGINT.

Другим интерфейсом Readline, который вы можете использовать, является интерфейс обратного вызова. Это используется приложениями, такими как Python или R, которые необходимо прослушивать сразу в нескольких дескрипторах файлов, например, чтобы определить, изменяется ли окно графика при активном интерфейсе командной строки. Они сделают это в цикле select().

Вот сообщение от Chet Рэми, который дает некоторые идеи о том, что нужно сделать, чтобы получить Баш поведение как при получении SIGINT в интерфейсе обратного вызова:

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

выводятся сообщения говорит о том, что вы делаете что-то вроде это:

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"); 

Когда SIGINT получен, вы можете установить флаг, а затем проверить флаг в вашем select() цикле - так как select() вызов будет получить прерван по сигналу с errno==EINTR. Если вы обнаружите, что флаг установлен, выполните вышеуказанный код.

Мое мнение таково, что Readline должен запускать что-то вроде вышеуказанного фрагмента в своем собственном коде обработки SIGINT. В настоящее время он более или менее выполняет только первые две строки, поэтому такие вещи, как макросы инкрементного поиска и клавиатуры, отменены на^C, но строка не очищается.

Другой плакат сказал «Звоните rl_clear_signals()», который все еще меня смущает. Я не пробовал, но я не вижу, как это будет выполнено, учитывая, что (1) обработчики сигналов Readline передают вам сигнал в любом случае, и (2) readline() устанавливает обработчики сигналов при вводе (и очищает их при выходе), поэтому они обычно не будут активны вне кода Readline.

1

Создание прыжка кажется взломанным и подверженным ошибкам. Реализация оболочки, с которой я добавлял эту поддержку, не позволяла это изменение.

К счастью, readline имеет более четкое, альтернативное решение. Мой SIGINT обработчик выглядит следующим образом:

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(); 
} 

Это не принял никакого другого дополнительного кода в другом месте, чтобы получить эту работу - никаких глобальных переменных, настройки не прыгает.

Смежные вопросы