waitpid/wexitstatus возвращает 0 вместо правильного кода возврата

У меня есть вспомогательная функция ниже, используемая для выполнения команды и получения возвращаемого значения в системах posix. Раньше я использовал popen, но невозможно получить код возврата приложения с popen, если оно запускается и завершается до того, как popen/pclose получит возможность выполнить свою работу.

Следующая вспомогательная функция создает ответвление процесса, использует execvp для запуска желаемого внешнего процесса, а затем родительская функция использует waitpid для получения кода возврата. Я вижу странные случаи, когда он отказывается работать.

При вызове с wait = true waitpid должен возвращать код выхода приложения, несмотря ни на что. Однако я вижу вывод stdout, который указывает, что код возврата должен быть ненулевым, хотя код возврата равен нулю. Тестирование внешнего процесса в обычной оболочке, затем echoing $? возвращает ненулевое значение, так что это не проблема, когда внешний процесс не возвращает правильный код. Если это поможет, то внешний процесс запущен 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, хотя должно возвращать что-то другое.


person Mahmoud Al-Qudsi    schedule 01.11.2012    source источник
comment
Вы должны проверить коды возврата ваших системных вызовов. Вызов dup2(pipefd[STDOUT_FILENO], STDERR_FILENO); потерпит неудачу с [EBADF], и waitpid(), скорее всего, тоже не удастся (для чего __WALL?). Кроме того, вы должны прочитать вывод перед ожиданием процесса, чтобы избежать взаимоблокировки, если вывод превышает буферизацию в канале. Кроме того, некоторые проверки wait являются избыточными.   -  person jilles    schedule 01.11.2012
comment
Ваше предположение о том, что элементы в std::vector непрерывны в памяти, неверно. Вместо этого разработчики стандарта C++ решили гарантировать, что push_back не изменит адрес существующих элементов. Вместо этого я предлагаю создать массив char *, но могут быть и другие приемы STL.   -  person jilles    schedule 01.11.2012
comment
@jilles C++03 и C++11 гарантируют непрерывное выделение памяти для vector, не так ли?   -  person Mahmoud Al-Qudsi    schedule 01.11.2012
comment
Я согласен, я думал, что это было задумано, чтобы обеспечить взаимодействие с существующими системными вызовами, которым нужны буферы символов.   -  person Joe    schedule 02.11.2012


Ответы (3)


Вероятно, это не ваша главная проблема, но я думаю, что вижу небольшую проблему. В вашем дочернем процессе у вас есть...

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

person Geoff Montee    schedule 01.11.2012
comment
Вы правы, это провалится. Но я не думаю, что это причина, код выхода не передается через stdout или stderr. - person Mahmoud Al-Qudsi; 01.11.2012
comment
Что ж, исправление этого ничего не изменило. waitpid не работает с ECHILD, хотя указанный PID вполне действителен. Кроме того, комментарий @jilles о смежности неверен для С++ 03 и С++ 11. - person Mahmoud Al-Qudsi; 01.11.2012
comment
Спасибо за вашу помощь. Голосование за это предложение по поиску ошибки в коде, но в конечном итоге это не было основной проблемой. - person Mahmoud Al-Qudsi; 01.11.2012
comment
@MahmoudAl-Qudsi рад помочь. И рад, что вы обнаружили реальную проблему. - person Geoff Montee; 01.11.2012

Я использую библиотеку mongoose, и мой код для SIGCHLD показал, что использование mg_start из mongoose приводит к установке SIGCHLD в SIG_IGN.

На waitpid справочной странице в Linux SIGCHLD, установленный на SIG_IGN, не создаст зомби. процесс, поэтому waitpid завершится ошибкой, если процесс уже успешно запущен и завершился, но будет работать нормально, если еще этого не произошло. Это было причиной спорадического сбоя моего кода.

Простой переустановки SIGCHLD после вызова mg_start в функцию void, которая абсолютно ничего не делает, было достаточно, чтобы записи зомби не были немедленно стерты.

Согласно совету @Geoff_Montee, в моем перенаправлении на STDERR была ошибка, но это не было причиной проблемы, как это делает execvp не хранить возвращаемое значение в STDERR или даже STDOUT, а скорее в объекте ядра, связанном с родительским процессом (запись зомби).

предупреждение @jilles о не -смежность vector в C++ не применяется для C++03 и выше (действительно только для C++98, хотя на практике большинство компиляторов C++98 в любом случае использовали непрерывное хранилище) и не было связано с этой проблемой. Тем не менее, совет по чтению из канала перед блокировкой и проверкой вывода waitpid является точным.

person Mahmoud Al-Qudsi    schedule 01.11.2012

Я обнаружил, что pclose НЕ блокируется и не ждет завершения процесса, вопреки документации (это в CentOS 6). Я обнаружил, что мне нужно вызвать pclose, а затем вызвать waitpid(pid,&status,0);, чтобы получить истинное возвращаемое значение.

person Mark Lakata    schedule 26.03.2015