C - creating process tree using pipes, select, fork, and execl - c

I'm trying to write the following, 2 part program. In one file ("root.c"), I read in a random string of 1's and 0's. I then split the resulting string in half, and send each half to its own process through fork(). Each child process uses execl() to run the second program ("bit_count.c").
Within bit_count.c, it:
a) checks if the length of the (half)string is 2 or less. If yes, it returns the number of
1's and 0's to it's parent process.
b) if not, it begins to recursively split the string in half, and sending each half to its own new process (replicating the procedure in root.c). This creates a binary process tree, until all pieces of the string are 2 characters long or less.
c) the count results of the left and right children are aggregated by the parent, and returned to its parent, until returning to the root process, which aggregates the highest two children, and outputs it to the user.
My problem with this project is returning the 2-character counts to the parent. My idea right now is to direct the parent's left and right read pipes to stdin with dup2(), and just to print to stdout with fprintf from the children. The select() function of the parent should catch the returning output, right?
My second problem is the format of the output. if the counts are in ints, what is the best way to return that using select() in this case? I've attached my code below, just be warned that it may be a mess - I'm rusty with C code and this is my first exposure to select() and execl().
root.c:
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char* argv[]) {
if (argc != 2) {
perror("input file name");
printf("%d", argc);
exit(1);
}
FILE* fp;
if((fp = fopen(argv[1], "r")) == NULL) {
perror("open file");
}
fseek(fp, 0, SEEK_END);
long fsize = ftell(fp);
fseek(fp, 0, SEEK_SET);
char *bits = malloc(fsize+1);
fread(bits, fsize, 1, fp);
fclose(fp);
char *left_half = malloc( fsize/2 + 1 );
char *right_half;
if (fsize%2) right_half = malloc( fsize/2 + 2 );
else right_half = malloc( fsize/2 + 1 );
if (!left_half || !right_half) perror("array split");
memcpy(left_half, bits, fsize/2);
if (fsize%2) memcpy(right_half, bits + fsize/2, fsize/2 + 1);
else memcpy(right_half, bits + fsize/2, fsize/2);
int fd_left[2], fd_right[2];
int zero, one;
int *left_res, *right_res;
pid_t left, right;
struct timeval tv;
fd_set readfds;
tv.tv_sec = 2;
tv.tv_usec = 500000;
if ((pipe(fd_left) == -1) || (pipe(fd_right) == -1)){
perror("Create pipe error");
exit(1);
}
FD_ZERO(&readfds);
FD_SET(fd_left[0], &readfds);
FD_SET(fd_right[0], &readfds);
if ((left=fork()) == 0) {
close(fd_left[0]);
execl("./bit_count", "bit_count", left_half, NULL);
perror("initiating recursion");
exit(1);
}
else if(left > 0) {
if ((right = fork())==0) {
close(fd_right[0]);
execl("./bit_count", "bit_count", right_half, NULL);
perror("initiating recursion");
exit(1);
}
else if (right > 0) {
close(fd_right[1]);
close(fd_left[1]);
char *left;
char *right;
dup2(fd_left[0], 0);
dup2(fd_right[0], 0);
int ret = select(2, &readfds, NULL, NULL, &tv);
read(fd_left[0], &left_res, 1);
read(fd_right[0], &right_res, 1);
printf("Back in root process!\n");
}
}
zero = (*right_res + *left_res);
one = (*(left_res+sizeof(int)) + *(right_res+sizeof(int)));
printf("%s had %d zeroes and %d ones\n", argv[1], zero, one);
return 0;
}
bit_count.c (only relevant part):
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
if (argc != 2) {
perror("sent bit string");
printf("%d", argc);
exit(1);
}
char *bit_string = argv[1];
int size = strlen(bit_string);
int counts[2];
counts[0] = 0;
counts[1] = 0;
if (!(size > 2)) {
int i=0;
for(; i < size; i++) {
if (bit_string[i]=='1') ++counts[1];
else ++counts[0];
}
fprintf(stdout, "%p", &counts);
fflush(stdout);
return 0;
}
}

