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 #include #include #include #include #include #include #include #include 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; }