SYSTEM(3POSIX)

๐ŸŒธ SYSTEM(3POSIX) โ€” POSIX Programmer's Manual ๐ŸŒธ

okay bestie, we're doing a POSIX manual page today, and this one is about system(), the function that just... hands your whole command over to a shell and vibes while it happens. iconic behaviour honestly. ๐Ÿ’…


โœจ PROLOG โœจ

So this page? It's part of the POSIX Programmer's Manual. The Linux implementation of this interface may differ โ€” like, consult the corresponding Linux manual page for details on Linux behaviour, bestie, don't @ me โ€” or honestly the interface might not even be implemented on Linux at all. ๐Ÿ™ˆ


๐Ÿ’– NAME ๐Ÿ’–

system โ€” issue a command

yes that's literally it. she issues a command. queen of delegation. ๐Ÿ‘‘


๐ŸŽ€ SYNOPSIS ๐ŸŽ€

#include <stdlib.h>

int system(const char *command);

๐Ÿฆ‹ DESCRIPTION ๐Ÿฆ‹

The functionality on this reference page is aligned with the ISO C standard. And like โ€” any conflict between what's described here and the ISO C standard? Not intentional, fr fr. This volume of POSIX.1โ€2017 defers to the ISO C standard. periodt.

So here's the tea. If command is a null pointer, then system() shall determine whether the host environment has a command processor. Just a little vibe check โ€” do you even have a shell, babe? ๐Ÿš

If command is not a null pointer, then system() shall pass the string pointed to by command to that command processor, to be executed in an implementation-defined manner. And lowkey, heads up: this might then cause the program calling system() to behave in a non-conforming manner or to terminate. ๐Ÿ˜ญ yeah. she's unpredictable like that.

The system() function shall behave as if a child process were created using fork(), and the child process invoked the sh utility using execl() as follows:

execl(<shell path>, "sh", "-c", command, (char *)0);

where <shell path> is an unspecified pathname for the sh utility. And it is unspecified whether the handlers registered with pthread_atfork() are called as part of the creation of the child process. like, nobody knows. it's a mystery. ๐Ÿ•ต๏ธโ€โ™€๏ธ

The system() function shall ignore the SIGINT and SIGQUIT signals, and shall block the SIGCHLD signal, while waiting for the command to terminate. If this might cause the application to miss a signal that would have killed it, then the application should examine the return value from system() and take whatever action is appropriate to the application if the command terminated due to receipt of a signal. so like, check the receipts, bestie. ๐Ÿงพ

The system() function shall not affect the termination status of any child of the calling processes other than the process or processes it itself creates. minding her own business, as she should. ๐Ÿ’…

The system() function shall not return until the child process has terminated. no ghosting. she waits. ๐Ÿซถ

The system() function need not be thread-safe. โš ๏ธ yeah. noted.


โญ RETURN VALUE โญ

If command is a null pointer, system() shall return non-zero to indicate that a command processor is available, or zero if none is available. And actually โ€” the system() function shall always return non-zero when command is NULL. yes bestie, always. ๐Ÿ’–

If command is not a null pointer, system() shall return the termination status of the command language interpreter in the format specified by waitpid(). The termination status shall be as defined for the sh utility; otherwise, the termination status is unspecified. ๐Ÿ˜ถโ€๐ŸŒซ๏ธ

If some error prevents the command language interpreter from executing after the child process is created, the return value from system() shall be as if the command language interpreter had terminated using exit(127) or _exit(127). ๐Ÿšช

If a child process cannot be created, or if the termination status for the command language interpreter cannot be obtained, system() shall return -1 and set errno to indicate the error. ๐Ÿ’€


๐Ÿ”ฅ ERRORS ๐Ÿ”ฅ

The system() function may set errno values as described by fork().

In addition, system() may fail if:

  • ECHILD โ€” The status of the child process created by system() is no longer available. gone. vanished. she looked away for one second. ๐Ÿ˜ญ

The following sections are informative. (so like, the vibe shifts from "requirements" to "helpful bestie advice" โ€” still read them tho ๐Ÿ’œ)


๐ŸŒˆ EXAMPLES ๐ŸŒˆ

None.

yes really. nothing. an empty section. she said "figure it out." ๐Ÿ˜Œ


๐Ÿ’œ APPLICATION USAGE ๐Ÿ’œ

If the return value of system() is not -1, its value can be decoded through the use of the macros described in <sys/wait.h>. And for convenience, those same macros are also provided in <stdlib.h>. so handy. ๐ŸŽ

Note that, while system() must ignore SIGINT and SIGQUIT and block SIGCHLD while waiting for the child to terminate, the handling of signals in the executed command is as specified by fork() and exec. For example, if SIGINT is being caught or is set to SIG_DFL when system() is called, then the child is started with SIGINT handling set to SIG_DFL. slay, inherited settings. ๐Ÿงฌ

