Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Readline: Get a new prompt on SIGINT

I've got code similar to the following, using 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;
}

I've got it set up to intercept SIGINT (i.e. user pressing Ctrl+C), so I can tell that the signal handler handle_signals() is working. However, when control returns to readline(), it's using the same line of text it was using prior to the input. What I'd like to happen is for readline to "cancel" the current line of text and give me a new line, much like the BASH shell. Something like so:

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

Any chance of getting this to work? Something on a mailing list I read mentioned using longjmp(2), but that really doesn't seem like a good idea.

like image 314
Vishal Kotcherlakota Avatar asked May 30 '13 05:05

Vishal Kotcherlakota


1 Answers

You are correct in your line of thinking to use longjmp. But because the longjmp would be in a signal handler, you need to use sigsetjmp/siglongjmp.

As a quick example using your code as a base:

#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 returns a value other than 0 (in this case a 1) to sigsetjmp so the while loop calls sigsetjmp again (a successful return value of sigsetjmp is 0) and will then call readline again.

it may also be helpful to set rl_catch_signals = 1 and then call rl_set_signals() so that the readline signal handling cleans up any variables it needs to before passing the signal to your program where you will then jump back to call readline a second time.

like image 145
jancheta Avatar answered Sep 21 '22 14:09

jancheta