Подтвердить что ты не робот

Внедрение оболочки в C и помощь в обработке перенаправления ввода/вывода

Раунд 2

После прочтения некоторых ответов мой пересмотренный код:

int pid = fork();

if (pid == -1) {
    perror("fork");
} else if (pid == 0) {   

    if (in) { //if '<' char was found in string inputted by user
        int fd0 = open(input, O_RDONLY, 0);
        dup2(fd0, STDIN_FILENO);
        close(fd0);
        in = 0;
    }

    if (out) { //if '>' was found in string inputted by user
        int fd1 = creat(output, 0644);
        dup2(fd1, STDOUT_FILENO);
        close(fd1);
        out = 0;
    }   

    execvp(res[0], res);
    perror("execvp");
    _exit(1);
} else {
    waitpid(pid, 0, 0);
    free(res);
}

Это работает, но, похоже, стандартный вывод не пересоединяется или что-то в этом роде. Вот выполнение:

SHELL$ cat > file
hello, world
this is a test
SHELL$ cat < file //no output
SHELL$ ls //no output

'<' и ' > ' работают, но после их выполнения нет выхода.


Раунд 1

Я работаю над относительно простой оболочкой в ​​C на некоторое время, но у меня возникли проблемы с реализацией ввода (<) и вывода ( > ) перенаправления. Помогите мне найти проблемы в следующем коде:

int fd;
int pid = fork();
int current_out;

if (in) { //if '<' char was found in string inputted by user
    fd = open(input, O_RDONLY, 0);
    dup2(fd, STDIN_FILENO);
    in = 0;
    current_out = dup(0);
}

if (out) { //if '>' was found in string inputted by user
    fd = creat(output, 0644);
    dup2(fd, STDOUT_FILENO);
    out = 0;
    current_out = dup(1);
}

if (pid == -1) {
    perror("fork");
} else if (pid == 0) {       
    execvp(res[0], res);
    perror("execvp");
    _exit(1);
} else {
    waitpid(pid, 0, 0);
    dup2(current_out, 1);
    free(res);
}

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

4b9b3361

Ответ 1

У вас есть слишком много файловых дескрипторов, открытых после перенаправления. Позвольте проанализировать два абзаца:

if (in) { //if '<' char was found in string inputted by user
    fd = open(input, O_RDONLY, 0);
    dup2(fd, STDIN_FILENO);
    in = 0;
    current_in = dup(0);  // Fix for symmetry with second paragraph
}

if (out) { //if '>' was found in string inputted by user
    fd = creat(output, 0644);
    dup2(fd, STDOUT_FILENO);
    out = 0;
    current_out = dup(1);
}

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

В первом абзаце вы открываете файл и записываете дескриптор файла (это может быть 3) в переменной fd. Затем вы дублируете файловый дескриптор над стандартным вводом (STDIN_FILENO). Однако обратите внимание, что файловый дескриптор 3 все еще открыт. Затем вы делаете dup(0) (который для согласованности должен быть STDIN_FILENO), получая еще один дескриптор файла, возможно, 4. Таким образом, у вас есть файловые дескрипторы 0, 3 и 4, указывающие на один и тот же файл (и, действительно, тот же открыть описание файла — отметить, что описание открытого файла отличается от дескриптора открытого файла). Если ваше намерение с current_in состояло в том, чтобы сохранить стандартный ввод оболочки (родительский), вы должны сделать это dup(), прежде чем вы выполните dup2(), который перезаписывает вывод. Однако вам лучше не изменять дескрипторы файла родительской оболочки; это меньше накладных расходов, чем повторное дублирование файловых дескрипторов.

Затем вы более или менее повторяете процесс во втором абзаце, сначала перезаписывая единственную запись дескриптора файла 3, которая открывается с вызовом fd = creat(...), но получая новый дескриптор, возможно, 5, а затем дублируя это по стандартным выводам. Затем вы выполните dup(1), получив другой файловый дескриптор, возможно, 6.

Итак, у вас есть stdin и stdout основной оболочки, перенаправленной на файлы (и никоим образом не восстанавливая их до исходных значений). Поэтому ваша первая проблема заключается в том, что вы выполняете перенаправление перед вами fork(); вы должны делать это после fork() — хотя, когда вы добираетесь до трубопроводов между процессами, вам нужно будет создавать трубы перед форкированием.

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

