Выполнение конвейера командной строки в C с помощью fork() и exec()

Я спросил о своем коде, и ответ был, что он неверен. использование perror в этом случае Теперь мне интересно, как я могу настроить и улучшить, чтобы я не больше не будет этих ошибок?

execvp не возвращает значение, за исключением случаев возникновения ошибки, поэтому, если все работает, объемлющая функция никогда не вернется.

значение «i» уже находится за концом массива в «cmd» из-за предыдущего цикла, поэтому «cmd[i].argv[0] неверно.

cmd не является массивом команды struct, поэтому не должен индексироваться

первая запись в cmd.argv является указателем на массив, где последняя запись имеет значение NULL. execvp будет работать с этим (и только с этим) массивом, поэтому все остальные указатели на массивы будут игнорироваться.

В коде большое количество ошибок. например, первый раз в цикле fork_pipe() 'in' содержит мусор. второй параметр, передаваемый в execvp(), должен быть указателем на символьные строки с конечным указателем NULL. Этот последний указатель NULL отсутствует, есть еще много проблем

#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
struct command
{
    const char **argv;
};
/* Helper function that spawns processes */
int spawn_proc (int in, int out, struct command *cmd) {
    pid_t pid;
    if ((pid = fork ()) == 0) {
        if (in != 0) {
            /*if (dup2(in, 0) == -1) {
                perror("dup2 failed");
                exit(1);
            }*/
            dup2 (in, 0);
            close (in);
        }
        if (out != 1) {
            dup2 (out, 1);
            close (out);
        }
        if (execvp(cmd->argv [0], (char * const *)cmd->argv) < 0) {
            perror("execvp failed");
            exit(1);
        }
    } else if (pid < 0) {
        perror("fork failed");
        exit(1);
    }
    return pid;
}
/* Helper function that forks pipes */
int fork_pipes (int n, struct command *cmd) {
    int i;
    int in, fd [2];
    for (i = 0; i < n - 1; ++i) {
        pipe (fd);
        spawn_proc (in, fd [1], cmd + i);
        close (fd [1]);
        in = fd [0];
    }
    dup2 (in, 0);
    /*return execvp (cmd [i].argv [0], (char * const *)cmd [i].argv);*/
    if (execvp (cmd [i].argv [0], (char * const *)cmd [i].argv) < 0) {
        perror("execvp failed");
        exit(1);
    } else {
        return execvp (cmd [i].argv [0], (char * const *)cmd [i].argv);
    }
}

int main (int argc, char ** argv) {
    int i;
    if (argc == 1) { /* There were no arguments */
        const char *printenv[] = { "printenv", 0};
        const char *sort[] = { "sort", 0 };
        const char *less[] = { "less", 0 };
        struct command cmd [] = { {printenv}, {sort}, {less} };
        return fork_pipes (3, cmd);
    }
    if (argc > 1) { /* I'd like an argument */

        if (strncmp(argv[1], "cd", 2) && strncmp(argv[1], "exit", 2)) {
            char *tmp;
            int len = 1;
            for( i=1; i<argc; i++)
            {
                len += strlen(argv[i]) + 2;
            }
            tmp = (char*) malloc(len);
            tmp[0] = '\0';
            int pos = 0;
            for( i=1; i<argc; i++)
            {
                pos += sprintf(tmp+pos, "%s%s", (i==1?"":"|"), argv[i]);
            }
            const char *printenv[] = { "printenv", 0};
            const char *grep[] = { "grep", "-E", tmp, NULL};
            const char *sort[] = { "sort", 0 };
            const char *less[] = { "less", 0 };
            struct command cmd [] = { {printenv}, {grep}, {sort}, {less} };
            return fork_pipes (4, cmd);
            free(tmp);
        } else if (! strncmp(argv[1], "cd", 2)) { /* change directory */
            printf("change directory to %s\n" , argv[2]);
            chdir(argv[2]);
        } else if (! strncmp(argv[1], "exit", 2)) { /* change directory */
            printf("exit\n");
            exit(0);
        }
    }
    exit(0);
}