My idea right now is to direct the parent's left and right read pipes to stdin with dup2(), and just to print to stdout with fprintf from the children. The select() function of the parent should catch the returning output, right?
No. You need to dup2(fd[1], STDOUT_FILENO) in the child before calling execl(). How else is bit_count supposed to know about the pipe? Then in the parent you can just read from fd[0]. To make things easier, you could just make bit_count a function, and call it directly in the child without using execl(). Then you could just write to fd[1] (if you made it global, or passed the value to the bit_count function) from the children.
My second problem is the format of the output. if the counts are in ints, what is the best way to return that using select() in this case?
You could use write(STDOUT_FILENO, &counts, 2*sizeof(int)) to write the ints directly to the pipe, rather than formatting them as a string. This way the parent does not need to convert them back to ints.

Related

is it possible to read and write with the same file descriptor in C

I am trying to write to a file and display the output of the thing i wrote with another process. The code i come up with:
void readLine (int fd, char *str) {
int n;
do {
n = read (fd, str, 1);
} while (*str++ != '\0');
}
int main(int argc,char ** argv){
int fd=open("sharedFile",O_CREAT|O_RDWR|O_TRUNC,0600);
if(fork()==0){
char buf[1000];
while(1) {
readLine(fd,buf);
printf("%s\n",buf);
}
}else{
while(1){
sleep(1);
write(fd,"abcd",strlen("abcd")+1);
}
}
}
the output i want (each result spaced from the other with a period of one second):
abcd
abcd
abcd
....
Unfortunately this code doesn't work, it seems that the child process (the reader of the file "sharedFile") reads junk from the file because somehow it reads values even when the file is empty.
When trying to debug the code, readLine function never reads the written file correctly,it always reads 0 bytes.
Can someone help?
First of all, when a file descriptor becomes shared after forking, both the parent and child are pointing to the same open file description, which means in particular that they share the same file position. This is explained in the fork() man page.
So whenever the parent writes, the position is updated to the end of the file, and thus the child is always attempting to read at the end of the file, where there's no data. That's why read() returns 0, just as normal when you hit the end of a file.
(When this happens, you should not attempt to do anything with the data in the buffer. It's not that you're "reading junk", it's that you're not reading anything but are then pretending that whatever junk was in the buffer is what you just read. In particular your code utterly disregards the return value from read(), which is how you're supposed to tell what you actually read.)
If you want the child to have an independent file position, then the child needs to open() the file separately for itself and get a new fd pointing to a new file description.
But still, when the child has read all the data that's currently in the file, read() will again return 0; it won't wait around for the parent to write some more. The fact that some other process has a file open for writing don't affect the semantics of read() on a regular file.
So what you'll need to do instead is that when read() returns 0, you manually sleep for a while and then try again. When there's more data in the file, read() will return a positive number, and you can then process the data you read. Or, there are more elegant but more complicated approaches using system-specific APIs like Linux's inotify, which can sleep until a file's contents change. You may be familiar with tail -f, which uses some combination of these approaches on different systems.
Another dangerous bug is that if someone else writes text to the file that doesn't contain a null byte where expected, your child will read more data than the buffer can fit, thus overrunning it. This can be an exploitable security vulnerability.
Here is a version of the code that fixes these bugs and works for me:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void readLine (int fd, char *str, size_t max) {
size_t pos = 0;
while (pos < max) {
ssize_t n = read(fd, str + pos, 1);
if (n == 0) {
sleep(1);
} else if (n == 1) {
if (str[pos] == '\0') {
return;
}
pos++;
} else {
perror("read() failure");
exit(2);
}
}
fprintf(stderr, "Didn't receive null terminator in time\n");
exit(2);
}
int main(int argc, char ** argv){
int fd=open("sharedFile", O_CREAT|O_RDWR|O_TRUNC, 0600);
if (fd < 0) {
perror("parent opening sharedFile");
exit(2);
}
pid_t pid = fork();
if (pid == 0){
int newfd = open("sharedFile", O_RDONLY);
if (newfd < 0) {
perror("child opening sharedFile");
exit(2);
}
char buf[1000];
while (1) {
readLine(newfd, buf, 1000);
printf("%s\n",buf);
}
} else if (pid > 0) {
while (1){
sleep(1);
write(fd,"abcd",strlen("abcd")+1);
}
} else {
perror("fork");
exit(2);
}
return 0;
}

C communicate parent and child to increase and print counter

I am trying to write a program so that the parent and child process can communicate back and forth between each other. The parent process and the child process ought to print the values from 1-100 where each process prints the value incrementing it by 1 each time. Now the issue I face is that, I know nothing much about pipes. What I gather from reading materials online is that I can use a pipe to read and write values. I have leveraged this to print something in the child process, and send back something to the parent. Now, I am not sure how to get the parent to return to the child after printing for itself? I know my code is probably all wrong, but I am really not sure what I should do.
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, const char * argv[]) {
int fd[2];
if (pipe(fd)== -1){
printf("An error occured while opening the pipe\n");
}
int id = fork();
int i = 0;
if (id == 0){
close(fd[0]);
printf("In child: %d", i);
i ++;
write(fd[1], &i, sizeof(int));
close(fd[1]);
} else {
wait(NULL);
close(fd[1]);
int y;
read(fd[0],&y, sizeof(int));
close(fd[0]);
}
}
To keep it simple, it's up to you to check return values and handle errors. This will only do it between 0 - 9 and you will have to expand the mathematics.
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int pipefd_1[2];
int pipefd_2[2];
pid_t cpid;
pipe(pipefd_1);
pipe(pipefd_2);
cpid = fork();
if (cpid == 0) { /* Child reads from pipe 1, writes to pipe 2*/
char cval[] = {'0'};
close(pipefd_1[1]); /* Close unused write and read ends */
close(pipefd_2[0]);
while (atoi(cval) != 9) {
read(pipefd_1[0], cval, 1);
printf("Child print %d\n", atoi(cval));
cval[0] += 1;
write(pipefd_2[1], cval, 1);
}
} else {
char cval[] = {'0'}; /* Parent writes buf to pipe 1 */
close(pipefd_1[0]); /* Close unused read end */
close(pipefd_2[1]);
while (atoi(cval) != 9) {
write(pipefd_1[1], cval, 1);
read(pipefd_2[0], cval, 1);
printf("Parent print %d\n", atoi(cval));
cval[0] += 1;
}
}
}
Output

