Multi-level pipes in C - c

I have to create a multi-level pipe in C that interprets Linux commands just like the Unix console. The code I have works for a 2-level pipe but I have to implement more (up to 16). The main problem here is I'm not sure how to take the output of the first two commands and then reroute it to be the input of the third command (etc. for the other levels). I know I need to use "1" of the first pipe and "0" of the second pipe since those are stdout and stdin respectively but I am unsure how to implement this in practice.
// tokenize is a separate function that uses strtok repeatedly on cmdline, setting the array
// segments to the strings separated by | and numTokens to the number of commands
char* segments[MAX_PIPE_SEGMENTS];
int x = 0;
int* numTokens = &x;
tokenize(segments, cmdline, numTokens, "|");
// the code for one command
if (*numTokens == 1) {
pid_t pid = fork();
if (pid == 0) {
char* strings[MAX_SEGMENT_LENGTH];
int y = 0;
int* numStrings = &y;
tokenize(strings, segments[0], numStrings, " ");
execvp(strings[0], strings);
} else {
wait(0);
return;
}
}
pid_t pid = fork();
if (pid == 0) {
int i = 0;
for (i = 0; i < *numTokens-1; i++) {
pid_t pid2 = fork();
if (pid2 == 0) {
int ps[2];
pipe(ps);
pid_t pid3 = fork();
if (pid3 == 0) {
close(1);
dup2(ps[1], 1);
close(ps[0]);
char* strings[MAX_SEGMENT_LENGTH];
int y = 0;
int* numStrings = &y;
tokenize(strings, segments[i], numStrings, " ");
execvp(strings[0], strings);
} else {
close(0);
dup2(ps[0], 0);
close(ps[1]);
wait(0);
char* strings[MAX_SEGMENT_LENGTH];
int y = 0;
int* numStrings = &y;
tokenize(strings, segments[i+1], numStrings, " ");
execvp(strings[0], strings);
}
} else {
wait(0);
}
}
} else {
wait(0);
return;
}

You see, treating the first and second command in different if/else blocks is not generalizable to more than two commands. Feasible is to treat the pipe commands and operations identically as far as possible and only add special conditions for the first and last:
int i, in, out = dup(1); // save standard output descriptor
for (i = 0; i < x; i++)
{
int ps[2];
if (i < x-1) pipe(ps); // if not last in line, make a pipe
pid_t pid = fork();
if (pid == 0)
{
// if not first in line, connect standard input to pipe
if (i) dup2(in, 0), close(in);
// if not last in line, connect standard output to pipe
if (i < x-1) dup2(ps[1], 1), close(ps[1]);
// if last in line, restore standard output to original
else dup2(out, 1), close(out);
char* strings[MAX_SEGMENT_LENGTH];
int y = 0;
int* numStrings = &y;
tokenize(strings, segments[i], numStrings, " ");
execvp(strings[0], strings);
exit(1);
}
if (i) close(in);
close(ps[1]);
in = ps[0]; // the current pipe's read end is the new input
}
close(out);
do ; while (wait(0) > 0);

Related

Multiple pipes in shell

Im trying to replicate the multiple pipes execution of bash, and Im getting no output from terminal. First I create the number of pipes that my program will need depending on the number of commands, then in a while loop I launch each process executing each command in the child proccess and working with the correct FDs of pipes, in case it is first command, I dont read from any pipe, on the other hand, in case it is last command, I dont redirect the output to any pipe. I printed each fd to check if my program is doing well and it looks everything okay but as I said I dont get any output of the last commands.
Here is a snippet of my code about how I deal with the process and the pipes, if you need something more just let me know.
int i;
int *pipes;
i = 0;
pipes = ft_calloc(sizeof(int), gdata->n_pipes * 2);
while (i < gdata->n_pipes)
{
pipe(pipes + (i * 2));
i++;
}
int cc = 0; //command count
int r = 0;
int m;
pid_t pid;
while (gdata->cmds[r]) //double pointer array that contains the commands to execute ex: [["ls -l"], ["grep i"], ["wc -l"]]
{
pid = fork();
if (pid < 0)
{
perror("Fork: ");
exit(EXIT_FAILURE);
}
if (pid == 0)
{
if (r > 0)
{
if (dup2(pipes[(cc - 1) * 2], STDIN_FILENO) < 0)
{
perror("dup");
exit(EXIT_FAILURE);
}
}
if (r < gdata->commands - 1)
{
if (dup2(pipes[cc * 2 + 1], STDOUT_FILENO) < 0)
{
perror("dup");
exit(EXIT_FAILURE);
}
}
int k = 0;
while (k < gdata->n_pipes * 2)
{
close(pipes[k]);
k++;
}
handle_path(gdata->cmds[r], gdata->envp); // function that calls execve to execute commands
}
waitpid(pid, &m, 0);
r++;
cc++;
}
int y = 0;
while (y < gdata->n_pipes * 2)
{
close(pipes[y]);
y++;
}
Do you have any idea what I'm doing wrong, Thanks for the help!

