2012-11-01 126 views
1

我有下面的幫助函數,用於執行命令並獲取posix系統上的返回值。我曾經使用popen,但如果它運行並且在popen/pclose有機會完成其工作之前退出,則無法獲得應用程序的返回碼popenwaitpid/wexitstatus返回0而不是正確的返回碼

以下幫助函數創建一個進程分叉,使用execvp運行所需的外部進程,然後父代使用waitpid獲取返回碼。我看到它拒絕運行的奇怪情況。

當用wait = true調用時,waitpid應該返回應用程序的退出代碼,無論如何。但是,我看到stdout輸出指定返回碼應該不爲零,但返回碼爲零。在一個普通的shell中測試外部進程,然後echo ing $?返回非零值,所以在外部進程沒有返回正確的代碼時這不成問題。如果有任何幫助,正在運行的外部進程是mount(8)(是的,我知道我可以使用mount(2),但除此之外)。

對於代碼轉儲,我提前表示歉意。大部分是調試/日誌:

inline int ForkAndRun(const std::string &command, const std::vector<std::string> &args, bool wait = false, std::string *output = NULL) 
{ 
    std::string debug; 

    std::vector<char*> argv; 
    for(size_t i = 0; i < args.size(); ++i) 
    { 
     argv.push_back(const_cast<char*>(args[i].c_str())); 
     debug += "\""; 
     debug += args[i]; 
     debug += "\" "; 
    } 
    argv.push_back((char*)NULL); 

    neosmart::logger.Debug("Executing %s", debug.c_str()); 

    int pipefd[2]; 

    if (pipe(pipefd) != 0) 
    { 
     neosmart::logger.Error("Failed to create pipe descriptor when trying to launch %s", debug.c_str()); 
     return EXIT_FAILURE; 
    } 

    pid_t pid = fork(); 

    if (pid == 0) 
    { 
     close(pipefd[STDIN_FILENO]); //child isn't going to be reading 
     dup2(pipefd[STDOUT_FILENO], STDOUT_FILENO); 
     close(pipefd[STDOUT_FILENO]); //now that it's been dup2'd 
     dup2(pipefd[STDOUT_FILENO], STDERR_FILENO); 

     if (execvp(command.c_str(), &argv[0]) != 0) 
     { 
      exit(EXIT_FAILURE); 
     } 
     return 0; 
    } 
    else if (pid < 0) 
    { 
     neosmart::logger.Error("Failed to fork when trying to launch %s", debug.c_str()); 
     return EXIT_FAILURE; 
    } 
    else 
    { 
     close(pipefd[STDOUT_FILENO]); 

     int exitCode = 0; 

     if (wait) 
     { 
      waitpid(pid, &exitCode, wait ? __WALL : (WNOHANG | WUNTRACED)); 

      std::string result; 
      char buffer[128]; 
      ssize_t bytesRead; 
      while ((bytesRead = read(pipefd[STDIN_FILENO], buffer, sizeof(buffer)-1)) != 0) 
      { 
       buffer[bytesRead] = '\0'; 
       result += buffer; 
      } 

      if (wait) 
      { 
       if ((WIFEXITED(exitCode)) == 0) 
       { 
        neosmart::logger.Error("Failed to run command %s", debug.c_str()); 
        neosmart::logger.Info("Output:\n%s", result.c_str()); 
       } 
       else 
       { 
        neosmart::logger.Debug("Output:\n%s", result.c_str()); 
        exitCode = WEXITSTATUS(exitCode); 
        if (exitCode != 0) 
        { 
         neosmart::logger.Info("Return code %d", (exitCode)); 
        } 
       } 
      } 

      if (output) 
      { 
       result.swap(*output); 
      } 
     } 

     close(pipefd[STDIN_FILENO]); 

     return exitCode; 
    } 
} 

注意,運行該命令使用正確的參數,函數收益沒有任何問題,並WIFEXITED回報TRUE確定。然而,WEXITSTATUS返回0,當它應該返回別的東西。

+3

您應該檢查系統調用的返回碼。調用'dup2(pipefd [STDOUT_FILENO],STDERR_FILENO);'會失敗,[EBADF]和'waitpid()'也可能失敗(什麼是__WALL?)。另外,如果輸出大於管道中的緩衝區,則應在等待進程前讀取輸出以避免死鎖。此外,「等待」的一些檢查是多餘的。 – jilles

+1

你假設'std :: vector'中的元素在內存中是連續的是不正確的。而C++標準開發人員卻選擇保證'push_back'不會改變現有元素的地址。我建議創建一個'char *'數組,但可能會有更多的STL技巧。 – jilles

+1

@jilles C++ 03和C++ 11保證'vector'的連續內存分配,不是嗎? –

回答

2

我使用mongoose庫和grepping我的代碼SIGCHLD透露,在設置SIGCHLDSIG_IGN使用從貓鼬結果mg_start

waitpid man page,在Linux上SIGCHLD設置爲SIG_IGN不會產生殭屍進程,所以waitpid如果過程已經成功運行將失敗並退出 - 但如果它尚未運行正常。這是我的代碼零星故障的原因。

在呼叫mg_start之後,只需重新設置SIGCHLD即可發揮無效功能,該功能絕對不會讓殭屍記錄立即被刪除。

@Geoff_Montee's advice,那裏是我的STDERR重定向錯誤,但是這是不負責的問題,因爲execvp不存儲在STDERR甚至STDOUT的返回值,而是與父進程相關聯的內核對象(殭屍記錄)。

@jilles' warning關於在C++中不連續的vector不適用於C++ 03及更高版本(只適用於C++ 98,儘管實際上大多數C++ 98編譯器確實使用連續存儲),並且與這個問題沒有關係。但是,在阻止和檢查waitpid的輸出之前,從管道讀取的建議是正確的。

2

可能不是你的主要問題,但我認爲我看到一個小問題。在你的孩子過程中,你有...

dup2(pipefd[STDOUT_FILENO], STDOUT_FILENO); 
close(pipefd[STDOUT_FILENO]); //now that it's been dup2'd 
dup2(pipefd[STDOUT_FILENO], STDERR_FILENO); //but wait, this pipe is closed! 

但我想你想要的是:

dup2(pipefd[STDOUT_FILENO], STDOUT_FILENO); 
dup2(pipefd[STDOUT_FILENO], STDERR_FILENO); 
close(pipefd[STDOUT_FILENO]); //now that it's been dup2'd for both, can close 

我沒有在Linux中叉和管太多的經驗,但我沒寫最近很類似的功能。如果您願意,您可以查看代碼進行比較。我知道我的功能起作用。

execAndRedirect.cpp

+0

你說得對,那會失敗。但我不認爲這是原因,退出代碼不是通過'stdout'或'stderr'傳遞的。 –

+0

那麼,修復,並沒有改變的事情。 waitpid與'ECHILD'失敗,儘管它給出的PID非常有效。另外,@jilles關於連續性的評論對於C++ 03和C++ 11是不正確的。 –

+0

感謝您的幫助。提出這個建議以找出代碼中的錯誤,但最終它不是核心問題。 –

0

我發現pclose不會阻止並等待進程結束,與文檔相反(這是在CentOS 6上)。我發現我需要撥打pclose,然後撥打waitpid(pid,&status,0);以獲得真實的返回值。