8.12 system函数
在程序中执行一个命令字符串很方便。例如,假定要将时间和日期放到一个文件中,则可使用6 . 9节中的函数实现这一点。调用time得到当前日历时间,接着调用localtime将日历时间变换为年、月、日、时、分、秒、周日形式,然后调用strftime对上面的结果进行格式化处理,最后将结果写到文件中。但是用下面的system函数则更容易做到这一点。
system("date > file");
ISO C定义了system函数,但是其操作对系统的依赖性很强。
#include <stdlib.h>
int system(const char *cmdstring) ;
如果cmdstring是一个空指针,则仅当命令处理程序可用时, system返回非0值,这一特征可以决定在一个给定的操作系统上是否支持system函数。在U N I X中,system总是可用的。
因为system在其实现中调用了fork、exec和waitpid,因此有三种返回值:
(1) 如果fork失败或者waitpid返回除E I N T R之外的出错,则system返回-1,而且errno中设置了错误类型。
(2) 如果exec失败(表示不能执行shell ),则其返回值如同shell执行了exit ( 1 2 7 )一样。
(3) 否则所有三个函数( fork , exec和waitpid )都成功,并且system的返回值是shell的终止状态,其格式已在waitpid中说明。
程序8 - 1 2是system函数的一种实现。它对信号没有进行处理。1 0 . 1 8节中将修改此函数使其进行信号处理。
#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);
}
shell的-c选项告诉shell程序取下一个命令行参数,上面是cmdstring作为命令输入而不是从标准输入或从一个给定的文件中读取命令。
注意,我们调用_exit而不是exit。这是为了防止任何一个标准IO缓冲区(这些缓存区会在fork中由父进程复制到子进程)在子进程中被冲洗。
下述程序8-13对system的这种版本进行了测试。
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
void pr_exit(int status)
{
if (WIFEXITED(status))
printf("normal termination, exit status = %d\n",
WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("abnormal termination, signal number = %d%s\n",
WTERMSIG(status),
#ifdef WCOREDUMP
WCOREDUMP(status) ? " (core file generated)" : "");
#else
"");
#endif
else if (WIFSTOPPED(status))
printf("child stopped, signal number = %d\n",
WSTOPSIG(status));
}
int main(void)
{
int status;
if ((status = system("date")) < 0)
printf("system() error");
pr_exit(status);
if ((status = system("nosuchcommand")) < 0)
printf("system() error");
pr_exit(status);
if ((status = system("who; exit 44")) < 0)
printf("system() error");
pr_exit(status);
exit(0);
}
程序运行输出为:
Tue Oct 23 00:32:42 PDT 2012
normal termination, exit status = 0
sh: nosuchcommand: not found
normal termination, exit status = 127
leo tty7 2012-10-23 00:29 (:0)
leo pts/0 2012-10-23 00:31 (:0.0)
normal termination, exit status = 44
使用system而不是直接使用fork和exec的优点是:system进行了所需的各种出错处理,以及各种信号处理。
设置用户ID程序
如果在一个设置用户ID程序中调用system,会产生一个安全性方面的漏洞。绝不应当这样做。
程序8-14用system执行命令行参数
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
void pr_exit(int status)
{
if (WIFEXITED(status))
printf("normal termination, exit status = %d\n",
WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("abnormal termination, signal number = %d%s\n",
WTERMSIG(status),
#ifdef WCOREDUMP
WCOREDUMP(status) ? " (core file generated)" : "");
#else
"");
#endif
else if (WIFSTOPPED(status))
printf("child stopped, signal number = %d\n",
WSTOPSIG(status));
}
int main(int argc, char *argv[])
{
int status;
if (argc < 2)
printf("command-line argument required");
if ((status = system(argv[1])) < 0)
printf("system() error");
pr_exit(status);
exit(0);
}
程序8-15打印实际和有效用户ID
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
printf("real uid = %d, effective uid = %d\n", getuid(), geteuid());
exit(0);
}
如果一个进程正以特殊的权限运行(设置用户ID或者设置组ID),它又想生成另一个进程执行另一个程序,则它应当直接使用fork和exec,而且在fork之后,exec之前要改回到普通权限。设置用户ID或者设置组ID程序决不应调用system函数。这种警告的一个理由是:system调用shell对命令字符串进行语法分析,而shell使用IFS变量作为其输入字段分隔符。早期的shell版本在被调用时不将此变量恢复为普通字符集。这就允许一个有恶意的用户在调用system之前设置IFS,造成system执行一个不同的程序。其中IFS为Internal Field Seperator)在Linux的shell中预设的分隔符,用来把command line分解成word(字段)。IFS可以是White Space(空白键)、Tab( 表格键)、Enter( 回车键)中的一个或几个。