Increment and forward an integer using pipes in C

I am trying to write a program that initializes an integer in the parent and then in the first fork() we take that value increment it and pass it along the tube to the next process called by the current child. This goes on for 2 more times. My problem is that I initiate the integer to 96 and ideally since we have 3 processes the program should return 99. But instead, it returns 'a', which means it has only incremented once.
This is my code:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
int main(int argc, char **argv)
{
int status;
int i;
int pipes[4];
pipe(pipes);
pipe(pipes + 2);
int num = 96;
if (fork() == 0)
{
dup2(pipes[1], 1);
close(pipes[0]);
close(pipes[1]);
close(pipes[2]);
close(pipes[3]);
num++;
write(pipes[1], &num, sizeof(int));
}
else
{
if (fork() == 0)
{
dup2(pipes[0], 0);
dup2(pipes[3], 1);
close(pipes[0]);
close(pipes[1]);
close(pipes[2]);
close(pipes[3]);
read(pipes[0], &num, sizeof(int));
num++;
write(pipes[3], &num, sizeof(int));
}
else
{
if (fork() == 0)
{
dup2(pipes[2], 0);
close(pipes[0]);
close(pipes[1]);
close(pipes[2]);
close(pipes[3]);
read(pipes[2], &num, sizeof(int));
num++;
write(1, &num, sizeof(int));
}
}
}
close(pipes[0]);
close(pipes[1]);
close(pipes[2]);
close(pipes[3]);
for (i = 0; i < 3; i++)
wait(&status);
}
How do I make it so that an integer is printed?
Why does my increment work only once?
You close(pipe[0]) and then try to read from it. Read from the dup'd fd instead. You would notice this error if you checked the values returned from read and write.
To print an integer, use printf("%d", num) instead of write. The reason num increments only once is that the read in the final child fails and does not change num, so after the read num retains the value it had when it was initialized.
All you need to do is either remove the close calls from inside the conditions (all of the pipes get closed twice in the current code (or, more accurately, they are closed once and then a second attempt to close them fails, but the failure goes unnoticed)) or read/write from/to the dup'd fd.

Forking a process, and using pipes to transfer data from file line by line to child process

