Перенаправление потока и каналы при создании оболочки Linux - PullRequest
5 голосов
/ 26 сентября 2011

У меня есть задание создать оболочку Linux на C. В настоящее время я застрял в реализации перенаправлений и конвейеров. Код, который у меня есть, приведен ниже. Функция main () анализирует вводимые пользователем данные. Если команда встроена, то эта команда выполняется. В противном случае токенизированный ввод передается в execute () (я знаю, что мне, вероятно, следует вытащить встроенные команды в их собственную функцию).

Функция execute () выполняет цикл по массиву. Если он встречает <, > или |, он должен предпринять соответствующие действия. Первое, что я пытаюсь заставить работать правильно, это трубопровод. Я определенно делаю что-то не так, потому что я не могу заставить это работать даже для одной трубы. Например, пример ввода / вывода:

/home/ad/Documents> ls -l | grep sh
|: sh: No such file or directory
|

Моя идея состояла в том, чтобы получить каждое из направлений и работу конвейера только для одного случая, а затем, сделав функцию рекурсивной, я надеюсь, что можно использовать несколько перенаправлений / каналов в одной командной строке. Например, я мог бы сделать program1 < input1.txt > output1.txt или ls -l | grep sh > output2.txt.

Я надеялся, что кто-то может указать на мои ошибки при попытке конвейерной передачи и, возможно, предложить несколько советов о том, как подойти к случаю, когда пользователь вводит несколько перенаправлений / каналов.

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>

int MAX_PATH_LENGTH = 1024; //Maximum path length to display.
int BUF_LENGTH = 1024; // Length of buffer to store user input
char * delims = " \n"; // Delimiters for tokenizing user input.
const int PIPE_READ = 0;
const int PIPE_WRITE = 1;

void execute(char **argArray){

  char **pA = argArray;
  int i = 0;
  while(*pA != NULL) {
    if(strcmp(argArray[i],"<") == 0) { 
        printf("<\n"); 
    }
    else if(strcmp(argArray[i],">") == 0) { 
        printf(">\n"); 
    }
    else if(strcmp(argArray[i],"|") == 0) {
        int fds[2];
        pipe(fds);
        pid_t pid;
        if((pid = fork()) == 0) {
            dup2(fds[PIPE_WRITE], 1);
            close(fds[PIPE_READ]);
            close(fds[PIPE_WRITE]);
            char** argList;
            memcpy(argList, argArray, i);
            execvp(argArray[0], argArray);            
        }
        if((pid = fork()) == 0) {
            dup2(fds[PIPE_READ], 0);
            close(fds[PIPE_READ]);
            close(fds[PIPE_WRITE]);
            execvp(argArray[i+1], pA);            
        }
        close(fds[PIPE_READ]);
        close(fds[PIPE_WRITE]);
        wait(NULL);
        wait(NULL);
        printf("|\n");
    }
    else { 
        if(pid == 0){
            execvp(argArray[0], argArray);
            printf("Command not found.\n");
        }
        else
            wait(NULL);*/
    }
    *pA++;
    i++;
  }
}

int main () {

  char path[MAX_PATH_LENGTH];
  char buf[BUF_LENGTH];
  char* strArray[BUF_LENGTH];
  /**
   * "Welcome" message. When mash is executed, the current working directory
   * is displayed followed by >. For example, if user is in /usr/lib/, then
   * mash will display :
   *      /usr/lib/> 
   **/
  getcwd(path, MAX_PATH_LENGTH);
  printf("%s> ", path);
  fflush(stdout);

  /**
   * Loop infinitely while waiting for input from user.
   * Parse input and display "welcome" message again.
   **/ 
  while(1) {
    fgets(buf, BUF_LENGTH, stdin);
    char *tokenPtr = NULL;
    int i = 0;
    tokenPtr = strtok(buf, delims);

    if(strcmp(tokenPtr, "exit") == 0){

        exit(0);
    }
    else if(strcmp(tokenPtr, "cd") == 0){
        tokenPtr = strtok(NULL, delims);
        if(chdir(tokenPtr) != 0){
            printf("Path not found.\n");
        }
        getcwd(path, MAX_PATH_LENGTH);
    }
    else if(strcmp(tokenPtr, "pwd") == 0){
        printf("%s\n", path);
    }
    else {
        while(tokenPtr != NULL) {
            strArray[i++] = tokenPtr;
            tokenPtr = strtok(NULL, delims);
        }
        execute(strArray);
    }

    bzero(strArray, sizeof(strArray)); // clears array
    printf("%s> ", path);
    fflush(stdout);
  }

}

1 Ответ

4 голосов
/ 26 сентября 2011

Часть проблемы заключается в коде обработки труб - как вы и подозревали.

else if (strcmp(argArray[i], "|") == 0) {
    int fds[2];
    pipe(fds);
    pid_t pid;
    if ((pid = fork()) == 0) {
        dup2(fds[PIPE_WRITE], 1);
        close(fds[PIPE_READ]);
        close(fds[PIPE_WRITE]);
        char** argList;
        memcpy(argList, argArray, i);
        execvp(argArray[0], argArray);            
    }
    if ((pid = fork()) == 0) {
        dup2(fds[PIPE_READ], 0);
        close(fds[PIPE_READ]);
        close(fds[PIPE_WRITE]);
        execvp(argArray[i+1], pA);            
    }
    close(fds[PIPE_READ]);
    close(fds[PIPE_WRITE]);
    wait(NULL);
    wait(NULL);
    printf("|\n");
}

Первый execvp(), вероятно, был предназначен для использования argList, поскольку вы только что скопировали туда какой-то материал.Однако вы скопировали i байт, а не i символьные указатели, и вы не гарантировали, что канал будет зарезан и заменен нулевым указателем.

memcpy(argList, argArray, i * sizeof(char *));
argList[i] = 0;
execvp(argList[0], argList);

Обратите внимание, что это не подтвердило отсутствие переполнения буфера на argList; Обратите внимание, что для argList не выделено место;если вы используете его, вы должны выделить память перед выполнением memcpy().

В качестве альтернативы, а проще говоря, вы можете обойтись без копии.Поскольку вы находитесь в дочернем процессе, вы можете просто zap заменить argArray[i] нулевым указателем, не затрагивая ни родительский, ни другой дочерний процесс:

argArray[i] = 0;
execvp(argArray[0], argArray);

Вы также можетеобратите внимание, что при втором вызове execvp() используется переменная pA, которую невозможно увидеть;это почти наверняка неправильно инициализировано.Как умеренно хорошее практическое правило, вы должны написать:

execvp(array[n], &array[n]);

Вышеприведенные вызовы не соответствуют этой схеме, но если вы будете следовать ей, вы не ошибетесь.

У вас также должны быть базовые отчеты об ошибках и exit(1) (или, возможно, _exit(1) или _Exit(1)) после каждого execvp(), чтобы дочерний процесс не продолжался, если не удалось выполнить.Нет успешного возврата из execvp(), но execvp() наверняка может вернуться.

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

> output < input command -opts arg1 arg2

Это обычное использование, но на самом деле разрешено.

Одна хорошая вещь - вы убедились, что исходные файловые дескрипторы из pipe() закрыты во всех трех процессах (родительский и оба дочерних).Это распространенная ошибка, которую вы избегали делать;молодец.

...