There are few bugs in your code, here are the my observation.
Case 1 :- In your code you are calling fork() two times, pipe write end p[1] contains some data 1st child output in first fork() rc1 process, but your code tried to read form p[0] in second rc2 process.
you should check read() return value whether it's success or not or may be it's reading from wrong/uninitialized file descriptors. This
char *_1st_child_out = NULL;
/* for process rc2, p[0] contains nothing, so what read() will read from p[0] ?? */
int ret = read(p[0], _1st_child_out, sizeof("1st child output"));
if(ret == -1) {
perror("read");
/* error handling */
}
Since data written into p[1] in rc1 process, not in rc2 process, but here when you tries to read from p[0] it gives you
read: Bad address
Case 2 :- To overcome above problem one way is
int main(int argc, char *argv[]) {
int p[2]; // p[0]: file descriptor for read end of pipe
// p[1]: file descriptor for write end of pipe
if (pipe(p) < 0) exit(1);
int rc1 = fork();
if (rc1 < 0){ fprintf(stderr, "fork error\n"); }
else if (rc1 == 0){
write(p[1], "1st child output",sizeof("1st child output"));
}
else{
char *_1st_child_out = NULL;
/* read() will read from p[0] and store into _1st_child_out but _1st_child_out not holding any valid memory ? So it causes Undefined behavior */
int ret = read(p[0], _1st_child_out, sizeof("1st child output"));
if(ret == -1) {
perror("read");
/* error handling */
}
strcat(_1st_child_out, ", AFTER PIPE YA FOOL");
printf("%s\n", _1st_child_out);
}
return 0;
}
Here _1st_child_out is a pointer and pointers should have valid memory location. you can initialize with NULL which is (void*)0, that's valid but not with \0 as it's just a single character.
But when you initialize _1st_child_out with NULL and read the data from p[0] & store into _1st_child_out, what it will store into it ? It causes segmentation fault and it's also undefined behavior.
So it's better to allocate memory dynamically for _1st_child_out and then call read() or create the stack allocated array like
char _1st_child_out[10];
Here is the sample working code
int main(int argc, char *argv[]) {
int p[2]; // p[0]: file descriptor for read end of pipe
// p[1]: file descriptor for write end of pipe
if (pipe(p) < 0) exit(1);
int rc1 = fork();
if (rc1 < 0){ fprintf(stderr, "fork error\n"); }
else if (rc1 == 0){
write(p[1], "1st child output",sizeof("1st child output"));
}
else{
char *_1st_child_out = malloc(BYTE); /* define BYTE value as how much memory needed, and free the dynamically allocated memory once job is done */
int ret = read(p[0], _1st_child_out, sizeof("1st child output"));
if(ret == -1) {
perror("read");
/* error handling */
}
/* make sure _1st_child_out has enough memory space to concatenate */
strcat(_1st_child_out, ", AFTER PIPE YA FOOL");
printf("%s\n", _1st_child_out);
}
return 0;
}
Note : Use strncat() instead of strcat(), reason you can find from manual page of strcat() https://linux.die.net/man/3/strcat It says
The strcat() function appends the src string to the dest string,
overwriting the terminating null byte ('\0') at
the end of dest, and then adds a terminating null byte. The strings may not overlap, and the dest string must
have enough space for the result. If dest is not large enough, program behavior is unpredictable; buffer over‐
runs are a favorite avenue for attacking secure programs.
The strncat() function is similar, except that
* it will use at most n bytes from src; and
* src does not need to be null-terminated if it contains n or more bytes.
As with `strcat()`, the resulting string in dest is always null-terminated.