My project is to fork and then use the parent process to read data from a file line by line and then send each line to the child process, which has to use execve to send the line as an argument for bc, and the output has to go back to the parent process. Right now, I'm just trying to send the data to the child and receive it properly, but it doesn't work. I have to use select to figure out if the child has output for the parent to get.
I have a file with 5 lines on it, and I use a while loop to go through the file. For each line I thought I would get the line back from the child, but it only does one line or two and stops. Then I get the same line twice for some reason.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <errno.h>
#include <wait.h>
int main(int argc, char *argv[])
{
alarm(60);
fd_set rfds;
fd_set r2fds;
struct timeval tv;
int retval;
int retval2;
int i = argc;
int rc;
FILE *fp;
fp = fopen (argv[1],"r");
char *args[3];
int j = 0;
while (j < i)
{
args[j] = argv[j+2];
j++;
}
int stdin_pipe_fds[2], stdout_pipe_fds[2], stderr_pipe_fds[2];
pipe(stdin_pipe_fds);
pipe(stdout_pipe_fds);
pipe(stderr_pipe_fds);
rc = fork();
if (rc == -1)
{
while (rc == -1)
{
rc = fork();
}
}
pid_t child;
pid_t parent;
if (rc == 0)
{
child = getpid();
close(stdin_pipe_fds[1]);
close(stdout_pipe_fds[0]);
close(stdout_pipe_fds[0]);
close(0);
dup(stdin_pipe_fds[0]);//, 0);
close(stdin_pipe_fds[0]);
close(1);
dup(stdout_pipe_fds[1]);//,1);
close(stdout_pipe_fds[1]);
close(2);
dup(stderr_pipe_fds[1]);//,2);
close(stderr_pipe_fds[1]);
}
if (rc > 0)
{
parent = getpid();
close(stdin_pipe_fds[0]);
close(stdout_pipe_fds[1]);
close(stderr_pipe_fds[1]);
}
char str[100];
char buf2[100];
char buf[100];
FD_ZERO(&rfds);
FD_SET(stdout_pipe_fds[0], &rfds);
tv.tv_sec = 1;
tv.tv_usec = 0;
while ((fgets(str,100,fp)) != NULL)
{
if (rc > 0)
{
int wstatus;
int wsta;
int status;
wsta = write(stdin_pipe_fds[1],str,strlen(str));
retval = select(FD_SETSIZE, &rfds, NULL, NULL, &tv);
if (FD_ISSET(stdout_pipe_fds[0], &rfds))
{
wstatus = read(stdout_pipe_fds[0], buf2, 100);
printf("From child: %s\n",buf2);
if (wstatus == -1)
{
printf("read failed\n");
//continue;
}
//wsta = write(stdin_pipe_fds[1],str,strlen(str));
}
}
if (rc == 0)
{
alarm(60);
scanf("%s",buf);
printf("%s", buf);
}
}
fclose(fp);
}
In the child
you close stdout_pipe_fds[0] twice, and not stderr_pipe_fds[0].
The scanf("%s", buf) will strip the newline from the string; you might not want that. Stdout, if you started from a terminal, will be line buffered; that is, it will take a newline to trigger an actual write. Since you snuck in and replaced the underlying FD, it doesn't know that a new buffering strategy may be appropriate.
In the parent:
you treat read and write as if they return a status; they return the number of bytes read or written. If it reads 0 bytes, the buf will contain the values from the previous read. I am not sure of the purpose of the read(), I would assume it just messes things up. I think you need to draw a picture of how the file descriptors are linked.
Suggestions: There is a generally accepted idiom when using fork:
if ((rc = fork()) == 0) {
/* do child stuff */
} else if (rc != -1) {
/* do parent stuff */
} else {
/* do error stuff */
}
Which is mainly followed to avoid warping peoples brains. It is really hard to read when it is interleaved. The "K" in K&R once quipped that "... if you're as clever as you can be when you write it, how will you ever debug it?"
The close(0); dup(); is much better expressed as dup2(fd, 0). The mere fact that you can compile code on your machine assures that dup2 functions correctly.

Piping between two programs in C

