add a console_callbacks::on_interrupt callback triggered by SIGINT while waiting
#include "console.h"
#include "log.h"
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <signal.h>
#include <assert.h>
struct console {
/** Configuration */
struct console_config config;
/** Input event */
struct event *ev;
/** Callback functions */
const struct console_callbacks *callbacks;
/** Callback context argument */
void *cb_arg;
/** Old SIGINT handler */
struct sigaction old_sigint;
/** Already initialized? */
bool initialized;
/** Set as log output function? */
bool log_output;
/** Prompt displayed? */
bool have_prompt;
/** Waiting for line to be processed */
bool waiting;
};
/** The global console state */
static struct console _console;
/**
* Our stdin input handler
*/
static void console_input (int fd, short what, void *arg)
{
struct console *console = arg;
(void) fd;
(void) what;
if (console->waiting)
// can't feed readline input while it's disabled
return log_warn("discrding input while waiting");
else
// tell readline to process it
rl_callback_read_char();
}
/**
* Our readline line handler
*/
static void console_line (char *line)
{
struct console *console = &_console;
enum console_line_status status = CONSOLE_CONTINUE;
// special-case EOF
if (!line) {
// prettify
rl_crlf();
rl_on_new_line();
if (console->callbacks->on_eof)
console->callbacks->on_eof(console->cb_arg);
return;
}
// update state for console_print during processing
console->have_prompt = false;
// invoke the console callback
if (console->callbacks && console->callbacks->on_line)
status = console->callbacks->on_line(line, console->cb_arg);
// add to history mechanism
add_history(line);
// release the line
free(line);
switch (status) {
case CONSOLE_CONTINUE:
// the prompt will be displayed again
console->have_prompt = true;
break;
case CONSOLE_WAIT:
// deactivate our read event
if (event_del(console->ev))
log_warn("unable to deactivate console read event");
// remove the readline stuff to stop it from displaying the prompt
rl_callback_handler_remove();
// ignore input
console->waiting = true;
break;
}
}
static void on_sigint (int sig)
{
struct console *console = &_console;
(void) sig;
if (console->waiting) {
// notify user
if (console->callbacks->on_interrupt)
console->callbacks->on_interrupt(console->cb_arg);
} else {
// interrupt the input line
// XXX: is this the right function to call?
rl_free_line_state();
// redisplay on new line
rl_crlf();
rl_callback_handler_install(console->config.prompt, console_line);
}
}
err_t console_init (struct console **console_ptr, struct event_base *ev_base, const struct console_config *config,
const struct console_callbacks *callbacks, void *cb_arg, struct error_info *err)
{
struct console *console = &_console;
// check it's not already initialized
assert(!console->initialized);
// store
console->config = *config;
// store callbacks?
if (callbacks)
console_set_callbacks(console, callbacks, cb_arg);
// setup the input event
if ((console->ev = event_new(ev_base, STDIN_FILENO, EV_READ | EV_PERSIST, &console_input, console)) == NULL)
JUMP_SET_ERROR(err, ERR_EVENT_NEW);
// set our SIGINT handler
struct sigaction sigact;
memset(&sigact, 0, sizeof(sigact));
sigact.sa_handler = on_sigint;
sigaction(SIGINT, &sigact, &console->old_sigint);
// setup state for initial readline prompt
console->have_prompt = true;
// setup readline
rl_callback_handler_install(config->prompt, console_line);
// mark it as initialized
console->initialized = true;
// enable input
if (event_add(console->ev, NULL))
JUMP_SET_ERROR(err, ERR_EVENT_ADD);
// ok
*console_ptr = console;
return SUCCESS;
error:
console_destroy(console);
return ERROR_CODE(err);
}
void console_set_callbacks (struct console *console, const struct console_callbacks *callbacks, void *cb_arg)
{
console->callbacks = callbacks;
console->cb_arg = cb_arg;
}
void console_continue (struct console *console)
{
if (!console->waiting)
return;
// re-enable input
console->waiting = false;
if (event_add(console->ev, NULL))
log_fatal("unable to re-enable console read event");
// the prompt will be displayed again
console->have_prompt = true;
// re-setup readline with prompt
rl_callback_handler_install(console->config.prompt, console_line);
}
err_t console_print (struct console *console, const char *line)
{
if (console->have_prompt)
// don't interrupt current input line
rl_crlf();
// output the line
if (printf("%s\n", line) < 0)
return _ERR_GENERAL;
if (console->have_prompt) {
// restore input
rl_on_new_line();
rl_redisplay();
}
// ok
return SUCCESS;
}
/**
* Our log_output_func implementation
*/
static void console_log_output_func (const char *line, void *_console)
{
struct console *console = _console;
console_print(console, line);
}
void console_set_log_output (struct console *console)
{
// replace old one
log_set_func(console_log_output_func, console);
// mark
console->log_output = true;
}
void console_destroy (struct console *console)
{
if (console->log_output)
// unset ourselves as the log handler
log_set_func(NULL, NULL);
// remove the input event
if (console->ev)
event_free(console->ev);
console->ev = NULL;
// de-init rl?
if (console->initialized)
rl_callback_handler_remove();
console->initialized = false;
// restore signal handler
sigaction(SIGINT, &console->old_sigint, NULL);
// remove stored stuff
console->callbacks = console->cb_arg = NULL;
}