Как обработать execvp (…) ошибки после ветвления ()?

Часто, прежде чем я делаю ОБНОВЛЕНИЕ или УДАЛЯЮ, я пишу эквивалентный ВЫБОР.

11
задан Łukasz Lew 18 October 2009 в 13:58
поделиться

5 ответов

Хорошо известный трюк с самотеканием может быть адаптирован для этой цели.

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <sysexits.h>
#include <unistd.h>

int main(int argc, char **argv) {
    int pipefds[2];
    int count, err;
    pid_t child;

    if (pipe(pipefds)) {
        perror("pipe");
        return EX_OSERR;
    }
    if (fcntl(pipefds[1], F_SETFD, fcntl(pipefds[1], F_GETFD) | FD_CLOEXEC)) {
        perror("fcntl");
        return EX_OSERR;
    }

    switch (child = fork()) {
    case -1:
        perror("fork");
        return EX_OSERR;
    case 0:
        close(pipefds[0]);
        execvp(argv[1], argv + 1);
        write(pipefds[1], &errno, sizeof(int));
        _exit(0);
    default:
        close(pipefds[1]);
        while ((count = read(pipefds[0], &err, sizeof(errno))) == -1)
            if (errno != EAGAIN && errno != EINTR) break;
        if (count) {
            fprintf(stderr, "child's execvp: %s\n", strerror(err));
            return EX_UNAVAILABLE;
        }
        close(pipefds[0]);
        puts("waiting for child...");
        while (waitpid(child, &err, 0) == -1)
            if (errno != EINTR) {
                perror("waitpid");
                return EX_SOFTWARE;
            }
        if (WIFEXITED(err))
            printf("child exited with %d\n", WEXITSTATUS(err));
        else if (WIFSIGNALED(err))
            printf("child killed by %d\n", WTERMSIG(err));
    }
    return err;
}

Вот полная программа.

$ ./a.out foo
child's execvp: No such file or directory
$ (sleep 1 && killall -QUIT sleep &); ./a.out sleep 60
waiting for child...
child killed by 3
$ ./a.out true
waiting for child...
child exited with 0

Как это работает:

Создайте канал и сделайте конечную точку записи CLOEXEC : он автоматически закрывается при успешном выполнении exec .

В дочернем элементе попробуйте ] exec . Если это удастся, у нас больше нет контроля, но труба закрыта. Если это не удается, запишите код ошибки в канал и выйдите.

В родительском цикле попытайтесь прочитать из другой конечной точки канала. Если read возвращает ноль, значит канал был закрыт, и дочерний элемент должен успешно выполнить exec . Если read возвращает данные, это код ошибки, написанный нашим потомком.

19
ответ дан 3 December 2019 в 03:35
поделиться

You terminate the child (by calling _exit()) and then the parent can notice this (through e.g. waitpid()). For instance, your child could exit with an exit status of -1 to indicate failure to exec. One caveat with this is that it is impossible to tell from your parent whether the child in its original state (i.e. before exec) returned -1 or if it was the newly executed process.

As suggested in the comments below, using an "unusual" return code would be appropiate to make it easier to distinguish between your specific error and one from the exec()'ed program. Common ones are 1, 2, 3 etc. while higher numbers 99, 100, etc. are more unusual. You should keep your numbers below 255 (unsigned) or 127 (signed) to increase portability.

Since waitpid blocks your application (or rather, the thread calling it) you will either need to put it on a background thread or use the signalling mechanism in POSIX to get information about child process termination. See the SIGCHLD signal and the sigaction function to hook up a listener.

You could also do some error checking before forking, such as making sure the executable exists.

If you use something like Glib, there are utility functions to do this, and they come with pretty good error reporting. Take a look at the "spawning processes" section of the manual.

6
ответ дан 3 December 2019 в 03:35
поделиться

1) Используйте _exit () не exit () - см. http://opengroup.org/onlinepubs/007908775/xsh /vfork.html - Примечание: применимо к fork () , а также к vfork () .

2) Проблема с выполнением более сложного IPC, чем статус выхода , заключается в том, что у вас есть общая карта памяти, и можно получить неприятное состояние, если вы сделаете что-нибудь слишком сложное - например, в многопоточном коде один из убитых потоков (в дочернем) мог удерживать блокировку.

1
ответ дан 3 December 2019 в 03:35
поделиться

Well, you could use the wait/waitpid functions in the parent process. You can specify a status variable that holds info about the status of the process that terminated. The downside is that the parent process is blocked until the child process finishes execution.

0
ответ дан 3 December 2019 в 03:35
поделиться

Вы не должны удивляться, как вы можете заметить это в родительском процессе, но также должны помнить, что вы должны заметить ошибку в родительском процессе процесс. Это особенно верно для многопоточных приложений.

После execvp вы должны выполнить вызов функции, которая в любом случае завершит процесс. Вы не должны вызывать какие-либо сложные функции, которые взаимодействуют с библиотекой C (например, stdio), поскольку их эффекты могут смешиваться с pthreads функциональности libc родительского процесса. Таким образом, вы не можете распечатать сообщение с помощью printf () в дочернем процессе и вместо этого должны сообщить родителю об ошибке.

Самый простой способ - это передать код возврата. Предоставьте ненулевой аргумент функции _exit () (см. Примечание ниже), которую вы использовали для завершения дочернего элемента, а затем проверьте код возврата в родительском элементе. Вот пример:

int pid, stat;
pid = fork();
if (pid == 0){
   // Child process
   execvp(cmd);
   if (errno == ENOENT)
     _exit(-1);
   _exit(-2);
}

wait(&stat);
if (!WIFEXITED(stat)) { // Error happened 
...
}

Вместо _exit () вы можете подумать о функции exit () , но это неверно, поскольку эта функция будет выполнять часть C-библиотеки очистка, которая должна выполняться только после завершения родительского процесса. Вместо этого используйте функцию _exit () , которая не выполняет такую ​​очистку.

так как эта функция будет выполнять часть очистки C-библиотеки, которая должна выполняться только после завершения родительского процесса. Вместо этого используйте функцию _exit () , которая не выполняет такую ​​очистку.

так как эта функция будет выполнять часть очистки C-библиотеки, которая должна выполняться только после завершения родительского процесса. Вместо этого используйте функцию _exit () , которая не выполняет такую ​​очистку.

1
ответ дан 3 December 2019 в 03:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: