Reputation: 916
I'm trying to make a shell that prompt a command and wait for user input.
I want my shell to print another prompt when the user press ctrl-c
and exit when he presses ctrl-d
.
Here's the main loop:
int my_shell(char **env)
{
char *cmd_line;
while (true)
{
print_prompt();
cmd_line = get_cmd();
process(cmd_line);
}
return (0);
}
I'm able to catch ctrl-c
and ctrl-d
signals but I don't know how to structure the main loop to exit at the good places. I tried using several fork()
, wait()
, getpid()
but I'm doing it wrong.
Here's one of my try:
int extern_pid;
int intern_pid;
int my_shell(char **env)
{
char *cmd_line;
extern_pid = getpid();
while (true)
{
if (fork() == 0)
{
intern_pid = getpid();
print_prompt();
cmd_line = get_cmd();
process(cmd_line);
exit(0);
}
else
{
wait(0);
}
}
return (0);
}
And with those signals handlers:
void ctrlc_handler(int signal)
{
if (getpid() == intern_pid)
exit(0);
}
void ctrld_handler(int signal)
{
if (getpid() == extern_pid)
exit(0);
}
note: the ctrl-d
signal is handled in the get_cmd()
function.
Upvotes: 0
Views: 1179
Reputation: 27106
One doesn't have to create a child process with fork to process the Ctrl-C signal in a custom shell. One possibility would be to use a signal handler together with sigsetjmp/siglongjmp.
The procedure would be as follows:
Since signal handlers can always be called, possibly even before calling sigsetjmp(), it must be ensured that siglongjmp() can be already called. This is done by setting a volatile variable sig_atomic_t called jmp_set.
The function process
knows only one internal command called exit
. As already noted in the comments to the question, if the user enters Ctrl-D as the first character at the beginning of a line, this automatically results in an EOF in the getchar call. The function get_cmd then returns the command exit
here. Alternatively, the user can enter the command exit
to end the program.
In the function process
the position is marked with a comment where you probably want to call external programs with fork/exec. It returns a bool whether the program should be quit or not. Possibly one could evaluate here also a status code from the calls of the external programs accordingly.
Here is a small, self-contained example program with your ctrlc_handler, get_cmd and process function layout, extended with sigsetjmp()/siglongjmp(), not complete of course, but maybe a starting point:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <setjmp.h>
#define MAX_CMD_LENGTH 1024
static const char *const EXIT_CMD = "exit";
static sigjmp_buf env;
static volatile sig_atomic_t jmp_set;
static void ctrlc_handler(int signal) {
if (jmp_set == 0)
return;
if (signal == SIGINT) {
siglongjmp(env, 1);
}
}
static char *get_cmd(void) {
char *cmd_buf = calloc(MAX_CMD_LENGTH, sizeof(char));
if (!cmd_buf) {
perror("malloc failed\n");
exit(1);
}
char *ptr = cmd_buf;
int ch = getchar();
while (ch != EOF && ch != '\n' && ptr < cmd_buf + MAX_CMD_LENGTH - 1) {
*ptr++ = (char) ch;
ch = getchar();
}
if (ch == EOF) {
strcpy(cmd_buf, EXIT_CMD);
}
return cmd_buf;
}
static bool process(char *cmd) {
if (strcmp(cmd, EXIT_CMD) == 0) {
return true;
}
// a call to fork together with a function from the
// exec family could be used to call external programs
printf("process '%s'\n", cmd);
return false;
}
static int cnt = 0;
int main(void) {
if (signal(SIGINT, ctrlc_handler) == SIG_ERR) {
perror("signal error");
exit(1);
}
if (sigsetjmp(env, 1)) {
printf("\n");
cnt++;
}
jmp_set = 1;
bool exit;
do {
printf("%d> ", cnt);
char *cmd = get_cmd();
exit = process(cmd);
free(cmd);
} while (!exit);
return 0;
}
Upvotes: 2