8.13. system FunctionIt is convenient to execute a command string from within a program. For example, assume that we want to put a time-and-date stamp into a certain file. We could use the functions we describe in Section 6.10 to do this: call time to get the current calendar time, then call localtime to convert it to a broken-down time, and then call strftime to format the result, and write the results to the file. It is much easier, however, to say system("date > file"); ISO C defines the system function, but its operation is strongly system dependent. POSIX.1 includes the system interface, expanding on the ISO C definition to describe its behavior in a POSIX environment.
If cmdstring is a null pointer, system returns nonzero only if a command processor is available. This feature determines whether the system function is supported on a given operating system. Under the UNIX System, system is always available. Because system is implemented by calling fork, exec, and waitpid, there are three types of return values.
Figure 8.22 shows an implementation of the system function. The one feature that it doesn't handle is signals. We'll update this function with signal handling in Section 10.18. Figure 8.22. The system function, without signal handling#include <sys/wait.h> #include <errno.h> #include <unistd.h> int system(const char *cmdstring) /* version without signal handling */ { pid_t pid; int status; if (cmdstring == NULL) return(1); /* always a command processor with UNIX */ if ((pid = fork()) < 0) { status = -1; /* probably out of processes */ } else if (pid == 0) { /* child */ execl("/bin/sh", "sh", "-c", cmdstring, (char *)0); _exit(127); /* execl error */ } else { /* parent */ while (waitpid(pid, &status, 0) < 0) { if (errno != EINTR) { status = -1; /* error other than EINTR from waitpid() */ break; } } } return(status); } The shell's -c option tells it to take the next command-line argumentcmdstring, in this caseas its command input instead of reading from standard input or from a given file. The shell parses this null-terminated C string and breaks it up into separate command-line arguments for the command. The actual command string that is passed to the shell can contain any valid shell commands. For example, input and output redirection using < and > can be used. If we didn't use the shell to execute the command, but tried to execute the command ourself, it would be more difficult. First, we would want to call execlp instead of execl, to use the PATH variable, like the shell. We would also have to break up the null-terminated C string into separate command-line arguments for the call to execlp. Finally, we wouldn't be able to use any of the shell metacharacters. Note that we call _exit instead of exit. We do this to prevent any standard I/O buffers, which would have been copied from the parent to the child across the fork, from being flushed in the child. We can test this version of system with the program shown in Figure 8.23. (The pr_exit function was defined in Figure 8.5.) Figure 8.23. Calling the system function#include "apue.h" #include <sys/wait.h> int main(void) { int status; if ((status = system("date")) < 0) err_sys("system() error"); pr_exit(status); if ((status = system("nosuchcommand")) < 0) err_sys("system() error"); pr_exit(status); if ((status = system("who; exit 44")) < 0) err_sys("system() error"); pr_exit(status); exit(0); } Running the program in Figure 8.23 gives us $ ./a.out Sun Mar 21 18:41:32 EST 2004 normal termination, exit status = 0 for date sh: nosuchcommand: command not found normal termination, exit status = 127 for nosuchcommand sar :0 Mar 18 19:45 sar pts/0 Mar 18 19:45 (:0) sar pts/1 Mar 18 19:45 (:0) sar pts/2 Mar 18 19:45 (:0) sar pts/3 Mar 18 19:45 (:0) normal termination, exit status = 44 for exit The advantage in using system, instead of using fork and exec directly, is that system does all the required error handling and (in our next version of this function in Section 10.18) all the required signal handling. Earlier systems, including SVR3.2 and 4.3BSD, didn't have the waitpid function available. Instead, the parent waited for the child, using a statement such as while ((lastpid = wait(&status)) != pid && lastpid != -1) ; A problem occurs if the process that calls system has spawned its own children before calling system. Because the while statement above keeps looping until the child that was generated by system terminates, if any children of the process terminate before the process identified by pid, then the process ID and termination status of these other children are discarded by the while statement. Indeed, this inability to wait for a specific child is one of the reasons given in the POSIX.1 Rationale for including the waitpid function. We'll see in Section 15.3 that the same problem occurs with the popen and pclose functions, if the system doesn't provide a waitpid function. Set-User-ID ProgramsWhat happens if we call system from a set-user-ID program? Doing so is a security hole and should never be done. Figure 8.24 shows a simple program that just calls system for its command-line argument. Figure 8.24. Execute the command-line argument using system#include "apue.h" int main(int argc, char *argv[]) { int status; if (argc < 2) err_quit("command-line argument required"); if ((status = system(argv[1])) < 0) err_sys("system() error"); pr_exit(status); exit(0); } We'll compile this program into the executable file tsys. Figure 8.25 shows another simple program that prints its real and effective user IDs. Figure 8.25. Print real and effective user IDs#include "apue.h" int main(void) { printf("real uid = %d, effective uid = %d\n", getuid(), geteuid()); exit(0); } We'll compile this program into the executable file printuids. Running both programs gives us the following: $ tsys printuids normal execution, no special privileges real uid = 205, effective uid = 205 normal termination, exit status = 0 $ su become superuser Password: enter superuser password # chown root tsys change owner # chmod u+s tsys make set-user-ID # ls -l tsys verify file's permissions and owner -rwsrwxr-x 1 root 16361 Mar 16 16:59 tsys # exit leave superuser shell $ tsys printuids real uid = 205, effective uid = 0 oops, this is a security hole normal termination, exit status = 0 The superuser permissions that we gave the tsys program are retained across the fork and exec that are done by system.
If it is running with special permissionseither set-user-ID or set-group-IDand wants to spawn another process, a process should use fork and exec directly, being certain to change back to normal permissions after the fork, before calling exec. The system function should never be used from a set-user-ID or a set-group-ID program.
|