Итак, вам может понадобиться:

if ((pid = fork()) < 0)
    ...error...
else if (pid == 0)
{
    /* Be childish */
    if (in)
    {
        int fd0 = open(input, O_RDONLY);
        dup2(fd0, STDIN_FILENO);
        close(fd0);
    }

    if (out)
    {
        int fd1 = creat(output , 0644) ;
        dup2(fd1, STDOUT_FILENO);
        close(fd1);
    }
    ...now the child has stdin coming from the input file, 
    ...stdout going to the output file, and no extra files open.
    ...it is safe to execute the command to be executed.
    execve(cmd[0], cmd, env);   // Or your preferred alternative
    fprintf(stderr, "Failed to exec %s\n", cmd[0]);
    exit(1);
}
else
{
    /* Be parental */
    ...wait for child to die, etc...
}

Прежде чем вы это сделаете, вы должны убедиться, что вы уже сбросили стандартные каналы ввода-вывода оболочки, возможно, используя fflush(0), чтобы, если разветвленный ребенок записывает стандартную ошибку из-за проблемы, не является посторонним дублированным выходом.

Также обратите внимание, что различные вызовы open() должны быть проверены с ошибкой.

Ответ 2

У вас есть слишком много файловых дескрипторов, открытых после перенаправления. Код, который вам нужен, это.

    if (pid == 0)
{          /* for the child process:         */

    // function for redirection ( '<' , '>' )

    int fd0,fd1,i,in=0,out=0;
    char input[64],output[64];

    // finds where '<' or '>' occurs and make that argv[i] = NULL , to ensure that command wont't read that

    for(i=0;argv[i]!='\0';i++)
    {
        if(strcmp(argv[i],"<")==0)
        {        
            argv[i]=NULL;
            strcpy(input,argv[i+1]);
            in=2;           
        }               

        if(strcmp(argv[i],">")==0)
        {      
            argv[i]=NULL;
            strcpy(output,argv[i+1]);
            out=2;
        }         
    }

    //if '<' char was found in string inputted by user
    if(in)
    {   

        // fdo is file-descriptor
        int fd0;
        if ((fd0 = open(input, O_RDONLY, 0)) < 0) {
            perror("Couldn't open input file");
            exit(0);
        }           
        // dup2() copies content of fdo in input of preceeding file
        dup2(fd0, 0); // STDIN_FILENO here can be replaced by 0 

        close(fd0); // necessary
    }

    //if '>' char was found in string inputted by user 
    if (out)
    {

        int fd1 ;
        if ((fd1 = creat(output , 0644)) < 0) {
            perror("Couldn't open the output file");
            exit(0);
        }           

        dup2(fd1, STDOUT_FILENO); // 1 here can be replaced by STDOUT_FILENO
        close(fd1);
    }

    execvp(*argv, argv);
    perror("execvp");
    _exit(1);

    // another syntax
    /*      if (!(execvp(*argv, argv) >= 0)) {     // execute the command  
            printf("*** ERROR: exec failed\n");
            exit(1);
     */ 
}


    else if((pid) < 0)
    {     
        printf("fork() failed!\n");
        exit(1);
    }

    else {                                  /* for the parent:      */

        while (!(wait(&status) == pid)) ; // good coding to avoid race_conditions(errors) 
    }
}

Ответ 3

Вот что происходит. После вызова fork() выполняется два процесса, которые являются дубликатами исходного процесса. Разница заключается в возвращаемом значении fork(), которое хранится в pid.

Затем оба процесса (оболочка и ребенок) перенаправляют свои stdin и stdout в одни и те же файлы. Я думаю, вы пытались сохранить предыдущий fd в current_out, но, как указывает Сет Робертсон, в настоящее время это не работает, поскольку сохраняется неправильный дескриптор файла. Родитель также восстанавливает свою версию, но не stdin.

Вы можете исправить эту ошибку, но вы можете сделать лучше. На самом деле вам не нужно перенаправлять родительский вывод, а только дочерний. Поэтому сначала просто проверьте pid. Тогда также нет необходимости восстанавливать дескрипторы файлов.