2012-11-01 58 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);以获得真实的返回值。