2

I'm not going to lie. This is a homework question. However, as far as I'm concerned, the points are gone baby gone. Right now, I'm just looking for an answer, because I -think- I might be insane.

The goal of this program is to execute the command ps -A | grep (inputstring) | wc -l in a way similar to how the shell does it. So, I spawn the processes, and have them wait on each other. The newest process, the great-grandchild, execlp("ps","ps","-A",NULL) which replaces itself with the ps -A process. Before it execlp, I make sure its standard output is going to the pipe output. The next process in line is wait()ing, and already has itself set up so that the input pipe goes to standard in, and standard out goes to the output pipe, and it will execute grep, and so on.

I'm almost positive I have it set up correctly. And yet... the program does. Not. Work.

#include <stdlib.h>
#include <iostream>
#include <string>

#define MAXLINE 1500
#define READ 0
#define WRITE 1

using namespace std;

int main( int argc, char** argv ) {
//* start of input block
if ( argc != 2 ) {
    cout << "Usage: ./a.out arg1" << endl;
    return 0;
}
string in = argv[1];
// end of input block */
int pipeA[2], pipeB[2], pid, stat;

// get our first set of pipes
if ( pipe(pipeA) < 0 ) {
    cerr << "Pipe error.\n";
    exit(-1);
}
if ( pipe(pipeB) < 0 ) {
    cerr << "Pipe error.\n";
    exit(-1);
}

// make the first fork
if ( (pid = fork() ) < 0 ) { cerr << "Fork error.\n"; exit(-1); }

if ( pid > 0 ) {    // parent case
    wait(&stat);
} else {            // child case
    if ( (pid = fork()) < 0 ) { cerr << "Fork Error\n"; exit(-1); }
    if ( pid > 0 ) {    // child
        wait(&stat);
        dup2(pipeA[READ],READ);
        execlp("wc","wc","-l",NULL);
    } else {    // grand-child
        if ( (pid = fork()) < 0 ) { cerr << "Fork Error\n"; exit(-1); }
        if ( pid > 0 ) {    // still grand-child
            wait(&stat);
            dup2(pipeB[READ],READ);  
            dup2(pipeA[WRITE],WRITE); 
            close(pipeB[READ]);
            execlp("grep","grep",in.c_str(),NULL);
        } else {    // great grand-child
            dup2(pipeB[WRITE],WRITE); // t now goes to pipeB[1]
            close(READ);
            close(pipeB[READ]);
            execlp("ps", "ps", "-A", NULL);
        }
    }
}
return 0;
}

EDIT: Changed to the two-pipe variant of my code.

