Linux system函數(shù)的正確應(yīng)用和異常處理
Linux系統(tǒng)中的system函數(shù)作為Linux應(yīng)用開發(fā)工程師來說是一個非常方便的調(diào)用shell腳本的方法,但是不理解system函數(shù)本身的調(diào)用機制,不進(jìn)行相應(yīng)的出錯處理,很容易造成程序的異常和bug。
一、system()理解
功能:system()函數(shù)調(diào)用“/bin/sh -c command”執(zhí)行特定的命令,阻塞當(dāng)前進(jìn)程直到command命令執(zhí)行完畢
原型:
int system(const char *command);
返回值:
如果無法啟動shell運行命令,system將返回127;出現(xiàn)不能執(zhí)行system調(diào)用的其他錯誤時返回-1。如果system能夠順利執(zhí)行,返回那個命令的退出碼。
說明:
man幫助:
#include <stdlib.h>
int system(const char *command);
DESCRIPTION
system() executes a command specified in command by calling /bin/sh -c
command, and returns after the command has been completed. During exe-
cution of the command, SIGCHLD will be blocked, and SIGINT and SIGQUIT
will be ignored.
RETURN VALUE
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 non-zero if the shell
is available, and zero if not.
system() does not affect the wait status of any other children.
二、system()函數(shù)原理
system函數(shù)執(zhí)行時,會調(diào)用fork、execve、waitpid等函數(shù)。
Linux版system函數(shù)的源碼:
- int system(const char * cmdstring)
- {
- pid_t pid;
- int status;
- if(cmdstring == NULL){
- return (1);
- }
- if((pid = fork())<0){
- status = -1;
- }
- else if(pid == 0){
- execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
- _exit(127); //子進(jìn)程正常執(zhí)行則不會執(zhí)行此語句
- }
- else{
- while(waitpid(pid, &status, 0) < 0){
- if(errno != EINTER){
- status = -1;
- break;
- }
- }
- }
- return status;
- }
函數(shù)說明
system()會調(diào)用fork()產(chǎn)生子進(jìn)程,由子進(jìn)程來調(diào)用/bin/sh-c string來執(zhí)行參數(shù)string字符串所代表的命令,此命>令執(zhí)行完后隨即返回原調(diào)用的進(jìn)程。
在調(diào)用system()期間SIGCHLD 信號會被暫時擱置,SIGINT和SIGQUIT 信號則會被忽略。
返回值
=-1:出現(xiàn)錯誤
=0:調(diào)用成功但是沒有出現(xiàn)子進(jìn)程
>0:成功退出的子進(jìn)程的id
如果system()在調(diào)用/bin/sh時失敗則返回127,其他失敗原因返回-1。若參數(shù)string為空指針(NULL),則返回非零值>。如果system()調(diào)用成功則最后會返回
執(zhí)行shell命令后的返回值,但是此返回值也有可能為 system()調(diào)用/bin/sh失敗所返回的127,因此最好能再檢查errno 來確認(rèn)執(zhí)行成功。
附加說明
在編寫具有SUID/SGID權(quán)限的程序時請勿使用system(),system()會繼承環(huán)境變量,通過環(huán)境變量可能會造成系統(tǒng)安全的問題。
system函數(shù)對返回值的處理,涉及3個階段:
階段1:創(chuàng)建子進(jìn)程等準(zhǔn)備工作。如果失敗,返回-1。
階段2:調(diào)用/bin/sh拉起shell腳本,如果拉起失敗或者shell未正常執(zhí)行結(jié)束(參見備注1),原因值被寫入到status的低8~15比特位中。system的man中只說明了會寫了127這個值,但實測發(fā)現(xiàn)還會寫126等值。
階段3:如果shell腳本正常執(zhí)行結(jié)束,將shell返回值填到status的低8~15比特位中。
備注1:
只要能夠調(diào)用到/bin/sh,并且執(zhí)行shell過程中沒有被其他信號異常中斷,都算正常結(jié)束。
比如:不管shell腳本中返回什么原因值,是0還是非0,都算正常執(zhí)行結(jié)束。即使shell腳本不存在或沒有執(zhí)行權(quán)限,也都算正常執(zhí)行結(jié)束。
如果shell腳本執(zhí)行過程中被強制kill掉等情況則算異常結(jié)束。
如何判斷階段2中,shell腳本子進(jìn)程是否正常執(zhí)行結(jié)束呢?系統(tǒng)提供了宏:WIFEXITED(status)。如果WIFEXITED(status)為真,則說明正常結(jié)束。
如何取得階段3中的shell返回值?你可以直接通過右移8bit來實現(xiàn),但安全的做法是使用系統(tǒng)提供的宏:WEXITSTATUS(status)。
由于我們一般在shell腳本中會通過返回值判斷本腳本是否正常執(zhí)行,如果成功返回0,失敗返回正數(shù)。
所以綜上,判斷一個system函數(shù)調(diào)用shell腳本是否正常結(jié)束的方法應(yīng)該是如下3個條件同時成立:
(1)-1 != status
(2)WIFEXITED(status)為真
(3)0 == WEXITSTATUS(status)
注意:
根據(jù)以上分析,當(dāng)shell腳本不存在、沒有執(zhí)行權(quán)限等場景下時,以上前2個條件仍會成立,此時WEXITSTATUS(status)為127,126等數(shù)值。
所以,我們在shell腳本中不能將127,126等數(shù)值定義為返回值,否則無法區(qū)分中是shell的返回值,還是調(diào)用shell腳本異常的原因值。shell腳本中的返回值最好多1開始遞增。
示例程序:
- #include <stdio.h>
- #include <unistd.h>
- #include <stdlib.h>
- #define EXIT_ERR(m) \
- do\
- {\
- perror(m);\
- exit(EXIT_FAILURE);\
- }\
- while (0);\
- int main(void)
- {
- int status ;
- status = system("ls -l|wc -l");
- if(status == -1){
- EXIT_ERR("system error");
- }
- else{
- if(WIFEXITED(status))
- {
- if(WEXITSTATUS(status) == 0)
- printf("run command successful\n");
- else
- printf("run command fail and exit code is %d\n",WEXITSTATUS(status));
- }
- else
- printf("exit status = %d\n",WEXITSTATUS(status));
- }
- return 0;
- }
運行結(jié)果: