Reaping zombies? [C] - c

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

Related

How to set status termination of a process C?

My program is a rudimental little shell.
It allow you to run programs in PATH as ls, cd..also with arguments.
To run the program type from terminal "./myshell2" then it starts and you can insert how many commands you want.
It starts a child process, runs execvp,it returns and restarts so you can type a new command.
When typed "Q" or "q" all the entire program should terminates.
The problem is that I don't know how to stop it,the code is below.
My idea is, when typed "Q" or "q", to kill the child process created and send a signal to comunicate its bad termination(of child process).
So the final status(from parent) 'll be not 1 and the function returns.
I commented some parts of the code hoping that it's easier to understand.
It works the problem is that to stop it I need of ctrl C.
I would like to say to child process that he must ends with a non-zero value.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <signal.h>
int main(int argc, char * argv[]) {
while(1)
{
pid_t pid = fork();
if (pid == -1) {
perror("fork error");
exit(EXIT_FAILURE);
}
if (pid == 0) { // child process
printf("type the command to start (and arguments if required) \n"
"Q to quit\n");
char *dest[10]; // allow you to insert
char line[4096];//commands from terminal
if (fgets(line,sizeof(line),stdin)==0) return 1;
int i;
line[strcspn(line, "\n")] = '\0';
char *st = line;
for (i=0; i< 10 && (dest[i]=strsep(&st," "))!=NULL;i++)
continue;//now you typed the command
if ( ( memcmp(dest[0],"Q",1)==0 ) // if Q or q the program
|| (memcmp(dest[0],"q",1)==0) ) //must end
{
printf("got it!\n");
if (kill(getpid(),SIGSEGV)==-1) printf("kill error\n");
//in theory the process should terminates with bad status
// and the value of the variable "status" 'll be not 0
// I think that the problem is in this part of the code
}
if( strcmp(dest[0]," ")!=0 )
{
int res = execvp(dest[0], dest);
}
else
{ int res= execvp(dest[1],dest+1);}
perror("execvp error");
exit(EXIT_FAILURE);
}
int status;
pid_t child = wait(&status);
if (child == -1) {
perror("wait error");
exit(EXIT_FAILURE);
}
if (status==1)
break; //so it can exit from the loop that creates new process
setenv("WAIT","TRUE",0); //dont' worry about
//perror("setenv error\n");
if (memcmp("TRUE",getenv("WAIT"),4) == 0 ) //these 6 lines
printf("WAIT=TRUE\n");
else if(memcmp("FALSE",getenv("WAIT"),4) == 0 )
printf("WAIT=FALSE\n");
printf("end current process (status=%d, child=%d)\n", WEXITSTATUS(status), son);
}
return EXIT_SUCCESS;
}
You're printing out WEXITSTATUS() for all cases, but that isn't right. You need to check if the status returned by wait is an exit status or not using WIFEXITED(). If it's non-zero then the child exited normally. Otherwise, you can use WIFSIGNALED() to see if the child was terminated and you'll get the signal from WTERMSIG()
if(WIFEXITED(status))
{
printf("end current process (status=%d, child=%d)\n", WEXITSTATUS(status), son);
}
else if(WIFSIGNALED(status))
{
printf("end current process (signal=%d, child=%d)\n", WTERMSIG(status), son);
}
You really should have the parent process handle the inputting of the command and leave the child process to run it though.

process loop in C