person Niklas R.    schedule 12.04.2015    source источник
comment
Вам не нужен тест на execvp() (если он возвращается, он не пройден), но вам нужен отчет об ошибке и вызов выхода после него.   -  person Jonathan Leffler    schedule 12.04.2015
comment
Комментарий «cmd не является массивом» кажется фиктивным; внутри fork_pipes() это массив. Он не используется как массив внутри spawn_proc().   -  person Jonathan Leffler    schedule 12.04.2015
comment
Я думаю, что комментарий «Первая запись в cmd.argv является указателем на массив, где последняя запись равна NULL. execvp будет работать с этим (и только с этим) массивом, поэтому все остальные указатели на массивы будут проигнорированы». Я думаю, они упустили из виду, что вы создаете массив struct command.   -  person Jonathan Leffler    schedule 12.04.2015
comment
Я думаю, что комментарий «значение i уже находится за концом массива в cmd из-за предыдущего цикла, поэтому cmd[i].argv[0] неверно» неверен, потому что цикл for (i = 0; i < n - 1; i++), поэтому i равен n-1 после цикла, а массив cmd имеет элементы 0..n-1 по адресу.   -  person Jonathan Leffler    schedule 12.04.2015
comment
Однако значение in в первом вызове spawn_proc() действительно мусор. Вероятно, вы можете просто установить его на 0 (STDIN_FILENO) и все будет в порядке, но вам нужно это проверить. А вот комментарий по поводу второго аргумента к execvp() своеобразен — приведение должно отсутствовать, а в остальном код мне кажется нормальным. Я должен добавить, что я еще не запускал компилятор над чем-либо из этого, поэтому все, что я сказал до сих пор, должно быть исправлено компилятором. Но и анализ я делаю не случайно… Вы компилируете компилятором, установленным как минимум: gcc -Wall -Wextra -Werror (я использую больше опций!)?   -  person Jonathan Leffler    schedule 12.04.2015
comment
В fork_pipes() проверка if на execvp() и предложение else выглядят странно. Вам просто нужны вызовы execvp(), perror() и exit() — никаких условий.   -  person Jonathan Leffler    schedule 12.04.2015


Ответы (1)


Перенос комментариев в (часть) ответа.

  • Вам не нужен тест на execvp() (если он возвращается, он не пройден), но вам нужен отчет об ошибке и вызов выхода после него.

  • Комментарий «cmd не является массивом» кажется фиктивным; внутри fork_pipes() это массив. Он не используется как массив внутри spawn_proc().

  • Я думаю, что комментарий «Первая запись в cmd.argv является указателем на массив, где последняя запись равна NULL. execvp будет работать с этим (и только с этим) массивом, поэтому все остальные указатели на массивы будут игнорироваться, это тоже подделка. Я думаю, они упустили из виду, что вы создаете массив команд struct.

  • Я думаю, что комментарий «значение i уже находится за концом массива в cmd из-за предыдущего цикла, поэтому cmd[i].argv[0] неверно» неверен, потому что цикл for (i = 0; i < n - 1; i++), поэтому i равен n-1 после цикла, а массив cmd имеет элементы 0..n-1 по адресу.

  • Однако значение in в первом вызове spawn_proc() действительно мусор. Вероятно, вы можете просто установить его на 0 (STDIN_FILENO) и все будет в порядке, но вам нужно это проверить. А вот комментарий по поводу второго аргумента к execvp() своеобразен — приведение должно отсутствовать, а в остальном код мне кажется нормальным. Я должен добавить, что я еще не запускал компилятор над чем-либо из этого, поэтому все, что я сказал до сих пор, должно быть исправлено компилятором. Но и анализ я делаю не случайно… Вы компилируете компилятором, установленным минимум: gcc -Wall -Wextra -Werror (я использую больше опций!)?

  • В fork_pipes() проверка if на execvp() и предложение else выглядят странно. Вам просто нужны звонки на execvp(), perror() и exit().


