相关函数 fork,execve,waitpid,popen
表头文件 #include
定义函数 int system(const char * string);
函数说明 system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数string字符串所代表的命令,此命令执行完后随即返回原调用的进程。在调用system( )期间SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信号则会被忽略。返回值 如果system() 在调用/bin/sh时失败则返回127,其他失败原因返回-1。若参数string为空指针(NULL),则返回非零值。如果system()调
用成功则最后会返回执行shell命令后的返回值,但是此返回值也有可能为system()调用/bin/sh失败所返回的127,因此最好能再检查
errno 来确认执行成功。
附加说明 在编写具有SUID/SGID权限的程序时请勿使用system( ),system( )会继承环境变量,通过环境变量可能会造成系统安全的问题。
范例:
#include
main()
{
system(“ls -al /etc/passwd /etc/shadow”);
}
相关函数 pipe,mkfifo,pclose,fork,system,fopen
表头文件 #include
定义函数 FILE * popen( const char * command,const char * type);
函数说明 popen()会调用fork()产生子进程,然后从子进程中调用/bin/sh -c来执行参数command的指令。参数type可使用“r”代表读取,“w”代表写入。依照此type值,popen()会建立管道连到子进程的标准输出设备或标准输入设备,然后返回一个文件指针。随后进程便可利用此文件指针来读取子进程的输出设备或是写入到子进程的标准输入设备中。此外,所有使用文件指针(FILE*)操作的函数也都可以使用,除了fclose()以外。返回值 若成功则返回文件指针,否则返回NULL,错误原因存于errno中。错误代码 EINVAL参数type不合法。
注意事项 在编写具SUID/SGID权限的程序时请尽量避免使用popen(),popen()会继承环境变量,通过环境变量可能会造成系统安全的问题。
范例:
#include
main()
{
FILE * fp;
char buffer[80];
fp=popen(“cat /etc/passwd”,”r”);
fgets(buffer,sizeof(buffer),fp);
printf(“%s”,buffer);
pclose(fp);
}
需要注意的是exec并不是1个函数, 其实它只是一组函数的统称, 它包括下面6个函数:
#include
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
可以见到这6个函数名字不同, 而且他们用于接受的参数也不同。
实际上他们的功能都是差不多的, 因为要用于接受不同的参数所以要用不同的名字区分它们, 毕竟c语言没有函数重载的功能嘛
但是实际上它们的命名是有规律的:
exec[l or v][p][e]
exec函数里的参数可以分成3个部分, 执行文件部分, 命令参数部分, 环境变量部分。
例如我要执行1个命令 ls -l /home/gateman
执行文件部分 就是 "/usr/bin/ls"
命令参赛部分 就是 "ls","-l","/home/gateman",NULL 见到是以ls开头 每1个空格都必须分开成2个部分, 而且以NULL结尾的啊.
环境变量部分, 这是1个数组,最后的元素必须是NULL 例如char * env[] = {"PATH=/home/gateman", "USER=lei", "STATUS = testing", NULL};
好了说下命名规则:
e后续, 参数必须带环境变量部分, 环境变零部分参数会成为执行exec函数期间的环境变量, 比较少用
l 后续, 命令参数部分必须以"," 相隔, 最后1个命令参数必须是NULL
v 后续, 命令参数部分必须是1个以NULL结尾的字符串指针数组的头部指针。例如char * pstr就是1个字符串的指针, char * pstr[] 就是数组了, 分别指向各个字符串。
p后续, 执行文件部分可以不带路径, exec函数会在$PATH中找
还有1个注意的是, exec函数会取代执行它的进程, 也就是说, 一旦exec函数执行成功, 它就不会返回了, 进程结束. 但是如果exec函数执行失败, 它会返回失败的信息, 而且进程继续执行后面的代码!
通常exec会放在fork() 函数的子进程部分, 来替代子进程执行啦, 执行成功后子程序就会消失, 但是执行失败的话, 必须用exit()函数来让子进程退出!
下面是各个例子:
int childpid;
int i;
if (fork() == 0){
//child process
char * execv_str[] = {"echo", "executed by execv",NULL};
if (execv("/usr/bin/echo",execv_str) <0 ){
perror("error on exec");
exit(0);
}
}else{
//parent process
wait(&childpid);
printf("execv done\n\n");
}
注意字符串指针数组的定义和赋值
if (fork() == 0){
//child process
char * execvp_str[] = {"echo", "executed by execvp",">>", "~/abc.txt",NULL};
if (execvp("echo",execvp_str) <0 ){
perror("error on exec");
exit(0);
}
}else{
//parent process
wait(&childpid);
printf("execvp done\n\n");
}
if (fork() == 0){
//child process
char * execve_str[] = {"env",NULL};
char * env[] = {"PATH=/tmp", "USER=lei", "STATUS=testing", NULL};
if (execve("/usr/bin/env",execve_str,env) <0 ){
perror("error on exec");
exit(0);
}
}else{
//parent process
wait(&childpid);
printf("execve done\n\n");
}
if (fork() == 0){
//child process
if (execl("/usr/bin/echo","echo","executed by execl" ,NULL) <0 ){
perror("error on exec");
exit(0);
}
}else{
//parent process
wait(&childpid);
printf("execv done\n\n");
}
if (fork() == 0){
//child process
if (execlp("echo","echo","executed by execlp" ,NULL) <0 ){
perror("error on exec");
exit(0);
}
}else{
//parent process
wait(&childpid);
printf("execlp done\n\n");
}
if (fork() == 0){
//child process
char * env[] = {"PATH=/home/gateman", "USER=lei", "STATUS=testing", NULL};
if (execle("/usr/bin/env","env",NULL,env) <0){
perror("error on exec");
exit(0);
}
}else{
//parent process
wait(&childpid);
printf("execle done\n\n");
}