views:

96

answers:

4

I am trying to write a c++ program that creates a child process, runs a command and pipes the output back to the input of a command the parent is running.

I have the parent execute the wait(NULL) or wait((void*)pid) command but it does not wait.

here is the code:

#include <string.h>
#include <fstream>
#include <iostream>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
using namespace std;

int main(int argc, char * argv[])
{    
        char* commands[strlen(argv[1])];
        char *command = NULL;
        command = strtok(argv[1],"|");
        int i = 0;
        while(command != NULL)
        {
                commands[i] = command;
                i++;
                command = strtok(NULL,"|");
        }

        int numberOfCommands = i;

        pid_t pid;
        int pfd[2];
        char* prgname = NULL;
        if(pipe(pfd) == -1)
        {
                perror("error on pipe call");
                return(1);
        }

        for(int j = 0;j<numberOfCommands;j++)
        {
                cout<<commands[j]<<endl;
        }

        pid = fork();
        if(pid == 0){//child process
                printf("Child: My PID = %d\n", getpid());
                printf("Child: Running...\n");
                close(pfd[0]); //close read end of pipe
                dup2(pfd[1],1);//connect the pipes
                close(pfd[1]);//close extra file descriptors
                prgname = commands[0];//first command
                cout<<"child starting command: "<<prgname<<endl;
                execlp(prgname, prgname, 0);//Load the program
                **printf("Child: Done sleeping, returning.\n");**
        }
        else
        {
                printf("Parent: My PID = %d\n", getpid());
                **wait((void*)pid); //also tried wait(NULL); same effect
                printf("Parent: Running...\n");**
                close(pfd[1]); //close the write end of the pipe
                dup2(pfd[0],0);//connect the pipes
                close(pfd[0]); //close extra file descriptor
                prgname = commands[1];//now run the second command
                cout<<"parent starting command: "<<prgname<<endl;
                execlp(prgname, prgname, 0);//Load the programm
        }
        cout<<"all done"<<endl;
        return 0;
}

Take not of the bolded lines. I would expect the parent process to wait at the wait() command and the child would print out "Child done sleeping..." and then finish and then the parent would print out "Parent: running..."

What am I doing wrong!

Thanks!

Update: full output to program is:

dmegs
more
Child: My PID = 30070
Child: Running...
Parent: My PID = 30066
Parent: Running...
parent starting command: more
child starting command: dmegs
Child: Done sleeping, returning.
all done
A: 

why are you doing

wait((void*)pid)

wait takes pointer to status

   #include <sys/types.h>
   #include <sys/wait.h>

   pid_t wait(int *status);

You are almost certanly passing non writable address. Test the wait retcode and I bet is is complaining big time;

Also mixing printf and couts is a way to confuse yourself, their buffering / flushing schemes can be different

pm100
I added in the include that I was missing and changed wait((void*)pid) to pid_t wait(int *status); I got the same exact output. Also changed all printf to cout. Any idea why it didn't work?
kralco626
did you check the return from wait? did you look at the status value?
pm100
OK. so now i really give up. I hard coded the commands to run into the program rather than using the array commands[]. and it works... with the wait commented out all-together! Now I'm confused. I hard coded in "dmesg" for the child and "more" for the parent and it worked. But why? howcome the parent does not have to wait for the child! AND why does it NOt wait when i ask it??? omg... so confused... – kralco626 0 secs ago
kralco626
it sounds like the child is dying instantly - this is why the wait doesn't wait
pm100
A: 
wait((void*)pid);

You shouldn't cast things to void* just to make the compiler stop complaining. :)

It looks like you probably want waitpid: http://linux.die.net/man/2/waitpid

Update:

You need to check whether the execlp call actually worked. Compare:

$ ./a.out "dmegs|more"
dmegs
more
Parent: My PID = 20806
Child: My PID = 20807
Child: Running...
Parent: Running...
parent starting command: more
child starting command: dmegs
Child: Done sleeping, returning.
all done

with:

$ ./a.out "dmesg|more"
dmesg
more
Parent: My PID = 20876
Child: My PID = 20877
Child: Running...
^C

