I'm currently writing my own shell as a project for a class, and have everything virtually working. My problem is with my pipes, sometimes they work, and sometimes, they just hang until I interrupt them. I've done research on this, and it seems that the function that is getting it's stdin written to isn't receiving an EOF from the first process; usually as I've learned the problem is that the pipe isn't being closed, but this isn't the case (to my knowledge) with my code.
All redirection works and any variation thereof:
ls -l > file1
wc < file1 > file2
The following piped commands work:
w | head -n 4
w | head -n 4 > file1
This doesn't work: ls | grep file1 it shows the correct output and never ends unless an interrupt signal is sent to it by the user. ls | grep file1 > file2 also does not work. It hangs without showing output, creates the file2, but never writes to it.
Anyway, I hope there's something I'm missing that someone else can notice; I've been at this for a while. Let me know if there's anymore code I can provide. The code I've posted below is the main file, nothing removed.
/*
* This code implemenFts a simple shell program
* At this time it supports just simple commands with
* any number of args.
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include "input.h"
#include "myShell.h"
#include "BackgroundStack.h"
/*
* The main shell function
*/
main() {
char *buff[20];
char *inputString;
BackgroundStack *bgStack = malloc(sizeof(BackgroundStack));
initBgStack(bgStack);
struct sigaction new_act;
new_act.sa_handler = sigIntHandler;
sigemptyset ( &new_act.sa_mask );
new_act.sa_flags = SA_RESTART;
sigaction(SIGINT, &new_act, NULL);
// Loop forever
while(1) {
const char *chPath;
doneBgProcesses(bgStack);
// Print out the prompt and get the input
printPrompt();
inputString = get_my_args(buff);
if (buff[0] == NULL) continue;
if (buff[0][0] == '#') continue;
switch (getBuiltInCommand(buff[0])) {
case EXIT:
exit(0);
break;
case CD:
chPath = (buff[1]==NULL) ? getenv("HOME") : buff[1];
if (chdir(chPath) < 0) {
perror(": cd");
}
break;
default:
do_command(buff, bgStack);
}
//free up the malloced memory
free(inputString);
}// end of while(1)
}
static void sigIntHandler (int signum) {}
/*
* Do the command
*/
int do_command(char **args, BackgroundStack *bgStack) {
int status, statusb;
pid_t child_id, childb_id;
char **argsb;
int pipes[2];
int isBgd = isBackgrounded(args);
int hasPipe = hasAPipe(args);
if (isBgd) removeBackgroundCommand(args);
if (hasPipe) {
int cmdBi = getSecondCommandIndex(args);
args[cmdBi-1] = NULL;
argsb = &args[cmdBi];
pipe(pipes);
}
// Fork the child and check for errors in fork()
if((child_id = fork()) == -1) {
switch(errno) {
case EAGAIN:
perror("Error EAGAIN: ");
return;
case ENOMEM:
perror("Error ENOMEM: ");
return;
}
}
if (hasPipe && child_id != 0) {
childb_id = fork();
if(childb_id == -1) {
switch(errno) {
case EAGAIN:
perror("Error EAGAIN: ");
return;
case ENOMEM:
perror("Error ENOMEM: ");
return;
}
}
}
if(child_id == 0 || (childb_id == 0 && hasPipe)) {
if (child_id != 0 && hasPipe) args = argsb;
if (child_id == 0 && isBgd) {
struct sigaction new_act;
new_act.sa_handler = SIG_IGN;
sigaction(SIGINT, &new_act, 0);
}
if (child_id == 0 && hasPipe) {
if (dup2(pipes[1], 1) != 1) fatalPerror(": Pipe Redirection Output Error");
close(pipes[0]);
close(pipes[1]);
}
if (child_id != 0 && hasPipe) {
if (dup2(pipes[0], 0) != 0) fatalPerror(": Pipe Redirection Input Error");
close(pipes[0]);
close(pipes[1]);
waitpid(child_id, NULL, 0);
}
if ((child_id != 0 && hasPipe) || !hasPipe) {
if (hasAReOut(args)) {
char outFile[100];
getOutFile(args, outFile);
int reOutFile = open(outFile, O_RDWR|O_CREAT|O_TRUNC, S_IREAD|S_IWRITE);
if (reOutFile<0) fatalPerror(": Redirection Output Error");
if (dup2(reOutFile,1) != 1) fatalPerror(": Redirection Output Error");
close(reOutFile);
}
}
if ( (child_id == 0 && hasPipe) || !hasPipe) {
if (hasAReIn(args)) {
char inFle[100];
getInFile(args, inFle);
int reInFile = open(inFle, O_RDWR);
if (reInFile<0) fatalPerror(": Redirection Input Error");
if (dup2(reInFile,0) != 0) fatalPerror(": Redirection Input Error");
close(reInFile);
} else if (isBgd && !hasPipe) {
int bgReInFile = open("/dev/null", O_RDONLY);
if (bgReInFile<0) fatalPerror(": /dev/null Redirection Input Error");
if (dup2(bgReInFile,0) != 0) fatalPerror(": /dev/null Redirection Input Error");
close(bgReInFile);
}
}
// Execute the command
execvp(args[0], args);
perror(args[0]);
exit(-1);
}
// Wait for the child process to complete, if necessary
if (!isBgd) waitpid(child_id, &status, 0);
else if (!hasPipe) {
printf("Child %ld started\n", (long)child_id);
BackgroundProcess *bgPrs = malloc(sizeof(BackgroundProcess));
bgPrs->pid = child_id;
bgPrs->exitStatus = -1;
addProcessToBgStack(bgStack, bgPrs);
}
if (hasPipe) waitpid(childb_id, &statusb, 0);
if ( WIFSIGNALED(status) && !isBgd ) printf("Child %ld terminated due to signal %d\n", (long)child_id, WTERMSIG(status) );
if ( hasPipe && WIFSIGNALED(statusb) ) printf("Child %ld terminated due to signal %d\n", (long)childb_id, WTERMSIG(status) );
} // end of do_command
The second child should not wait for the first child to exit - it should just start running straight away (it will block until some output is produced on the pipe by the first child), so remove that waitpid() executed by childb. Instead, the parent process should wait for both child processes (or perhaps just the second one). (Indeed, as noted by JeremyP, this waitpid() call is failing anyway, since childb is not the parent of child).
Your problem, though, is that the parent process is mainintaining open file descriptors to the pipe. Right before the comment // Wait for the child process to complete, if necessary, the parent process should close its pipe file descriptors:
close(pipes[0]);
close(pipes[1]);
The open file descriptor in the parent means that the child grep process never sees EOF, so it doesn't exit.
I don't know the answer but I have spotted one issue.
You'll agree that the condition for
if(child_id == 0 || (childb_id == 0 && hasPipe))
is true only for the two child processes, but inside the if statement block you have this:
if (child_id != 0 && hasPipe) {
if (dup2(pipes[0], 0) != 0) fatalPerror(": Pipe Redirection Input Error");
close(pipes[0]);
close(pipes[1]);
waitpid(child_id, NULL, 0);
}
The waitpid() call is incorrect because it is called from the second child to wait for the first child. It's probably failing with ECHILD because the first child is not a child of the second child.
As for your real problem, I suspect it has to do with the fact that the grep command will not terminate until its input is closed. There might be some deadlock condition going on that stops that from happening. You need to run this in a debugger or put some logging in to see where the parent process is hanging.
Edit
caf's answer tells us everything.
I was assuming that the input to grep was being closed because ls will close its output when it terminates, but of course, the parent process also has grep's input file descriptor open. The version using head works properly because head -n 4 terminates after four lines regardless of whether its input file descriptor is closed or not.
Related
I am working on a program where the main program forks itself and the child process calls exec. I have set it up so that the parent process has 2 pipes StdOutPipe and StdInPipe, and the child process calls dup so that stdout writes to the StdOutPipe and stdin reads from StdInPipe. Then the parent process calls wait, after which i would like to read the entirety of the StdOutPipe into a buffer. I know you can do so by reading one character at a time, but is there a faster way to do so?
For performance reasons, one typically reads a chunk at a time, not a character at a time.
Loop,
Attempt to enlarge the buffer so it can fit CHUNK_SIZE more bytes.
If an error occurred,
Fail.
Attempt to read CHUNK_SIZE bytes from the pipe into the unused part of the buffer.
If an error occurred,
Fail.
If EOF was reached,
Break.
Increased the total number of bytes read by the number of bytes read.
A pipe is basically a byte stream which means:
There's no concept of messages or message boundaries with pipes
The process reading from a
pipe can read blocks of data of any size, regardless of the size of blocks written by
the writing process
A read from a pipe is usually blocked until atleast a byte is written to the pipe.
That said, here's how i would implement your issue.
Create two pipes, stdinpipe and stdoutpipe
Do a fork
Parent process should close the write end of the pipes and sit in a
loop, waiting until data is written to pipe
Child process should close the read end of the pipes and duplicate
STDOUT to stdoutpipe and STDIN to stdinpipe
Child process can then do an exec.
Sample code:
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#define STDPIPE_BUFFER_SIZE 4096
#define ARGV_SIZE 3
int main()
{
// Stdoutpipe and stdint pipe
int stdoutpipe[2], stdinpipe[2], stdin_char_count, stdout_char_count, stdout_read, stdin_read;
pid_t pid;
char stdinbuffer[STDPIPE_BUFFER_SIZE], stdoutbuffer[STDPIPE_BUFFER_SIZE];
char *argv[ARGV_SIZE]; // arguments to exec
if (pipe(stdinpipe) == -1 || pipe(stdoutpipe) == -1)
exit(1); // error occurred
// Fork and exec
switch (pid = fork())
{
case -1:
exit(1); // error
case 0:
// child close the read end of both pipes
if (close(stdinpipe[0]) == -1 || close(stdoutpipe[0]) == -1)
exit(1);
// have the pipes as the new STDIN and STDOUT
if (dup2(stdinpipe[1], STDIN_FILENO) == -1 || dup2(stdoutpipe[1], STDOUT_FILENO) == -1)
exit(1);
argv[0] = "/usr/bin/ssh"; // replace with your own program [ssh -V in my case]
argv[1] = "-V";
argv[2] = NULL;
execve(argv[0], argv, NULL);
exit(1); // if we get here something horribly bad happened
default:
// parent process
stdin_char_count = 0;
stdout_char_count = 0;
// parent close write end of both pipes
if (close(stdinpipe[1]) == -1 || close(stdoutpipe[1]) == -1)
exit(1);
for (;;)
{
stdin_read = read(stdinpipe[0], stdinbuffer, STDPIPE_BUFFER_SIZE);
stdout_read = read(stdinpipe[0], stdinbuffer, STDPIPE_BUFFER_SIZE);
if (stdin_read == 0 && stdout_read == 0)
{
stdinbuffer[stdin_char_count] = '\0';
stdoutbuffer[stdout_char_count] = '\0';
break;
}
if (stdin_read == -1 && stdout_read == -1)
exit(1); // we cant recover from this
stdin_char_count += stdin_read;
stdout_char_count += stdout_read;
}
printf("%s\n", stdoutbuffer);
wait(NULL);
}
}
source: https://man7.org/linux/man-pages/man2/pipe.2.html
You can convert the pipe into an ordinary stream and then use whatever function you find convenient to read the data. Here, getdelim() can be used to read all text up to a NUL byte which need not be sent over the pipe. Error checking is partially omitted for brevity.
Also be aware that if you want to continue interacting directly with the pipe even after opening the stream, you'll probably want to disable buffering on the stream.
#define _POSIX_C_SOURCE 200809L
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void) {
int fds[2];
if(pipe(fds) == -1) {
perror("Failed to create pipe");
exit(EXIT_FAILURE);
}
const pid_t pid = fork();
if(pid == -1) {
perror("Failed to fork");
exit(EXIT_FAILURE);
}
if(!pid) {
close(fds[0]);
const char *const msg = "Hello, world!";
if(write(fds[1], msg, strlen(msg)) == -1) {
perror("Failed to write");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
close(fds[1]);
FILE *const stream = fdopen(fds[0], "r");
if(!stream) {
perror("Failed to create stream");
exit(EXIT_FAILURE);
}
char *text = NULL;
assert(wait(NULL) != -1);
getdelim(&text, &(size_t){0}, '\0', stream);
fclose(stream);
assert(text);
puts(text);
free(text);
}
I am beginning to code a shell in UNIX to practice with API calls such as fork() dup2(), read(), and wait(). Currently, my shell opens and runs fine. When I type a command to run, such as ls -a, it parses this command properly, and executes it. The problem is, the main loop terminates early, exiting the shell after a single command. I need the loop to continue to run until 'exit' is read from stdin. Here is my current code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#include <unistd.h>
static const char prompt[] = "myshell> ";
static const char sep[] = " \t\n\r";
int main()
{
int ac; // arg count
char *av[10]; //argument vector
int tty = open("/dev/tty", O_RDWR); // open tty for read/write
int pid; // process id
int status; // child process exit status
int w;
void (*istat)(int), (*qstat)(int);
if (tty == -1)
{
fprintf(stderr, "can't open /dev/tty\n");
exit(EXIT_FAILURE);
}
while (1)
{
char *arg, line[256]; // buffer to hold line of input
int i;
// prompt and read
write(tty, prompt, sizeof(prompt) - 1);
i = read(tty, line, sizeof(line));
if (i <= 0)
break;
line[i] = '\0';
// tokenize the line into av[]
ac = 0;
for (arg = strtok(line, sep); arg && ac < 10; arg = strtok(NULL, sep))
av[ac++] = arg;
if (ac > 0 && strcmp(av[0], "exit") == 0)
break;
if ((pid = fork()) == 0)
{
// this is the forked child process that is a copy of the running program
dup2(tty, 0); // stdin from tty
dup2(tty, 1); // stdout to tty
dup2(tty, 2); // stderr to tty
close(tty);
// last argument must be NULL for execvp()
av[ac] = NULL;
// execute program av[0] with arguments av[0]... replacing this program
execvp(av[0], av);
fprintf(stderr, "can't execute %s\n", av[0]);
exit(EXIT_FAILURE);
}
close(tty);
// disable interrupt (^C and kill -TERM) and kill -QUIT
istat = signal(SIGINT, SIG_IGN);
qstat = signal(SIGQUIT, SIG_IGN);
// wait until forked child process terminated, get its exit status
while ((w = wait(&status)) != pid && w != -1)
continue;
if (w == -1)
status = -1;
}
// restore interrupt and quit signals
signal(SIGINT, istat);
signal(SIGQUIT, qstat);
exit(EXIT_SUCCESS);
}
I tried moving these lines right above exit success (so they're outside of the loop and inside main)
close(tty);
// disable interrupt (^C and kill -TERM) and kill -QUIT
istat = signal(SIGINT, SIG_IGN);
qstat = signal(SIGQUIT, SIG_IGN);
// wait until forked child process terminated, get its exit status
while ((w = wait(&status)) != pid && w != -1)
continue;
if (w == -1)
status = -1;
}
// restore interrupt and quit signals
signal(SIGINT, istat);
signal(SIGQUIT, qstat);
exit(EXIT_SUCCESS);
}
What this did is loop the shell properly, but printed the prompt over top the last line of the output from the executed command, because the program wasn't waiting for the child process to end before printing the prompt and waiting for input. I tried moving some of the lines around, but every time it just ends up terminating the shell after one command is executed
You do not want to call close(tty) within your main loop. Doing so causes your next read(tty,... to fail, exiting the shell.
Additionally, if you really want to disable SIGQUIT/SIGQUIT, you should symmetrically restore them within the loop.
the following posted code:
skips the handling of the signals, you may want to add that back in.
cleanly compiles
properly checks for and documents any errors
performs the desired functionality, except for the disabling of certain signals
continues to execute the loop until the user enters 'exit'
and now, the proposed code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
// added next statement for 'wait()' and 'waitpid()'
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
//#include <signal.h>
#include <unistd.h>
static const char prompt[] = "myshell> ";
static const char sep[] = " \t\n\r";
int main( void )
{
int ac; // arg count
char *av[10]; //argument vector
pid_t pid; // process id
int status; // child process exit status
int tty = open("/dev/tty", O_RDWR); // open tty for read/write
if (tty == -1)
{
perror( "open for /dev/tty failed");
exit(EXIT_FAILURE);
}
while (1)
{
char *arg;
char line[256]; // buffer to hold line of input
// prompt and read
write(tty, prompt, sizeof(prompt) - 1);
ssize_t i = read(tty, line, sizeof(line));
if (i == 0)
break;
if (i < 0)
{
perror( "read failed" );
exit( EXIT_FAILURE );
}
line[i] = '\0';
// tokenize the line into av[]
ac = 0;
for (arg = strtok(line, sep); arg && ac < 10; arg = strtok(NULL, sep))
av[ac++] = arg;
// last argument must be NULL for execvp()
av[ac] = NULL
if (ac > 0 && strcmp( av[0], "exit" ) == 0)
break;
pid = fork();
switch( pid )
{
case -1:
perror( "fork failed" );
exit( EXIT_FAILURE );
break;
case 0: // child process
// this is the forked child process that is a copy of the running program
dup2(tty, 0); // stdin from tty
dup2(tty, 1); // stdout to tty
dup2(tty, 2); // stderr to tty
close(tty);
;
// execute program av[0] with arguments av[0]... replacing this program
execvp(av[0], av);
perror( "execvp failed" );
exit(EXIT_FAILURE);
break;
default: // parent process
// wait until forked child process terminated, get its exit status
waitpid( pid, &status, 0 );
break;
}
}
return 0;
}
I'm having a few zombie processes that are running (defunct) in the background and I'm not quite sure how to kill them. I'm making a mini-shell, so it's basically like the terminal, but my own version of it.
Here is my code:
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include "main.h"
int main() {
/* Declared variables */
char buff[100];
char* args[20];
int arguments = 20;
/* Boolean value */
int done = 0;
while(done != 1) {
/* Print directory */
printf("%s>", getcwd(0,0));
/* Gets input */
fgets(buff, 100, stdin);
/* Checks to see if anything was entered */
if (buff[0] == '\n') {
printf("Error: Enter a command! (Example: ls -l)\n");
} else {
parseArgs(buff, args, 20, &arguments);
if (*args[0] == '\n') {
printf("Error: Enter a command! (Example: ls -l)\n");
} else if (strcmp(args[0], "exit") == 0) {
done = 1;
} else if (strcmp(args[0], "cd") == 0) {
/* Changes the directory */
int dir = chdir(args[1]);
if (dir != 0) {
printf("That directory isn't valid!\n");
}
} else {
int background = 0;
int count = 0;
/* Create a new process */
int process = fork();
if (process == -1) {
printf("Error: Unable to create a process!");
} else if (process == 0) {
/* Run user input */
int res = execvp(args[0], args);
if (res == -1) {
printf("\nError: Enter a command! (Example: ls -l)\n");
done = 1;
}
int reapingInfo;
waitpid(process, &reapingInfo, 0);
}
}
}
}
return (0);
}
Here is what I'm getting as an output when I run ls -l a few times and run the command: ps:
20978 pts /6 00:00:00 bash
21049 pts /6 00:00:00 main
21050 pts /6 00:00:00 ls <defunct>
21051 pts /6 00:00:00 ls <defunct>
21062 pts /6 00:00:00 ps
Any clues on how to reap these defunct processes?
Your logic following fork() doesn't look right. You have else if (process == 0), and in that branch (in the child process), you exec a new program and after that you attempt to call waitpid(). Since execvp never returns if the execution is successful, most of the time your waitpid never gets called.
I think you are missing an else somewhere. The waitpid should be done in the parent (the process in which fork() returned a strictly positive value). Then waitpid will reap the zombies; that's its job.
(By the way, if execvp fails, you probably want to call _exit(). Letting the child continue to run to completion is probably not right. For instance, any data in stdio buffers could be written twice.)
Here you can see an example for fork/exec/waitpid usage that should work in your code:
/* Create a new process */
int process = fork();
if (process == -1) {
printf("Error: Unable to create a process!");
} else if (process == 0) {
/* Run user input */
int res = execvp(args[0], args);
/* execvp only returns if it fails, not need to check if res == -1 */
printf("\nError: Enter a command! (Example: ls -l)\n");
exit(1); /* kill this process, if exec fails! */
} else {
int reapingInfo;
waitpid(process, &reapingInfo, 0);
}
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.
This is a followup to my previous question. I am writing a linux shell, and so I need to deal with the possibility of users inputting multiple pipe commands. It is almost working correctly, except for after calling execvp() on the last command the I/O hangs. My prompt never reappears and I have to ctrl+C to leave the shell. I do not think it is an infinite loop happening, but rather I am not closing my streams correctly. I cannot figure out the correct way to do it.
Example - If I do not use a pipe at all, the shell runs correctly:
ad#ubuntu:~/Documents$ gcc mash.c -o mash
ad#ubuntu:~/Documents$ ./mash
/home/ad/Documents> ls
a.out bio1.odt blah.cpp controller.txt mash.c
bio1.doc blahblah.txt Chapter1Notes.odt mash
/home/ad/Documents>
However, if I type in:
/home/ad/Documents> ls -l | grep sh
-rwxr-xr-x 1 ad ad 13597 2011-09-26 00:03 mash
-rw-r--r-- 1 ad ad 3060 2011-09-25 23:58 mash.c
The prompt does not appear again. main() originally calls execute() with stdin and stdout.
Thanks for your time!
Code:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
int MAX_PATH_LENGTH = 1024; //Maximum path length to display.
int BUF_LENGTH = 1024; // Length of buffer to store user input
char * delims = " \n"; // Delimiters for tokenizing user input.
const int PIPE_READ = 0;
const int PIPE_WRITE = 1;
void execute(char **argArray, int read_fd, int write_fd){
dup2(read_fd, 0);
dup2(write_fd, 1);
//Problem when entering only newline character
char **pA = argArray;
int i = 0;
while(*pA != NULL) {
if(strcmp(argArray[i],"<") == 0) {
int input = open(argArray[i+1], O_RDWR | O_CREAT);
pid_t pid = fork();
if(pid == 0) {
dup2(input, 0);
argArray[i] = 0;
execvp(argArray[0], &argArray[0]);
printf("Error redirecting input.\n");
exit(1);
}
wait(pid);
}
else if(strcmp(argArray[i],">") == 0) {
int output = open(argArray[i+1], O_RDWR | O_CREAT);
pid_t pid = fork();
if(pid == 0){
dup2(output,1);
close(output);
argArray[i] = 0;
execvp(argArray[0], &argArray[0]);
printf("Error redirecting output.\n");
exit(1);
}
close(output);
wait(NULL);
}
else if(strcmp(argArray[i],"|") == 0) {
int fds[2];
pipe(fds);
pid_t pid = fork();
if(pid == 0) {
dup2(fds[PIPE_WRITE], 1);
close(fds[PIPE_READ]);
close(fds[PIPE_WRITE]);
argArray[i] = 0;
execvp(argArray[0], &argArray[0]);
printf("%s: command not found.\n", argArray[0]);
exit(1);
} else {
dup2(fds[PIPE_READ], 0);
execute(&argArray[i+1], 0, 1);
close(fds[PIPE_READ]);
close(fds[PIPE_WRITE]);
wait(pid);
printf("herp\n");
}
}
*pA++;
i++;
}
pid_t pid = vfork();
if(pid == 0){
execvp(argArray[0], &argArray[0]);
printf("%s: command not found.\n", argArray[0]);
exit(1);
}
else {
wait(NULL);
}
}
int main () {
char path[MAX_PATH_LENGTH];
char buf[BUF_LENGTH];
char* strArray[BUF_LENGTH];
/**
* "Welcome" message. When mash is executed, the current working directory
* is displayed followed by >. For example, if user is in /usr/lib/, then
* mash will display :
* /usr/lib/>
**/
getcwd(path, MAX_PATH_LENGTH);
printf("%s> ", path);
fflush(stdout);
/**
* Loop infinitely while waiting for input from user.
* Parse input and display "welcome" message again.
**/
while(1) {
fgets(buf, BUF_LENGTH, stdin);
char *tokenPtr = NULL;
int i = 0;
tokenPtr = strtok(buf, delims);
if(strcmp(tokenPtr, "exit") == 0){
exit(0);
}
else if(strcmp(tokenPtr, "cd") == 0){
tokenPtr = strtok(NULL, delims);
if(chdir(tokenPtr) != 0){
printf("Path not found.\n");
}
getcwd(path, MAX_PATH_LENGTH);
}
else if(strcmp(tokenPtr, "pwd") == 0){
printf("%s\n", path);
}
else {
while(tokenPtr != NULL) {
strArray[i++] = tokenPtr;
tokenPtr = strtok(NULL, delims);
}
execute(strArray, 0, 1);
}
bzero(strArray, sizeof(strArray)); // clears array
printf("%s> ", path);
fflush(stdout);
}
}
This line - dup2(fds[PIPE_READ], 0); - overwrites your current stdin file descriptor with a descriptor referring to the pipe. Once the pipe command completes, any attempt to read from stdin will fail.
This line - fgets(buf, BUF_LENGTH, stdin); - doesn't check for error conditions.
Finally - you wait for the second process in the pipe to finish before you have started the second one. This is what is causing your deadlock; the "grep" command is waiting for input, but you haven't exec'd the "ls" command yet. You wait for the grep command to finish, but it can't finish because it is waiting for input.
In your latest incarnation of the code: When the execute() function is called, it scans the arguments and finds a pipe; it then forks and runs the first command ("ls"):
pid_t pid = fork();
if(pid == 0) {
dup2(fds[PIPE_WRITE], 1);
close(fds[PIPE_READ]);
close(fds[PIPE_WRITE]);
argArray[i] = 0;
execvp(argArray[0], &argArray[0]);
printf("%s: command not found.\n", argArray[0]);
exit(1);
Then it recurses, calling execute() again:
} else {
dup2(fds[PIPE_READ], 0);
execute(&argArray[i+1], 0, 1); // <--- HERE
... this will of course fork and run "grep" before returning. Note that it does so with /both/ the pipe filed descriptors /open/. Therefore, the grep process itself will hold both ends of the pipe open. execute() performs a wait(NULL) before returning; this however will actually wait for the "ls" to finish (since that is the process that completes first). Then it returns, and proceeds:
close(fds[PIPE_READ]);
close(fds[PIPE_WRITE]);
wait(pid); // <--- WRONG, compile with -Wall to see why
printf("herp\n");
}
I've pointed out one error. Try compiling with "-Wall", or reading the documentation for the wait() function! If you change it to wait(NULL) it will be correct, however, in that case it will block. The reason is that the "grep" command hasn't completed and is still reading input. The reason it is still reading input is because the grep process itself has the write end of the pipe open!! So, grep never sees the "end" of the input coming from the pipe. A simple fix is to close the pipe fds before calling execute() recursively (there remains other problems with your code however, including, as I have already pointed out, that you are trashing your stdin descriptor).
These two lines have the arguments in the wrong order:
dup2(0, read_fd);
dup2(1, write_fd);
You should be writing:
dup2(read_fd, 0);
dup2(write_fd, 1);
Or:
dup2(read_fd, STDIN_FILENO);
dup2(write_fd, STDOUT_FILENO);
However, whether amended or original, the call to execute is:
execute(strArray, 0, 1);
which means that these two dup2() calls do nothing (duplicating 0 to 0 and 1 to 1).