Program Execution 585
An improved system() implementation
Listing 27-9 shows an implementation of system() conforming to the rules described
above. Note the following points about this implementation:
z As noted earlier, if command is a NULL pointer, then system() should return non-
zero if a shell is available or 0 if no shell is available. The only way to reliably
determine this information is to try to execute a shell. We do this by recursively
calling system() to execute the : shell command and checking for a return status
of 0 from the recursive call q. The : command is a shell built-in command that
does nothing, but always returns a success status. We could have executed the
shell command exit 0 to achieve the same result. (Note that it isn’t sufficient to
use access() to check whether the file /bin/sh exists and has execute permission
enabled. In a chroot() environment, even if the shell executable is present, it
may not be executable it if it is dynamically linked and the required shared
libraries are not available.)
z It is only in the parent process (the caller of system()) that SIGCHLD needs to be
blocked w, and SIGINT and SIGQUIT need to be ignored e. However, we must
perform these actions prior to the fork() call, because, if they were done in the par-
ent after the fork(), we would create a race condition. (Suppose, for example,
that the child exited before the parent had a chance to block SIGCHLD.) Conse-
quently, the child must undo these changes to the signal attributes, as
described shortly.
z In the parent, we ignore errors from the sigaction() and sigprocmask() calls used
to manipulate signal dispositions and the signal mask w e o. We do this for
two reasons. First, these calls are very unlikely to fail. In practice, the only thing
that can realistically go wrong with these calls is an error in specifying their
arguments, and such an error should be eliminated during initial debugging.
Second, we assume that the caller is more interested in knowing if fork() or
waitpid() failed than in knowing if these signal-manipulation calls failed. For
similar reasons, we bracket the signal-manipulation calls used at the end of
system() with code to save i and restore errno a, so that if fork() or waitpid()
fails, then the caller can determine why. If we returned –1 because these signal-
manipulation calls failed, then the caller might wrongly assume that system()
failed to execute command.
SUSv3 merely says that system() should return –1 if a child process could not be
created or its status could not be obtained. No mention is made of a –1 return
because of failures in signal-manipulation operations by system().
z Error checking is not performed for signal-related system calls in the child r
t. On the one hand, there is no way of reporting such an error (the use of
_exit(127) is reserved for reporting an error when execing the shell); on the
other hand, such failures don’t affect the caller of system(), which is a separate
process.