I've been stuck on getting piping to work between two programs for the last couple of hours and I'm stuck and not sure if I'm doing something wrong. The idea of my program is that I'm going to use interface.c to open a pipe, and then execute db.c. I want to use two pipes to communicate between the two different programs. Now, with interface.c being the 'parent' and db.c being the 'child', I'm not sure if I'm passing in the parameters to my pipe correctly via the execl command. Everything compiles correctly, but when I try to run the interface program, I'm getting an error stating: 'Bad File Number.' Is it possible that I'm not using pipes correctly? Currently, I'm just trying to get my program to send an integer, value, over the pipe to db.c. Any help would be much appreciated.
Code for interface.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/wait.h>
//PIPES:
//
//Parent: reads from P1_READ, writes on P1_WRITE
//Child: reads from P2_READ, writes on P2_WRITE
#define P1_READ 0
#define P2_WRITE 1
#define P2_READ 2
#define P1_WRITE 3
// the total number of pipe *pairs* we need
#define NUM_PIPES 2
int main(int argc, char *argv[])
{
//Create Pipe Array
int fd[2*NUM_PIPES];
//For Parameter Passing:
char param0[20]; //P1_Read
char param1[20]; //P2_Write
char param2[20]; //P2_Read
char param3[20]; //P1_Write
snprintf(param0, sizeof(param0), "%d" , fd[0]);
snprintf(param1, sizeof(param1), "%d" , fd[1]);
snprintf(param2, sizeof(param2), "%d" , fd[2]);
snprintf(param3, sizeof(param3), "%d" , fd[3]);
//Variables
pid_t pid;
int val = 42;
//Allocate the PIPES
for (int i=0; i<NUM_PIPES; ++i)
{
if(pipe(fd+(i*2)) < 0)
{
perror("Failed to allocate the pipes");
exit(EXIT_FAILURE);
}
}
//If the fork of the program does not work:
if ((pid = fork()) < 0)
{
perror("Failed to fork process");
return EXIT_FAILURE;
}
if(pid == 0)
{ //Child Process
execl("./db", "db", param0, param1, param2, param3, (char *)NULL);
}
else
{ //Parent Process
//SENDING VALUES HERE
close(fd[P2_READ]);
close(fd[P2_WRITE]);
printf("Interface is sending|%d| to DB\n", val);
if(write(fd[P1_WRITE],&val, sizeof(val)) != sizeof(val))
{
perror("Interfae failed to send value to DB");
exit(EXIT_FAILURE);
}
}
return 0;
}
This is for db.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/types.h>
//Typedef-Class-
typedef struct Information
{
int accountId;
int checkNumber;
int date;
float amount;
} Information;
int main(int argc, char *argv[])
{
//For Input
//Account Data
Information acctData[25];
int dataStorageLooper = 0; //How many db entries
//For File Input
int aVal;
int bVal;
int cVal;
float dVal;
//Prepare for file input:
FILE * fp;
fp = fopen ("accountData.txt", "r");
//Reads Input
while(1)
{
if (fscanf(fp, "%d %d %d %f", &aVal, &bVal, &cVal, &dVal)!=4)
{
break;
}
//Puts data into appropriate arrays
acctData[dataStorageLooper].accountId= aVal;
acctData[dataStorageLooper].checkNumber= bVal;
acctData[dataStorageLooper].date= cVal;
acctData[dataStorageLooper].amount= dVal;
dataStorageLooper++;
}
//Decrement index to point to last item
dataStorageLooper--;
//Displays all values
printf("\nDisplaying AccountData.txt\n");
for( int i = 0; i < dataStorageLooper; i++)
{
printf("Line|%d|: Account|%d|: Check|%d|: Date|%d|: Amount|%.2f|\n",i,acctData[i].accountId,acctData[i].checkNumber,acctData[i].date,acctData[i].amount);
}
//Closes File
fclose(fp);
//End Input
//Parameter Receiving:
int pipes[4]; //Pipe Array
int value = 7;
int test;
//Build the pipes
pipes[0] = atoi(argv[1]); //P1_Read
pipes[1] = atoi(argv[2]); //P2_Write
pipes[2] = atoi(argv[3]); //P2_Read
pipes[3] = atoi(argv[4]); //P1_Write
//Troubleshooting
printf("The number of parameters: %d\n",argc);
printf("Parameter 1: %s\n", argv[0]);
printf("I stared correctly\n");
//Testing
close(pipes[0]);
close(pipes[3]);
//SHOULD RECEIVE VALUE HERE
test = read(pipes[2], &value, sizeof(value));
if (test < 0)
{
perror("DB: Failed to read data from parent");
exit(EXIT_FAILURE);
}
else if (test == 0)
{
//Unexpected
fprintf(stderr, "DB: Read End-Of-File from pipe");
}
else
{
//What did the child receive?
printf("DB: Received Value:(%d)\n", value);
}
close(pipes[2]);
close(pipes[1]);
return 0;
}
One of the things you're doing wrong is snprintfing the value of the various elements in fd before you've assigned any value to them. That's undefined behaviour, and the values you're passing as parameters are totally meaningless (at best).
This strikes me as a very odd way to do things, though. Usually you would just dup2 fds 0 and 1 so that the child's stdin and stdout are redirected to the appropriate pipe fds.

Resources