system使用、危害和解决办法总结

来源:互联网 发布:北京seo教学 华网天下 编辑:程序博客网 时间:2024/04/30 20:08

system的简单介绍

#include <stdlib.h>

int system(const char *command);

system() executes a command specified in command by calling /bin/sh -c command, and returns after the command has been completed. During execution of the command, SIGCHLD will be blocked, and SIGINT and SIGQUIT will be ignored.

system()函数调用/bin/sh来执行参数指定的命令,/bin/sh 一般是一个软连接,指向某个具体的shell,比如bash-c选项是告诉shell从字符串command中读取命令;

在该command执行期间,SIGCHLD是被阻塞的,好比在说:hi,内核,这会不要给我送SIGCHLD信号,等我忙完再说;

在该command执行期间,SIGINTSIGQUIT是被忽略的,意思是进程收到这两个信号后没有任何动作。

 

system的返回值

The value returned is -1 on error (e.g. fork(2) failed), and the return status of the command otherwise. This latter return status is in the format specified in wait(2). Thus, the exit code of the command will be WEXITSTATUS(status). In case /bin/sh could not be executed, the exit status will be that of a command that does exit(127).

If the value of command is NULL, system() returns nonzero if the shell is available, and zero if not.

system()函数执行了三步操作:

1.fork一个子进程;

2.在子进程中调用exec函数去执行command

3.在父进程中调用wait去等待子进程结束。

对于fork失败,system()函数返回-1

如果exec执行成功,也即command顺利执行完毕,则返回command通过exitreturn返回的值。

(注意,command顺利执行不代表执行成功,比如command"rm debuglog.txt",不管文件存不存在该command都顺利执行了)

如果exec执行失败,也即command没有顺利执行,比如被信号中断,或者command命令根本不存在,system()函数返回127.

如果commandNULL,则system()函数返回非0值,一般为1.

 

system的实现

int system(const char * cmdstring)

{

    pid_t pid;

    int status;

 

if(cmdstring == NULL)

{

    return (1); //如果cmdstring为空,返回非零值,一般为1

}

 

if((pid = fork())<0)

{

    status = -1; //fork失败,返回-1

}

else if(pid == 0)

{

    execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);

    _exit(127); // exec执行失败返回127,注意exec只在失败时才返回现在的进程,成功的话现在的进程就不存在啦~~

}

else //父进程

{

    while(waitpid(pid, &status, 0) < 0)

    {

        if(errno != EINTR)

        {

            status = -1; //如果waitpid被信号中断,则返回-1

            break;

        }

    }

}

    return status; //如果waitpid成功,则返回子进程的返回状态

}

 

监控system()函数执行状态

int status;

if(NULL == cmdstring) //如果cmdstring为空趁早闪退吧,尽管system()函数也能处理空指针

{

    return XXX;

}

status = system(cmdstring);

if(status < 0)

{

    printf("cmd: %s\t error: %s", cmdstring, strerror(errno)); // 这里务必要把errno信息输出或记入Log

    return XXX;

}

if(WIFEXITED(status))

{

    printf("normal termination, exit status = %d\n", WEXITSTATUS(status)); //取得cmdstring执行结果

}

else if(WIFSIGNALED(status))

{

    printf("abnormal termination,signal number =%d\n", WTERMSIG(status)); //如果cmdstring被信号中断,取得信号值

}

else if(WIFSTOPPED(status))

{

    printf("process stopped, signal number =%d\n", WSTOPSIG(status)); //如果cmdstring被信号暂停执行,取得信号值

}


system函数的危害

在编写具有SUID/SGID权限的程序时请勿使用system()system()会继承环境变量,通过环境变量可能会造成系统安全的问题。

system()会接受父进程的环境变量,但是用system改变环境变量后,system一返回主函数还是没变

 

system替换方法

system()函数用起来很容易出错,返回值太多,而且返回值很容易跟command的返回值混淆。这里推荐使用popen()函数替代,关于popen()函数的简单使用也可以通过上面的链接查看。

 

popen()函数较于system()函数的优势在于使用简单,popen()函数只返回两个值:

成功返回子进程的status,使用WIFEXITED相关宏就可以取得command的返回结果;

失败返回-1,我们可以使用perro()函数或strerror()函数得到有用的错误信息。

 

IPC通信】基于管道的popenpclose函数

标准I/O函数库提供了popen函数,它启动另外一个进程去执行一个shell命令行。

 

这里我们称调用popen的进程为父进程,由popen启动的进程称为子进程。

 

