firstworks Programming with Rudiments using the process class

Using the process class

- Introduction
- Process Information
- Exiting
- Abnormal Shutdown
- Setting User and Group
- Running Other Processes
- Managing Zombie Processes
- Detaching

### Introduction

The process class provides static methods for accessing information about and controlling processes, including methods for forking, spawning and executing child processes.

### Process Information

The following program prints various ID's associated with the process.

> #include <rudiments/process.h>
> #include <rudiments/stdio.h>
> int main(int argc, const char **argv) {
> stdoutput.printf("Process ID : %d\n",
> process::getProcessId());
> stdoutput.printf("Parent Process ID : %d\n",
> process::getParentProcessId());
> stdoutput.printf("Process Group ID : %d\n",
> process::getProcessGroupId());
> stdoutput.printf("Session ID : %d\n",
> process::getSessionId());
> }

### Exiting

The process class also provides methods for managing how a program exits.

The exit() method allows you to set an exit status. Traditionally, an exit status of 0 indicates that the program exited normally and 1 (or any other number) indicates that an error occurred.

> #include <rudiments/process.h>
> #include <rudiments/stdio.h>
> int main(int argc, const char **argv) {
> stdoutput.write("exiting with status 0 (success)\n");
> process::exit(0);
> }

The atExit() method allows you to register functions that will be called when the program exits. Multiple functions may be registered. Functions are called in the reverse of the order of their registration.

> #include <rudiments/process.h>
> #include <rudiments/stdio.h>
> // define a function to run at exit
> void runAtExit() {
> stdoutput.write("running at exit!\n");
> }
> int main(int argc, const char **argv) {
> // configure runAtExit to run at exit
> process::atExit(runAtExit);
> stdoutput.write("\"running at exit!\" ought to be printed below...\n");
> // exit normally
> process::exit(0);
> }

The exitImmediately() allows the program to terminate immediately, bypassing any functions registered to run at exit.

> #include <rudiments/process.h>
> #include <rudiments/stdio.h>
> // define a function to run at exit
> void runAtExit() {
> stdoutput.write("running at exit!\n");
> }
> int main(int argc, const char **argv) {
> // configure runAtExit to run at exit
> process::atExit(runAtExit);
> stdoutput.write("nothing should be printed below...\n");
> // exit immediately, bypassing functions configured to run at exit
> process::exitImmediately(0);
> }

### Abnormal Shutdown

The process class also provides methods for managing various abnormal shutdown conditions such as a crash or termination by another process.

Ordinarily, if a program encounters a floating point exception, illegal instruction, segmentation fault, bus error, or a few other unusual conditions, it will "crash", display a short message indicating why, and possibly offer to debug the program. The exitOnCrash() method causes the program to exit more gracefully when one of these conditions occurs by "waiting on child processes" (see Managing Zombie Processes below), calling functions registered with atExit(), and setting the exit status to 1, indicating that an error occurred.

> #include <rudiments/process.h>
> #include <rudiments/stdio.h>
> int main(int argc, const char **argv) {
> // configure the process to exit gracefully on crash
> process::exitOnCrash();
> stdoutput.write("exiting instead of \"Segmentation Fault\"\n");
> // this will crash...
> void (*f)(void)=0;
> f();
> }

However, if you want to do something different when the program crashes, then you can use the handleCrash() method to specify a function to run when the program crashes.

> #include <rudiments/process.h>
> #include <rudiments/stdio.h>
> // define a function to run when a crash occurs
> void shutDown(int32_t signal) {
> stdoutput.write("shutting down gracefully...\n");
> process::exit(1);
> }
> int main(int argc, const char **argv) {
> // configure the process to call shutDown on crash
> process::handleCrash(shutDown);
> stdoutput.write("handling crash, instead of \"Segmentation Fault\"\n");
> // this will crash...
> void (*f)(void)=0;
> f();
> }

Ordinarily, if a program is shut down, either aborted using ctrl-C or killed, it will exit immediately. The exitOnShutdown() method causes the program exit more gracefully when it is shut down by "waiting on child processes" (see Managing Zombie Processes below), calling functions registered with atExit(), and setting the exit status to 0, indicating that no error occurred.

> #include <rudiments/process.h>
> #include <rudiments/snooze.h>
> #include <rudiments/stdio.h>
> int main(int argc, const char **argv) {
> // configure the process to exit gracefully on shut down
> process::exitOnShutDown();
> stdoutput.write("kill the process or press ctrl-C to exit\n");
> stdoutput.write("(should not display \"Terminated\")\n");
> // loop forever, waiting for ctrl-C or to be killed
> for (;;) {
> snooze::macrosnooze(1);
> }
> }

However, if you want to do something different when the program is shut down, then you can use the handleShutDown() method to specify a function to run when the program is shut down.

> #include <rudiments/process.h>
> #include <rudiments/snooze.h>
> #include <rudiments/stdio.h>
> // define a function to run when shut down occurs
> void shutDown(int32_t signal) {
> stdoutput.write("shutting down gracefully...\n");
> process::exit(1);
> }
> int main(int argc, const char **argv) {
> // configure the process to call shutDown on shut down
> process::handleShutDown(shutDown);
> stdoutput.write("kill the process or press ctrl-C to exit\n");
> stdoutput.write("(should not display \"Terminated\")\n");
> // loop forever, waiting for ctrl-C or to be killed
> for (;;) {
> snooze::macrosnooze(1);
> }
> }

### Setting User and Group

The process class also provides methods for changing who the current process is running as. Both the "real" and "effective" user and group can be set.

The "real" user corresponds to who the user logged in as. The "real" group corresponds to that user's default group. The "real" user and group can only be changed if the user is logged in as root (or the equivalent on non-unix-like platforms).

> #include <rudiments/process.h>
> #include <rudiments/stdio.h>
> int main(int argc, const char **argv) {
> // display the user and group
> stdoutput.printf("running as user id %d, group id %d\n",
> process::getUserId(),
> process::getGroupId());
> // set the user and group to "nobody"
> process::setUser("nobody");
> process::setGroup("nobody");
> // display the user and group again
> stdoutput.printf("now running as user id %d, group id %d\n",
> process::getUserId(),
> process::getGroupId());
> }

The "effective" user can be only be changed if the program is running as root. This could be either because the user logged in as root, or because the program's permissions are configured so that it always runs as root. The "effective" group can be changed to any group that the user is a member of.

> #include <rudiments/process.h>
> #include <rudiments/stdio.h>
> int main(int argc, const char **argv) {
> // display the effective user and group id
> stdoutput.printf("running as effetive user id %d, group id %d\n",
> process::getEffectiveUserId(),
> process::getEffectiveGroupId());
> // set the effective user and group to "nobody"
> process::setEffectiveUser("nobody");
> process::setEffectiveGroup("nobody");
> // display the effective user and group id again
> stdoutput.printf("now running as effetive user id %d, group id %d\n",
> process::getEffectiveUserId(),
> process::getEffectiveGroupId());
> }

Note that some platforms don't have the notion of an effective user and group. On those platforms, the methods for setting and getting the effective user and group fail.

### Running Other Processes

The process class also provides methods for running other processes.

The spawn() method allows you to spawn a child process.

> #include <rudiments/process.h>
> #include <rudiments/stdio.h>
> int main(int argc, const char **argv) {
> stdoutput.write("running \"ls -l /\"...\n");
> // spawn the comand: ls -l /
> const char * const arguments[]={"ls","-l","/",NULL};
> process::spawn("ls",arguments,false);
> }

There are several things to note when using spawn():

- The spawn() method will search the user's PATH for the specified program. The full pathname of the program to run need not be specified unless it is located outside of the user's PATH.
- Even though the program is specified, the first argument passed to it should be the name of the program.
- The parent process doesn't wait for the child process to finish executing, but rather continues executing itself, right away.

The fork() method allows you to fork the current process. I.e. to create a child process that is an copy of the current process' address space, open file descriptors, and other things.

Note though, that fork() only works on unix-like systems and does not work on Windows and other non-unix-like systems. The supportsFork() method returns true or false, indicating whether fork is supported or not.

In the parent process, fork() returns the process-id of the child process. In the child process, fork() returns 0.

> #include <rudiments/process.h>
> #include <rudiments/stdio.h>
> int main(int argc, const char **argv) {
> // see if fork() is supported and exit if it's not...
> if (!process::supportsFork()) {
> stdoutput.write("sorry, fork() not supported\n");
> process::exit(1);
> }
> // fork the process
> pid_t pid=process::fork();
> if (pid) {
> // the parent process will run this code...
> stdoutput.write("I am the parent. ");
> stdoutput.printf("The child's process id is %d\n",pid);
> } else {
> // the child process will run this code...
> stdoutput.write("I am the child. ");
> stdoutput.printf("My process id is %d\n",
> process::getProcessId());
> }
> // both processes will run this code to exit
> process::exit(0);
> }

