8.12 system函数

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在其实现中调用了forkexecwaitpid,因此有三种返回值:

(1) 如果fork失败或者waitpid返回除E I N T R之外的出错,则system返回-1,而且errno中设置了错误类型。

(2) 如果exec失败(表示不能执行shell ),则其返回值如同shell执行了exit ( 1 2 7 )一样。

(3) 否则所有三个函数( fork , execwaitpid )都成功,并且system的返回值是shell的终止状态,其格式已在waitpid中说明。

程序8 – 1 2system函数的一种实现。它对信号没有进行处理。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-13system的这种版本进行了测试。

#include <stdio.h>

#include <stdlib.h>

#include <sys/wait.h>



void pr_exit(int status)

{

if (WIFEXITED(status))

printf("normal termination, exit status = %dn",

WEXITSTATUS(status));

else if (WIFSIGNALED(status))

printf("abnormal termination, signal number = %d%sn",

WTERMSIG(status),

#ifdef WCOREDUMP

WCOREDUMP(status) ? " (core file generated)" : "");

#else

"");

#endif

else if (WIFSTOPPED(status))

printf("child stopped, signal number = %dn",

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而不是直接使用forkexec的优点是:system进行了所需的各种出错处理,以及各种信号处理。

设置用户ID程序

      如果在一个设置用户ID程序中调用system,会产生一个安全性方面的漏洞。绝不应当这样做。

      程序8-14system执行命令行参数

#include <stdio.h>

#include <stdlib.h>

#include <sys/wait.h>



void pr_exit(int status)

{

if (WIFEXITED(status))

printf("normal termination, exit status = %dn",

WEXITSTATUS(status));

else if (WIFSIGNALED(status))

printf("abnormal termination, signal number = %d%sn",

WTERMSIG(status),

#ifdef WCOREDUMP

WCOREDUMP(status) ? " (core file generated)" : "");

#else

"");

#endif

else if (WIFSTOPPED(status))

printf("child stopped, signal number = %dn",

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 = %dn", getuid(), geteuid());

exit(0);

}

      如果一个进程正以特殊的权限运行(设置用户ID或者设置组ID),它又想生成另一个进程执行另一个程序,则它应当直接使用forkexec,而且在fork之后,exec之前要改回到普通权限。设置用户ID或者设置组ID程序决不应调用system函数。这种警告的一个理由是:system调用shell对命令字符串进行语法分析,而shell使用IFS变量作为其输入字段分隔符。早期的shell版本在被调用时不将此变量恢复为普通字符集。这就允许一个有恶意的用户在调用system之前设置IFS,造成system执行一个不同的程序。其中IFSInternal Field Seperator)Linuxshell中预设的分隔符,用来把command line分解成word(字段)。IFS可以是White Space(空白键)、Tab( 表格键)、Enter( 回车键)中的一个或几个。