1

I'm writing a program that consists of 2 processes. The parent reads data through stdin and then sends it to the child through a pipe. I'm using fgets() and fputs() to write and read the data.

The issue is that in certain situations the fgets() call of the child process seems to block. These are:

  1. When a text file is sent to stdin, everything works fine until the very last line of the file. The fgets of the child never executes and the process hangs.
  2. When I send /dev/urandom to the program the same situation occurs at a seemingly random moment.

I suspect the issue has something to do with EOF or /0 at the ends of these strings, but i can't quite pinpoint the issue. Any help would be greatly appreciated.

I'm using the message queues as a means of synchronisation. I'm also aware there is no cleaning up in this code. Here it is:

#define KEY5 7626

struct pidbuf
{
    long mtype;
    int mtext;
} msgpid;

int main(){
    sleep(5);
    int mqpid;
    FILE * strumien;
    int fd[2];
    if (pipe(fd)== -1){
        perror("Create pipe error");
        exit(1);}

    int buf_len = 128;

    msgpid.mtext=0;
    int size1=sizeof(struct pidbuf)-sizeof(long);

    if((mqpid = msgget(KEY5, 0666 | IPC_CREAT))==-1){
        return(-1);
    }   
    char data[buf_len];

    if(fork()!=0){
        close(fd[0]);
        strumien=fdopen(fd[1],"w");
        while (fgets(data,buf_len,stdin) != NULL ) {
            printf("1:\n%s\n",data);
            fputs(data,strumien);
            fflush(strumien);
            msgpid.mtype=2;
            if(msgsnd(mqpid,&msgpid,size1,0)==-1){perror("msgsnd failed: 1");}

            msgrcv(mqpid,&msgpid,size1, 1, 0);
        }
        if(feof(stdin)!=0) printf("\nEnd of file\n");
    }
    else{   
        close(fd[1]);
        strumien=fdopen(fd[0],"r");
        msgrcv(mqpid,&msgpid,size1, 2, 0);
        while(fgets(data,buf_len,strumien)!=NULL){
            printf("2:\n%s\n\n",data);

            msgpid.mtype=1;
            if(msgsnd(mqpid,&msgpid,size1,0)==-1){perror("msgsnd failed: 1");}
            msgrcv(mqpid,&msgpid,size1, 2, 0); 
        }
    }
    return 0;
}
Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
tomullus
  • 11
  • 2
  • Here it is *again*: `if(feof(stdin)!=0) printf("\nEnd of file\n");` http://stackoverflow.com/questions/5431941/why-is-while-feof-file-always-wrong `feof()` does *not* mean end-of -file. It tells you you attempted to read beyond the end of the file. Although in this case it seems benign as you used the return value from `fgets()` to end the read. – Weather Vane Sep 26 '15 at 19:01
  • 1
    @WeatherVane: This is actually a (rare) correct use of `feof` -- it is being used after another stdio function has already returned an EOF-or-error result to determine if that is actually EOF, or some other error. – Chris Dodd Sep 26 '15 at 22:28
  • @ChrisDodd are you sure? After reaching end-of-file with `fgets() == NULL`, `feof` won't be true, because the code has not read beyond end of file. Why is this different from any other mis-usage? – Weather Vane Sep 26 '15 at 22:32
  • @ChrisDodd second thoughts, yes. The `fgets` loop *did* attempt to read beyond end-of-file, returning `NULL`, so `feof` will be non-0. – Weather Vane Sep 26 '15 at 22:41
  • @WeatherVane `fgets()` returns `NULL` for more than 1 reason. Another reason would be a read error in which case "`fgets` loop did attempt to read beyond end-of-file" may be false. `if (feof()` helps determine which. A 3rd pathological reason is that `buf_len <= 0`. – chux - Reinstate Monica Sep 27 '15 at 12:16
  • Suggest more detail explaining "child process _seems_ to block". – chux - Reinstate Monica Sep 27 '15 at 12:21
  • to help you with this runtime problem, we need code that cleanly compiles and shows the problem. Are we expected to guess as to which header files the code is using? – user3629249 Sep 27 '15 at 15:02
  • the system function `fork()` has three kinds of return values: >0 means this is parent, =0 means this is child, <0 means error. When calling `fork()` always check for all three conditions, not just parent and child – user3629249 Sep 27 '15 at 15:09

1 Answers1

1

Not too suprising -- the parent process sends one message of type 2 for each line of the file read, and waits for a message of type 1 after writing each line. The child on the other hand is different -- while it sends a type 1 and waits for a type 2 after each line, it ALSO waits for a type 2 before reading the very first line.

So the parent will send N type 2 messages while the child will wait for N+1 type 2 messages for an N line file. Which means that after the parent is done and has exited, the child will be blocked waiting for that last message that will never come.

The obvious fix is to have the parent send an extra message -- probably after the loop, depending on exactly what you are trying to achieve.

For /dev/urandom (or any binary input), you have an additional problem when the input contains a NUL byte. Then, the fgets in the parent will happily read the NUL and continue past it to a newline (or a buffer full), but the folloing fputs will only write up to (and not including) the NUL. So some data will be lost and the child will keep reading looking for a newline, leaving the child blocked in fgets and the parent blocked in msgrcv before sending the next line.

There's no easy way of dealing with this problem, as fgets/fputs are designed to work with text rather than binary data. You could instead use fread or getline and fwrite, which will give you explicit lengths for the amount of data read/written, so you can know when it has read a NUL that is not the end of that data.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • Thanks for the help. I decided to change the functions used to fwrite/fread. I also made a loop to replace nulls inside the string read from stdin with '/0' . Seems to be the best of both words. I get to read text line by line and it works with /dev/urandom. – tomullus Sep 28 '15 at 09:43