Попытка построить оболочку с конвейерами и фоном - PullRequest
1 голос
/ 13 июля 2020

Привет, я пытаюсь воссоздать оболочку, и у меня две основные проблемы:

1. После выполнения одной единственной команды программа завершает работу

2. Не работает конвейер

Вот часть кода, которая касается конвейеров, перенаправлений ...

int pfd[2];
if (pipe(pfd) < 0) exit(-1);
for (int i = 0; i < cmd.pipes; i++) {
    pid_t pid;
    pid = fork();
    int fd;
    if (pid < 0) exit(-1);
    else if (pid == 0) {
        close(pfd[0]);
        dup2(pfd[1], STDOUT_FILENO);
        close(pfd[1]);
        if (cmd.filev[0] != NULL && i == 0) {
            fd = open(cmd.filev[0], O_RDONLY, 0);
            dup2(fd, STDIN_FILENO);
            close(fd);
        }
        if (cmd.filev[2] != NULL) {
            fd = creat(cmd.filev[2], 0644);
            dup2(fd, STDERR_FILENO);
            close(fd);
        }
        if (execvp(cmd.argv[i][0], cmd.argv[i]) < 0)
            levenshtein(cmd.argv[i][0], commands);
    } else if (pid > 0) {
        if (cmd.bg > 0) wait(NULL);
        close(pfd[1]);
        dup2(pfd[0], STDIN_FILENO);
        close(pfd[0]);
        if (cmd.filev[1] != NULL && i == (cmd.pipes - 1)) {
            fd = creat(cmd.filev[1], 0644);
            dup2(fd, STDOUT_FILENO);
            close(fd);
        }
        if (cmd.filev[2] != NULL) {
            fd = creat(cmd.filev[2], 0644);
            dup2(fd, STDERR_FILENO);
            close(fd);
        }
        if (execvp(cmd.argv[i][0], cmd.argv[i]) < 0)
            levenshtein(cmd.argv[i][0], commands);
    }
}

PD: Levenshtein это функция для работы, когда пользователь допустил ошибку

1 Ответ

1 голос
/ 14 июля 2020

Это требует небольшого рефакторинга ...

Если есть N этапов конвейера, нам потребуется N-1 отдельных вызовов pipe. Код имеет только один.

Каждая стадия конвейера может иметь свою / частную stderr переадресацию (например):

cmd0 | cmd1 2>cmd1_stderr | cmd2 2>cmd2_stderr | cmd3

Но код предполагает все stderr будет таким же.

И предполагается, что имеет для открытия stderr [вообще]. Ребенок должен открывать stderr, только если у нас есть что-то вроде выше. В противном случае каждый дочерний процесс должен наследовать родительский stderrничего ].

Процесс родительский выполняет то, что только дочерний следует сделать (например): изменить stdin/stdout/stderr и выполнить команду. В основном это должно просто облегчить конвейер.

Родитель делает wait в создании / fork l oop [для каждого дочернего элемента]. Это приводит к блокировке родителя на каждом шаге.

Родитель должен делать только wait за секунду l oop и ждать всех потомков сразу.

Поскольку ваше определение struct для cmd было не опубликовано, мне пришлось немного догадаться о намерениях. Но я думаю, вам нужно иметь массив структур, по одному для каждой команды, вместо того, чтобы помещать все аргументы для всех команд в одну структуру.

Я создал две версии кода. Один с аннотациями к ошибкам. Второй, очищенный и реструктурированный. Оба они не тестировались, но должны дать вам некоторые идеи.

Вот аннотированная версия:

// NOTE/BUG: each command in the pipeline can have its own/private
// stderr diversion
#if 0
struct cmd {
    int pipes;
    char *filev[3];
    char **argv[MAXCMD][MAXARG];
};
#else
struct cmd {
    char *stderr;
    char *argv[100];
};
#endif

// NOTE/BUG: we need separate filev [and argv] for each pipeline stage
// so we need an _array_ of structs
int cmdcnt;
struct cmd cmdlist[30];