How the parent creates a pipe between each child and itself, and each child sends number of the words to the parent via the pipe

The code I've written finds out the number of words in multiple text files by creating multiple processes with each process being responsible for one file to count its words.
What I want to do is using pipes to find out total number of words in all files.
So the parent should:
creates a pipe between the each child and itself so it can get the number of words from each child
reports the total number of words in all the files by adding the numbers received through pipes
checks the exit status of each child and prints out how that child
exited
also let each child:
sends the number of the words to the parent via the pipe
send 0 as word count through the pipe to the parent if the file does
not exist or any other error happens
returns/exits with 0 if it is successfull in opening the file and
counting the words in that file, returns/exits with 1 if there is an
error (e.g., file does not exist etc.)
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#define MAX_CHAR 100
pid_t getpid(void);
pid_t getppid(void);
char* itoa(int i, char b[]){
char const digit[] = "0123456789";
char* p = b;
if(i<0){
*p++ = '-';
i *= -1;
}
int shifter = i;
do{ //Move to where representation ends
++p;
shifter = shifter/10;
}while(shifter);
*p = '\0';
do{ //Move back, inserting digits as u go
*--p = digit[i%10];
i = i/10;
}while(i);
return b;
}
int countWords(char * fp, int pid) {
FILE * file;
int words = 0;
char word[MAX_CHAR];
//execute this function only if child process of parent, no gradchild is allowed to execute this function!
if (pid == getppid()) {
file = fopen(fp, "r");
if (file == NULL) {
return -1;
}
//find string in the file and count the words.
while (fscanf(file, "%s", word) != EOF) {
words++;
}
return words;
} else {
return -1;
}
return 0;
}
int main(int argc, char * arvg[]) {
//if invalid arguments
if (argc < 2) {
fprintf(stderr, "ERROR: INVALID ARGUMENTS");
exit(-1);
}
int count = 0, pid, ppid, status, totalwords;
int result = -1;
int fd[2];
char string[100];
char readbuffer[80];
int *write_fd = &fd[1];
int *read_fd = &fd[0];
result = pipe(fd);
if(-1 == result){
perror("pipe");
return -1;
}
//creates (argc - 1) child processes using fork()
pid = (int) malloc((argc - 1) * sizeof(int));
//parent pid
ppid = getpid();
//each child process to count the number of words in each file
for (int i = 1; i < argc; i++) {
//child process
pid = fork();
if( pid == -1){
perror("failed to fork");
return -1;
}else if (pid == 0) {
// call a function to count the number of words in file arvg[i]
int words = countWords(arvg[i], ppid);
close(*read_fd);
if (words >= 0) {
printf("Child process pid_%d for %s :number of words is %d\n", i, arvg[i], words);
//I don't know how to write int into the pipe,so below might be wrong
write(*write_fd, words, 1);
return 0;
} else if (words == -1) {
printf("Child process pid_%d for %s :does not exists\n", i, arvg[I]);
//I don't know how to write int into the pipe,so below might be wrong
write(STDOUT_FILENO, words, 1);
exit(1);
}
} else {
close(*write_fd);
//and I have no idea how to read int from pipes
read(*read_fd, &readbuffer, 1);
totalwords += ???
close(*read_fd);
//Wait until all child processes exit/return
if (ppid == getpid()) {
wait( & status);
}
//inspect their exit codes, WEXITSTATUS = return code when child exits
if (WEXITSTATUS(status) == 1) {
count++;
}
}
}
printf("Main process created %d child processes to count words in %d files\n", argc - 1, argc - 1);
printf("Total words is %d", totalwords);
printf("%d files have been counted sucessfully!\n", argc - 1 - count);
printf("%d files did not exist.\n", count);
return 0;
}```
Can someone help me to figure out this? I don't really know how to achieve my goal with pipe.
found some issues with the code. I fixed them for you (however, I would have done the same thing slight differently)
reading and writing int from a pipe is pretty straight forward, just typecast correctly while reading or writing to an int.
malloc to a pid was not necessary. Also malloc returns a pointer and should have been typecasted with (int*)
always add the right includes while using calls. Manual page or reading about the calls while trying to understand the parameters passed and return values is extremely useful.
Enough said, here is your working code
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#define MAX_CHAR 100
pid_t getpid(void);
pid_t getppid(void);
char* itoa(int i, char b[]){
char const digit[] = "0123456789";
char* p = b;
if(i<0){
*p++ = '-';
i *= -1;
}
int shifter = i;
do{ //Move to where representation ends
++p;
shifter = shifter/10;
}while(shifter);
*p = '\0';
do{ //Move back, inserting digits as u go
*--p = digit[i%10];
i = i/10;
}while(i);
return b;
}
int countWords(char * fp, int pid) {
FILE * file;
int words = 0;
char word[MAX_CHAR];
//execute this function only if child process of parent, no gradchild is allowed to execute this function!
if (pid == getppid()) {
file = fopen(fp, "r");
if (file == NULL) {
return -1;
}
//find string in the file and count the words.
while (fscanf(file, "%s", word) != EOF) {
words++;
}
return words;
} else {
return -1;
}
return 0;
}
int main(int argc, char * arvg[]) {
//if invalid arguments
if (argc < 2) {
fprintf(stderr, "ERROR: INVALID ARGUMENTS");
exit(-1);
}
int count = 0, pid, ppid, status, totalwords = 0;
int result = -1;
int fd[2];
char string[100];
char readbuffer[80];
int *write_fd = &fd[1];
int *read_fd = &fd[0];
int recvd = 0;
result = pipe(fd);
if(-1 == result){
perror("pipe");
return -1;
}
//creates (argc - 1) child processes using fork()
//pid = (int) malloc((argc - 1) * sizeof(int));
//parent pid
ppid = getpid();
//each child process to count the number of words in each file
for (int i = 1; i < argc; i++) {
//child process
pid = fork();
if( pid == -1){
perror("failed to fork");
return -1;
}else if (pid == 0) {
printf ("%d child running \n", i);
// call a function to count the number of words in file arvg[i]
int words = countWords(arvg[i], ppid);
close(*read_fd);
if (words >= 0) {
printf("Child process pid_%d for %s :number of words is %d\n", i, arvg[i], words);
//I don't know how to write int into the pipe,so below might be wrong
write(*write_fd, (void *)&words, 1);
return 0;
} else if (words == -1) {
printf("Child process pid_%d for %s :does not exists\n", i, arvg[i]);
//I don't know how to write int into the pipe,so below might be wrong
write(STDOUT_FILENO, (void *)&words, 1);
exit(1);
}
} else {
close(*write_fd);
//and I have no idea how to read int from pipes
read(*read_fd, (void*)&recvd, 1);
totalwords += recvd;
printf("recvd %d \n", totalwords);
close(*read_fd);
//Wait until all child processes exit/return
if (ppid == getpid()) {
wait( & status);
}
//inspect their exit codes, WEXITSTATUS = return code when child exits
if (WEXITSTATUS(status) == 1) {
count++;
}
}
}
printf("Main process created %d child processes to count words in %d files\n", argc - 1, argc - 1);
printf("Total words is %d\n", totalwords);
printf("%d files have been counted sucessfully!\n", argc - 1 - count);
printf("%d files did not exist.\n", count);
return 0;
}
Well, the first time around, I did not focus on the algo. I fixed all of it. The problem is forking in a loop and reading and writing it would lead to wrong results. Moreover, Parent needs to look for EOF to ensure all read has happened. Anyways, Here is the code that should work
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#define MAX_CHAR 100
pid_t getpid(void);
pid_t getppid(void);
char* itoa(int i, char b[]){
char const digit[] = "0123456789";
char* p = b;
if(i<0){
*p++ = '-';
i *= -1;
}
int shifter = i;
do{ //Move to where representation ends
++p;
shifter = shifter/10;
}while(shifter);
*p = '\0';
do{ //Move back, inserting digits as u go
*--p = digit[i%10];
i = i/10;
}while(i);
return b;
}
// count word from file provided
int countWords(char * fp, int pid) {
FILE * file;
int words = 0;
char word[MAX_CHAR];
//execute this function only if child process of parent, no gradchild is allowed to execute this function!
if (pid == getppid()) {
file = fopen(fp, "r");
if (file == NULL) {
return -1;
}
//find string in the file and count the words.
while (fscanf(file, "%s", word) != EOF) {
words++;
}
return words;
} else {
return -1;
}
return 0;
}
//do everything related to child here in this function
void child_process(int write_fd, char *filename, int ppid)
{
// call a function to count the number of words in file argv[i]
printf("counting words of %s\n", filename);
int words = countWords(filename, ppid);
if (words >= 0) {
printf("Child process pid for %s :number of words is %d\n", filename, words);
write(write_fd, (void *)&words, 1);
close(write_fd);
exit(0);
} else if (words == -1) {
printf("Child process pid for %s :does not exist\n", filename);
write(STDOUT_FILENO, (void *)&words, 1);
close(write_fd);
exit(1);
}
return;
}
int main(int argc, char * argv[]) {
//if invalid arguments
if (argc < 2) {
fprintf(stderr, "ERROR: INVALID ARGUMENTS");
exit(-1);
}
int pid = 0;
int ppid = 0;
int totalwords = 0;
int fd[2] = {0};
int write_fd = 0;
int read_fd = 0;
int recvd = 0;
// open a pipe
if(-1 == pipe(fd)){
perror("pipe");
return -1;
}
// assign write_fd and read_fd
write_fd = fd[1];
read_fd = fd[0];
//parent pid
ppid = getpid();
//each child process to count the number of words in each file
pid = fork();
for (int i = 0; i < argc-1; i++)
{
//child process
if (pid == 0) {
close(read_fd);
child_process(write_fd, argv[i+1], ppid);
break;
} else {
pid = fork();
}
}
// don't let child run beyond this point
if (pid == 0) {
exit(0);
}
// parent only code
if (pid > 0)
{
close(write_fd);
while (read(read_fd, (void*)&recvd, 1) > 0)
{
wait(NULL);
totalwords += recvd;
}
close(read_fd);
}
printf("Main process created %d child processes to count words in %d files\n", argc - 1, argc - 1);
printf("Total words is %d\n", totalwords);
printf("%d files have been counted sucessfully!\n", argc - 1);
}

Child process prints on redirected stdout, parent never receives

I'm a beginner when it comes to C and I'm writing this program where I have an array at the beginning and have to fork twice so that I have two child processes. Each child process gets half of the parent's array and executes the program based on it, making it recursive.
When a child gets an array containing only one element, that element is printed on stdout and then it exits.
Each parent then reads what its two children send on stdout and makes a calculation based on it. The result of the calculation is an array that is then being printed also on the stdout for the parent's parent's calculation.
What's not working:
I'm getting to the end of the recursion and printing out the value of the 1-element array in each child, but the parent can't seem to read it.
It never enters the while loop where I'm using fgets().
This is the part in parent where I'm splitting the array in two. Here I'm also giving out the value (without doing anything else) in case I get an array with one value:
while( fgets(strBuffer, sizeof(strBuffer), stdin)!= NULL){
if(counter % 2 == 0){
if(evenCounter == evenMax){
evenMax++;
evenBuffer = (char**) realloc(evenBuffer, evenMax *
sizeof(char*));
}if(evenBuffer == NULL){
fprintf(stderr, "error reallocating evenBuffer\n");
exit(1);
}
evenBuffer[evenCounter] = (char*) malloc(100*sizeof(char));
strcpy(evenBuffer[evenCounter], strBuffer);
evenCounter += 1;
} else {
if(oddCounter == oddMax){
oddMax ++;
oddBuffer = (char**) realloc(oddBuffer, oddMax *
sizeof(char*));
}if(oddBuffer == NULL){
fprintf(stderr, "error reallocating oddBuffer\n");
exit(1);
}
oddBuffer[oddCounter] = (char*) malloc(100*sizeof(char));
strcpy(oddBuffer[oddCounter], strBuffer);
ddCounter += 1;
}
counter ++;
}
if(counter == 1){
fprintf(stdout, "%s\n", evenBuffer[0]);
// fprintf(stderr, "LAAAAST %s\n", evenBuffer[0]);
fflush(stdout);
exit(0);
}
if(oddCounter != evenCounter){
fprintf(stderr, "evenCounter size:%d, oddCounter:%d\n",
evenCounter, oddCounter);
exit(EXIT_FAILURE);
}
Here's the code part where the children are created and the pipes are created and redirected.
int k1pipe_from[2];
int k1pipe_to[2];
pipe(k1pipe_from);
pipe(k1pipe_to);
int k2pipe_from[2];
int k2pipe_to[2];
pipe(k2pipe_from);
pipe(k2pipe_to);
pid_t pid = fork();
pid_t pid2;
switch(pid){
case -1:
fprintf(stderr, "Cannot fork!\n");
exit(EXIT_FAILURE);
case 0://child 1
fprintf(stderr, "Child 1 created, pid: %d\n", getpid());
close(k2pipe_from[1]);
close(k2pipe_to[0]);
close(k1pipe_from[1]);
close(k1pipe_to[0]);
dup2(k1pipe_from[0], STDIN_FILENO);
close(k1pipe_from[0]);
dup2(k1pipe_to[1], STDOUT_FILENO);
close(k1pipe_to[1]);
fflush(stdout);
execl("forkFFT", "forkFFT", NULL);
break;
default:
close(k1pipe_to[1]);
close(k1pipe_from[0]);
fflush(stdout);
}
pid2 = fork();
switch(pid2){
case -1:
fprintf(stderr, "Cannot fork!\n");
exit(EXIT_FAILURE);
case 0://child 2
close(k1pipe_from[1]);
close(k1pipe_to[0]);
close(k2pipe_from[1]);
close(k2pipe_to[0]);
dup2(k2pipe_from[0], STDIN_FILENO);
close(k2pipe_from[0]);
dup2(k2pipe_to[1], STDOUT_FILENO);
close(k2pipe_to[1]);
fflush(stdout);
execl("forkFFT", "forkFFT", NULL);
break;
default:
close(k2pipe_to[1]);
close(k2pipe_from[0]);
}
Here's the part where I'm reading what one of the child processes has written on its redirected stdout.
FILE* k1File = fdopen(k1pipe_to[0], "r+");
char r1Buffer[1000];
evenResCounter = 0;
char* pend1;
while(fgets(r1Buffer, strlen(r1Buffer), k1File) != NULL){
double real = (double) strtof(r1Buffer, &pend1);
double img = 0.00;
if(pend1 != NULL){
img = (double) strtof(pend1, NULL);
}
evenRes[evenResCounter] = real + img * I;
evenResCounter ++;
}
close(k1pipe_to[0]);
Here's the part with the calculation, after which the parent prints out the calculated array:
double pi = 3.141592654;
int total_elem = evenResCounter + oddResCounter;
double complex transArray[total_elem];
int k = 0;
int half = total_elem/2;
while(k <= half){
transArray[k] = evenRes[k] + (cos(-2*pi/total_elem*k) + I * sin(-2*pi/total_elem*k)) * oddRes[k];
transArray[k + half] = evenRes[k] - (cos(-2*pi/total_elem*k) + I * sin(-2*pi/total_elem*k)) * oddRes[k];
k++;
}
int final_counter = 0;
while(final_counter != total_elem){
fprintf(stdout, "%f %f*i\n", creal(transArray[final_counter]), cimag(transArray[final_counter]));
}
Would be super grateful for any help.
You should get pipe for child send message to parent
In child, redirect stdout to pipe, recursive or just output data
In parent, redirect stdin to pipe, get data, if in main process, call calculation(), else just call output_data()
The following code just get the sum of each data, you could change it in calculation():
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int sum = 0;
void output_data(int data) {
printf("%d\n", data);
}
void calculation(int data) {
sum += data;
}
void child(int* array, int left, int right, bool in_main_process) {
if (left == right)
exit(0);
if (left + 1 == right) {
in_main_process ? calculation(array[left]) : output_data(array[left]);
exit(0);
}
int mid = (left + right) / 2;
int pipe_fd_1[2];
pipe(pipe_fd_1);
if (fork() == 0) { // child 1
close(pipe_fd_1[0]);
dup2(pipe_fd_1[1], STDOUT_FILENO);
close(pipe_fd_1[1]);
child(array, left, mid, false);
exit(1);
}
int pipe_fd_2[2];
pipe(pipe_fd_2);
if (fork() == 0) { // child 2
close(pipe_fd_2[0]);
dup2(pipe_fd_2[1], STDOUT_FILENO);
close(pipe_fd_2[1]);
child(array, mid, right, false);
exit(1);
}
close(pipe_fd_1[1]);
dup2(pipe_fd_1[0], STDIN_FILENO);
close(pipe_fd_1[0]);
int data;
while (scanf("%d", &data) == 1)
in_main_process ? calculation(data) : output_data(data);
close(pipe_fd_2[1]);
dup2(pipe_fd_2[0], STDIN_FILENO);
close(pipe_fd_2[0]);
while (scanf("%d", &data) == 1)
in_main_process ? calculation(data) : output_data(data);
}
int main() {
int array[6] = {1, 2, 3, 4, 5, 6};
int left = 0;
int right = sizeof(array) / sizeof(array[0]);
child(array, left, right, true);
printf("%d\n", sum);
return 0;
}

How processes work sequentially with C pipe?

I want to do that 2 child processes will put their names and wait until other process put his name. For instance, if there are first and second process, first will put her name and will wait for other's name in screen. So I want to work with processes and I wanna to see they are working sequentially.
Output:
first
second
first
second
first
second
I just tried something about C(linux).
int main(void)
{
pid_t child_a, child_b;
int pipe1[2], pipe2[2];
char mesazhi1[] = "first";
char mesazhi2[] = "second";
char buf[1024];
int first_pipe = pipe(pipe1);
pipe(pipe2);
if(first_pipe == -1){
perror("pipe");
exit(1);
}
child_a = fork();
if (child_a == 0)
{
/* Child A code */
int i;
for (i = 0; i < 3; i++)
{
write(pipe1[1],mesazhi1, strlen(mesazhi1) + 1);
//printf("first\n");
int a = read(pipe2[0], buf, strlen(mesazhi2) + 1);
printf("%s - %d\n", buf, a);
}
}
else
{
child_b = fork();
if (child_b == 0)
{
int i;
for (i = 0; i < 3; i++)
{
write(pipe2[1],mesazhi2, strlen(mesazhi2) + 1);
//printf("second\n");
int a = read(pipe1[0], buf, strlen(mesazhi1) + 1);
printf("%s - %d\n", buf, a);
}
}
else
{
/* Parent Code */
int returnStatusA,returnStatusB;
waitpid(child_a, &returnStatusA, 0); // Parent process waits here for child to terminate.
waitpid(child_b, &returnStatusB, 0); // Parent process waits here for child to terminate.
if (returnStatusA == 0 && returnStatusB == 0) // Verify child process terminated without error.
{
printf("%s\n", "The child processes terminated normally.\n");
}
if (returnStatusA == 1 && returnStatusB == 1)
{
printf("%s\n", "The child processes terminated with an error!. \n" );
}
}
}
}
It is putting name randomly. I mean that I think, sometimes second process works faster than first. Output like that:
first
second
second
first
second
...
So why second process doesn't wait for first one, because I think that read() function should wait until there is something in pipe1.
In the posted code, both processes write to their respective pipes, and then read. After that, it's a race to see which process gets to print first.
For a more controlled situation, have child B call read and printf before calling write. That way B has to wait for A before printing, and vice versa.
if (child_b == 0)
{
int i;
for (i = 0; i < 3; i++)
{
int a = read(pipe1[0], buf, strlen(mesazhi1) + 1);
printf("%s - %d\n", buf, a);
write(pipe2[1],mesazhi2, strlen(mesazhi2) + 1);
}
}

C Programming pipe only half working

I'm working on a mini shell for a college assignment. We have to read in the command, find the binary to execute from the path var, and execute command, both with and without pipes. I have everything working (I think) except for the pipe.
Through web searches I've been able to build a test program that use two hard coded commands and pipes one to the other, with the expected results. Now when I copy and paste that code into my actual program, the first command outputs fine (actually outputs the command as if there were no pipe), while the second I don't think actually does anything (the output from the first is not piped through to the second).
Here is the entire code:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define BUFFSIZE 1024
#define MAXWORDS 17
#define MAXCHAR 64
static char *path;
extern char **environ;
//split cmd "string" on pipe (|) symbol
void split(char **pipe, char **left, char **right, int n)
{
int i, x;
for(i = 0; i < n; i++)
{
if (strchr(&pipe[i][0], '|') != 0)
{
for(x = 0; x < i; x++)
strcpy(left[x], pipe[x]);
left[x++] = 0;
break;
}
}
i++;
for(x = 0; i < n; x++)
strcpy(right[x], pipe[i++]);
right[x++] = 0;
}
//Find directory where cmd can be executed from (PATH or direct access)
char *finddir(char *s)
{
char *pp;
char *pf;
int ok;
strcpy(path, getenv("PATH"));
pp = strtok(path, ":");
while (pp != NULL)
{
pf = (char *)malloc(strlen(pp) + strlen(s) + 2);
if (pf == NULL)
{
fprintf(stderr, "Out of memory in finddir\n");
return NULL;
}
strcpy(pf,pp);
strcat(pf,"/");
strcat(pf,s);
ok = !access(pf, X_OK);
free(pf);
if (ok)
return pp;
pp = strtok(NULL, ":");
}
return NULL;
}
int cmdcheck(char *cmd, char *p)
{
char *dir;
if (strchr(p, '/') != NULL)
sprintf(cmd, "%s\0", p);
else
{
dir = finddir(p);
if (dir == NULL)
return 1;
else
sprintf(cmd, "%s/%s\0", dir, p);
}
return 0;
}
void runpipe(int pfd[], char *cmd1, char *p1[], char *cmd2, char *p2[])
{
int pid;
int status;
switch (pid = fork())
{
case 0: //Child
dup(pfd[0]);
close(pfd[1]); //the child does not need this end of the pipe
execve(cmd2, p2, environ);
perror(cmd2);
default: //Parent
dup(pfd[1]);
close(pfd[0]); //the parent does not need this end of the pipe
execve(cmd1, p1, environ);
perror(cmd1);
case -1: //ERROR
perror("fork-RP");
exit(1);
}
}
int main(void)
{
int status; //read status when reading cmd in
char ch; //character currently reading
int n, i, x; //(n) count of chars read; (i) cmd args iter; (x) cmd arg iter in cmd array
char buffer[BUFFSIZE]; //read buffer
char *token; //token var when splitting buffer
int pid0, pid1, pid2; //return ID from fork call
int which; //return value from wait (child pID that just ended)
char msg[100]; //messages to print out
char *cmd1, *cmd2; //cmds when piping
char *params[MAXWORDS]; //cmd parameters to send to execve
int fd[2]; //pipe file descriptors
char *pparam1[MAXWORDS]; //cmd "string" on left side of pipe
char *pparam2[MAXWORDS]; //cmd on right side of pipe
for(;;)
{
for (i = 0; i < MAXWORDS; i++)
params[i] = malloc(MAXCHAR);
n = 0;
write(1, "# ", 2);
for(;;)
{
status = read(0, &ch, 1);
if (status == 0)
return 0; //End of file
if (status == -1)
return 1; //Error
if(n == BUFFSIZE)
{
write(1, "Line too long\n", 14);
return 1;
}
buffer[n++] = ch;
if(ch == '\n')
break;
}
buffer[n] = '\0';
x = 0;
token = strtok(buffer, " \t\n\0");
while(token != NULL)
{
strcpy(params[x++], token);
token = strtok(NULL, " \t\n\0");
}
params[x] = 0;
path = getenv("PATH");
if (path == NULL)
{
fprintf(stderr, "PATH environment variable not found.\n");
return 1;
}
n = strlen(path);
path = (char *)malloc(n+1);
if (path == NULL)
{
fprintf(stderr, "Unable to allocate space for copy of PATH.\n");
return 1;
}
cmd1 = malloc(MAXCHAR);
cmd2 = malloc(MAXCHAR);
for (i = 0; i < MAXWORDS; i++)
pparam1[i] = malloc(MAXCHAR);
for (i = 0; i < MAXWORDS; i++)
pparam2[i] = malloc(MAXCHAR);
split(params, pparam1, pparam2, x);
//Check first cmd
if(cmdcheck(cmd1, pparam1[0]))
{
sprintf(msg, "cmd '%s' is not executable\n", pparam1[0]);
write(1, msg, strlen(msg));
break;
}
//Check second cmd
if(cmdcheck(cmd2, pparam2[0]))
{
sprintf(msg, "cmd '%s' is not executable\n", pparam2[0]);
write(1, msg, strlen(msg));
break;
}
pipe(fd);
switch (pid0 = fork())
{
case 0: //Child
switch (pid1 = fork())
{
case 0: //Child
runpipe(fd, cmd1, pparam1, cmd2, pparam2);
exit(0);
default:
exit(0);
//break;
case -1: //ERROR
perror("fork-2");
exit(1);
}
default: //Parent
which = wait(&status);
if (which == -1)
{
write(1, "wait failed\n", 12);
exit(1);
}
if (status & 0xff)
sprintf(msg, "process %d terminated abnormally for reason %d\n", which, status & 0xff);
else
sprintf(msg, "process %d terminated normally with status %d\n", which, (status >> 8) & 0xff);
write(1, msg, strlen(msg));
break;
case -1: //ERROR
perror("fork-1");
exit(1);
}
free(cmd1);
free(cmd2);
for (i = 0; i < MAXWORDS; i++)
free(pparam1[i]);
for (i = 0; i < MAXWORDS; i++)
free(pparam2[i]);
free(path);
for (i = 0; i < MAXWORDS; i++)
free(params[i]);
}
return 0;
}
Typing echo one | wc -l at the prompt will only output one with the respective wait print statement following. It has been a few years since I've used C, so am I on the right track?
Thanks.
EDIT:
Here is the runpipe function as it stands now. But the only thing that is printed is the wait statement.
void runpipe(int pfd[], char *cmd1, char *p1[], char *cmd2, char *p2[])
{
const int READ = 0;
const int WRITE = 1;
int pid;
int status;
switch (pid = fork())
{
case 0: //Child
close(pfd[WRITE]);
dup2(pfd[READ], STDIN_FILENO);
close(pfd[READ]);
execve(cmd2, p2, environ);
perror(cmd2);
default: //Parent
close(pfd[READ]);
dup2(pfd[WRITE], STDOUT_FILENO);
close(pfd[WRITE]);
execve(cmd1, p1, environ);
perror(cmd1);
case -1: //ERROR
perror("fork-RP");
exit(1);
}
}
There are a couple of things going on there that are contributing to the unexpected behavior.
The first is that you're forking too much. If you unroll your runpipe() function call into the switch statement in main(), you'll see that you reach the great-grandchild level:
switch (pid0 = fork())
{
case 0: // Child
switch (pid1 = fork())
{
case 0: // GRAND-Child
// function call to runpipe()
switch (pid = fork())
{
case 0: // GREAT-GRAND-Child
close(pfd[WRITE]);
dup2(pfd[READ], STDIN_FILENO);
close(pfd[READ]);
execve(cmd2, p2, environ);
perror(cmd2);
default: // GRAND-Child
close(pfd[READ]);
dup2(pfd[WRITE], STDOUT_FILENO);
close(pfd[WRITE]);
execve(cmd1, p1, environ);
perror(cmd1);
Which is not necessary. Fork once in main() and then call your runpipe() function.
Related to this issue is where you're creating your pipe. When you fork, the newly created child process inherits all of the parent process's open files (among many other things). This includes the default descriptors 0, 1, and 2 (stdin, stdout, and stderr), as well as any other open files, including the pipe you created called fd. This means that the parent, child, grandchild, and great-grandchild are all inheriting a copy of both ends of the pipe. You correctly close the unused ends inside the runpipe() function (the grandchild's and great-grandchild's copies), but the parent and child in your main() function also have copies!
Since the only pair of processes using the pipe are those created in runpipe(), you can move the declaration of fd and the call to pipe(2) into that function.
These two modifications will resolve your issues.
A completely unrelated issue that just relates to the flow of your shell is that your main() ends up doing its wait(2) on the "parent" process of the runpipe() function. Since that parent is the one running cmd1, your shell is going to return its prompt as soon as cmd1 finishes, instead of when the last command (cmd2 in this case) in the pipeline finishes. You can see the behavioral difference by running something like echo | sleep 10 into your shell and a real shell.
The dup function duplicates a file descriptor, and returns the new duplicate. However, this will not work, as stdin in the child still exists, and the new file descriptor will not be put in place of the standard input.
You must close the standard input file descriptor first, before doing dup. Or use dup2 which will close the destination file descriptor automatically first before doing the duplication:
dup2(pfd[0], STDIN_FILENO);

Resources