popen函数还创建一个管道用于父子进程间通信。父进程要么从管道读信息,要么向管道写信息,至于是读还是写取决于父进程调用popen时传递的参数。下在给出popenpclose的定义:

 

 

#include <stdio.h>

/*

函数功能:popen()会调用fork()产生子进程,然后从子进程中调用/bin/sh -c来执行参数command的指令。

        参数type可使用“r”代表读取,“w”代表写入。

        依照此type值,popen()会建立管道连到子进程的标准输出设备或标准输入设备,然后返回一个文件指针。

        随后进程便可利用此文件指针来读取子进程的输出设备或是写入到子进程的标准输入设备中

返回值:若成功则返回文件指针,否则返回NULL,错误原因存于errno

*/

FILE * popen( const char * command,const char * type);

/*

函数功能:pclose()用来关闭由popen所建立的管道及文件指针。参数stream为先前由popen()所返回的文件指针

返回值:若成功返回shell的终止状态(也即子进程的终止状态),若出错返回-1,错误原因存于errno

*/

int pclose(FILE * stream);

下面通过例子看下popen的使用:

 

假如我们想取得当前目录下的文件个数,在shell下我们可以使用: 

 

1

ls | wc -l

我们可以在程序中这样写:

 

/*取得当前目录下的文件个数*/

#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <sys/wait.h>

 

#define MAXLINE 1024

 

int main()

{

    char result_buf[MAXLINE], command[MAXLINE];

    int rc = 0; // 用于接收命令返回值

    FILE *fp;

 

    /*将要执行的命令写入buf*/

    snprintf(command, sizeof(command), "ls ./ | wc -l");

 

    /*执行预先设定的命令,并读出该命令的标准输出*/

    fp = popen(command, "r");

    if(NULL == fp)

    {

        perror("popen执行失败!");

        exit(1);

    }

    while(fgets(result_buf, sizeof(result_buf), fp) != NULL)

    {

        /*为了下面输出好看些,把命令返回的换行符去掉*/

        if('\n' == result_buf[strlen(result_buf)-1])

        {

            result_buf[strlen(result_buf)-1] = '\0';

        }

        printf("命令【%s】 输出【%s\r\n", command, result_buf);

    }

 

    /*等待命令执行完毕并关闭管道及文件指针*/

    rc = pclose(fp);

    if(-1 == rc)

    {

        perror("关闭文件指针失败");

        exit(1);

    }

    else

    {

        printf("命令【%s】子进程结束状态【%d】命令返回值【%d\r\n", command, rc, WEXITSTATUS(rc));

    }

    return 0;

}

编译并执行:

$ gcc popen.c

$ ./a.out

命令【ls ./ | wc -l】 输出【2

命令【ls ./ | wc -l】子进程结束状态【0】命令返回值【0

上面popen只捕获了command的标准输出,如果command执行失败,子进程会把错误信息打印到标准错误输出,父进程就无法获取。比如,command命令为“ls nofile.txt” ,事实上我们根本没有nofile.txt这个文件,这时shell会输出“ls: nofile.txt: No such file or directory”。这个输出是在标准错误输出上的。通过上面的程序并无法获取。

 

注:如果你把上面程序中的command设成“ls nofile.txt,编译执行程序你会看到如下结果:

 

$ gcc popen.c  

$ ./a.out


ls: nofile.txt: No such file or directory

 

命令【ls nofile.txt】子进程结束状态【256】命令返回值【1

 需要注意的是第一行输出并不是父进程的输出,而是子进程的标准错误输出。

有时子进程的错误信息是很有用的,那么父进程怎么才能获取子进程的错误信息呢?

 

这里我们可以重定向子进程的错误输出,让错误输出重定向到标准输出(2>&1),这样父进程就可以捕获子进程的错误信息了。例如command为“ls nofile.txt 2>&1,输出如下:

命令【ls nofile.txt 2>&1】 输出【ls: nofile.txt: No such file or directory

命令【ls nofile.txt 2>&1】子进程结束状态【256】命令返回值【1

附:子进程的终止状态判断涉及到的宏,设进程终止状态为status.

 

WIFEXITED(status)如果子进程正常结束则为非0值。

WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用WIFEXITED 来判断是否正常结束才能使用此宏。

WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真。

WTERMSIG(status)取得子进程因信号而中止的信号代码,一般会先用WIFSIGNALED 来判断后才使用此宏。

WIFSTOPPED(status)如果子进程处于暂停执行情况则此宏值为真。一般只有使用WUNTRACED 时才会有此情况。

WSTOPSIG(status)取得引发子进程暂停的信号代码,一般会先用WIFSTOPPED 来判断后才使用此宏。

0 0