Program crash in forking process with pipes - c

I'm writing a basic shell for course homework that will find a command in the given list of paths, and execute the command. It is also meant to handle pipes.
However, when I fork a child process, I get a "Write error : Broken Pipe" message in gdb, and the program terminates abruptly.
I cannot seem to understand why this is happening, since I've been cautious about opening and closing correct pipes and process forking seems to work as desired. Can someone with more experience in C and unix programming please help me diagnose the problem? Is there something logically incorrect with my fork implementation / pipe implementation?
//commands is of the format {"ls -al", "more", NULL}
//it represents commands connected by pipes, ex. ls -al | more
char **commands = parseArgv(consoleinput, SPECIAL_CHARS[4]);
int numcommands = 0;
while( commands[numcommands]!=NULL )
{
numcommands++;
}
const int numpipes = 2*(numcommands-1);
int pipefds[numpipes];
int i=0;
for(i=0; i<numpipes;i=i+2)
{
pipe(pipefds+i);
}
int pipe_w = 1;
int pipe_r = pipe_w - 3;
int curcommand = 0;
while(curcommand < numcommands)
{
if(pipe_w < numpipes)
{
//open write end
dup2(pipefds[pipe_w], 1);
}
if(pipe_r > 0)
{
//open read end
dup2(pipefds[pipe_r], 0);
}
for(i=0;i<numpipes;i++) //close off all pipes
{
close(pipefds[i]);
}
//Parse current command and Arguments into format needed by execv
char **argv = parseArgv(commands[curcommand], SPECIAL_CHARS[0]);
//findpath() replaces argv[0], i.e. command name by its full path ex. ls by /bin/ls
if(findPath(argv) == 0)
{
int child_pid = fork();
//Program crashes after this point
//Reason: /bin/ls: write error, broken pipe
if(child_pid < 0)
{
perror("fork error:");
}
else if(child_pid == 0) //fork success
{
if(execv(argv[0], argv) == -1)
{
perror("Bad command or filename:");
}
}
else
{
int child_status;
child_pid = waitpid(child_pid, &child_status, 0);
if(child_pid < 0)
{
perror("waitpid error:");
}
}
}
else
{
printf("Bad command or filename");
}
free(argv);
curcommand++;
pipe_w = pipe_w + 2;
pipe_r = pipe_r + 2;
}
//int i=0;
for(i=0;i<numpipes;i++) //close off all pipes
{
close(pipefds[i]);
}
free(commands);

Duplicating the file descriptors after the fork() call, i.e. in the child process, is the correct way.
Also, the waitpid() call makes one child process wait for the other, and the shell hangs. The wait() call should be moved to after the loop, i.e. the parent should wait for all the children.

Related

Minibash in C, problem making pipes between execvp and parent proccess

