Implementing pipelining in a Linux shell - c

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);
}

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.

Issues Calling Execve In Forked Process

I'm trying to create a very basic telnet server to practice memory corruption exploits. When I try to issue a command, in the first iteration, nothing happens. Second iteration I am getting multiple bad file descriptor errors printing on my server side. On the client side, everything seems ok. I get all the required prompts. Here's my relevant code:
int piper[2];
pipe(piper);
...
while (1) {
n = write(newsockfd,"Enter a command...\n",21);
if (n < 0) error("ERROR writing to socket");
bzero(buffer,4096);
n = read(newsockfd,buffer,4095);
strcpy(command, buffer);
pid_t childpid;
childpid = fork();
if(childpid == -1) {
perror("Failed to fork");
return 1;
}
if(childpid == 0) { //child
printf("I am child %ld\n", (long)getpid());
if(dup2(piper[1], 1) < 0) {
perror("Failed to pipe in child process");
}
else {
close(piper[0]);
close(piper[1]);
char *args[] = {command, NULL};
execve(command, args, NULL);
}
}
else { // parent
if(dup2(piper[0], 0) < 0) {
perror("Failed to pipe in parent process");
}
else {
// read command output from child
while(fgets(command_out, sizeof(command_out), stdin)) {
printf("%s", command_out);
}
}
}
}
If I enter /bin/ls into my client, I get the following outputted onto my server:
I am child 26748
2nd time I do it, I get the following outputted to my server:
Failed to pipe in parent process: Bad file descriptor
0I am child 26749
Failed to pipe in child process: Bad file descriptor
There's a possibility that closing the pipe in the child process closes it in the parent process also. Consider moving your piper(pipe) in the beginning of the while loop. And to be safe, close the pipe at the end of the file loop not forgetting to test the return value of close.
Actually read puts a newline character at the end of input so your command could be for example testprog but in reality, when using read(), it is testprog\n so you have to get rid of the newline added or execve() will expect a program name with a newline in it.
#define STDIN 0
int n = read(STDIN, command, 4096);
command[n - 1] = '\0'; // get rid of newline
char *args = { command, NULL };
execve(buf, &args[0], NULL);

Program crash in forking process with pipes

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.

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.

How to catch the ouput from a execl command

I'm using the execl function to run a Linux process from C. When I do, for example:
int cmd_quem() {
int result;
result = fork();
if(result < 0) {
exit(-1);
}
if (result == 0) {
execl("/usr/bin/who", "who", NULL);
sleep(4); //checking if father is being polite
exit(1);
}
else {
// father's time
wait();
}
return 0;
}
I get on the console the result of doing "who" on the terminal. What I'd like to know is if there is any function to "catch" the output result from a command. What I mean is, if there is anyway to catch this:
feuplive tty5 2009-11-21 18:20
Which is one of the lines resulting from the who command.
To do this, you need to open a pipe. You then replace the child's stdout with the writing end of the pipe, and read from the reading end of the pipe in the parent. Like this modified version of your code:
int cmd_quem(void) {
int result;
int pipefd[2];
FILE *cmd_output;
char buf[1024];
int status;
result = pipe(pipefd);
if (result < 0) {
perror("pipe");
exit(-1);
}
result = fork();
if(result < 0) {
exit(-1);
}
if (result == 0) {
dup2(pipefd[1], STDOUT_FILENO); /* Duplicate writing end to stdout */
close(pipefd[0]);
close(pipefd[1]);
execl("/usr/bin/who", "who", NULL);
_exit(1);
}
/* Parent process */
close(pipefd[1]); /* Close writing end of pipe */
cmd_output = fdopen(pipefd[0], "r");
if (fgets(buf, sizeof buf, cmd_output)) {
printf("Data from who command: %s\n", buf);
} else {
printf("No data received.\n");
}
wait(&status);
printf("Child exit status = %d\n", status);
return 0;
}
First, execl does not return unless there's a problem like the executable is not found. That sleep(4) is probably never executed.
As for redirecting and getting the output, check out the Unix Programming FAQ. Look for spawn_background_command.
The exec() family of functions creates a new process image from a regular, executable file. This file is either an executable object file, or an interpreter script. There is no return from a successful call to an exec() function, because the calling process is functionally replaced by the new process.
So any code after exec() is never executed unless it is failed.
If you want to capture output of a shell command you need popen.

Resources