2013-05-30 1 views
10

에 새로운 프롬프트가 : 나는 SIGINT (Ctrl+C을 눌러 즉, 사용자)를 차단하도록 설정되어있어의 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; 
} 

를, 그래서 신호 처리기 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 그래서 while 루프 호출 sigsetjmp를 다시 (성공적인 리턴 값을 만일 sigsetjmp (이 경우 1 년) 0이 아닌 값을 반환에있는 코드를 사용하여 간단한 예를 들어

sigsetjmp가 0 인 경우) readline을 다시 호출합니다.

rl_catch_signals = 1을 설정 한 다음 rl_set_signals()을 호출하면 신호를 프로그램에 전달하기 전에 readline 신호 처리가 필요한 변수를 정리하여 다시 readline을 호출 할 수있게됩니다.

+1

신호 처리기에서'printf'를 안전하게 호출 할 수 없습니다. – pat

4

나는 siglongjmp의 목적이 점프를하기 전에 신호 마스크에서 수신 된 신호를 차단 해제한다는 것을 알게 될 때까지 jancheta의 대답으로 혼란스러워했다. 핸들러가 자신을 인터럽트하지 않도록 신호 핸들러의 항목에서 신호가 차단됩니다. 정상 실행을 재개 할 때 신호를 차단하지 않으려면 longjmp 대신 siglongjmp을 사용해야합니다. AIUI, 이것은 그냥 축약 된 것입니다. sigprocmask 다음에 longjmp을 호출 할 수도 있습니다. 이것은 glibc가 siglongjmp에서 무엇을하고있는 것처럼 보입니다.

readline()mallocfree을 호출하기 때문에 점프하는 것이 안전하지 않을 수 있다고 생각했습니다. malloc 또는 free과 같은 일부 비동기 신호 안전하지 않은 기능이 전역 상태를 수정하는 동안 신호가 수신되면 신호 처리기에서 벗어나면 약간의 손상이 발생할 수 있습니다. 그러나 Readline은 이것에주의를 기울이는 자신의 시그널 핸들러를 설치합니다. 그들은 깃발을 놓고 나가기 만하면됩니다. Readline 라이브러리가 다시 제어를 받으면 (보통 'read()'호출이 중단 된 후) RL_CHECK_SIGNALS()을 호출하고 보류중인 모든 신호를 kill()을 사용하여 클라이언트 응용 프로그램에 전달합니다. 따라서 을 사용하여 readline()에 대한 호출을 인터럽트 한 신호의 신호 처리기를 종료하는 것이 안전합니다. 신호는 비동기 신호가 안전하지 않은 기능에서 수신되지 않았 음을 보증합니다.rl_set_prompt()malloc()free()에 전화를 몇 통, readline() 호출 직전 rl_set_signals()가 있기 때문에

사실, 그것은 완전히 사실이 아니다. 이 부름 명령이 바뀌어야하는지 궁금합니다. 어쨌든 경쟁 조건의 가능성은 매우 희박합니다.

나는 Bash 소스 코드를 보았고 SIGINT 핸들러에서 튀어 나온 것처럼 보인다.

사용할 수있는 또 다른 Readline 인터페이스는 콜백 인터페이스입니다. 이는 Python이나 R과 같이 여러 파일 기술자를 한 번에 청취해야하는 응용 프로그램에서 사용됩니다. 예를 들어 명령 줄 인터페이스가 활성화되어있는 상태에서 플롯 창이 크기가 조정되는지 여부를 알 수 있습니다. 루프는 select() 루프에서이를 수행합니다. 여기

콜백 인터페이스에서 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 처리 코드에서 실행해야한다고 생각합니다. 현재는 첫 번째 두 줄만 실행하기 때문에 incremental-search 및 키보드 매크로와 같은 항목이^C에 의해 취소되지만 줄은 지워지지 않습니다.

다른 포스터는 "Call 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(); 
} 

이 작업 얻기 위해 다른 곳에서 다른 코드를 추가했다 - 아니 전역 변수를 더 설정은 점프하지 않습니다.