Эти комментарии выше в основном точны. Вот некоторый измененный код, но изменения в основном косметические. Функция err_syserr() основана на том, что я использую для сообщения об ошибках. Это функция с переменным числом аргументов, которая также сообщает о системной ошибке. Это лучше, чем perror(), потому что (а) он может форматировать более полно и (б) он закрывается.

Я получал предупреждения о компиляции, например:

ft13.c: In function ‘spawn_proc’:
ft13.c:45:9: error: passing argument 2 of ‘execvp’ from incompatible pointer type [-Werror]
         execvp(cmd->argv[0], cmd->argv);
         ^
In file included from ft13.c:6:0:
/usr/include/unistd.h:440:6: note: expected ‘char * const*’ but argument is of type ‘const char **’
 int  execvp(const char *, char * const *);

Самый простой способ исправить это — поместить const в правильное место в struct command и удалить const из списков аргументов для различных команд.

Другие изменения скорее косметические, чем существенные (неинициализированный in был единственной серьезной ошибкой, которую нужно было исправить). Я использовал свой код сообщения об ошибках и проверил некоторые дополнительные системные вызовы (например, dup2()) и очистил execvp() и сообщения об ошибках. Я переместил тесты для exit и cd перед общим кодом, чтобы избежать повторения тестов. Кроме того, вы использовали strncmp(), а тест для exit рассматривал только ex, а ex — системная команда… Используйте strcmp(). Я использую strcmp(x, y) == 0 в условии; используемый реляционный оператор имитирует реляционную операцию, которую я тестирую (поэтому strcmp(x, y) >= 0 проверяет x больше или равно y и т. д.).

Современный POSIX не требует включения #include <sys/types.h>. Другие заголовки включают его по мере необходимости.

Источник: ft13.c

Скомпилировано в ft13.

#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

struct command
{
    char * const *argv;
};

static _Noreturn void err_syserr(char *fmt, ...)
{
    int errnum = errno;
    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    if (errnum != 0)
        fprintf(stderr, "(%d: %s)\n", errnum, strerror(errnum));
    exit(EXIT_FAILURE);
}

/* Helper function that spawns processes */
static int spawn_proc(int in, int out, struct command *cmd)
{
    pid_t pid;
    if ((pid = fork()) == 0)
    {
        if (in != 0)
        {
            if (dup2(in, 0) < 0)
                err_syserr("dup2() failed on stdin for %s: ", cmd->argv[0]);
            close(in);
        }
        if (out != 1)
        {
            if (dup2(out, 1) < 0)
                err_syserr("dup2() failed on stdout for %s: ", cmd->argv[0]);
            close(out);
        }
        fprintf(stderr, "%d: executing %s\n", (int)getpid(), cmd->argv[0]);
        execvp(cmd->argv[0], cmd->argv);
        err_syserr("failed to execute %s: ", cmd->argv[0]);
    }
    else if (pid < 0)
        err_syserr("fork failed: ");
    return pid;
}

/* Helper function that forks pipes */
static void fork_pipes(int n, struct command *cmd)
{
    int i;
    int in = 0;
    int fd[2];
    for (i = 0; i < n - 1; ++i)
    {
        pipe(fd);
        spawn_proc(in, fd[1], cmd + i);
        close(fd[1]);
        in = fd[0];
    }
    if (dup2(in, 0) < 0)
        err_syserr("dup2() failed on stdin for %s: ", cmd[i].argv[0]);
    fprintf(stderr, "%d: executing %s\n", (int)getpid(), cmd[i].argv[0]);
    execvp(cmd[i].argv[0], cmd[i].argv);
    err_syserr("failed to execute %s: ", cmd[i].argv[0]);
}

