2013-05-30 64 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 shell。像這樣的東西:

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所以while循環調用sigsetjmp再次(一個成功的返回值的sigsetjmp爲0),然後再調用readline。

設置rl_catch_signals = 1然後調用rl_set_signals()以便readline信號處理可以在將信號傳遞到程序之前清除它需要的所有變量,然後再跳回到第二次調用readline。

+1

您不能從信號處理程序安全地調用'printf'。 – pat

4

剛開始時,jancheta的回答讓我感到困惑,直到我發現siglongjmp的目的是在跳轉前解除信號掩碼中接收到的信號。信號在信號處理程序的入口處被阻塞,以便處理程序不會自行中斷。當我們恢復正常執行時,我們不想讓信號被阻塞,這就是爲什麼我們使用siglongjmp而不是longjmp。 AIUI,這只是簡寫,我們也可以撥打sigprocmask後跟longjmp,這似乎是glibc在siglongjmp中所做的。

我認爲做跳轉可能不安全,因爲readline()調用mallocfree。如果接收到信號,而像mallocfree這樣的異步信號不安全功能正在修改全局狀態,那麼如果我們然後跳出信號處理程序,可能會導致一些損壞。但是Readline安裝了自己的信號處理程序,對此非常小心。他們只是設置一個標誌並退出;當Readline庫再次獲得控制時(通常在中斷'read()'調用之後),它調用RL_CHECK_SIGNALS(),然後使用kill()將任何未決信號轉發到客戶端應用程序。因此,使用siglongjmp()可以安全地退出信號處理程序,以處理中斷對readline()的呼叫的信號 - 確保信號在異步信號不安全功能期間未收到。

事實上,因爲有內rl_set_prompt()了幾個電話給malloc()free(),這readline()調用之前rl_set_signals()這是不完全正確。我想知道這個呼叫順序是否應該改變。無論如何,競賽條件的可能性非常渺茫。

我看着Bash源代碼,它似乎跳出它的SIGINT處理程序。

您可以使用的另一個Readline接口是回調接口。這被諸如Python或R這樣的應用程序使用,它們需要一次偵聽多個文件描述符,例如在命令行界面處於活動狀態時判斷是否調整了繪圖窗口的大小。他們會在select()循環中執行此操作。

這裏是切特·拉梅的消息這給什麼就做什麼時回調接口接收SIGINT獲得bash類似的行爲的一些想法:

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

創建一個跳轉看起來很詭異,對我來說很容易出錯。我加入這個支持的shell實現不允許這種改變。

幸運的是,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(); 
} 

這沒有采取其他額外的代碼在其他地方得到這個工作 - 沒有全局變量,沒有設置跳躍。