I'm trying to write a code about a process that executes programs from $PATH using the execlp() command.(it doesn't need to be the execlp command but I've found it useful for this one) I've achieved my expected output, but I need to run more than one commands. More specifically I want the child process to run the exec command, then the parent process to print a text indicating that it's ready to accept another command. Then the child process will run the new exec command. My code is this:
int main ( int argc, char *argp[]) {
pid_t progpid = fork(); // the fork command for the creation of the child process
int status = 0;
char com[256];
if (progpid < 0) // the check in case of failure
{
printf("PROGRAM ABORTED!");
return 0;
}
do
{
if (progpid == 0) // the child process
{
scanf( "%s", com);
if (com == "exit")
{
exit(0);
}
else
{
execlp(com, com, NULL);
}
}
else //the parent process
{
wait(&status);
printf("$");
}
}while (com != "exit");
return 0;
}
The expected output is :
<program which I input from keyboard> ( for example : ls )
<output of the program>
$<next program>
<output of the next program>
.
.
.
$exit
In short I want to keep running programs till I enter exit where it ends without doing anything else. However the output I get is this:
<program>
<output of program>
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
It keeps printing $ until I shut it down. I'm new to processes so please don't be too harsh about my code so far.
Thank you in advance!
This
if (com == "exit")
should be
if (strcmp(com, "exit") == 0)
Similarly change the while condition as well.
In C, string comparisons are done using strcmp(). == in your case, simply compares the address of com and the address of the string literal "exit". (In expressions, an array gets converted into a pointer to its first element. Hence, "address" comparison. Also see: What is array decaying?).
Note that your execlp() call has an issue. NULL may be defined as 0, in which case execlp(), being a variadic function, may be able to recognize it as the last argument.
I'd suggest to change it to:
execlp(com, com, (char*)0);
You'd also want to check if wait() failed or not by checking its return code.
Here's a simple example based on yours with improved error checking.
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main ( int argc, char *argp[]) {
for(;;) {
char com[1024];
printf("$ ");
fgets(com, sizeof com, stdin);
com[strcspn(com, "\n")] = 0; /* Remove if there's a newline at the end */
if (strcmp(com, "exit") == 0) {
exit(0);
}
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid == 0) { /* child process */
execlp(com, com, (char*)0);
}
int status;
int rc = wait(&status);
/* You can inspect 'status' for further info. */
if (rc == -1) {
perror("wait");
exit(1);
}
}
return 0;
}
Note that if you want your to execute commands with arguments then you need to do argument processing.

Dummy Shell in C get bugged after closing external process

I am writing a shell for an assignment in C. My only problem now is that when I call an external program with execvp() and then close is using CTRL + C, any further commands that I send are printed after the prompt not before it. I made an dummy program that is just an infinite loop to test this.
So after closing this program if I try and run a program that doesn't exist, it will print out
MINISHELL (cwd) $: and then a blank line
and then it will print my error message saying that I have entered a program that does not exist and then it will not print a prompt since it already has. So It ends with an empty new line and it appears that the program has not ended but it actually has.
It ONLY does this after I have closed a non terminating program with CTRL + C. Otherwise it works fine executing programs. Am I not using close() somewhere to close a file descriptor? I am not sure. Any help is appreciated. Thank you.
#include "minishell.h"
void intHandler(int sig)
{
my_char('\n');
}
int main(int argc, char **argv)
{
int n;
int pid = 0;
char* s = (char*)malloc(256*sizeof(char));
char cwd[1024];
char** input;
while (1)
{
signal(SIGINT, intHandler);
my_str("MINISHELL:");
if (getcwd(cwd, sizeof(cwd)) != NULL)
my_str(cwd);
else
{
my_str("Error in current directory");
return -1;
}
my_str(" $: ");
n = read(0, s, 256);
s[n] = '\0';
if (n > 1)
{
input = my_str2vect(s);
if (my_strcmp(input[0], "cd") == 0)
{
/*CHANGE DIRECTORY*/
if (input[1] != '\0')
{
if (chdir(input[1]) < 0)
my_str("MINISHELL: Error in path. Make sure the directory exists.\n");
}
else
my_str("MINISHELL: Error. No directory specified.\n");
}
else if (my_strcmp(input[0], "help") == 0)
{
/*HELP*/
my_str("\nMINISHELL COMMANDS:\n\ncd *directory\nChanges the current working directory to *directory\n\nexit\nExits the minishell\n\nhelp\nPrints a help message listing the built in commands\n\n");
}
else if (my_strcmp(input[0], "exit") == 0)
{
/*EXIT*/
my_str("Thank you for using MINISHELL\n");
exit(0);
}
else if (input[0] != NULL)
{
/*EXECUTE AN EXTERNAL PROGRAM*/
if ((pid = fork()) < 0)
my_str("MINISHELL: Error forking\n");
else if (pid > 0)
{
wait(NULL);
}
else
{
if (execvp(input[0], input) < 0)
{
my_str("MINISHELL: Error. Program does not exist in current directory.\n");
}
else
{
exit(0);
close(0);
}
}
}
else
{
my_str("MINISHELL: Error reading command. Type help to see available commands.");
}
}
}
return 0;
}
You cannot expect that the error message will always print before your prompt. The forked process is running in parallel with your main program. The order in which the output to stdout from the two programs occur is dependent upon how the OS scheduler runs the processes.
I think most real shells like bash actually do the check for the file existing in the main process before doing the fork(). Alternatively you could use pipe() and manually redirect the output. Then you can enforce flushing the entire contents before doing your prompt.

I/O hangs after recursive pipe() calls

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).

Writing own shell - code hangs when handling certain pipes - in C

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.

Resources