int main(int argc, char **argv)
{
    int i;
    if (argc == 1)   /* There were no arguments */
    {
        char *printenv[] = { "printenv", 0};
        char *sort[] = { "sort", 0 };
        char *less[] = { "less", 0 };
        struct command cmd[] = { {printenv}, {sort}, {less} };
        fork_pipes(3, cmd);
    }
    else
    {
        if (strcmp(argv[1], "cd") == 0)      /* change directory */
        {
            printf("change directory to %s\n", argv[2]);
            chdir(argv[2]);
        }
        else if (strcmp(argv[1], "exit") == 0)
        {
            printf("exit\n");
            exit(0);
        }
        else
        {
            char *tmp;
            int len = 1;
            for (i = 1; i < argc; i++)
            {
                len += strlen(argv[i]) + 2;
            }
            tmp = (char *) malloc(len);
            tmp[0] = '\0';
            int pos = 0;
            for (i = 1; i < argc; i++)
            {
                pos += sprintf(tmp + pos, "%s%s", (i == 1 ? "" : "|"), argv[i]);
            }
            char *printenv[] = { "printenv", 0};
            char *grep[] = { "grep", "-E", tmp, NULL};
            char *sort[] = { "sort", 0 };
            char *less[] = { "less", 0 };
            struct command cmd[] = { {printenv}, {grep}, {sort}, {less} };
            fork_pipes(4, cmd);
            free(tmp);
        }
    }
    return(0);
}

Пример запуска

Образец 1:

$ ./ft13 | cat
1733: executing less
1735: executing printenv
1736: executing sort
Apple_PubSub_Socket_Render=/private/tmp/com.apple.launchd.sl7NmyZPgI/Render
BASH_ENV=/Users/jleffler/.bashrc
CDPATH=:/Users/jleffler:/Users/jleffler/src:/Users/jleffler/src/perl:/Users/jleffler/src/sqltools:/Users/jleffler/lib:/Users/jleffler/doc:/Users/jleffler/work:/Users/jleffler/ids
CLICOLOR=1
…lots of environment omitted…
VISUAL=vim
XPC_FLAGS=0x0
XPC_SERVICE_NAME=0
_=./ft13
__CF_USER_TEXT_ENCODING=0x1F7:0x0:0x0
$

Образец 2:

$ ./ft13 PATH | cat
1739: executing printenv
1737: executing less
1740: executing grep
1741: executing sort
CDPATH=:/Users/jleffler:/Users/jleffler/src:/Users/jleffler/src/perl:/Users/jleffler/src/sqltools:/Users/jleffler/lib:/Users/jleffler/doc:/Users/jleffler/work:/Users/jleffler/ids
DYLD_LIBRARY_PATH=/usr/lib:/usr/informix/11.70.FC6/lib:/usr/informix/11.70.FC6/lib/esql:/usr/informix/11.70.FC6/lib/cli
GOPATH=/Users/jleffler/Software/go-1.2
LD_LIBRARY_PATH=/usr/lib:/usr/gnu/lib:/usr/gcc/v4.9.1/lib
MANPATH=/Users/jleffler/man:/Users/jleffler/share/man:/usr/local/mysql/man:/usr/gcc/v4.9.1/share/man:/Users/jleffler/perl/v5.20.1/man:/usr/local/man:/usr/local/share/man:/opt/local/man:/opt/local/share/man:/usr/share/man:/usr/gnu/man:/usr/gnu/share/man
PATH=/Users/jleffler/bin:/usr/informix/11.70.FC6/bin:.:/usr/local/mysql/bin:/usr/gcc/v4.9.1/bin:/Users/jleffler/perl/v5.20.1/bin:/usr/local/go/bin:/Users/jleffler/Software/go-1.2/bin:/usr/local/bin:/opt/local/bin:/usr/bin:/bin:/usr/gnu/bin:/usr/sbin:/sbin
$
person Jonathan Leffler    schedule 12.04.2015