void
pipeline(void)
{
    int pfd[2];

// NOTE/BUG: this only creates a single pipe -- we need N-1 pipes
#if 0
    if (pipe(pfd) < 0)
        exit(-1);
#endif

// NOTE/BUG: if child does _not_ have a private stderr it should just
// use the parent's stderr _unchanged_

    for (int i = 0; i < cmdcnt; i++) {
        pid_t pid;

// NOTE/BUG: here is the correct place to create the pipe
        pid = fork();
        int fd;

        if (pid < 0)
            exit(-1);

        char **argv = cmd->argv;
        char **filev = cmd->filev;

        // child process
        if (pid == 0) {
            close(pfd[0]);
            dup2(pfd[1], STDOUT_FILENO);
            close(pfd[1]);

// NOTE/BUG: this does _not_ connect the input of cmd[N] to cmd[N-1]
#if 0
            if (filev[0] != NULL && i == 0) {
                fd = open(filev[0], O_RDONLY, 0);
                dup2(fd, STDIN_FILENO);
                close(fd);
            }
#endif

            if (filev[2] != NULL) {
                fd = creat(filev[2], 0644);
                dup2(fd, STDERR_FILENO);
                close(fd);
            }

            if (execvp(cmd->argv[i][0], cmd->argv[i]) < 0)
                levenshtein(cmd->argv[i][0], commands);
        }

        // parent process
        if (pid > 0) {
// NOTE/BUG: parent should _not_ wait in the middle of the creation
// loop
            if (cmd->bg > 0)
                wait(NULL);

// NOTE/BUG: _parent_ should _not_ change its stdin/stderr/stdout

            close(pfd[1]);
            dup2(pfd[0], STDIN_FILENO);
            close(pfd[0]);

            if (filev[1] != NULL && i == (cmd->pipes - 1)) {
                fd = creat(filev[1], 0644);
                dup2(fd, STDOUT_FILENO);
                close(fd);
            }

            if (filev[2] != NULL) {
                fd = creat(filev[2], 0644);
                dup2(fd, STDERR_FILENO);
                close(fd);
            }

// NOTE/BUG: _parent_ should _not_ execute the command
            if (execvp(cmd->argv[i][0], cmd->argv[i]) < 0)
                levenshtein(cmd->argv[i][0], commands);
        }
    }
}

Вот обновленная версия:

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

struct cmd {
    char *stderr;
    char *argv[100];
};

char *filev[3];

#define CLOSEME(_fd) \
    do { \
        if (_fd < 0) \
            break; \
        close(_fd); \
        _fd = -1; \
    } while (0)

int run_in_background;
int cmdcnt;
struct cmd cmdlist[30];

int
pipeline(void)
{
    int pfd[2] = { -1, -1 };
    struct cmd *cmd;
    char *file;
    int oldfd;
    pid_t pid;
    pid_t lastpid = -1;

    // open the common stderr
    int errfd = -1;
    if (filev[2] != NULL)
        errfd = open(filev[2],O_APPEND | O_CREAT);

    // start all commands
    for (int i = 0; i < cmdcnt; i++) {
        int iam_first = (i == 0);
        int iam_last = (i == (cmdcnt - 1));

        cmd = &cmdlist[i];

        // get previous stage pipe descriptor
        oldfd = pfd[0];

        // create the pipe to the next stage
        if (! iam_last) {
            if (pipe(pfd) < 0)
                exit(1);
        }

        pid = fork();
        lastpid = pid;

        int fd;

        if (pid < 0)
            exit(-1);

        char **argv = cmd->argv;

        // parent process
        if (pid > 0) {
            CLOSEME(pfd[1]);
            continue;
        }

        // child process ...

        // open stdin for _first_ command
        fd = -1;
        if (iam_first) {
            file = filev[0];
            if (file != NULL) {
                fd = open(file, O_RDONLY, 0);
            }
        }

        // connect stdin to previous stage pipe
        else {
            fd = oldfd;
            oldfd = -1;
        }

        // connect stdin to correct source
        if (fd >= 0) {
            dup2(fd, STDIN_FILENO);
            close(fd);
        }
        CLOSEME(oldfd);

        // connect to stderr
        file = cmd->stderr;
        if (file != NULL) {
            fd = creat(file, 0644);
            dup2(fd, STDERR_FILENO);
            close(fd);
        }
        else {
            if (errfd >= 0)
                dup2(errfd, STDERR_FILENO);
        }
        CLOSEME(errfd);

        // connect stdout
        // NOTE: does _not_ handle ">> outf" [only does "> outf"]
        fd = -1;
        if (iam_last) {
            file = filev[1];
            if (file != NULL) {
                fd = open(file, O_WRONLY | O_CREAT, 0644);
                dup2(fd, STDOUT_FILENO);
                close(fd);
            }
        }

        // execute the command
        execvp(argv[0], argv);
        exit(9);
    }

    CLOSEME(errfd);

    int status;
    int last_status = 0;

    // parent waits for all pipeline stages to complete
    if (! run_in_background) {
        while (1) {
            pid = wait(&status);
            if (pid <= 0)
                break;
            if (pid == lastpid) {
                last_status = status;
                break;
            }
        }
    }

    return last_status;
}
...