In the first case, since execlp can't find "dmegs", the child process basically exits immediately. This unblocks the parent process and allows it to execute.

Paul Baumgart
haha, ya i know. Normally i would not do something like that. I'm just fusterated. I very good with many langauges c#,java,asp.net,html,javascript,t-sql but programming on a linux remote terminal in C/C++ is not my life long dream... I'll try your advice and report back
kralco626
tried int status; waitpid(-1, but I received the same results! any other ideas?
kralco626
Yeah, updated my answer.
Paul Baumgart
A: 

You would normally switch on the result to allow for the error case

pid = fork();
switch( pid ) {
 case -1: // parent fail
 case 0: // child success
 default: // parent success
}

Waiting for a specific child you would want to use

waitpid( pid, NULL, 0 );

or waiting for any child

pid_t child = waitpid( -1, NULL, 0 );
Greg Domjan
+2  A: 

I see four problems:

1) execlp() is failing: execlp() (or any of the exec family of functions) completely replaces the currently running process image if successful - it is not expected to return, unless something goes wrong. But you are seeing the "Child: Done sleeping, returning" message, so it cannot have succeeded. (In your example, I would guess that this is probably because dmegs should have been dmesg.)

2) printf() and cout output buffering means that there is no guarantee whatsoever that you are getting the output in the order in which it happens. If you want to debug this by printing output, you would be better off printing to stderr (e.g. with fprintf(stderr, ...)) which is (by default) unbuffered.

3) As noted by others, wait((void *)pid) is wrong. wait(NULL) or waitpid(pid, NULL, 0).

4) Whether this one is a problem or not is platform-dependent, but... the terminating null pointer argument to execlp() should be explicitly written as (char *)0 rather than just 0, to ensure that it is passed as a pointer rather than an integer. In general in C, 0 in a pointer context is by definition a null pointer, but when passing parameters to functions with variable numbers of arguments, the compiler does not have enough information to know that you are trying to use it in a pointer context, and so will pass it as an integer unless you explicitly cast it. This can get you into trouble on platforms where pointers and integers are not the same size.


So I reckon the wait() is working, the child is not actually running the command you want, and the output from parent and child is getting mixed up due to buffering.


Here is a slightly modified version of your code, which doesn't use any C++, cuts out the command handling stuff, and just pipes the output of sleep 5 to cat (which is rather pointless, as sleep doesn't generate any output anyway, but the delay is useful to see what's going on):

#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(void)
{    
        pid_t pid;
        int pfd[2];
        if(pipe(pfd) == -1)
        {
                perror("error on pipe call");
                return(1);
        }

        pid = fork();
        if(pid == 0){//child process
                fprintf(stderr, "Child: My PID = %d\n", getpid());
                fprintf(stderr, "Child: Running...\n");
                close(pfd[0]); //close read end of pipe
                dup2(pfd[1],1);//connect the pipes
                close(pfd[1]);//close extra file descriptors
                fprintf(stderr, "child starting command: sleep 5\n");
                execlp("sleep", "sleep", "5", (char *)0);//Load the program
                fprintf(stderr, "child: execlp failed\n");
        }
        else
        {
                fprintf(stderr,"Parent: My PID = %d\n", getpid());
                wait(NULL);
                fprintf(stderr,"Parent: Running...\n");
                close(pfd[1]); //close the write end of the pipe
                dup2(pfd[0],0);//connect the pipes
                close(pfd[0]); //close extra file descriptor
                fprintf(stderr,"parent starting command: cat\n");
                execlp("cat", "cat", (char *)0);//Load the programm
        }
        fprintf(stderr,"all done\n");
        return 0;
}

Output:

$ gcc -Wall -o wait wait.c
$ ./wait
Child: My PID = 27846
Child: Running...
child starting command: sleep 5
Parent: My PID = 27845

(there is a 5 second delay here)

Parent: Running...
parent starting command: cat
$
Matthew Slattery
NICE! It was that i was just spelling the name of the command wrong. Thanks for picking up on the fact that execlp() was failing. never would have realized that! Also you made me notice I was passing the program paramaters into execlp() incorrectly. I'll have to review the documentation. Can't believe I spent an hour on two characters being inverted. Thanks!
kralco626