简体   繁体   中英

execve and pipe issues - how to recover original pipe?

I have been making simple shell which performs pipe.

Here is some code for operating pipe syntax.

int fd[2];
int stdin_copy;
int stdout_copy;

int status;
char * msg;

if (pipe(fd) == -1) {
    perror("pipe");
    exit(1);
}
// fd[0] : process read from fd[0]
// fd[1] : process write to fd[1]

if (execok(pr_words) == 0) { /* is it executable? */
    status = fork(); /* yes; create a new process */

    if (status == -1) { /* verify fork succeeded */
        perror("fork");
        exit(1);
    } else if (status == 0) { /* in the child process... */
        stdout_copy = dup(1);

        close(1); // close standard output        
        dup(fd[1]);
        close(fd[0]);
        close(fd[1]); // close and fd[1] will be stdout 
        pr_words[l_nwds] = NULL; /* mark end of argument array */

        status = execve(path, pr_words, environ); /* try to execute it */

        perror("execve"); /* we only get here if */
        exit(0); /* execve failed... */
    }
    /*------------------------------------------------*/
    /* The parent process (the shell) continues here. */
    /*------------------------------------------------*/
    else if (status > 0) { // in the parent process....
        wait( & status); /* wait for process to end */

        if (execok(af_words) == 0) {
            if (pipe(fd2) == -1) {
                perror("pipe");
                exit(1);
            }

            status = fork();

            if (status == -1) {
                perror("fork");
                exit(1);
            } else if (status == 0) { // in the child process...
                stdin_copy = dup(0);
                close(0);
                dup(fd[0]);

                close(fd[1]);
                close(fd[0]);

                read(fd[0], readbuffer, sizeof(readbuffer));

                af_words[r_nwds] = NULL; /* mark end of argument array */
                status = execve(path, af_words, environ); /* try to execute it */

            } else if (status > 0) {
                wait( & status);

                msg = "over";
                write(2, msg, strlen(msg));
                close(fd[0]);
                close(fd[1]);
                dup2(stdin_copy, 0);
                dup2(stdout_copy, 1);
                close(stdin_copy);
                close(stdout_copy);
                printf("%s", "hi");
            }
        } else {
            /*----------------------------------------------------------*/
            /* Command cannot be executed. Display appropriate message. */
            /*----------------------------------------------------------*/
            msg = "*** ERROR: '";
            write(2, msg, strlen(msg));
            write(2, af_words[0], strlen(af_words[0]));
            msg = "' cannot be executed.\n";
            write(2, msg, strlen(msg));
        }

    }

} else {
    /*----------------------------------------------------------*/
    /* Command cannot be executed. Display appropriate message. */
    /*----------------------------------------------------------*/
    msg = "*** ERROR: '";
    write(2, msg, strlen(msg));
    write(2, pr_words[0], strlen(pr_words[0]));
    msg = "' cannot be executed.\n";
    write(2, msg, strlen(msg));
}

pr_words and af_words is two-dimensional pointer containing command, right-side and left-side of pipe. (ex. ls | cat -> pr_words = "ls\\0" , af_words = "cat\\0")

And, first I make child process using fork() and register fd[1] for standard output. (and also save stdin file descriptor before closing stdin) And after execute left side of command, make other child process for handling right side of command.

Similarly, I saved stdout file descriptor before close stdout and made fd[0] standard input. By using input from first outcome of execve function, I thought every outcome would be saved in fd[1]. (Because this was currently registered as std output).

And, finally, restore pipe input and output to standard output. (I don't want to use dup2 but I have no choice because of my lack of knowledge )

However, in execution of this code, after I enter the 'ls | cat', there is no output. Furthermore, I set every entry of terminal will print '#'. (which means that '# ls' or '# cat' ...) But, after enter above pipe command, that program even does not print '#'.

I guess input and output stream of this program are completely twisted after dealing with pipe command.

How can I fix it? I mean, I want save outcome of first execve into fd[1] and after using this fd[1] for performing second execve, make final outcome will be printed through stdout file description.

I see a few issues with your code at least:

First off, you shouldn't wait() on the first process before starting the second one. A pipe only has a few KB of buffer in it, after which your shell will hang if the first child process tries to continue to write there. You need to start both children before wait()ing for each of them. Just move the first wait(&status) call down next to the other one. You'll probably want to use waitpid or something later so you know which one finished first and which status goes to which, but you can address that once you get the basics working.

Secondly, all variables and file descriptor mappings in your program are copied when you fork(). Therefore, you don't need to save stdin or stdout in either child process, because none of the changes you make in the child processes will affect the parent. Furthermore, because you only initialize stdin_copy and stdout_copy in the child processes, the versions of those variables you use in the parent process after the second fork() are uninitialized. This is what's causing the parent shell's I/O to be messed up after executing this code. You don't actually need to do anything in the parent after forking the second time to maintain the original stdin and stdout there -- you never change them in that process before that point. You probably want to remove all of this from the post-fork parent code:

            close(fd[0]);
            close(fd[1]);
            dup2(stdin_copy, 0);
            dup2(stdout_copy, 1);
            close(stdin_copy);
            close(stdout_copy);

Thirdly, why are you reading from the pipe before calling execve() in the second child? That's just going to strip data out of the pipe that your exec'd child will never see. That's probably what's causing the pipe itself to appear not to work. You probably want to remove this:

read(fd[0], readbuffer, sizeof(readbuffer));    

Lastly, this line probably needs to go before the execok() call (and similarly for the other similar one):

pr_words[l_nwds] = NULL; /* mark end of argument array */

The skeleton of the code should look about like this, leaving off error handling and execok checks, and demonstrating the use of waitpid() if you want to know which status code is for which child:

int child_pid[2];
child_pid[0] = fork();
if (child_pid[0] == 0) {
    // first child, close stdout and replace with pipe, then exec
} else {
    child_pid[1] = fork();
    if (child_pid[1] == 0) {
        // second child, close stdin and replace with pipe, then exec
    } else {
        // parent, and now we have the pids of the children
        waitpid(child_pid[0], &status, 0); // wait for first child
        waitpid(child_pid[1], &status, 0); // wait for second child
        // *do not* mess with stdin/stdout, they are okay here
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM