How to prevent garbage reading from end of PIPE read in Linux
I wrote a little code using pipe and fork. The child process calls the child function, which writes to the pipe. The parent process calls the parent function, which reads from the pipe.
The problem arises when the first call to the program after fork () goes to the parent function. Here the end of the recording is closed. Now the problem is that the read call reads some garbage in buf, and nread has a value> 0. How can this be prevented.
Using Linux 2.6.32-30-generic and gcc 4.4.3. Below is the code ::
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#define MSGSIZE 16
void parent(int* p);
void child(int* p);
char* msg1 = "hello";
char* msg2 = "bye";
int main()
{
int pfd[2];
if(pipe(pfd) == -1)
{
printf("Unable to create pipe\n");
exit(1);
}
fcntl(pfd[0],F_SETFL,O_NDELAY);
if(fork() == 0)
child(pfd);
else
parent(pfd);
return 0;
}
void parent(int p[2])
{
int nread;
char buf[MSGSIZE];
buf[0] = '\0';
close(p[1]);
for(;;)
{
nread = read(p[0] , buf , MSGSIZE);
if(nread == 0)
{
printf("pipe Empty/n");
sleep(1);
}
else
{
if(strcmp(buf,msg2) == 0)
{
printf("End of conversation\n");
exit(0);
}
else
printf("MSG=%s\n" , buf);
}
}
}
void child(int p[2])
{
int count;
close(p[0]);
for(count = 0 ; count < 3 ; count++)
{
write(p[1],msg1 , MSGSIZE);
sleep(3);
}
write(p[1],msg2,MSGSIZE);
exit(0);
}
source to share
One problem is this:
char buf[MSGSIZE];
buf[0] = '\0';
it only sets the first character in buf
to the null terminator: the remaining characters in buf
are concatenated. read()
tries to read 16
bytes, which mean that characters in buf
will not be zero-terminated, but printf("%s", buf)
requires that it be buf
zero-terminated. Even if it buf
was initialized correctly, it would still be undersized due to its size 16
, but read()
reading it 16
, also leaving no room for a null terminator.
Possible fix:
char buf[MSGSIZE + 1] = ""; /* +1 added to store the null terminator and
all characters set to 0 (null terminator). */
Another problem is write()
(as Joachim Pileborg commented ):
write(p[1],msg1 , MSGSIZE);
write(p[1],msg2 , MSGSIZE);
msg1
and the bytes msg2
are not long 16
. Change to:
write(p[1],msg1 , strlen(msg1));
write(p[1],msg2 , strlen(msg2));
It also read()
returns -1
on failure, so not enough:
nread = read(p[0] , buf , MSGSIZE);
if(nread == 0)
{
...
}
Also check -1
:
else if(nread == -1)
{
fprintf(stderr, "read() failed: %s\n", strerror(errno));
}
else
{
...
}
EDIT:
See nos 's answer regarding locking / non-locking configuration issues.
source to share
Your real problem is this line:
fcntl(pfd[0],F_SETFL,O_NDELAY);
This ensures that the end of the read channel is not blocked. Thus, each call to read () returns the same amount of data as in the buffer, or returns -1 and sets errno to EWOULDBLOCK if there is no data at that particular time.
However, your code does not handle this case, it only checks if(nread == 0)
and outputs the buffer even if you haven't read anything. So remove this line.
If you don't want to send messages of a fixed size, or you want to prevent the end of reading from blocking, things get more complicated as you have to consider at least these cases:
- read () returns -1 and sets errno to EWOULDBLOCK (just try read () again).
- read () reads the first 4 bytes of your "message", and the next read returns the rest of the message.
- read () reads the first message and half of the subsequent message.
i.e. you need some form of frames / delimiters for your posts that you need to process, unless you just need to pass the feed on.
source to share
Read
does not nul-complete input.
To print a character buffer that is not nul-terminated, follow these steps:
printf("MSQ=%.*s\n", nread, buf);
If you want to nul-terminate the read buffer, you need to make 2 changes.
1. Increase the buffer size to MSGSIZE + 1:
char buf[MSGSIZE + 1];
2.nul-terminate buf after every read.
buf[nread > 0 ? nread : 0] = 0; // read returns -1 on error
source to share