user1299656
user1299656

Reputation: 650

dup2, execv, pipes, forking, and code running in the wrong order

So what this is supposed to do is fork, have the child process get the text of a file, and then have the parent process modify that text and write it to a new file. I've got assorted weirdness coming off of this. The whole code is roughly this.

#include <iostream>
#include <termios.h>
#include <cstdio>
#include <cstdlib>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>

using namespace std;

int parentPID;

int main(int argc, char* argv[]){

    parentPID = getpid();
    int pipey[2];

    int worked = pipe(pipey);
    if( worked == - 1){
        cout << "Oops.  Didn't make a pipe.";
    }

    //cout << "About to fork!!!";

    fork();

    if(getpid() != parentPID){//Only run in the child process
        char* argvec1[3] = {"cat", "colorfile.txt", (char*)0};
        dup2(pipey[1], 1);
        execv("/bin/cat", argvec1);
    }
    else{//Only run in the parent process.
        int someInt;
        cout << "In the parent process";
        pid_t status = wait(&someInt);
        dup2(pipey[0], 0);

        creat("newfile.txt", 0777);
        chmod("newfile.txt", 0777);
        int targetFile = open("newfile.txt", O_WRONLY);

        if(targetFile == -1){
            cout << "\nOops, couldn't open targetFile, ";
            perror("because ");
        }
        else{
            cout << "\nOpened target file.";
        }

        dup2(targetFile, 1);

        //char* argvec2[] = {"sed", "-e", "s/color/colour/g", (char*)0};
        //execv("/bin/sed", argvec2);
        cout << "something went terribly wrong";
    }
}

Of particular trouble are three things, the first, this code fragment...

    creat("newfile.txt", 0777);
    chmod("newfile.txt", 0777);
    int targetFile = open("newfile.txt", O_WRONLY);

    if(targetFile == -1){
        cout << "\nOops, couldn't open targetFile, ";
        perror("because ");
    }
    else{
        cout << "\nOpened target file.";
    }

    dup2(targetFile, 1);

...does not write "Opened target file" to the standard output. Instead, it puts it in newfile.txt, so dup2 is changing the output on output commands that appear before it?... if I comment out the dup2, at the end there, it doesn't happen, it's definitely that specific dup2 call making it happen.

second, this code fragment...

    creat("newfile.txt", 0777);
    chmod("newfile.txt", 0777);
    int targetFile = open("newfile.txt", O_WRONLY);

    if(targetFile == -1){
        cout << "\nOops, couldn't open targetFile, ";
        perror("because ");
    }
    else{
        cout << "\nOpened target file.";
    }

    //dup2(targetFile, 1);

    char* argvec2[] = {"sed", "-e", "s/color/colour/g", (char*)0};
    execv("/bin/sed", argvec2);
    cout << "something went terribly wrong";

...does not output any kind of success/failure regarding opening the file at all. It DOES print out the contents of the original file, appropriately modified, but then does not terminate. It just kind of sits around forever until I use ctrl-C to kill the current process. The final cout is not displayed.

Finally, this...

    creat("newfile.txt", 0777);
    chmod("newfile.txt", 0777);
    int targetFile = open("newfile.txt", O_WRONLY);

    if(targetFile == -1){
        cout << "\nOops, couldn't open targetFile, ";
        perror("because ");
    }
    else{
        cout << "\nOpened target file.";
    }

    dup2(targetFile, 1);

    char* argvec2[] = {"sed", "-e", "s/color/colour/g", (char*)0};
    execv("/bin/sed", argvec2);
    cout << "something went terribly wrong";

...doesn't get me any output, either to the standard output or to newfile.txt.

It feels like some of these system calls are just executing in whatever order they feel like, semi-independently of the order I've written them, making it pretty well impossible to do anything with them.

Upvotes: 0

Views: 641

Answers (1)

sjnarv
sjnarv

Reputation: 2374

Using an input file named 'colorfile.txt', like:

color
color color
color color color
color color color color

I updated your code to do its dup2-ing and debug messages differently. The most important change (as I alluded to in my comment to your question), is to close the write side of the pipe in the parent process, to avoid that process hanging. Also important (for debugging purposes, and dealing with the confusion about order of events) is the use of 'endl' to flush output. Otherwise, your dup2 calls occur while buffered data is still held, and can change the destination of that data, apparently after it was written, since writes occur when the underlying libraries flush output, say, at process exit.

Note that for pipes I use the terms "write side" and "read side" to refer to indexes 1 and 0, respectively, of that small array of file descriptors created by pipe(2): the sides that the user of the pipe writes to, and reads from.

These updates are aimed at g++ (GCC) 4.8.2 on a Linux 3.10.17 kernel. Presumably others' includes, etc., will vary.

#include <iostream>
#include <cstdio>
#include <fcntl.h>
#include <unistd.h>

using namespace std;

int main() {

    int pipey[2];

    if (pipe(pipey) == -1) {
        perror("pipe failed");
        return 1;
    }

    cout << "About to fork!!!" << endl;

    pid_t pid = fork();

    if (pid == -1) {
        perror("fork failed");
        return 1;
    }

    if (pid == 0) { // child process

        const char *av[] = { "cat", "colorfile.txt", 0 };

        cout << "In the child process" << endl;

        cout << "colorful child message before dup2" << endl;

        dup2(pipey[1], 1);
        close(pipey[1]);

        cout << "colorful child message after dup2" << endl;

        close(pipey[0]);

        execvp(av[0], (char * const *) av);

        perror("failed to exec cat");
        return 1;

    } else { // parent process.

        const char *av[] = { "sed", "-e", "s/color/colour/g", 0 };

        cout << "In the parent process" << endl;

        int targetFd = open("newfile.txt", O_CREAT|O_TRUNC|O_WRONLY, 0644);

        if (targetFd == -1){
            perror("failed to open newfile.txt");
            return 1;
        } else {
            cout << "Opened target file." << endl;
        }

        cout << "colorful parent message before dup2s" << endl;

        dup2(pipey[0], 0);
        close(pipey[0]);

        dup2(targetFd, 1);
        close(targetFd);

        cout << "colorful parent message after dup2s" << endl;

        // The soon-to-be exec'd cat process will have the read side of
        // the pipe duped to its stdin, and the child process created
        // above *will* write data that cat will see, but unless the
        // write side is closed by *all* its writers prior to exec (and
        // this parent process is one of the writers), cat will see no
        // end-of-file on its stdin.
        //
        // Note how easily this deadlock can be created, within a single
        // process.

        close(pipey[1]);

        execvp(av[0], (char * const *) av);

        perror("failed to exec sed");
        return 1;
    }
}

When I run this, I see:

About to fork!!!
In the parent process
In the child process
colorful child message before dup2
Opened target file.
colorful parent message before dup2s

And the content of the output file newfile.txt is:

colorful parent message after dup2s
colourful child message after dup2
colour
colour colour
colour colour colour
colour colour colour colour

If you understand why one message is colorful and the other colourful, you're catching on.

Upvotes: 0

Related Questions