Related
I'm writing my own custom shell, and as a part of my project, when the program reads either "<" or ">" character from user input, it needs to redirect stdin and stdout to a custom file. I wrote a function for this below. The part where I'm struggling is that even though I wrote the code for input and output redirections pretty much in the same manner, output redirection seems to work as expected, while input redirection does not. See the code below:
void inOutReDirector(char **toks, char *inputFile, char *outputFile, int *fd0, int *fd1, int *in, int *out) {
fflush(0);
for (int i = 0; toks[i] != NULL; i++) {
if (strcmp(toks[i], "<") == 0) {
toks[i] = NULL;
strcpy(inputFile, toks[i + 1]);
toks[i + 1] = NULL;
*in = 1;
}
if (strcmp(toks[i], ">") == 0) {
toks[i] = NULL;
strcpy(outputFile, toks[i + 1]);
toks[i + 1] = NULL;
*out = 1;
}
}
//input redirection
if (*in == 1) {
if ((*fd0 = open(inputFile, O_RDONLY, 0)) < 0) {
printf("Couldn't open input file: %s", strerror(errno));
exit(1);
}
dup2(*fd0, STDIN_FILENO);
close(*fd0);
}
//output redirection
if (*out == 1) {
if ((*fd1 = creat(outputFile, 0644)) < 0) {
printf("Couldn't open output file: %s", strerror(errno));
exit(1);
}
dup2(*fd1, STDOUT_FILENO);
close(*fd1);
}
}
And here is how I call this function from my main:
int main() {
char *toks[STD_INPUT_SIZE] = {0};
int fd0, fd1, in = 0, out = 0;
char inputFile[64], outputFile[64];
pid_t pid;
while (1) {
//print prompt
//get user input
pid = fork();
if (pid < 0) {
printf("%s \n", strerror(errno));
exit(1);
}
int stopNeeded = strcmp(toks[0], "exit") == 0;
if (pid == 0 && !stopNeeded) {
pathFinder(toks, shellInput, currentDir); //finding path for execv input
inOutReDirector(toks, inputFile, outputFile, &fd0, &fd1, &in, &out); // I/O redirection
if (execv(shellInput, toks) != 0) {
char *errMsg = strerror(errno);
printf("%s \n", errMsg);
//clean the old contents of toks
for (int i = 0; i < STD_INPUT_SIZE; ++i) {
free(toks[i]);
toks[i] = NULL;
}
exit(1);
}
}
if (pid > 0) {
pid_t childPid = waitpid(pid, NULL, 0);
(void) childPid;
}
}
return 0;
}
And here is an example of output redirection from my terminal screen
$ ls > output.txt
$
This creates "output.txt" file and prints the result inside this file in the current directory.
And here is an example of input redirection from my terminal screen
$ cat < output.txt
$
Input redirection does not work correctly. It prints the prompt and waits for the input instead of showing the contents of output.txt in my terminal.
I appreciate any help you can provide in advance!
The problem should be on this line of the function inOutReDirector
if (strcmp(toks[i], ">") == 0) {
should be changed to
else if (strcmp(toks[i], ">") == 0) {
When toks[i] is equal to "<", toks[i] will be set to NULL above, this line calling strcmp will cause SIGSEGV, so the child process will exit, and the subsequent execv will not be executed.
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);
}
I'm trying to implement a C shell that allows for unlimited unidirectional pipes using the character '>'
So it can handle ls -A > tail > grep '.zip'
I understand that pipes are supposed to talk between processes, but I thought I came up with an idea that could use one pipe and multiple children.
This is what I have so far
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
/*#include <wait.h>*/
char *args[1000][1000];//array of arguments
int args_count = 0;//count of the arguments in the array
int runCommand(char **arguments, int *fd, int pipeHasSomeData, int baseCase) {
pid_t pid;
int x = 0;
int status;
pid = fork();
if(pid != 0) {
waitpid(pid, &status, 0);
if(baseCase) {
if(WIFEXITED(status))
{
if(WEXITSTATUS(status) == 0)
{
/*it worked*/
} else if(WEXITSTATUS(status) == 255) {
printf("The program %s does not exist \n", arguments[0]);
} else {
printf("ERROR: Error code: %d", WEXITSTATUS(status));
}
}
else
{
printf("There was a problem that is not normal");
}
printf("\n \n");
}
return 1;
} else {
if(pipeHasSomeData == 1) {// read from the pipe
dup2(fd[0], 0);//read from pipe
}
if(baseCase == 0) {// not the base case
dup2(fd[1], 1);//write to pipe
} else {
close(fd[1]);//close write
}
exit(execvp(arguments[0], arguments));
return 0;
}
}
int execute_commands(char *arguments[1000][1000], int pd[2] = NULL) {
int current_count = args_count;
int iterator = 0;
int fd[2];
int useAPipeInCommand = 0;
pipe(fd);
while(iterator <= args_count) {//go through and execute all the commands
if(current_count == 0) {//base case
return runCommand(arguments[iterator], fd, useAPipeInCommand, 1);
} else {
runCommand(arguments[iterator], fd, useAPipeInCommand, 0);
useAPipeInCommand = 1;
}
iterator++;
current_count--;
}//end while
return 1;
}
int main () {
int i = 0;
char text[1024]; /* the input line */
char *tok2;
while (1) { /* repeat until done .... */
fflush(stdin);
fflush(stdout);
printf("Shell -> "); /* display a prompt */
*text = 0;
fgets(text, sizeof text, stdin); /* read in the command line */
fflush(stdout);
printf("\n");
char * tok = strtok(text, " \n\t");
if (strcmp(tok, "exit") == 0) { /* is it an "exit"? */
return 0; /* exit if it is */
}
if (strcmp(tok, " ") == 0) { /* is it an "exit"? */
continue; /* exit if it is */
}
tok2 = tok;
memset(args, 0, sizeof(args[0][0]) * 1000 * 1000);//clear the arguments array
args_count = 0;
int count = 0;
while(tok2 != NULL) {
if(strcmp(tok2, ">") != 0) {
args[args_count][count] = tok2;
count++;
tok2 = strtok(NULL, " \n\t");
} else {//pipe was found, up the argument counter and set count to 0
args[args_count][count] = NULL;
args_count++;
count = 0;
tok2 = strtok(NULL, " \n\t");
}
}
args[args_count][count] = NULL;
execute_commands(args);
}//end while
return 0;
}
It is running the single base case no problem but the shell freezes when I do a pipe. Any ideas on the issue?
Correct answer from Comments by #beau-bouchard and #rici:
Pipes have a (small) finite buffer; you cannot write more than a little bit to the pipe without blocking unless the other end of the pipe is being read.
For a correct implementation, check out "multiple pipes in C" Coding multiple pipe in C
--UPDATE:
Here is my final working code for anyone that is having a similar issue:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <wait.h>
int READ = 0;
int WRITE = 1;
char *args[1000][1000];//array of arguments
int args_count = 0;//count of the arguments in the array
int execute_commands(char *arguments[1000][1000]) {
int pd[2];
int iterator = 0;
int fd[2];
int f_in = 0;
while(iterator <= args_count) {//go through and execute all the commands
pid_t pid;
int status;
pipe(fd);
pid = fork();
if(pid != 0) {
waitpid(pid, &status, 0);//wait for child to exit
close(fd[WRITE]);//close the writing end
if(WIFEXITED(status))
{
if(WEXITSTATUS(status) == 0)
{
/*it worked*/
} else if(WEXITSTATUS(status) == 255) {
printf("The program %s does not exist \n", arguments[iterator][0]);
} else {
printf("ERROR: Error code: %d", WEXITSTATUS(status));
}
}
else
{
printf("There was a problem that is not normal %d", status);
}
f_in = fd[READ];//set the pipe to the in
if(iterator == args_count) {
printf("\n \n");
}
//return 1;
} else {
dup2(f_in, 0);
if(iterator != args_count) {//its not the main value
dup2(fd[WRITE], 1);//write to pipe
}
close(fd[READ]);
exit(execvp(arguments[iterator][0], arguments[iterator]));
return 0;
}
iterator++;
}//end while
return 1;
}
int main () {
int i = 0;
char text[1024]; /* the input line */
char *tok2;
while (1) { /* repeat until done .... */
fflush(stdin);
fflush(stdout);
printf("Shell -> "); /* display a prompt */
*text = 0;
fgets(text, sizeof text, stdin); /* read in the command line */
fflush(stdout);
printf("\n");
char * tok = strtok(text, " \n\t");
if (strcmp(tok, "exit") == 0) { /* is it an "exit"? */
return 0; /* exit if it is */
}
if (strcmp(tok, " ") == 0) { /* is it an "exit"? */
continue; /* exit if it is */
}
tok2 = tok;
memset(args, 0, sizeof(args[0][0]) * 1000 * 1000);//clear the arguments array
args_count = 0;
int count = 0;
while(tok2 != NULL) {
if(strcmp(tok2, ">") != 0) {
args[args_count][count] = tok2;
count++;
tok2 = strtok(NULL, " \n\t");
} else {//pipe was found, up the argument counter and set count to 0
args[args_count][count] = NULL;
args_count++;
count = 0;
tok2 = strtok(NULL, " \n\t");
}
}
args[args_count][count] = NULL;
execute_commands(args);
}//end while
return 0;
}
I am trying to write a C program that uses pipes to send information between the parent and two children. The goal of the program is to achieve something similar to merge sort, for strings. I read the number of strings and then the Strings. The strings get divided between the 2 children, recursively until each child has only one string. I have to redirect the stdin of the child to read from the stdout of the parent.
For some reason none of the children read more than the first string.
How could I solve this problem?
int main(int argc, char * argv[]) {
int nrrows = 0;
char * buffer = NULL;
size_t n = 0;
getline(&buffer, &n, stdin);
char * endptr;
nrrows = strtol(buffer, &endptr, 10);
char rows[nrrows][MAX_LEN];
int i = 0;
n = 0;
while(i < nrrows) {
char * row = NULL;
getline(&row, &n, stdin);
strcpy(rows[i], row);
i++;
}
if(nrrows == 1) {
fprintf(stderr, "%s", rows[0]);
return 0;
}
int fdcp1[2];
int fdcp2[2];
if(pipe(fdcp1) < 0) {
fprintf(stderr, "pipe unsuccessfull\n");
return EXIT_FAILURE;
}
if(pipe(fdcp2) < 0) {
fprintf(stderr, "pipe unsuccessfull\n");
return EXIT_FAILURE;
}
pid_t chpid1 = fork();
if(chpid1 < 0) {
fprintf(stderr, "fork unsuccessfull\n");
return EXIT_FAILURE;
}
else if(chpid1 == 0) {
close(fdcp2[0]);
close(fdcp2[1]);
close(fdcp1[1]);
dup2(fdcp1[0], STDIN_FILENO);
execlp("./forksort", "child1", NULL);
}else {
close(fdcp1[0]);
dup2(fdcp1[1], STDOUT_FILENO);
double half = (nrrows / 2);
int h = half;
char b[2];
b[0] = '0' + h;
b[1] = '\n';
write(fdcp1[1], b, sizeof(b));
for(i = 0; i < h; i ++) {
rows[i][strlen(rows[i])] = '\0';
write(fdcp1[1], rows[i], sizeof(rows[i]));
}
pid_t chpid2 = fork();
if(chpid2 < 0) {
fprintf(stderr, "fork unsuccessfull\n");
return EXIT_FAILURE;
}else if(chpid2 == 0) {
close(fdcp1[0]);
close(fdcp1[1]);
close(fdcp2[1]);
dup2(fdcp2[0], STDIN_FILENO);
execlp("./forksort", "child2", NULL);
}else {
close(fdcp2[0]);
dup2(fdcp2[1], STDOUT_FILENO);
half = (nrrows / 2);
h = half;
char b[2];
b[0] = '0' + (nrrows - h);
b[1] = '\n';
write(fdcp2[1], b, sizeof(b));
for(i = h; i < nrrows; i ++) {
rows[i][strlen(rows[i])] = '\0';
write(fdcp2[1], rows[i], sizeof(rows[i]));
}
}
}
return 0;
}
It's bad news to modify a file descriptor that is associated with an open stream. I would account it highly likely to cause you trouble, and there is, moreover, no need to do that here. The parent should instead use fdopen() to open new streams on top of its ends of the pipes, and conduct I/O with its children via those instead of via the standard streams. In addition to being safer, that leaves the process's original standard streams available for it to communicate with its parent process.
With that approach, you could even stream the strings to be sorted back and forth among the processes, instead of redundantly buffering blocks of them in each process's memory. For instance, you might do something like this:
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char * argv[]) {
char * buffer = NULL;
size_t buflen = 0;
int nrrows;
int fdpc1[2];
int fdcp1[2];
int fdpc2[2];
int fdcp2[2];
pid_t chpid1;
pid_t chpid2;
FILE *pipeout;
FILE *pipein1;
FILE *pipein2;
int half;
int i;
fprintf(stderr, "%s!!!!!!!!!!!!!!!!!\n", argv[0]);
getline(&buffer, &buflen, stdin);
fprintf(stderr, "number: %s from %s\n", buffer, argv[0]);
nrrows = strtol(buffer, NULL, 10);
if(nrrows <= 0) {
fprintf(stderr, "This is not a valid >0 number\n");
return EXIT_FAILURE;
} else if (nrrows == 1) {
/* ... read and echo back the one row ... */
getline(&buffer, &buflen, stdin);
fprintf(stderr, "%s", buffer);
return EXIT_SUCCESS;
}
/* There are at least two rows to sort */
if (pipe(fdcp1) < 0) {
fprintf(stderr, "pipe unsuccessfull\n");
return EXIT_FAILURE;
}
if (pipe(fdpc1) < 0) {
fprintf(stderr, "pipe unsuccessfull\n");
return EXIT_FAILURE;
}
chpid1 = fork();
if (chpid1 == 0) {
/* this is child process 1 */
close(fdcp1[1]);
close(fdpc1[0]);
dup2(fdcp1[0], STDIN_FILENO);
close(fdcp1[0]);
dup2(fdpc1[1], STDOUT_FILENO);
close(fdpc1[1]);
execlp("./forksort", "child1", NULL);
} else if (chpid1 < 0) {
fprintf(stderr, "fork unsuccessfull\n");
return EXIT_FAILURE;
}
/* this is the parent process */
close(fdcp1[0]);
close(fdpc1[1]);
if (pipe(fdcp2) < 0) {
fprintf(stderr, "pipe unsuccessfull\n");
return EXIT_FAILURE;
}
if (pipe(fdpc2) < 0) {
fprintf(stderr, "pipe unsuccessfull\n");
return EXIT_FAILURE;
}
chpid2 = fork();
if (chpid2 == 0) {
/* this is child process 2 */
close(fdcp1[1]);
close(fdpc1[0]);
close(fdcp2[1]);
close(fdpc2[0]);
dup2(fdcp2[0], STDIN_FILENO);
close(fdcp2[0]);
dup2(fdpc2[1], STDOUT_FILENO);
close(fdpc2[1]);
execlp("./forksort", "child2", NULL);
} else if (chpid2 < 0) {
fprintf(stderr, "fork unsuccessfull\n");
return EXIT_FAILURE;
}
/* this is the parent process */
close(fdcp2[0]);
close(fdpc2[1]);
/* copy the first half of the lines from input to child 1 */
pipeout = fdopen(fdcp1[1], "w");
if (pipeout == NULL) {
fprintf(stderr, "fdopen unsuccessful\n");
return EXIT_FAILURE;
}
half = nrrows / 2;
fprintf(pipeout, "%d\n", half);
for (i = 0; i < half; i += 1) {
getline(&buffer, &buflen, stdin);
fprintf(stderr,"row[%d] from %s: %s", i, argv[0], buffer);
fputs(buffer, pipeout);
}
fclose(pipeout);
/* copy the second half of the lines from input to child 2 */
pipeout = fdopen(fdcp2[1], "w");
if (pipeout == NULL) {
fprintf(stderr, "fdopen unsuccessful\n");
return EXIT_FAILURE;
}
fprintf(pipeout, "%d\n", nrrows - half);
for (; i < nrrows; i += 1) {
getline(&buffer, &buflen, stdin);
fprintf(stderr,"row[%d] from %s: %s", i, argv[0], buffer);
fputs(buffer, pipeout);
}
fclose(pipeout);
/* now read and merge sorted lines from the children */
pipein1 = fdopen(fdpc1[0], "r");
pipein2 = fdopen(fdpc2[0], "r");
if (pipein1 == NULL || pipein2 == NULL) {
fprintf(stderr, "fdopen unsuccessful\n");
return EXIT_FAILURE;
}
/* ... */
fclose(pipein1);
fclose(pipein2);
return 0;
}
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);