The exec() method allows you to replace the currently running process with a new process. This is useful if you want to fork a child process which needs to do a few things and then just run another process.

There are several things to note when using exec():

- The exec() method will search the user's PATH for the specified program. The full pathname of the program to run need not be specified unless it is located outside of the user's PATH.
- Even though the program is specified, the first argument passed to it should be the name of the program.
- The process calling exec() won't execute any code past its call to exec(). As such, it doesn't need to call exit().
- Due to differences in implementations for different paltforms, it should not be assumed that the exec'ed process will have the same process ID as the process that called the exec() method.

> #include <rudiments/process.h>
> #include <rudiments/stdio.h>
> int main(int argc, const char **argv) {
> // see if fork() is supported and exit if it's not...
> if (!process::supportsFork()) {
> stdoutput.write("sorry, fork() not supported\n");
> process::exit(1);
> }
> // fork the process
> pid_t pid=process::fork();
> if (pid) {
> // the parent process will run this code...
> stdoutput.write("I am the parent. ");
> stdoutput.printf("The child's process id is %d\n",pid);
> process::exit(0);
> } else {
> // the child process will run this code...
> stdoutput.write("I am the child. ");
> stdoutput.printf("My process id is %d\n",
> process::getProcessId());
> stdoutput.write("and I'm running \"ls -l /\"...\n");
> // execute the command: ls -l /
> const char * const arguments[]={"ls","-l","/",NULL};
> process::exec("ls",arguments);
> }
> }

It's also worth noting that this same thing could be accomplished by calling spawn(), followed by exit(), instead of exec(). However, on unix-like systems, simply calling exec() is lighter weight.

### Managing Zombie Processes

The process class also provides methods for managing zombie processes.

On unix-like systems, when a process creates a child process, the parent process must "wait on the child process" or the child process will become a "zombie" and persist in the list of running processes, even though it has exited, until the parent process also exits.

This does not happen on non-unix-like systems.

The waitForChildren() method causes the program to automatically "wait on child processes". The dontWaitForChildren() method disables this behavior.

> #include <rudiments/process.h>
> #include <rudiments/snooze.h>
> #include <rudiments/stdio.h>
> int main(int argc, const char **argv) {
> // configure the process to "wait on child processes"
> process::waitForChildren();
> // five times...
> for (uint16_t i=0; i<5; i++) {
> // fork the process
> pid_t pid=process::fork();
> if (pid) {
> // the parent process will run this code...
> stdoutput.printf("forked a child with pid: %d\n",pid);
> } else {
> // the child process will run this code...
> stdoutput.write("I'm the child, and I'm exiting...\n");
> process::exit(0);
> }
> // wait a second
> snooze::macrosnooze(1);
> }
> stdoutput.write('\n');
> stdoutput.write("check process list, there should be no zombies\n");
> stdoutput.write("kill the process or press ctrl-C to exit\n");
> // loop forever, waiting for ctrl-C or to be killed
> for (;;) {
> snooze::macrosnooze(1);
> }
> }

As an alternative to calling waitForChildren(), the process can register a signal handler for the SIGCHLD signal and call getChildStateChange() to "wait on child processes". See Using the signal classes for more information on how to do this. The supportsGetChildStateChange method returns true or false, indicating whether this method is supported or not.

### Detaching

The process class also provides a method for detaching from the controlling terminal.

This is useful for programs which need to continue running "in the background". Note that once a program detaches, it cannot be shut down using ctrl-C. It must be killed.

> #include <rudiments/process.h>
> #include <rudiments/snooze.h>
> #include <rudiments/stdio.h>
> int main(int argc, const char **argv) {
> // detach from the controlling terminal
> process::detach();
> // print "I'm running in the background 5 times",
> // snoozing 1 second between each line...
> for (uint16_t i=0; i<5; i++) {
> stdoutput.write("I'm running in the background...\n");
> snooze::macrosnooze(1);
> }
> process::exit(0);
> }

Note that running "in the background" may have different meanings and consequences on different platforms.

On all platforms, once a process has called detach(), if it was run from a terminal, the terminal that it was run from will display a new prompt, and new commands may be run.

On unix-like platforms, once a process has called detach(), it is safe to close the tty that the process was run from. On Windows platforms though, closing the console that the process was run from will kill the process unless Windows-specific functions are called to dissociate it from the console, create a new console, and associate it with that console.