The previous section demonstrated an easy method to create a pipe to an external process and either read its output or feed it input. It often happens, however, that C programs need only to invoke another process, without using a pipe. The standard C library provides the system(3) function for this purpose.
#include <stdlib.h> int system(const char *command);
In general, there are two ways to use the system(3) function: with a null argument or with a non-null command string argument.
Almost all systems document the fact that when system(3) is called with a null command pointer, the function call checks on the availability of the shell that would normally be used to carry out the command (system(3) for HPUX-11 does not mention this feature). The shell is considered available if it exists and is executable. If the shell is available, system(3) returns non-zero to indicate true. Otherwise, 0 indicates that no shell is available.
When the argument command is not a null pointer, it is expected to point to a null terminated C string containing a shell command to be executed as a child process. The function system(3) does not return until the indicated command has completed. The return status for this type of system(3) call is somewhat complicated, and is explained in full in Table 18.1 later in the chapter.
The shell program that is checked or invoked by the system(3) call varies somewhat with the UNIX platform. The following gives a partial list:
FreeBSD release 3.4 | /bin/sh |
SGI IRIX 6.5 | /sbin/sh |
HPUX-11 | /usr/bin/sh |
UnixWare 7 | $SHELL or /bin/sh |
Solaris 8 (native) | /usr/bin/sh |
Solaris 8 (standard) | /usr/bin/ksh |
IBM AIX 4.3 | /usr/bin/sh |
IBM AIX 4.3 (trusted) | /usr/bin/tsh |
Linux | /bin/sh |
The actual shell used on some platforms depends upon certain conditions. UnixWare 7 looks for the existence of the environment variable SHELL and uses that pathname for the shell. Otherwise it falls back to the default of /bin/sh. The choice for Solaris 8 depends upon whether it was compiled and linked to a particular standard. IBM's AIX 4.3 has a Trusted Computing Base for certain file system objects. If this feature is installed and enabled, the trusted shell /usr/bin/tsh can be invoked under some circumstances. Linux normally has /bin/sh linked to the GNU bash(1) shell.
Note
On some platforms, the signals SIGINT and SIGQUIT are ignored for the duration of the system(3) call. Furthermore, the signal SIGCHLD may be blocked until system(3) returns. IBM AIX 4.3 and HPUX-11 document this behavior.
Warning
Calling system(3) from a set-user-ID program is dangerous. The system(3) function uses fork(2) and exec(2) to invoke the new shell, and consequently it is possible for a security leak to occur (the current effective user and group ID values are saved by exec(2)). The shell and the commands invoked are subject to environment variable settings such as PATH and SHELL.
Review Table 12.2 in Chapter 12, "User ID, Password, and Group Management," if you are unclear how the current effective user and group ID values are affected by the exec(2) family of functions.
The return value for system(3) is complex when the command string is not null. It requires care to arrive at the correct conclusion. Table 18.1 contains a summary of the return values from system(3) when the command argument is not null. The errno value must be cleared to zero before calling system(3) to use this table. This permits the distinction between a failure to start the command and a command returning an exit code of 127.
To illustrate the system(3) function and its complex return values, a program has been provided in Listing 18.3.
1: /* smail.c */ 2: 3: #include <stdio.h> 4: #include <stdlib.h> 5: #include <unistd.h> 6: #include <errno.h> 7: #include <string.h> 8: #include <pwd.h> 9: #include <sys/types.h> 10: 11: int 12: main(int argc,char **argv) { 13: struct passwd *pw = 0; /* Password info */ 14: char cmd[256]; /* Command buffer */ 15: int rc; /* Command return code */ 16: 17: /* 18: * Lookup our userid: 19: */ 20: if ( !(pw = getpwuid(geteuid())) ) { 21: fprintf(stderr,"%s: unknown userid ",strerror(errno)); 22: return 13; 23: } 24: 25: /* 26: * Format command : 27: */ 28: sprintf(cmd,"ps -l|mail -s 'PID %ld'%s", 29: (long) getpid(), /* Process ID */ 30: pw->pw_name); /* User name */ 31: 32: /* 33: * Run the command : 34: */ 35: errno = 0; /* Clear errno */ 36: rc = system(cmd); /* Execute the command */ 37: 38: if ( rc == 127 && errno != 0 ) { 39: /* Command failed to start */ 40: fprintf(stderr,"%s: starting system(%s) ", 41: strerror(errno),cmd); 42: } else if ( rc == -1 ) { 43: /* Other errors occurred */ 44: fprintf(stderr,"%s: system(%s) ", 45: strerror(errno),cmd); 46: } else { 47: printf("Command '%s' returned code %d ",cmd,rc); 48: puts("Check your mail."); 49: } 50: 51: return 0; 52: } |
The smail.c program looks up your effective user ID in lines 20ā23. Then a command is formatted into character array cmd[] to list your current processes and email it to you (lines 28ā30). The actual process list and mailing occurs in lines 35 and 36, where cmd is carried out. Lines 38ā49 show how to make sense of the return code from system(3).
When the program in Listing 18.3 is compiled and executed under FreeBSD, the following results are obtained:
$ make smail cc -c -Wall smail.c cc -o smail smail.o $ ./smail Command 'ps -l|mail -s 'PID 10424'ehg' returned code 0 Check your mail. $
At this point, the program is telling you that mail has been sent. Now check your mail with the mail(1) command (the output lines from ps(1) have been shortened for readability):
$ mail Mail version 8.1 6/6/93. Type ? for help. "/var/mail/ehg": 1 message 1 new >N 1 ehg Wed Jun 21 21:56 20/931 "PID 10424" & 1 Message 1: From ehg Wed Jun 21 21:56:26 2000 Date: Wed, 21 Jun 2000 21:56:26 -0400 (EDT) From: "Earl H. Grey" <ehg> To: ehg Subject: PID 10424 UID PID PPID CPU PRI NI TIME COMMAND 1001 10200 1 0 10 0 0:00.05 -sh (sh) 1001 10203 10202 0 10 0 0:00.20 -sh (sh) 1001 10424 10203 1 10 0 0:00.01 ./smail 1001 10425 10424 2 10 0 0:00.01 sh -c ps -l|mail -s 'PID 10424' 1001 10426 10425 1 28 0 0:00.00 ps -l 1001 10427 10425 2 28 0 0:00.00 sh -c ps -l|mail -s 'PID 10424' & d 1 & q $
Your message content may vary somewhat from the message shown here. The timing is always such that it appears that two processes are executing the command sh -c ps -l|mail -s 'PID 10424'. In fact, what you see here is a snapshot of how things appear after fork(2) has created a new process, but before it has been able to perform an exec(2) call. The following explains what you see in the message:
Process 10424 is the initial ./smail program that was started.
Process 10425 is the shell process that has been started because of the system(3) call. This shell process must execute the command ps -l|mail -s ehg.
Process 10426 is the ps(1) command that has been started by the shell (note its parent process ID is 10425).
Process 10427 was to be the mail(1) command. However, it shows the command line of the shell because the shell had not yet carried out the call to exec(2) before the ps(1) command took a snapshot. Had the exec(2) call taken place, you would have seen the command mail -s 'PID 10424'ehg instead.
If you are unfamiliar with fork(2) and exec(2) this may be difficult to understand. Chapter 19 will cover fork(2) and exec(2) in detail.
Although the system(3) function is quite easy to use, it has drawbacks. One of them is the complex set of return values when the command string is not a null pointer (review Table 18.1).
The system(3) call is also considered a security risk, especially for programs that are setuid(2) or setgid(2). If this applies to your application, you would be wise to shun the system(3) call, and carefully craft fork(2) and exec(2) calls directly.
3.19.29.89