My goal is to mimic the linux pipe command for example ls | sort. instead of simply sort. but instead of typing | the user types : for example "./program ls : sort" == "ls | sort"
I need to complete this task using a fork() and a pipe(). i have a MRE setup that only allows me to run one command at a time but i have no idea as to how to make it to where the stdout is the stdin for the second command. whenever i attempt to dup() close() and exec()
in the parent something seems to be going wrong? I have a setup where parse the input given by the user and i acquire argA for argument A which contains the command like ls or sort, and ArgAP for argument A parameter in case the user wants to specify -lh or -r etc. same thing for argB.
I have this program currently setup to be hardcoded to execute the bc command but that can be easily fixed by assigning some parameters around. please help me as i am firmly stuck on this!
//################ #-for include
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
#include <dirent.h>
//################
int main(int b, char ** locations) {
int ok = 0;
int dots = 0;
char argA[1000];
char argB[1000];
char argAP[1000];
char argBP[1000];
while (locations[ok] != NULL) {
//printf("%s \n", locations[ok]);
if (strcmp(locations[ok], ":") == 0) {
dots = 1;
}
ok++;
} //printf("%s %d \n", locations[2], b);
strcpy(argA, "");
strcpy(argB, "");
strcpy(argAP, "");
strcpy(argBP, "");
if (dots == 0) {
int x = 1;
strcat(argA, locations[x]);
strcat(argA, " ");
x++;
while (locations[x] != NULL) {
strcat(argAP, locations[x]);
strcat(argAP, " ");
x++;
}
printf("%s%s \n", argA, argAP);
}
if (dots == 1) {
int x = 1;
int compare = strcmp(locations[x], ":");
if (strcmp(locations[1], ":") == 0) {
printf("one arg\n");
strcat(argA, locations[x]);
strcat(argA, " ");
x++;
while (locations[x] != NULL) {
strcat(argAP, locations[x]);
strcat(argAP, " ");
x++;
}
printf("%s%s \n", argA, argAP);
} else {
printf("two args\n");
strcat(argA, locations[x]);
strcat(argA, " ");
compare = strcmp(locations[x], ":");
x++;
compare = strcmp(locations[x], ":");
while (compare != 0) {
printf("%d \n", x);
strcat(argAP, locations[x]);
strcat(argAP, " ");
compare = strcmp(locations[x], ":");
x++;
}
printf("argA: %s%s \n", argA, argAP);
x++;
strcat(argB, locations[x]);
strcat(argB, " ");
x++;
while (locations[x] != NULL) {
strcat(argBP, locations[x]);
strcat(argBP, " ");
x++;
}
printf("argB: %s%s \n", argB, argBP);
}
}
// fork/piping
int i, n;
int fd[2];
pipe(fd);
int rd = fd[0]; // rd points to 0 (read) in pipe
int wt = fd[1]; // wt points to 1 (write) in pipe
if (fork()) {
close(rd);
write(wt, "2*1*9*1", strlen("2*1*9*1"));
write(wt, "\n", 1);
close(wt);
exit(0);
} else {
close(wt);
close(0); // close zero
dup(rd); // dup rd into lowest possible orbit
close(rd);
execlp("bc", "bc", 0, NULL); // reading from zero means reading from rd!
exit(1);
}
return 0;
}
I have commented off the section where i need the help. how do i feed the results through the fork using the pipe of one command into the second? I deem this as nearly impossible as through my attempts i have only been able to get only one command working through this setup that i have written here.
Once you want to replace the stdin
of the child process, you need to use dup2()
function.
Here is the manual section that explains why the dup()
function will never work for your purposes:
The dup() system call creates a copy of the file descriptor oldfd, using the lowest-numbered unused file descriptor for the new descriptor.
Here is the manual section that explains why the dup2()
function can solve your problem:
The dup2() system call performs the same task as dup(), but instead of using the lowest-numbered unused file descriptor, it uses the file descriptor number specified in newfd.
To solve your problem, replace the dup(rd)
call for dup2(rd, STDIN_FILENO)
. You may also remove the close(0)
call, once the dup2()
function closes the newfd
if it is already in use.
If the file descriptor newfd was previously open, it is silently closed before being reused.
What I previously wrote does not fix the problem, once close(0); dup(rd);
will have the same effect as dup2(rd, 0)
, as this user mentioned below. So, I compiled your code as it is and, after running, I
had this result:
$ gcc -std=c99 -o program program.c
$ ./program ls : sort
two args
argA: ls
argB: sort
18
$
As you can see, the last line shows 18
, the result of 2*1*9*1
.
Now, notice that the parent process exits right after it writes to the file described as wt
- the new stdin
of bc
command being executed in the child process. This means that the parent process may exit before the child process is done. I highly recommend you to test your code using a wait()
or waitpid()
call right before the parent process exits. For example:
// (...)
if (fork()) {
close(rd);
write(wt, "2*1*9*1", strlen("2*1*9*1"));
write(wt, "\n", 1);
close(wt);
wait(NULL);
exit(0);
} else {
close(wt);
close(0); // close zero
dup(rd); // dup rd into lowest possible orbit
close(rd);
execlp("bc", "bc", NULL);
exit(1);
}
I also replaced the line execlp("bc", "bc", 0, NULL);
with the line execlp("bc", "bc", NULL);
. The zero I removed is equivalent to NULL
and means the end of the argument list for the command being executed with execlp()
.
Reading the entire code, we can divide your implementation in two parts:
execlp()
function;If you read the man pages of the exec()
function family, you will notice that the function execvp()
is way more useful in this program, since the second argument of the execvp()
function is the same type as the program's arguments: an array of strings NULL
-terminated.
Following this steps, you can easily parse the program's arguments to fit in the execvp()
:
NULL
to signalize the end of the first command's arguments;After parsing the program's arguments, it is time to create a pipe and fork the process. In the child process, replace the stdout
with the write-end of the pipe before executing the first command. In the parent process, replace the stdin
with the read-end of the pipe before executing the second command.
Here is the entire code I wrote, ran and tested:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#define PIPE_SYMBOL ":"
int main ( int argc , char **argv ) {
/* Validates the usage. At least is needed the program's name, two commands and the pipe symbol */
if ( argc < 4 ) {
fprintf(stderr, "usage: command-1 [args-1...] : command-2 [args-2...]\n");
return EXIT_FAILURE;
}
/* The start of the first comment is allways the start of the program arguments array */
char **command1 = &argv[1];
/* The start of the second command is undefined, once it depends where the pipe symbol is located */
char **command2 = NULL;
/* Finds the position of the pipe symbol */
for ( int i = 0 ; argv[i] != NULL ; i++ ) {
/* When found, ... */
if ( strcmp(PIPE_SYMBOL, argv[i]) == 0 ) {
/* ... replaces it for NULL, so the first command array is NULL terminated and... */
argv[i] = NULL;
/* ... the next position is the start of the second command */
command2 = &argv[i+1];
break;
}
}
/* If the pipe symbol is missing or if there is no command after the pipe symbol, bad usage */
if ( command2 == NULL || command2[0] == NULL ) {
fprintf(stderr, "usage: command-1 [args-1...] : command-2 [args-2...]\n");
return EXIT_FAILURE;
}
pid_t pid;
int pipefd[2];
if ( pipe(pipefd) == -1 ) {
perror("creating pipe");
return EXIT_FAILURE;
}
if ( (pid = fork()) == -1 ) {
perror("creating child process");
return EXIT_FAILURE;
}
/* Child process executes the first command */
if ( pid == 0 ) {
close(pipefd[0]);
close(STDOUT_FILENO);
dup(pipefd[1]);
close(pipefd[1]);
execvp(command1[0], command1);
perror("executing first command");
return EXIT_FAILURE;
}
/* Parent process executes the second command */
close(pipefd[1]);
close(STDIN_FILENO);
dup(pipefd[0]);
close(pipefd[0]);
execvp(command2[0], command2);
perror("executing second command");
return EXIT_FAILURE;
}