I am trying to write a minimal implementation of a shell for a school project. To write such an implementation I use the readline
function and I have to handle SIGINT
so that I display a new prompt on a new line when it occurs within the program life (which could be at two key moments that are just after the user input has been read (beginning of the while
loop) or after the line has been executed (end of the loop).
First a minimal reproducible example of my problem (with some prints for me to better understand what happens. I left the comments explaining what I tried and will explain them right after the chunk of code you need to see to understand.
#include <signal.h>
#include <sys/wait.h>
#include <stdio.h>
#include <readline/readline.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
#define RED "\e[0;31m"
#define GRN "\e[0;32m"
#define CRESET "\e[0m"
volatile sig_atomic_t g_termsig;
extern char **environ;
int event_hook(void)
{
return (0);
}
int init_sig(void (*handler)(int, siginfo_t *, void *), const int signum)
{
struct sigaction sa;
bzero(&sa, sizeof sa);
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = handler;
//if (sigemptyset(&sa.sa_mask) || sigaddset(&sa.sa_mask, signum))
// return (1);
return (sigaction(signum, &sa, NULL));
}
_Noreturn void exit_message(const char *message, const int status)
{
const char *color;
color = RED;
if (! status)
color = GRN;
fprintf(stderr, "%s%s%s\n", color, message, CRESET);
exit(status);
}
void interrupt_handler(int sig, siginfo_t *info, void *ucontext)
{
(void)info;
(void)ucontext;
g_termsig = 128 + sig;
rl_done = 1;
/*
if (! rl_done)
ioctl(0, TCSTI, "\n");
*/
//fprintf(stdout, "\nThis is readline_buffer : %s\n", rl_line_buffer);
}
void restore_display(void)
{
fprintf(stdout, "Here is yo signal : %d\n", g_termsig);
g_termsig = 0;
/*
fprintf(stdout, "In function : %s\nThis is readline_buffer : %s\n", \
__func__, rl_line_buffer);
*/
// rl_replace_line("", 0);
//fprintf(stdout, "This is the new line buffer : %s\n", rl_line_buffer);
// rl_on_new_line();
// rl_redisplay();
}
int execute_line(const char *line)
{
pid_t child_pid;
char *args[2] = {(char *)line, NULL};
int status;
child_pid = fork();
/*Here we set status to 0x100 so that WIFEXITED(status) is true
and WEXITSTATUS(status) is -1*/
status = 0x100;
if (child_pid < 0)
exit_message("No fork my G, gotta spoon dis time\n", 1);
if (! child_pid)
{
signal(SIGQUIT, SIG_DFL);
signal(SIGINT, SIG_DFL);
execve(line, args, environ);
exit_message("Dude wtf?! Is that even a real command you typed?\n", 1);
}
wait(&status);
return (status);
}
int main(void)
{
char *line;
signal(SIGQUIT, SIG_IGN);
if (init_sig(interrupt_handler, SIGINT))
exit_message("Yo, can't setup handler my G\n", 1);
/*
printf("This is readline output stream : %p\n", rl_outstream);
printf("This is the address of rl_startup_hook : %p\n", rl_startup_hook);
printf("This is the address of rl_pre_input_hook : %p\n", rl_pre_input_hook);
printf("This is the address of rl_event_hook : %p\n", rl_event_hook);
*/
rl_event_hook = event_hook;
while (1)
{
line = readline("Enter yo commands> ");
if (g_termsig)
{
free(line);
restore_display();
continue ;
}
if (! line)
break ;
execute_line(line);
//It doesn't happen with that example but in the rest of my code
//g_termsig could be set in the execute_line function
if (g_termsig)
restore_display();
free(line);
line = NULL;
}
exit_message("Everything good\n", 0);
}
So the thing is that I wanted the readline function to quit when SIGINT
was received. I first settled for using ioctl
with the TCSTI
request (which is detailed in man 2 ioctl_tty
) to fake a user input in the handler. I finally (after reading this question and reading a good amount of this documentation settled for an event hook that seems to work better.
So far so good you'll tell me (in fact I hope not and that you'll be able to correct me from here). The matter is that when I input for example /usr/bin/cat
and kill it with ^C
then the prompts get all messed up with the commands output. For example when I run /usr/bin/cat
and then /usr/bin/ls
or some non working command, it doesn't matter, the prompt get messed up with the ouput.
I try a combination of rl_redisplay, rl_on_new_line and rl_replace_line functions (the only ones from readline library I am allowed to use in this project) but nothing seems to do the trick so I am a bit lost.
Thanks in advance for reading me and taking time to share your knowledge with me