I have tried to find solution all over the internate, but forums keep saying it is impossible, so here I a with my question. How does a shell (any shell like bash) keep track of exit codes. Is it by tracking their child process? (if so how could I go about implementing such a thing in a program where I am creating and killing many children processes) Or Is there a global variable that is equivalent to $? that I can access in c? Or do they store it in a file?
Here is an example of executing grep on a path that doesn't exist and getting the return code in the parent:
code:
#include <errno.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
pid_t childPid;
switch(childPid = fork()) {
case -1:
fprintf(stderr, "Error forking");
return 1;
case 0:
printf("CHILD: my pid is: %d\n", getpid());
int ret = execlp(argv[1], argv[1], argv[2], argv[3], (char *) NULL);
if (ret == -1) {
printf("CHILD: execv returned: %d\n", errno);
return errno;
}
break;
default:
printf("I am the parent with a child: %d\n", childPid);
int childRet;
wait(&childRet);
printf("PARENT, child returned: %d\n", childRet >> 8);
}
return 0;
}
Execution:
# Example of Failure execution:
[ttucker#zim c]$ cc -o stackoverflow so.c && ./stackoverflow grep test /does/not/exists
I am the parent with a child: 166781
CHILD: my pid is: 166781
grep: /does/not/exists: No such file or directory
PARENT, child returned: 2
# Example of a successful execution:
[ttucker#zim c]$ cc -o stackoverflow so.c && ./stackoverflow echo foo bar
I am the parent with a child: 166809
CHILD: my pid is: 166809
foo bar
PARENT, child returned: 0
'wait' or 'waitpid'.
With that, you can keep track of child processes and how they were terminated.
You can check the 'wstatus' with the macro 'WEXITSTATUS', which is the return value of the 'main' function (if terminated with 'exit').
Related
I want to interact with the child process through PTY, the parent process code is as follows:
#include <pty.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
pid_t child_pid, current_pid;
char name[BUFSIZ], buffer[BUFSIZ];
int master;
current_pid = getpid();
fprintf(stdout, "pid: %u\n", current_pid);
child_pid = forkpty(&master, &name[0], NULL, NULL);
if (child_pid == -1) {
perror("forkpty faild.");
return EXIT_FAILURE;
} else if (child_pid == 0) {
execl("./child", "./child");
} else {
read(master, &buffer[0], BUFSIZ);
fprintf(stdout, "%u: child message:\n%s", current_pid, buffer);
}
return EXIT_FAILURE;
}
The subprocess code is as follows:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
fprintf(stdout, "%s\n", isatty(fileno(stdout)) ? "true": "false");
return EXIT_SUCCESS;
}
compile and execute:
$ gcc main.c -o main && gcc child.c -o child
$ ./main
Why must the parent process fork first, and then execute the instruction through execl to be effective?
Can't exchange data directly?
Why must the parent process fork first, and then execute the instruction through execl to be effective?
Because that's how fork and everything that wraps it up works. After a successful fork you have two processes that are virtually identical, except for the return value of fork and the process' PID.
To launch a different program, one process must replace its program image with a different one, which is what execl does.
I am referring to this question - How to get the return value of a program ran via calling a member of the exec family of functions? from SO as I am trying to attempt similar actions.
Instead of the execl command, I am using execlp command. As my code, is supposed to take in a list of command line arguments, eg. ./myCode /bin/ls /bin/date in the event even if one of the argument is wrong, for example /bin/lsa, it is printing out the wrong line - Child Exit Code is 0.
Here is the code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
int number, statval;
int child_pid;
printf("%d: I'm the parent !\n", getpid());
child_pid = fork();
if(child_pid == -1)
{
printf("could not fork! \n");
exit( 1 );
}
else if(child_pid == 0)
{
execlp("/bin/lsa", "/bin/ls" ,(char *) NULL);
// error
printf("ERROR - Cannot run command\n");
}
else
{
printf("PID %d: waiting for child\n", getpid());
waitpid( child_pid, &statval, WUNTRACED);
if(WIFEXITED(statval))
printf("Child's exit code %d\n", WEXITSTATUS(statval));
else
printf("Child did not terminate with exit\n");
}
return 0;
}
And here is the terminal output:
119: I'm the parent !
PID 119: waiting for child
ERROR - Cannot run command
Child's exit code 0
What are some of the other ways that I can do to improve the last portion of the if-else code block? Using the example ./myCode /bin/lsa /bin/date, I am trying to achieve the following output:
Command /bin/lsa cannot be executed
<display output of /bin/date>
Command /bin/date is a success
After the child prints "ERROR - Cannot run command\n", it falls through to the return 0; statement, causing the process to exit with status code 0. You can return some other small positive value to change the exit code.
Running an unknown command in the Bash shell seems to set Bash's $? variable to 127, so perhaps that is a reasonable choice of return value.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
char *getcwd(char *buf, size_t size); //define getcwd
char PATH_MAX[1024]; //define max size of path
int chdir(const char *path);
int main(int argc, char *argv[]) { // gets arguments when program ran, no arguments means argv=1
pid_t pid; //process ID = pid
pid=fork();
char cwd[1024]; //compare directory to max character size
if(pid==0){ //child has been forked! //child process created
int ret;
printf("Child PID=%d\n", getpid());
getcwd(PATH_MAX, sizeof(PATH_MAX));
printf(" My current working directory is: %s\n", PATH_MAX);
ret= execl("/bin/ls", "ls", "-a", "-l", "-h", NULL);
printf("%d\n", ret); //why isn't this printed out?
}
//}
else {
int status;
//parent process
//wait for child to complete
printf("Parent PID=%d\n", getpid());
if (waitpid(pid, &status, 0) == -1) {
printf("ERROR");
}
else {
printf("Child done.\n");
getcwd(PATH_MAX, sizeof(PATH_MAX));
printf("0");
exit(0);
}
}
}
I left my commented out code so you can see my thought process. If my understanding is correct the shell(terminal) is its own process so when you call fork, it creates a new child process and its parent becomes the shell. So trying to chdir in the child process will not translate over to the shell and you will remain in the same Directory so you would need to execute the chdir function in the parent PID, which is now the shell, yes?
I am having a hard time trying to figure out where exactly I should be putting this chdir() command and what flavor of exec I need to use to execute the terminal commands.
I am testing 3 different commands as command line arguments when running in terminal. This is after making the file with gcc -o script script.c
$ ./script
result - print out current directory
print out "Usage: "<dir>" string. no command executed
$ ./script .
result -"Executing ls . --all -l --human-readable" string
executes above commands
$./script /
result - should execute above commands but change directory before
executing
$./script /blah/blah
result - can't execute chdir
exit status: 1
I believe this code should cause the child process to return a -1 which would terminate it, or if my if statement is correct it would print out the error message.
Any help would be appreciated, I believe I got the logic down, or at least somewhat. Just having a hard time implementing chdir.
I cooked your program a little and got the following:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
int main(int argc, char *argv[]) // gets arguments when program ran, no arguments means argv=1
{
int ret;
pid_t pid; //process ID = pid
pid=fork();
char cwd[1024]; //compare directory to max character size
char newPath[200]=".";
if( argc > 1 )
{
strcpy(newPath,argv[1]);
}
ret=chdir(newPath);
if( ret < 0 )
{
printf("Problem switching to :%s\n", newPath);
perror("chdir");
exit(ret);
}
if(pid==0){ //child has been forked! //child process created
int ret;
printf("Child PID=%d\n", getpid());
getcwd(cwd, sizeof(cwd));
printf(" My current working directory is: %s\n", cwd);
ret= execl("/bin/ls", "ls", "-a", "-l", "-h", NULL);
printf("%d\n", ret); //why isn't this printed out?
}
else {
int status;
//parent process
//wait for child to complete
printf("Parent PID=%d\n", getpid());
if (waitpid(pid, &status, 0) == -1) {
printf("ERROR");
} else
{
printf("Child done. stat=%d\n", status);
getcwd(cwd, sizeof(cwd));
printf("Parent cwd:%s\n", cwd);
printf("0");
exit(0);
}
}
}
Your understanding is not quite correct. When you execute a program from the command line on a terminal, the shell forks and does an execute of the process you are running. Normally, the parent shell process waits until the child process is done. You created a child process and did another exec and another wait. The parent shell patiently waits for you to finish, forked processes and all.
Let's see:
I put the chdir in your main program to show the child follows the parent.
A child process is not going to change the working directory of the parent. Unix and Linux don't work that way.
When you run execl, that is it. There is no return unless it can't do the execute. The point of exec* is you are blowing away your currently running program with a new executable. There is nothing to return to.
If you want to see the return code of the child, look at the status returned by the wait. In this case, the ls ran fine so the return code is zero. If you added a last argument of "baddir" (that is not there) you would see the ls non-zero return code, in this case, 512.
This is not running on my Mac for some reasons that I can't figure out. the output I am getting is only from the main.c
the output is
Parent PID 4066
Child PID 4067
Process 4067 exited with status 5
I need the main.c to execute counter.c and pass the argument 5 which I will then have to use it inside the for a loop, but I can't get exec to run at all no matter what path I put.
//main.c
int main(int argc, char *argv[]) {
pid_t childOrZero = fork();
if (childOrZero < 0){
perror("Error happened while trying to fork\n");
exit(-1);
}
if (childOrZero == 0){
printf("Child PID %d\n", (int)getpid());
execl("./counter", "5", NULL);
exit(5);
}
// THis must be parent
printf("Parent PID %d\n", (int)getpid());
int status = 0;
pid_t childpid = wait(&status);
int childReturnedValue = WEXITSTATUS(status);
printf("Process %d exited with status %d\n", (int)childOrZero, childReturnedValue);
return 0;
}
counter.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main (int argc, char *argv[]){
for (int i = 0; i<5; i++){
printf("Process: %d %d\n", (int)getpid(),i);
//sleep(3);
}
}
In a comment, you mention you compile counter.c into an executable called a.out. This is the default executable name when you do not provide an output name explicitly to the compiler. Thus, if you compile both counter.c and main.c, only one of them will be the a.out.
You can provide gcc an option to name your executable different from the default:
gcc -o counter counter.c
Also, your invocation of execl is not quite correct. The first argument is the path to the executable, but the remaining arguments will become argv[0], argv[1], etc. Thus, you really want to invoke execl this way:
execl("./counter", "counter", "5", NULL);
Read documentation of execl for MacOSX and the POSIX specification of execl
It can fail (and that is the only case when it is returning). So code:
if (childOrZero == 0){
printf("Child PID %d\n", (int)getpid());
execl("./counter", "5", NULL);
perror("execl counter");
exit(5);
}
I have the following code draft.
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
printf( "usage: %i filename", argc );
pid_t pID = fork();
if (pID == 0) // child
{
// Code only executed by child process
printf("Child PID: %i", pID);
int file = open("/tmp/rtail", O_CREAT | O_WRONLY);
//Now we redirect standard output to the file using dup2
dup2(file,1);
char tmp[30];
sprintf(tmp, "cat `tail -f %s`", argv[1]);
}
else if (pID < 0) // failed to fork
{
printf("Failed to fork");
exit(1);
// Throw exception
}
else // parent
{
}
// Code executed by both parent and child.
return 0;
}
How do I pass command line arguments to a child process? For example, running ./app alch.txt I want
sprintf(tmp, "cat `tail -f %s`", argv[1]);
to produce
cat `tail -f alch.txt`
in tmp.
How do I pass command line arguments to a child process?
You don't need to do anything special; fork ensures that each process gets all local variables, including argv.
Sorry for the trouble, it indeed works fine. My earlier version didn't work for some reason, but apparently I've changed something to make it right. Will run my code before a question next time.