«  2.7. Events and Signals   ::   Contents   ::   3.1. Concurrency with IPC  »

2.8. Extended Example: Listing Files with Processes

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;
}
«  2.7. Events and Signals   ::   Contents   ::   3.1. Concurrency with IPC  »

Contact Us License