Reputation: 31
I'm trying to write a program that takes multiple command line arguments from the same line of input (e.g. /bin/uname /bin/date) before executing them and indicating that the process has completed successfully. When I use an infinite loop the program prints each execution out perfectly, however the question stipulates that the parent process must terminate after all of the children terminate. Attempting to make this happen leads to only one process completing, despite the fact that their is a for loop that should allow it to repeat. Any help telling me how I should go about it would be awesome.
I have tried moving the for loop to various different locations, as well as remove exit calls to try and make it successfully loop
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#define KGRN "\x1B[32m"
#define RESET "\x1B[0m"
int main(int argc,char* argv[])
{
int counter;
int status;
int COMMAND_LINE_SIZE = 64;
char command[COMMAND_LINE_SIZE];
pid_t pid;
printf("$ ");
scanf("%s", command);//read a line from user and store it in array command
printf("\n");
for(counter=0;counter<argc;counter++){
if ((pid = fork()) < 0) {
perror("forking child process failed\n");
exit(1);
}
if (pid==0){ // child
char *argv[] = {command, NULL};
execvp(argv[counter], argv);
exit(0);
}
//in parent
pid = wait(&status);
if(WIFEXITED(status) == 1)
printf(KGRN "Command %s has completed successfully\n" RESET, command);
else
printf("failure\n");
}
}
Expected output:
$ /bin/uname /bin/date /bin/ls
Linux
Command /bin/uname has completed successfully
$
Sun Sep 15 12:24:39 UTC 2019
Command /bin/date has completed successfully
$
a.out new.c q3.c trial.c
Command /bin/ls has completed successfully
Current output:
$ /bin/ls /bin/uname
a.out new.c q3.c trial.c
Command /bin/ls has completed successfully
Upvotes: 1
Views: 2667
Reputation: 754480
You're close, but there are a number of problems, many of them pointed out in the comments to the question:
argv[0]
— the program — which bodes ill.wait()
call outside the loop that forks them.argv
shadowing (hiding) the argv
argument to main()
. Thus, the code in the execvp()
is accessing out of bounds of the argv
array after counter
reaches 2, and you use a null pointer when counter
is 1.exit(0);
) after a child fails to execute.Incidentally, your sample command lines all use the full path to the command, making the use of execvp()
unnecessary. You could perfectly well type just the name of the command, like you would at the shell prompt, and then execvp()
would find the command and run it (see my example runs).
Fixing those and adding a few minor tweaks, and running the command line arguments asynchronously (possibly many commands running at the same time) yields code like this:
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
for (int counter = 1; counter < argc; counter++)
{
pid_t pid = fork();
if (pid < 0)
{
perror("forking child process failed\n");
exit(EXIT_FAILURE);
}
else if (pid == 0) // child
{
char *args[] = { argv[counter], NULL };
execvp(args[0], args);
fprintf(stderr, "%s: failed to execute %s: (%d) %s\n",
argv[0], argv[counter], errno, strerror(errno));
exit(EXIT_FAILURE);
}
}
int corpse;
int status;
int failed = 0;
while ((corpse = waitpid(0, &status, 0)) > 0)
{
if (WIFEXITED(status))
{
printf("Process %d exited with status %d (0x%.4X)\n",
corpse, WEXITSTATUS(status), status);
if (WEXITSTATUS(status) != 0)
failed++;
}
else if (WIFSIGNALED(status))
{
printf("Process %d was signalled %d (0x%.4X)\n",
corpse, WTERMSIG(status), status);
failed++;
}
}
return((failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
}
The failed
variable tracks whether any of the commands failed, and the process only reports success (exits with 0 or EXIT_SUCCESS) if every one of the commands succeeded; otherwise, it exits with a failure indication (exits with EXIT_FAILURE, which is usually 1).
When run on my Mac (as args41
created from args41.c
), it produced:
$ args41 uname date ls
Sun Sep 15 07:26:58 MDT 2019
Darwin
Process 1907 exited with status 0 (0x0000)
Process 1908 exited with status 0 (0x0000)
20000-leagues-under-the-sea.txt bin maxargs.sh
LICENSE.md conn11 overlap.data
Metriseis_2012.dat conn11.c overlap47.c
README.md conn11.dSYM overlap61.c
Safe conn13 overlap73
Untracked conn13.c overlap73.c
acr.list conn13.dSYM overlap73.dSYM
acronym29.c conn17 packages
acronym43 conn17.c pseudo-json.md
acronym43.c conn17.dSYM question.md
acronym43.dSYM conn29 sll43.c
acronym47 conn29.c so-0167-2112
acronym47.c conn29.dSYM so-0167-2112.c
acronym47.dSYM doc so-0167-2112.dSYM
acronym53 dr41 so-1043-1305
acronym53.c dr41.c so-4921-8019
acronym53.dSYM dr41.dSYM so-4970-8730
acronym59 dw41 so-4971-1989
acronym59.c dw41.c so-4985-0919
acronym59.dSYM dw41.dSYM so-5102-0102
argc37 etc so-5134-1743
argc37.c gccw67 so-5225-1783
argc37.dSYM gccw67.c so-5279-4924
args41 gccw67.dSYM so-5358-5962
args41.c gccw67.o so-5394-5215
args41.dSYM get.jl.activity so-5416-6308
argv89 inc so-5424-4465.md
argv89.c lib src
argv89.dSYM makefile
Process 1909 exited with status 0 (0x0000)
$ args41 many things to do
args41: failed to execute many: (2) No such file or directory
args41: failed to execute things: (2) No such file or directory
args41: failed to execute to: (2) No such file or directory
args41: failed to execute do: (2) No such file or directory
Process 1939 exited with status 1 (0x0100)
Process 1941 exited with status 1 (0x0100)
Process 1940 exited with status 1 (0x0100)
Process 1942 exited with status 1 (0x0100)
$
Clearly, if you want the processes run synchronously, you should place the waitpid()
loop inside the for
loop. You should still use a loop because a process can inherit children it didn't fork in obscure circumstances. You might prefer to use waitpid(pid, &status, 0)
in this case; then you don't need the loop around waitpid()
.
If you want to track process names and yet run the processes asynchronously, then the parent process woul need to keep a record of the PID of each child in an array as they're forked off, and the reporting loop would search through the list of known children to report which one died.
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int failed = 0;
for (int counter = 1; counter < argc; counter++)
{
pid_t pid = fork();
if (pid < 0)
{
perror("forking child process failed\n");
exit(EXIT_FAILURE);
}
else if (pid == 0) // child
{
char *args[] = { argv[counter], NULL };
execvp(args[0], args);
fprintf(stderr, "%s: failed to execute %s: (%d) %s\n",
argv[0], argv[counter], errno, strerror(errno));
exit(EXIT_FAILURE);
}
else
{
int status;
if (waitpid(pid, &status, 0) < 0)
{
fprintf(stderr, "failed to wait for %s process %d: (%d) %s\n",
argv[counter], (int)pid, errno, strerror(errno));
failed++;
}
else if (WIFEXITED(status))
{
printf("Process %s exited with status %d (0x%.4X)\n",
argv[counter], WEXITSTATUS(status), status);
if (WEXITSTATUS(status) != 0)
failed++;
}
else if (WIFSIGNALED(status))
{
printf("Process %s was signalled %d (0x%.4X)\n",
argv[counter], WTERMSIG(status), status);
failed++;
}
else
{
printf("Process %s died unexpectedly (0x%.4X)\n",
argv[counter], status);
failed++;
}
}
}
return((failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
}
Example output (args89
created from args89.c
):
$ args89 date uname ls
Sun Sep 15 08:34:00 MDT 2019
Process date exited with status 0 (0x0000)
Darwin
Process uname exited with status 0 (0x0000)
20000-leagues-under-the-sea.txt argv89.c makefile
LICENSE.md argv89.dSYM maxargs.sh
Metriseis_2012.dat bin overlap.data
README.md conn11 overlap47.c
Safe conn11.c overlap61.c
Untracked conn11.dSYM overlap73
acr.list conn13 overlap73.c
acronym29.c conn13.c overlap73.dSYM
acronym43 conn13.dSYM packages
acronym43.c conn17 pseudo-json.md
acronym43.dSYM conn17.c question.md
acronym47 conn17.dSYM sll43.c
acronym47.c conn29 so-0167-2112
acronym47.dSYM conn29.c so-0167-2112.c
acronym53 conn29.dSYM so-0167-2112.dSYM
acronym53.c doc so-1043-1305
acronym53.dSYM dr41 so-4921-8019
acronym59 dr41.c so-4970-8730
acronym59.c dr41.dSYM so-4971-1989
acronym59.dSYM dw41 so-4985-0919
argc37 dw41.c so-5102-0102
argc37.c dw41.dSYM so-5134-1743
argc37.dSYM etc so-5225-1783
args41 gccw67 so-5279-4924
args41.c gccw67.c so-5358-5962
args41.dSYM gccw67.dSYM so-5394-5215
args89 gccw67.o so-5416-6308
args89.c get.jl.activity so-5424-4465.md
args89.dSYM inc src
argv89 lib
Process ls exited with status 0 (0x0000)
$
This variant runs the commands asynchronously but also tracks which process belonged to each PID. Note that it uses a function (shock, horror) to determine the argument that corresponds to the PID that just died. The array is set up so that it is easy to track numbers — the element 0 of the array of PIDs is not used.
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
static int find_pid(int pid, int num_pids, pid_t pids[num_pids])
{
for (int i = 1; i < num_pids; i++)
{
if (pids[i] == pid)
return i;
}
return -1;
}
int main(int argc, char *argv[])
{
pid_t pids[argc];
for (int counter = 1; counter < argc; counter++)
{
pid_t pid = fork();
if (pid < 0)
{
perror("forking child process failed\n");
exit(EXIT_FAILURE);
}
else if (pid == 0) // child
{
char *args[] = { argv[counter], NULL };
execvp(args[0], args);
fprintf(stderr, "%s: failed to execute %s: (%d) %s\n",
argv[0], argv[counter], errno, strerror(errno));
exit(EXIT_FAILURE);
}
else
pids[counter] = pid;
}
int corpse;
int status;
int failed = 0;
while ((corpse = waitpid(0, &status, 0)) > 0)
{
int index = find_pid(corpse, argc, pids);
if (index < 0)
{
fprintf(stderr, "Unrecognized PID %d exited with status 0x%.4X\n",
corpse, status);
failed++;
}
else if (WIFEXITED(status))
{
printf("Process %s (PID %d) exited with status %d (0x%.4X)\n",
argv[index], corpse, WEXITSTATUS(status), status);
if (WEXITSTATUS(status) != 0)
failed++;
}
else if (WIFSIGNALED(status))
{
printf("Process %s (PID %d) was signalled %d (0x%.4X)\n",
argv[index], corpse, WTERMSIG(status), status);
failed++;
}
else
{
printf("Process %s (PID %d) died from indeterminate causes (0x%.4X)\n",
argv[index], corpse, status);
failed++;
}
}
return((failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
}
Example run (args79
created from args79.c
):
$ args79 uname date pwd
/Users/jleffler/soq
Process pwd (PID 2105) exited with status 0 (0x0000)
Darwin
Sun Sep 15 09:04:37 MDT 2019
Process uname (PID 2103) exited with status 0 (0x0000)
Process date (PID 2104) exited with status 0 (0x0000)
$
Upvotes: 1