In this Extended Example, the main process starts by overwriting the SIGINT
signal handler. The
process then enters a loop (line 29) where it repeatedly retrieves user input in the
process_input()
function. This function (lines 72 – 102) provides a simplified shell that
accepts two possible commands: ls
and exit
. If the user enters the ls
command, the main
process uses fork()
and exec()
to list files in the current directory (list_files()
in
lines 49 – 65), similar to how bash
works. The exit
command will terminate the program by
returning false from process_input()
. Finally, the overwritten SIGINT
handler (lines 36 –
46) will also check for and kill()
child processes that may have become stuck (e.g., if the
directory contains thousands of files).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | #include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
pid_t child_pid = 0;
static void sigint_handler (int signum); /* SIGINT handler */
static bool list_files (void); /* child that lists files */
static bool process_input (void); /* retrieve user input */
int
main (int argc, char *argv[])
{
/* Create a sigaction and link it to the handler */
struct sigaction sa;
sa.sa_handler = sigint_handler;
/* Overwrite the SIGINT handler, exiting on failure */
assert (sigaction (SIGINT, &sa, NULL) != -1);
/* Keep running until the user requests the program stop */
bool running = false;
while ((running = process_input ())) ;
return 0;
}
/* Shutdown with Ctrl-c. This will terminate the infinite
loops */
static void
sigint_handler (int signum)
{
/* Kill any lingering child process if it exists */
if (child_pid != 0)
{
write (STDOUT_FILENO, "Killing file list process before exiting.\n", 43);
kill (child_pid, SIGKILL);
}
exit (0);
}
/* Create new process and run the "ls" command in that process */
static bool
list_files (void)
{
/* Create child process */
child_pid = fork ();
if (child_pid < 0)
return false;
/* Child process will print the files */
if (child_pid == 0)
assert ((execlp ("ls", "ls", "-1tr", NULL)) == 0);
/* Parent waits on the child to finish listing files */
waitpid (child_pid, NULL, WUNTRACED);
child_pid = 0; /* Child no longer exists */
return true;
}
/* Show a prompt, get up to 99 bytes of command input, and
respond to the user's request. Possible commands are "ls"
"loop" and "exit". This loop can be thought of as a simplified
bash shell, as it illustrates the basic mechanics of how bash
receives commands and runs them in new processes. */
static bool
process_input (void)
{
char buffer[100];
char *rc = NULL;
printf ("$ ");
/* Clear the buffer and read in up to 99 bytes */
memset (buffer, 100, '\0');
rc = fgets (buffer, 99, stdin);
/* Check for errors reading from stdin */
if (rc == NULL)
return false;
/* Get the first token as the command */
char *token = strtok (buffer, " ");
if (token == NULL)
return true;
/* ls command is supported */
if (!strncmp (token, "ls", 2))
return list_files ();
/* also allow exiting with the exit keyword */
if (!strncmp (token, "exit", 4))
return false;
printf ("Invalid command: %s\n", token);
return true;
}
|