Gearov
  • 87
  • 6
  • How doesn't it work? Segmentation fault? Error messages? Wrong output? No output or unfiltered output of the `ps` or something else? – sth Oct 14 '13 at 08:28
  • 2
    You should not make your child processes wait for anything. – n. m. could be an AI Oct 14 '13 at 08:37
  • 1
    Furthermore, the parent process should fork both children (there should be no grandchildren). – n. m. could be an AI Oct 14 '13 at 08:42
  • Oops, sorry, should've specified. It's not piping, basically. I can see what's going on using another shell, and all four processes spawn correctly, and if I don't try to pipe I can even get them to perform their commands correctly. But put the piping code in, and they seem to not want to talk to each other. n.m. - actually, children are waiting on their children. Parent makes child makes grandchild make great-grandchild, each prior generation waiting on its next generation. That part, I'm fairly certain I got right. – Gearov Oct 14 '13 at 08:46
  • Further yet, you have too few calls to `pipe`. You have two (count 'em) `|` symbols. Each one is a pipe. Act accordingly. ("both children" in the prev comment should read "all children"). – n. m. could be an AI Oct 14 '13 at 08:54

2 Answers2

3

I'm virtually certain this is what you're trying to do. Apologies in advance for the sloppy coding. its somewhat late here and I really should be sleeping right now:

#include <iostream>
#include <string>
#include <cstdlib>
#include <cstdio>
#include <unistd.h>

#define READ 0
#define WRITE 1

// ps -A | grep argv[1] | wc -l

int main( int argc, char** argv )
{
    // start of input block
    if ( argc != 2 )
    {
        std::cout << "Usage: ./a.out arg1" << std::endl;
        return 0;
    }
    
    // make local copy of argument
    std::string in = argv[1];
    int fd1[2], fd2[2], pid;
    
    // allocate two pipe sets
    if (pipe(fd1) < 0 || pipe(fd2) < 0)
    {
        perror("Failed to create pipe.");
        return EXIT_FAILURE;
    }
    
    // launch first child process.
    if ((pid = fork()) < 0)
    {
        perror("Failed to fork child(1)");
        return EXIT_FAILURE;
    }
    
    if (pid == 0)
    {
        // wc -l process. 
        //  stdin  = fd2(read)
        close(fd1[READ]);
        close(fd1[WRITE]);
        close(fd2[WRITE]);
        dup2(fd2[READ],STDIN_FILENO);
        execlp("wc","wc","-l",NULL);
    }
    
    // fork again. this time for grep
    if ((pid = fork()) < 0)
    {
        perror("Failed to fork child(2)");
        return EXIT_FAILURE;
    }
    
    if (pid == 0)
    {
        // grep argv[1] process.
        //  stdin  = fd1(read)
        //  stdout = fd2(write)            
        close(fd1[WRITE]);
        close(fd2[READ]);
        dup2(fd2[WRITE], STDOUT_FILENO);
        dup2(fd1[READ], STDIN_FILENO);
        execlp("grep", "grep", in.c_str(), NULL);
    }
    
    //  fork once more. this time for ps -A
    if ((pid = fork()) < 0)
    {
        perror("Failed to fork child(3)");
        return EXIT_FAILURE;
    }
    
    if (pid == 0)
    {
        // ps -A process.
        //  stdout = fd1(write)
        close(fd2[WRITE]);
        close(fd2[READ]);
        close(fd1[READ]);
        dup2(fd1[WRITE], STDOUT_FILENO);
        execlp("ps", "ps", "-A", NULL);
    }
    
    int stat=0;
    wait(&stat);

    return EXIT_SUCCESS;
}

On my system, ps -A reports 141 lines, of those 41 have the word System somewhere within, verified by simply running ps -A | grep System | wc -l. The above code generates precisely the same output.

WhozCraig
  • 65,258
  • 11
  • 75
  • 141
  • Your code is working on my system as well! I'm comparing yours to my own two-pipe variant to see if I can figure out what the major difference is and why yours works but mine doesn't. Looks like it may be the wait() statements, the nesting, or that I wasn't closing pipes that weren't in use. Either way, immensely grateful; I should be able to figure this out within an hour and finally my brain might let me sleep. – Gearov Oct 14 '13 at 10:19
  • This doesn't need more than one level of nesting (parent-child only). I've tried to ensure the comments in the code reflect why each pipe is being used, and by inference, why the others for that fork are *not* needed and should therefore be closed. Though technically functional, this really should wait until the `wc` process pid is finished, but I leave that to you. – WhozCraig Oct 15 '13 at 02:51
0

I'm not sure but maybe calling dup2 before waiting on the child would fix the pipe problem.

The reason I'm not sure is that it normally stdin and stdout are bufferized so I suppose that even if you hook your pipe up with them after the child has finish running you should get the same results but maybe (if someone knows the answer to this please correct me) the buffers for stdin and stdout get wiped with the end of the child process.

Also, could you update the code in your question to contain the modified code with two sets of pipes ?

sokkyoku
  • 2,161
  • 1
  • 20
  • 22
  • Updated with the two-pipe system in place. I'm going to try your suggestion as well on the next iteration. – Gearov Oct 14 '13 at 10:20