Ignoring SIGINT and SIGQUIT in the parent process prevents coordination problems โ€” two processes reading from the same terminal, for example โ€” when the executed command ignores or catches one of the signals. It is also usually the correct action when the user has given a command to the application to be executed synchronously (as in the '!' command in many interactive applications). In either case, the signal should be delivered only to the child process, not to the application itself. boundaries! ๐Ÿšง

There is one situation where ignoring the signals might have less than the desired effect. This is when the application uses system() to perform some task invisible to the user. If the user typed the interrupt character ("^C", for example) while system() is being used in this way, one would expect the application to be killed, but only the executed command is killed. ๐Ÿ’€ Applications that use system() in this way should carefully check the return status from system() to see if the executed command was successful, and should take appropriate action when the command fails. check the receipts, always. ๐Ÿงพ

Blocking SIGCHLD while waiting for the child to terminate prevents the application from catching the signal and obtaining status from system()'s child process before system() can get the status itself. no cutting the line, bestie. ๐Ÿ™…โ€โ™€๏ธ

The context in which the utility is ultimately executed may differ from that in which system() was called. For example, file descriptors that have the FD_CLOEXEC flag set are closed, and the process ID and parent process ID are different. Also, if the executed utility changes its environment variables or its current working directory, that change is not reflected in the caller's context. different timeline, different vibes. ๐ŸŒ€

There is no defined way for an application to find the specific path for the shell. However, confstr() can provide a value for PATH that is guaranteed to find the sh utility. so use that. ๐Ÿซถ

Using the system() function in more than one thread in a process, or when the SIGCHLD signal is being manipulated by more than one thread in a process, may produce unexpected results. โš ๏ธ chaos energy. you've been warned. ๐Ÿซ 


๐Ÿ’… RATIONALE (aka the โœจwhyโœจ behind all of this)

Okay bestie, real talk first: the system() function should NOT be used by programs that have set user (or group) ID privileges. Like, hard no. Instead, use the fork() and exec family of functions โ€” but heads up, that's except execlp() and execvp(). Why? Because this prevents any unforeseen manipulation of the environment of the user that could cause execution of commands not anticipated by the calling program. It's giving "trust issues, but valid ones." ๐Ÿซถ


There are literally three levels of specification for the system() function, and we're going through all of them, no skipping:

  • The ISO C standard gives the most basic. It requires that the function exists, and defines a way for an application to query whether a command language interpreter exists. It says nothing about the command language or the environment in which the command is interpreted. Very demure, very minimal. ๐Ÿ™ˆ
  • POSIX.1โ€2008 places additional restrictions on system(). It requires that if there is a command language interpreter, the environment must be as specified by fork() and exec. This ensures, for example, that close-on-exec works, that file locks are not inherited, and that the process ID is different. It also specifies the return value from system() when the command line can be run, thus giving the application some information about the command's completion status. Slay, we love a spec that actually tells you things. ๐Ÿ’–
  • Finally, POSIX.1โ€2008 requires the command to be interpreted as in the shell command language defined in the Shell and Utilities volume of POSIX.1โ€2017. Periodt. ๐ŸŽ€

Not me about to explain the system(NULL) discourse ๐Ÿ˜ญ because this is genuinely iconic lore.

Note that system(NULL) is required to return non-zero, indicating that there is a command language interpreter. At first glance, this would seem to conflict with the ISO C standard which allows system(NULL) to return zero. There is no conflict, however. A system must have a command language interpreter, and is non-conforming if none is present. It is therefore permissible for the system() function on such a system to implement the behavior specified by the ISO C standard as long as it is understood that the implementation does not conform to POSIX.1โ€2008 if system(NULL) returns zero. Read that twice, bestie, it's a vibe check. ๐Ÿฆ‹

It was explicitly decided that when command is NULL, system() should not be required to check to make sure that the command language interpreter actually exists with the correct mode, that there are enough processes to execute it, and so on. The call system(NULL) could, theoretically, check for such problems as too many existing child processes, and return zero. However, it would be inappropriate to return zero due to such a (presumably) transient condition. If some condition exists that is not under the control of this application and that would cause any system() call to fail, that system has been rendered non-conforming. The audacity of a system like that, honestly. ๐Ÿ’€


Early drafts required, or allowed, system() to return with errno set to [EINTR] if it was interrupted with a signal. This error return was removed, and a requirement that system() not return until the child has terminated was added. This means that if a waitpid() call in system() exits with errno set to [EINTR], system() must reissue the waitpid(). The way that got fixedโ€ฆ we love a glow-up. โœจ

This change was made for two reasons:

  1. There is no way for an application to clean up if system() returns [EINTR], short of calling wait(), and that could have the undesirable effect of returning the status of children other than the one started by system().
  2. While it might require a change in some historical implementations, those implementations already have to be changed because they use wait() instead of waitpid().

Note that if the application is catching SIGCHLD signals, it will receive such a signal before a successful system() call returns. Surprise! ๐ŸŽ‰

To conform to POSIX.1โ€2008, system() must use waitpid(), or some similar function, instead of wait().


๐Ÿงš The Reference Implementation (yes, it's giving main character)

The following code sample illustrates how system() might be implemented on an implementation conforming to POSIX.1โ€2008.

#include <signal.h>
int system(const char *cmd)
{
    int stat;
    pid_t pid;
    struct sigaction sa, savintr, savequit;
    sigset_t saveblock;
    if (cmd == NULL)
        return(1);
    sa.sa_handler = SIG_IGN;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigemptyset(&savintr.sa_mask);
    sigemptyset(&savequit.sa_mask);
    sigaction(SIGINT, &sa, &savintr);
    sigaction(SIGQUIT, &sa, &savequit);
    sigaddset(&sa.sa_mask, SIGCHLD);
    sigprocmask(SIG_BLOCK, &sa.sa_mask, &saveblock);
    if ((pid = fork()) == 0) {
        sigaction(SIGINT, &savintr, (struct sigaction *)0);
        sigaction(SIGQUIT, &savequit, (struct sigaction *)0);
        sigprocmask(SIG_SETMASK, &saveblock, (sigset_t *)0);
        execl("/bin/sh", "sh", "-c", cmd, (char *)0);
        _exit(127);
    }
    if (pid == -1) {
        stat = -1; /* errno comes from fork() */
    } else {
        while (waitpid(pid, &stat, 0) == -1) {
            if (errno != EINTR){
                stat = -1;
                break;
            }
        }
    }
    sigaction(SIGINT, &savintr, (struct sigaction *)0);
    sigaction(SIGQUIT, &savequit, (struct sigaction *)0);
    sigprocmask(SIG_SETMASK, &saveblock, (sigset_t *)0);
    return(stat);
}

Note that, while a particular implementation of system() (such as the one above) can assume a particular path for the shell, such a path is not necessarily valid on another system. The above example is not portable, and is not intended to be. So don't go copy-pasting this into prod and then texting me, okay? ๐Ÿ™ˆ


Note also that the above example implementation is not thread-safe. Implementations can provide a thread-safe system() function, but doing so involves complications such as how to restore the signal dispositions for SIGINT and SIGQUIT correctly if there are overlapping calls, and how to deal with cancellation. The example above would not restore the signal dispositions and would leak a process ID if cancelled. This does not matter for a non-thread-safe implementation since canceling a non-thread-safe function results in undefined behavior (see Section 2.9.5.2, Cancellation Points). To avoid leaking a process ID, a thread-safe implementation would need to terminate the child process when acting on a cancellation. Big yikes energy, but it is what it is. ๐Ÿ’€


Okay so, one reviewer suggested that an implementation of system() might want to use an environment variable such as SHELL to determine which command interpreter to use. The supposed implementation would use the default command interpreter if the one specified by the environment variable was not available. This would allow a user, when using an application that prompts for command lines to be processed using system(), to specify a different command interpreter. Such an implementation is discouraged. ๐Ÿšซ

Why, you ask? Because if the alternate command interpreter did not follow the command line syntax specified in the Shell and Utilities volume of POSIX.1โ€2017, then changing SHELL would render system() non-conforming. This would affect applications that expected the specified behavior from system(), and since the Shell and Utilities volume of POSIX.1โ€2017 does not mention that SHELL affects system(), the application would not know that it needed to unset SHELL. Not the SHELL variable catching strays, but here we are. ๐Ÿ˜ญ


๐Ÿ”ฎ FUTURE DIRECTIONS

None.

Like, nothing. Empty. A void. The future is cancelled (politely). ๐ŸŒˆ


๐Ÿ‘€ SEE ALSO

  • Section 2.9.5.2, Cancellation Points, exec, pipe(), pthread_atfork(), wait()
  • The Base Definitions volume of POSIX.1โ€2017, <limits.h>, <signal.h>, <stdlib.h>, <sys_wait.h>
  • The Shell and Utilities volume of POSIX.1โ€2017, sh

Go read those, bestie, iykyk. ๐Ÿ’œ


Portions of this text are reprinted and reproduced in electronic form from IEEE Std 1003.1-2017, Standard for Information Technology -- Portable Operating System Interface (POSIX), The Open Group Base Specifications Issue 7, 2018 Edition, Copyright (C) 2018 by the Institute of Electrical and Electronics Engineers, Inc and The Open Group. In the event of any discrepancy between this version and the original IEEE and The Open Group Standard, the original IEEE and The Open Group Standard is the referee document. The original Standard can be obtained online at http://www.opengroup.org/unix/online.html .

Any typographical or formatting errors that appear in this page are most likely to have been introduced during the conversion of the source files to man page format. To report such errors, see https://www.kernel.org/doc/man-pages/reporting_bugs.html .


And that's a wrap on SYSTEM(3POSIX)! ๐ŸŽ€ This one's from IEEE/The Open Group, dated 2017, page name SYSTEM(3POSIX). Thanks for reading the whole thing, bestie โ€” you ate that. Periodt. ๐Ÿ’…โœจ

๐Ÿ“ฆ where this page comes from