I have to do this as a university project so I cant share the whole code, im sorry for that.
I have to create a function called "read" that enables the user to create new env variables, thats the easy part. The problem comes when I call that function as the last one of the commands array e.g "ls | grep aux.txt | read a" this should give the env var A the value aux.txt, the problem is that it get stuck in the
fgets(value, sizeof(value),stdin);
and I cant even recover the terminal.
Thanks in advance for the help if you need more info about the problem I will happily give it.
I can't reproduce exactly the main function as there are parts that are not mine but I hope this helps:
char **argvv;
int fd[2][2];
int pid;
int main(int argc, char ***argvv) {
argvv[0][0] = "echo";
argvv[0][1] = "elpmaxe";
argvv[1][0] = "rev";
argvv[2][0] = "read";
argvv[2][1] = "a";
for (int i = 0; i < 2; i++) {
pipe(fd[i]);
}
for(int i = 0; i< 3; i++){
pid = fork();
if(pid == 0){
if(i ==0){
dup2(fd[0][1], 1);
fun_close(fd);
execvp(argvv[0][0], argvv[0]);
}
if(i == 1){
dup2(fd[0][0], 0);
dup2(fd[1][1], 1);
fun_close(fd);
execvp(argvv[1][0], argvv[0]);
}
}else{
if(i == 2){
close(fd[0][1]);
close(fd[0][0]);
fun_read("read a", 3, fd[1]);
}
}
}
int corpse;
int status;
while ((corpse = wait(&status)) > 0)
printf("Child %d exited with status 0x%.4X\n", corpse, status);
return 0;
void fun_close(int **fd){
close(fd[0][0]);
close(fd[0][1]);
close(fd[1][0]);
close(fd[1][1]);
}
And here is the fun_read:
int fun_read(char **command, int argc, int fd[]){
char **env_varv;
char value[1024];
char last_var[1024];
long size = 0;
char *token;
int status;
char *delim = " \t\n";
env_varv = malloc((argc-1) * sizeof(char *));
for(int i = 1; i < argc; i++){
env_varv[i-1] = strdup(command[i]);
wait(status);
}
if (fd[0] !=0){
printf("%d\n", fd[0]);
dup2(fd[0],0);
close(fd[0]);
close(fd[1]);
}
fgets(value, sizeof(value),stdin);
int i = 0;
token = strtok(value, delim);
last_var[0] = '\0';
while(token != NULL){
if(i == argc-2){
while (token != NULL){
strcat(last_var,token);
setenv(env_varv[i],last_var,1);
token = strtok(NULL,delim);
strcat(last_var," ");
}
}
else if (env_varv[i] != NULL){
setenv(env_varv[i],token,1);
token = strtok(NULL,delim);
i++;
}
else{
break;
}
}
return 0;
The program should put an envariomental variable called a with the value of example.
postscript: it seems like there is no problem if the previous command is a builtin "echo hi | echo hi2 | read a" $a=hi2
Sincerely I have tried all, changing the pipes doesnt work, changing fgets for read doesn't help either. Is the only part of the code I haven't been able to fix
This fragment of code shows some problems:
char ***argvv;
int fd[2][2];
int pid;
int main(int argc, char ***argvv) {
argvv[0][0] = "echo";
argvv[0][1] = "elpmaxe";
argvv[1][0] = "rev";
argvv[2][0] = "read";
argvv[2][1] = "a";
for (int i = 0; i < 2; i++) {
pipe(fd[i]);
}
for(int i = 0; i< 3; i++){
pid = fork();
if(pid == 0){
if(i ==0){
close(fd[0][0]);
close(fd[1][1]);
close(fd[1][0]);
dup2(fd[0][1], 1);
execvp(argvv[0][0], argvv[0]);
}
if(i = 1){
close(fd[0][1]);
close(fd[1][0]);
dup2(fd[0][0], 0);
dup2(fd[1][1], 1);
execvp(argvv[1][0], argvv[0]);
}
if(i = 2){
close(fd[0][1]);
close(fd[0][0]);
close(fd[1][1]);
dup2(fd[1][0], 0);
fun_read("read a", 3, fd[1]);
}
}
}
Rule of Thumb
You aren't closing enough pipe file descriptors in any of the processes.
If you dup2()
one end of a pipe to standard input or standard output, close both of the
original file descriptors returned by
pipe()
as soon as possible.
In particular, you should close them before using any of the
exec*()
family of functions.
The rule also applies if you duplicate the descriptors with either
dup()
or
fcntl()
with F_DUPFD or F_DUPFD_CLOEXEC.
Other comments on the use of pipes
If the parent process will not communicate with any of its children via
the pipe, it must ensure that it closes both ends of the pipe early
enough (before waiting, for example) so that its children can receive
EOF indications on read (or get SIGPIPE signals or write errors on
write), rather than blocking indefinitely.
Even if the parent uses the pipe without using dup2(), it should
normally close at least one end of the pipe — it is extremely rare for
a program to read and write on both ends of a single pipe.
Note that the O_CLOEXEC option to
open(),
and the FD_CLOEXEC and F_DUPFD_CLOEXEC options to fcntl() can also factor
into this discussion.
If you use
posix_spawn()
and its extensive family of support functions (21 functions in total),
you will need to review how to close file descriptors in the spawned process
(posix_spawn_file_actions_addclose(),
etc.).
Note that using dup2(a, b) is safer than using close(b); dup(a);
for a variety of reasons.
One is that if you want to force the file descriptor to a larger than
usual number, dup2() is the only sensible way to do that.
Another is that if a is the same as b (e.g. both 0), then dup2()
handles it correctly (it doesn't close b before duplicating a)
whereas the separate close() and dup() fails horribly.
This is an unlikely, but not impossible, circumstance.
Analyzing your code
The parent process has the pipes open; if the commands are reading from the pipes, they won't get EOF until the parent process closes them. Although you close most of the pipes in the child processes, you don't close those that you duplicate to the standard I/O channels — and yet that is required too.
Note that if (i = 1) should be if (i == 1), and if (i = 2) should be if (i == 2). The first of those bugs prevents your fun_read() from being invoked — which is why it isn't responding. Using diagnostic printing to standard error would confirm that fun_read() is never called.
So, at bare minimum, you need to have code like this:
char ***argvv;
int fd[2][2];
int pid;
int main(int argc, char ***argvv)
{
argvv[0][0] = "echo";
argvv[0][1] = "elpmaxe";
argvv[1][0] = "rev";
argvv[2][0] = "read";
argvv[2][1] = "a";
for (int i = 0; i < 2; i++)
{
pipe(fd[i]);
}
for (int i = 0; i < 3; i++)
{
pid = fork();
if (pid == 0)
{
if (i == 0)
{
dup2(fd[0][1], 1);
close(fd[0][0]);
close(fd[0][1]);
close(fd[1][0]);
close(fd[1][1]);
execvp(argvv[0][0], argvv[0]);
fprintf(stderr, "failed to execute %s\n", argvv[0][0]);
exit(EXIT_FAILURE);
}
if (i == 1)
{
dup2(fd[0][0], 0);
dup2(fd[1][1], 1);
close(fd[0][0]);
close(fd[0][1]);
close(fd[1][0]);
close(fd[1][1]);
execvp(argvv[1][0], argvv[0]);
fprintf(stderr, "failed to execute %s\n", argvv[1][0]);
exit(EXIT_FAILURE);
}
if (i == 2)
{
dup2(fd[1][0], 0);
close(fd[0][0]);
close(fd[0][1]);
close(fd[1][0]);
close(fd[1][1]);
fun_read("read a", 3, fd[1]);
exit(EXIT_SUCCESS);
}
}
}
close(fd[0][0]);
close(fd[0][1]);
close(fd[1][0]);
close(fd[1][1]);
/* wait loop here - and not before */
int corpse;
int status;
while ((corpse = wait(&status)) > 0)
printf("Child %d exited with status 0x%.4X\n", corpse, status);
return 0;
}
Note that it is important to handle failure to execute. And error messages should be reported to standard error, not to standard output.
Given that the same sequence of 4 calls to close() is made 4 times, a function to do the job seems appropriate. You could make it:
static inline void close_pipes(int fd[2][2])
{
close(fd[0][0]);
close(fd[0][1]);
close(fd[1][0]);
close(fd[1][1]);
}
There is a decent chance the compiler will inline the function, but it is easier to see that the same 4 descriptors are closed if one function always does the closing. For bigger arrays of pipes (more processes), you'd have a loop inside the close_pipes() function with a counter as well as the array.
There are still some issues to be resolved, notably with the fun_read() function. The fd[1] file descriptors were both closed, so passing those to fun_read() doesn't seem likely to be useful. Since fun_read() is executed in a separate process, any changes made by fun_read() won't be reflected in the parent process. There are probably other problems too.
AFAICT, on looking at fun_read() more closely, the fd argument should not be needed at all. The paragraph of code:
if (fd[0] != 0) {
printf("%d\n", fd[0]);
dup2(fd[0], 0);
}
is not useful. You've already redirected standard input so it comes from the pipe and then closed the pipe file descriptor. This paragraph then changes standard input to come from the closed descriptor, which isn't going to help anything. But none of this helps you with the fact that anything done by fun_read() is done in a child process of your shell, so the environment in the main shell is not going to be affected.

custom shell multiple pipes succeed, but no output to stdout

I am creating a custom shell and currently working on getting multiple piping to work. Eg, ls -al | wc -l returns the number of all file directories in current directory. I am closely following the "solution code" at this link.
Here is my implementation, with very similar command input structure:
// every command is a
// struct command
// with their arguments attached
struct command
{
char **cmd; // argument list for execvp(eg: {"ls", "-al"})
int numArgs; // number of total arguments in current line
};
// my implementation of multiple pipes based on link supplied with slight modifications
void runPipedCommands(struct command *commands)
{
// this code just counds the number of pipes in commands
int numPipes = 0;
for (int i = 0; i < commands->numArgs; i++)
{
if (!strcmp(commands[i].cmd[0], "|"))
numPipes++;
}
printf("number of pipes: %d\n", numPipes);
int status;
int i = 0;
pid_t pid;
int pipefds[2 * numPipes];
// create the pipes
for (i = 0; i < (numPipes); i++)
{
if (pipe(pipefds + i * 2) < 0)
{
perror("couldn't pipe");
exit(EXIT_FAILURE);
}
}
int j = 0;
int c = 0;
while (commands[c].cmd)
{
// skip the pipe operators
if (!strcmp(commands[c].cmd[0], "|"))
c++;
pid = fork();
if (pid == 0)
{
printf("cmd to execute: %s\n", commands[c].cmd[0]);
//if not last command
if (commands[c + 1].cmd[0])
{
// everyone even fd gets this, set stdout to write
// end of pipe
if (dup2(pipefds[j + 1], STDOUT_FILENO) < 0)
{
perror("dup2");
exit(EXIT_FAILURE);
}
}
else
{
exit(EXIT_SUCCESS);
}
//if not first command&& j!= 2*numPipes
if (j != 0)
{
// every odd fd gets this, set stdin to
// read end of pipe
if (dup2(pipefds[j - 2], STDIN_FILENO) < 0)
{
perror(" dup2");
exit(EXIT_FAILURE);
}
}
// close dup2ed fds
for (i = 0; i < 2 * numPipes; i++)
{
close(pipefds[i]);
}
// execvp
if (execvp(commands[c].cmd[0], commands[c].cmd) < 0)
{
perror(commands[c].cmd[0]);
exit(EXIT_FAILURE);
}
}
else if (pid < 0)
{
perror("error");
exit(EXIT_FAILURE);
}
c++;
j += 2;
}
/**Parent closes the pipes and wait for children*/
for (i = 0; i < 2 * numPipes; i++)
{
close(pipefds[i]);
}
for (i = 0; i < numPipes + 1; i++)
wait(&status);
}
// my main function with my test input:
int main(int argc, char *argv[])
{
// my parseW function simply returns a list of struct commands
struct command *results = parseW("ls -al | wc -l");
runPipedCommands(results);
return 0;
}
The above code with parseW omitted results in a terminal output of:
Everything looks correct in the execution of the pipes, and I've gone over the file descriptors (there are only 2 to account for in my specific test case). I'm not sure what I'm doing wrong.
Without posting parseW(), the content of a commands[] array is unknown.
Within runPipedCommands() while loop, there are 3 areas that need revisions:
Provided a commands[x].cmd[] may privately contain a pipe, there's nothing to execute
and no need to fork() and exit; instead add a continue :
// skip the pipe operators
if (!strcmp(commands[c].cmd[0], "|")) {
c++;
continue;
}
After a fork(), the child process tries to test the next element of
commands[], assumes that member .cmd is non-NULL and deferences
with .cmd[0]; provided the last element of commands[] contains a NULL .cmd,
the child would crash here:
if (commands[c + 1].cmd[0])
instead, update the test without the dereference:
if (commands[c + 1].cmd)
Related with the added continue above, when this updated .cmd test is false, there's no need to exit and prevent
the child from reaching the execvp(); delete this block:
else {
exit(EXIT_SUCCESS);
}
Also: when the parent calls wait(), the status can be checked with
macros and indicate whether the child terminated normally, see a
wait manual

execvp in C not going through ar

I'm trying to use exec to execute a list of commands given as arguments.
Example input when In run the program would be ./assn2 ls date.
When I do this only the first command is executed.
#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<unistd.h>
int main(int argc, char *argv[])
{
int args = argc-1;
pid_t childpid = fork();
// error
if (childpid < 0)
{
perror("fork() error");
exit(-1);
}
// parent process
if (childpid != 0)
{
printf("Parent Process started, now waiting for ID: %d\n", childpid);
wait(NULL);
printf("Parent Process resumeed. Child exit code 0. Now terminating\n");
exit(0);
}
// child process
if (args > 0)
{
printf("Child process has begun. %d argument/s provided\n", args);
int i;
for (i = 1; i <= argc; i++)
{
execlp(argv[i], argv[i], NULL);
}
execvp(argv[1], argv);
}
else
{
printf("No arguments provided, terminating child\n");
}
return 0;
}
Once the first child process execs (and succeeds), the for loop no longer continues because the an execlp would just replace the current process image with the command being exec'ed.
What you want to do is to loop over the command line arguments in the parent process and exec once for each of the command. Something like is probably what you're after:
for(int i = 1; i < argc; i++) {
pid_t pid = fork();
if (pid == 0) {
execlp(argv[i] ,argv[i], (char*)0);
perror("exec");
} else if (pid > 0) {
wait(NULL);
} else {
perror("fork");
exit(1);
}
}
What are you trying to achieve with the sequential calls to execlp() and execvp()? These functions are not meant to return. I think you should read the ref:
The exec() family of functions replaces the current process image with a new process image. [..] The exec() functions only return if an error has occurred.
As a result you cannot execute them one after another in the same process.
Read about fork():
fork() creates a new process by duplicating the calling process.
Moreover, here:
for(i = 1; i <= argc; i++)
you go out of bounds, since argv starts indexing from 0, and ends at argc - 1.
Chnage it to:
for(i = 1; i < argc; i++)

trying to run "ls | grep r" with "execvp()"

I created a pipe between two child processes,
first, I run ls, which writes to the proper fd,
then, I run grep r, which reads from the proper fd,
I can see in the terminal that the grep command works fine (the output)
The problem is that grep doesn't quit, it stays there, even though ls isn't running anymore
for other programs the pipe works fine..
for (i = 0; i < commands_num ; i++) { //exec all the commands instants
if (pcommands[i]._flag_pipe_out == 1) { //creates pipe if necessary
if (pipe(pipe_fd) == -1) {
perror("Error: \"pipe()\" failed");
}
pcommands[i]._fd_out = pipe_fd[1];
pcommands[i+1]._fd_in = pipe_fd[0];
}
pid = fork(); //the child exec the commands
if (pid == -1) {
perror("Error: \"fork()\" failed");
break;
} else if (!pid) { //child process
if (pcommands[i]._flag_pipe_in == 1) { //if there was a pipe to this command
if (dup2(pcommands[i]._fd_in, STDIN) == -1) {
perror("Error: \"dup2()\" failed");
exit(0);
}
close(pcommands[i]._fd_in);
}
if (pcommands[i]._flag_pipe_out == 1) { //if there was a pipe from this command
if (dup2(pcommands[i]._fd_out, STDOUT) == -1) {
perror("Error: \"dup2()\" failed");
exit(0);
}
close(pcommands[i]._fd_out);
}
execvp(pcommands[i]._commands[0] , pcommands[i]._commands); //run the command
perror("Error: \"execvp()\" failed");
exit(0);
} else if (pid > 0) { //father process
waitpid(pid, NULL, WUNTRACED);
}
}
//closing all the open fd's
for (i = 0; i < commands_num ; i++) {
if (pcommands[i]._fd_in != STDIN) { //if there was an other stdin that is not 0
close(pcommands[i]._fd_in);
}
if (pcommands[i]._fd_out != STDOUT) { //if there was an other stdout that is not 1
close(pcommands[i]._fd_out);
}
}
So, I have a "command" instant pcommands[i]
It has:
a flag of pipein,pipeout
fdin,fdout,
and a char** (for the real command, like "ls -l")
lets say everything is good,
that means that:
pcommands[0]:
pipein=0
pipeout=1
char** = {"ls","-l",NULL}
pcommands[1]:
pipein=1
pipeout=0
char** = {"grep","r",NULL}
now, the loop will go twice (because I have two commands instants)
at the first time, it will see the pcommands[0] has pipeout==1
create pipe
do fork
pcommands[0] has pipeout==1
child: dup2 to the stdout
execvp
second time:
doesn't create pipe
do fork
child:
the pcomands[1] has pipein==1
then: dup2 to the input
exevp
..
this command works, my output is:
errors.log exer2.pdf multipal_try
(all the things with 'r')
but then it get stuck, and doesn't get out of grep..
in an other terminal i can see grep is still working
I hope I close all the fd's I need to close...
I don't understand why doesn't it work, it seems like I do it right (well, it works for other commands..)
can someone please help? thanks
You aren't closing enough pipe file descriptors.
Rule of Thumb:
If you use dup() or dup2() to duplicate a pipe file descriptor to standard input or standard output, you should close both of the original pipe file descriptors.
You also need to be sure that if the parent shell creates the pipe, it closes both of its copies of the pipe file descriptors.
Also note that the processes in a pipeline should be allowed to run concurrently. In particular, pipes have a limited capacity, and a process blocks when there's no room left in the pipe. The limit can be quite small (POSIX mandates it must be at least 4 KiB, but that's all). If your programs deal with megabytes of data, they must be allowed to run concurrently in the pipeline. Therefore, the waitpid() should occur outside the loop that launches the children. You also need to close the pipes in the parent process before waiting; otherwise, the child reading the pipe will never see EOF (because the parent could, in theory, write to the pipe, even though it won't).
You have structure members whose names start with an underscore. That's dangerous. Names starting with an underscore are reserved for the implementation. The C standard says:
ISO/IEC 9899:2011 §7.1.3 Reserved Identifiers
— All identifiers that begin with an underscore and either an uppercase letter or another
underscore are always reserved for any use.
— All identifiers that begin with an underscore are always reserved for use as identifiers
with file scope in both the ordinary and tag name spaces.
That means that if you run into problems, then the trouble is yours, not the system's. Obviously, your code works, but you should be aware of the problems you could run into and it is wisest to avoid them.
Sample Code
This is a fixed SSCCE based on the code above:
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
typedef struct Command Command;
struct Command
{
int _fd_out;
int _fd_in;
int _flag_pipe_in;
int _flag_pipe_out;
char **_commands;
};
typedef int Pipe[2];
enum { STDIN = STDIN_FILENO, STDOUT = STDOUT_FILENO, STDERR = STDERR_FILENO };
int main(void)
{
char *ls_cmd[] = { "ls", 0 };
char *grep_cmd[] = { "grep", "r", 0 };
Command commands[] =
{
{
._fd_in = 0, ._flag_pipe_in = 0,
._fd_out = 1, ._flag_pipe_out = 1,
._commands = ls_cmd,
},
{
._fd_in = 0, ._flag_pipe_in = 1,
._fd_out = 1, ._flag_pipe_out = 0,
._commands = grep_cmd,
}
};
int commands_num = sizeof(commands) / sizeof(commands[0]);
/* Allow valgrind to check memory */
Command *pcommands = malloc(commands_num * sizeof(Command));
for (int i = 0; i < commands_num; i++)
pcommands[i] = commands[i];
for (int i = 0; i < commands_num; i++) { //exec all the commands instants
if (pcommands[i]._flag_pipe_out == 1) { //creates pipe if necessary
Pipe pipe_fd;
if (pipe(pipe_fd) == -1) {
perror("Error: \"pipe()\" failed");
}
pcommands[i]._fd_out = pipe_fd[1];
pcommands[i+1]._fd_in = pipe_fd[0];
}
pid_t pid = fork(); //the child exec the commands
if (pid == -1) {
perror("Error: \"fork()\" failed");
break;
} else if (!pid) { //child process
if (pcommands[i]._flag_pipe_in == 1) { //if there was a pipe to this command
assert(i > 0);
assert(pcommands[i-1]._flag_pipe_out == 1);
assert(pcommands[i-1]._fd_out > STDERR);
if (dup2(pcommands[i]._fd_in, STDIN) == -1) {
perror("Error: \"dup2()\" failed");
exit(0);
}
close(pcommands[i]._fd_in);
close(pcommands[i-1]._fd_out);
}
if (pcommands[i]._flag_pipe_out == 1) { //if there was a pipe from this command
assert(i < commands_num - 1);
assert(pcommands[i+1]._flag_pipe_in == 1);
assert(pcommands[i+1]._fd_in > STDERR);
if (dup2(pcommands[i]._fd_out, STDOUT) == -1) {
perror("Error: \"dup2()\" failed");
exit(0);
}
close(pcommands[i]._fd_out);
close(pcommands[i+1]._fd_in);
}
execvp(pcommands[i]._commands[0] , pcommands[i]._commands); //run the command
perror("Error: \"execvp()\" failed");
exit(1);
}
else
printf("Child PID %d running\n", (int)pid);
}
//closing all the open pipe fd's
for (int i = 0; i < commands_num; i++) {
if (pcommands[i]._fd_in != STDIN) { //if there was another stdin that is not 0
close(pcommands[i]._fd_in);
}
if (pcommands[i]._fd_out != STDOUT) { //if there was another stdout that is not 1
close(pcommands[i]._fd_out);
}
}
int status;
pid_t corpse;
while ((corpse = waitpid(-1, &status, 0)) > 0)
printf("Child PID %d died with status 0x%.4X\n", (int)corpse, status);
free(pcommands);
return(0);
}
Just for my knowledge, how would you do it, so it won't get "indisputably messy"?
I'd probably keep the pipe information so that I the child didn't need to worry about the conditionals contained in the asserts (accessing the child information for the child before or after it in the pipeline). If each child only needs to access information in its own data structure, it is cleaner. I'd reorganize the 'struct Command' so it contained two pipes, plus indicators for which pipe contains information that needs closing. In many ways, not radically different from what you've got; just tidier in that child i only needs to look at pcommands[i].
You can see a partial answer in a different context at C Minishell adding pipelines.

Implementing pipelining in a Linux shell

I'm trying to develop a shell in Linux as an Operating Systems project. One of the requirements is to support pipelining (where calling something like ls -l|less passes the output of the first command to the second). I'm trying to use the C pipe() and dup2() commands but the redirection doesn't seem to be happening (less complains that it didn't receive a filename). Can you identify where I'm going wrong/how I might go about fixing that?
EDIT: I'm thinking that I need to use either freopen or fdopen somewhere since I'm not using read() or write()... is that correct?
(I've heard from others who've done this project that using freopen() is another way to solve this problem; if you think that would be better, tips for going that direction would also be appreciated.)
Here's my execute_external() function, which executes all commands not built-in to the shell. The various commands in the pipe (e.g. [ls -l] and [less]) are stored in the commands[] array.
void execute_external()
{
int numCommands = 1;
char **commands;
commands = malloc(sizeof(char *));
if(strstr(raw_command, "|") != NULL)
{
numCommands = separate_pipeline_commands(commands);
}
else
{
commands[0] = malloc(strlen(raw_command) * sizeof(char));
commands[0] = raw_command;
}
int i;
int pipefd[2];
for (i = 0; i < numCommands; i++)
{
char **parameters_array = malloc(strlen(commands[i]) * sizeof(char *));
int num_params;
num_params = str_to_str_array(commands[i], parameters_array);
if (numCommands > 1 && i > 0 && i != numCommands - 1)
{
if (pipe(pipefd) == -1)
{
printf("Could not open a pipe.");
}
}
pid_t pid = fork();
pmesg(2, "Process forked. ID = %i. \n", pid);
int status;
if (fork < 0)
{
fprintf(to_write_to, "Could not fork a process to complete the external command.\n");
exit(EXIT_FAILURE);
}
if (pid == 0) // This is the child process
{
if (numCommands > 1) { close(pipefd[1]); } // close the unused write end of the pipe
if (i == 0) // we may be pipelining and this is the first process
{
dup2(1, pipefd[1]); // set the source descriptor (for the next iteration of the loop) to this proc's stdout
}
if (i !=0 && (i != numCommands-1)) // we are pipelining and this is not the first or last process
{
dup2(pipefd[0], 0); // set the stdin of this process to the source of the previous process
}
if (execvp(parameters_array[0], parameters_array) < 0)
{
fprintf(to_write_to, "Could not execute the external command. errno: %i.\n", errno);
exit(EXIT_FAILURE);
}
else { pmesg(2, "Executed the child process.\n");}
}
else
{
if (numCommands > 1) { close(pipefd[0]); } // close the unused read end of the pipe
if (backgrounding == 0) { while(wait(&status) != pid); }// Wait for the child to finish executing
}
free(parameters_array);
}
free(commands);
}
It looks like there are a couple of bugs going on in your code.
First, all your dup2's are only in the child. In order to connect a pipe you will need to dup2 the stdout of the parent to the write end pipefd[1] of the pipe. Then you would hook up the read end to stdin.
Also it looks like on of your dup2's is backwards with dup2 fildes is duplicated to fildes2. So when you reassign stdin you want dup2(in, 0) and for stdout you want dup2(out, 1).
So a stripped down piece of piping code is going to look like:
int pipefd[2];
pipe(pipefd);
pid_t pid = fork();
if (pid == 0) //The child
{
dup2(pipefd[0], 0);
}
else
{
dup2(pipefd[1], 1);
}

Resources