Shell job control - c

For my school project I am implementing a shell and I need help with job control.
If we type a command, say cat &, then because of the & it should run in background, but it's not working. I have this code:
{
int pid;
int status;
pid = fork();
if (pid == 0) {
fprintf(stderr, "Child Job pid = %d\n", getpid());
execvp(arg1, arg2);
}
pid=getpid();
fprintf(stderr, "Child Job pid is = %d\n", getpid());
waitpid(pid, &status, 0);
}

Rather than just going straight to waiting, you should set up a signal handler for the SIGCHLD signal. SIGCHLD is sent whenever a child process stops or is terminated. Check out the GNU description of process completion.
The end of this article has a sample handler (which I've more or less copied and pasted below). Try modeling your code off of it.
void sigchld_handler (int signum) {
int pid, status, serrno;
serrno = errno;
while (1) {
pid = waitpid(WAIT_ANY, &status, WNOHANG);
if (pid < 0) {
perror("waitpid");
break;
}
if (pid == 0)
break;
/* customize here.
notice_termination is in this case some function you would provide
that would report back to your shell.
*/
notice_termination (pid, status);
}
errno = serrno;
}
Another good source of information on this subject is Advanced Programming in the UNIX Environment, chapters 8 and 10.

The parent process is calling waitpid on the child, which will block until the child process changes state (i.e. terminates).

Related

is possible to get the exit status from the child of a child process in the parent

I try to get the exit code from a child of a child process in the parent process.
If the process goes in the while loop to fork again i don't get the exit code.
I tried some options for waitpid like WNOHANG but then the program hangs.
Maybe what i wan't is not possible because it's some like a zombie child?
This is my code.
void parrent_process(t_token *token, t_info *info)
{
pid_t pid;
int wstatus;
pid = fork();
if (pid == -1)
return (print_error_msg(FORK_FAIL, NULL, NULL));
if (pid == 0)
{
child_process(token);
}
if (info->in)
close(info->in);
waitpid(pid, &wstatus, 0);
if (WIFEXITED(wstatus))
info->exit_code = WEXITSTATUS(wstatus);
}
void child_process(t_token *token)
{
t_token *cmd_token;
pid_t pid;
pid = 0;
cmd_token = token;
while (token->next && ((token->next)->type == GREAT))
{
pid = fork();
if (pid == -1)
return (print_error_msg(FORK_FAIL, NULL, NULL));
if (pid == 0)
{
redirect_output(token);
break;
}
token = token->next->next;
}
if (execve(cmd_token->path, cmd_token->args, cmd_token->envp) == -1)
{
print_error_msg(CMD_NOT_FOUND, "minishell", cmd_token->args[0]);
exit(127);
}
}
In most POSIX-like systems, the answer is "No — a process can only wait on its own children, those it created directly with fork()1".
However, on Linux, there is the prctl(2) system call. The option PR_SET_CHILD_SUBREAPER allows a process to wait for more distant descendants (grandchildren, great-grandchildren, …) too. However, if the direct parent of a process waits for it, the ancestral process will not get the exit status information.
1 Or posix_spawn()

Background process killing with Parent process in C

I have the following code in my main function
pid_t pid;
pid = fork(); //Two processes are made
if (pid > 0 && runBGflag==0) //Parent process. Waits for child termination and prints exit status
{
int status;
if (waitpid(pid, &status, 0) == pid && WIFEXITED(status))
{
printf("Exitstatus [");
for (int i = 0; i < noOfTokens; i++)
{
printf("%s ", commands[i]);
}
printf("\b] = %d\n", WEXITSTATUS(status));
}
}
else if (pid == 0) //Child process. Executes commands and prints error if something unexpected happened
{
if (runBGflag==1) insertElement(getpid(),ptr);
execvp(commands[0], commands);
printf ("exec: %s\n", strerror(errno));
exit(1);
}
In a nutshell, a child process is made and if the runBackGround flag is set, the parent process will not wait for the child process to exit, but rather continue running. If a background process is made, the PID of the background process is stored in a list. At a later point, this function is called
void delete_zombies(void)
{
pid_t kidpid;
int status;
char buffer[1337];
while ((kidpid = waitpid(-1, &status, WNOHANG)) > 0)
{
removeElement(kidpid,buffer,1337);
printf("Child %ld terminated\n", kidpid);
printf("its command was %s\n",buffer);
}
}
This function simply checks if any child processes have died and in that case deletes them. It will then search for the childs PID in the list, remove it and print it out.
The problem is, the delete_zombies function will find that a child has died and will then try to remove it from the list, but it only finds an empty list, as if the child process never inserted its PID into the list.
This is really strange, because delete_zombies only finds a dead child process, when there was one created with the background flag set, so we know insertElement must have been called, but strangely when the parent checks in the list nothing is there
Is the cause for that, that child process and parent process have seperate lists, or is the PID maybe wrong?

Setting parent to child process group with setpgid fails

I'm trying to change the pgrp of the processes to that of the child's so i can setsid on the parent process. The only thing is I keep getting an EPERM error code. Both processes have the same session group, according to htop.
I'm basing this off of this blog post, so I can change which terminal output gets directed to.
void sig_exit(int signum)
{
_Exit(0);
}
pid_t change_process_group()
{
pid_t child_pid;
if ((child_pid = fork()) < 0)
{
perror("fork failed while attaching to term");
exit(1);
}
if (child_pid == 0)
{
pid_t parent = getppid();
setpgid(0, getpid());
signal(SIGUSR1, sig_exit); // wait till parent tells child to exit
//sleep(5);
//kill(parent, SIGUSR2);
pause();
printf("Shouldn't reach this\n");
}
//sleep(5);
//signal(SIGUSR2, sig_wait);
//pause();
int parent_pid = getpid();
int code = setpgid(parent_pid, child_pid); // need child process group
printf("%s\n", strerror(errno));
setsid();
return child_pid;
}
main()
{
pid_t child = change_process_group();
kill(child, SIGUSR1);
}
The commented out lines were from me thinking the processes might not be executing in the correct order, but those don't appear to fix the problem.
How may I correctly use setpgid to change the pgrp of the parent process to the child's?
This is a race condition and it works if you uncomment the sleep(5) line in the parent. When you call setpgid(parent_pid, child_pid), the child_pid process group must exist. It isn't enough that there exists a process with the PID child_pid: setpgid needs an existing process group unless the process is putting itself into its own group. If setpgid(parent_pid, child_pid) in the parent runs after setpgid(0, getpid()) in the child, it works.
Sleeping is both inefficient and fragile, so instead of that the parent should wait for a notification from the child. Signals are fragile because there aren't many different signals and they could come from anywhere. A good way to communicate between related processes is a pipe. Since all you need here is a one-time notification, you can set up a pipe and read from it in the parent (with the write end closed in the parent). The parent will wait until the child writes to the pipe or closes it. In the child, just close the write end of the pipe when you've finished the preparations. The parent's read call (or select if you need to do other things at the same time) will return.
Proof-of-concept code:
pid_t change_process_group()
{
pid_t child_pid;
int child_ready_pipe[2];
if (pipe(child_ready_pipe) < 0)
{
perror("pipe");
exit(1);
}
if ((child_pid = fork()) < 0)
{
perror("fork failed while attaching to term");
exit(1);
}
if (child_pid == 0)
{
close(child_ready_pipe[0]);
sleep(1); // mimic slow start of the child
if (setpgid(0, 0))
perror("child setpgid to create group");
close(child_ready_pipe[1]);
signal(SIGUSR1, sig_exit); // wait till parent tells child to exit
pause();
printf("Shouldn't reach this\n");
}
close(child_ready_pipe[1]);
int parent_pid = getpid();
char ignored;
read(child_ready_pipe[0], &ignored, 1);
close(child_ready_pipe[0]);
if (setpgid(parent_pid, child_pid) < 0) // need child process group
perror("parent setpgid");
if (setsid() < 0)
perror("parent setsid");
return child_pid;
}

Why are my child background process (fork-execvp) die instatly, but work well while in the foreground?

I'm doing a homework assigment (the regular "write your own unix shell in c" assigment)
and cant make my child process run in the background properly, the are KILLED right before calling execvp
my intuition tells me the problem is in the signal handler or the usage of return in the parent process after fork().
This is how I've implemented it (just for the background process) :
void ExeExternal(char *args[MAX], char* cmd,ExecutionMode ExeMode) {
int pID;
switch(pID = fork())
{
case -1:
// Add your code here (error)
/*
your code
*/
perror("fail to fork in : ExeExternal(...)\n");
exit(-1);
case 0 :
// Child Process
signal(SIGTSTP, SIG_DFL);
signal(SIGINT, SIG_DFL);
signal(SIGCONT, SIG_DFL);
usleep(20000);
setpgrp();
printf("trying to execvp\n");
int run_result=execvp(args[0],args);
if(run_result==-1)
{
perror("execvp(...) result in execvp\n");
exit(-1);
}
break; //wont reach this part ever!
default://the parent. pID holds child's pID
setpgid(pID, pID); //note, this is also done in the child
if(ExeMode==BACKGROUND) //no need to WAIT()
{
return;
}
}
}
and this is my signal handler (registered in my main() with signal(SIGCHLD,handle_sigchld)
void handle_sigchld(int sig) {
pid_t pid;
int status;
pid=waitpid(WAIT_ANY,&status,WUNTRACED | WNOHANG);
if (pid >0)
{
if(WIFEXITED(status)) //if terminated normally, return or exit
{
printf("\n[%d] %d Done \n", ID, pid); //‪[1‬] ‫‪a.out‬‬ ‫‪:‬‬ ‫‪12340‬‬ ‫‪214‬‬ ‫‪secs‬‬
}
else if (WIFSIGNALED(status)) //killed with SIGINT
{
printf("handle_sigchld:[%d] pid %d KILLED\n",ID,pid);
}
else if (WIFSTOPPED(status)) //suspended with SIGCONT
{
printf("\n[%d] %s %d %d secs (stopped)\n", ID, value,pid,time);
}
}
}
I've tried to plant some prinft's and my child seems to be killed right before executing execvp(...)
Ive tried to print the args[0] and args[1] and it exited there, so it looks like it falls upon accessing them,
also - is that the right way to use return ? ive tried to replace it with waitpid(pid,status,WNOHANG) but it didnt helped
ANY help is greatly appreciated! even just a hint in the right direction

How to properly fork() a process

I'm trying to understand how to properly used fork() and execvp() to execute a command. So far I have the following code:
When I run ./test vim myFile.c it correctly opens myFile.c but I get strange behavior. It seems as though there are two processes running because whenever I enter anything it seems to happen twice. Why is this?
int main (int argc, char* argv[]) {
int fdin, pid, w, status;
fdin = 0;
if ((pid = fork()) < 0)
errorExit (EXIT_FAILURE);
execvp(argv[0],argv);
do {
w = waitpid(cpid, &status, WUNTRACED | WCONTINUED);
if (w == -1) {
perror("waitpid");
exit(EXIT_FAILURE);
}
if (WIFEXITED(status)) {
printf("exited, status=%d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("killed by signal %d\n", WTERMSIG(status));
} else if (WIFSTOPPED(status)) {
printf("stopped by signal %d\n", WSTOPSIG(status));
} else if (WIFCONTINUED(status)) {
printf("continued\n");
}
} while (!WIFEXITED(status) && !WIFSIGNALED(status))
}
When you call fork(), you create two almost completely identical processes, the parent process and the child process. The only difference between these two processes is the return value of fork(), which returns 0 to the child process and the pid of the child to the parent.
Hence, assuming fork succeeded, fork will return a nonnegative integer to both the parent and child process in line 4. Then, both the parent and child process will execute line 6, the execvp, and hence, you end up with two different processes running your vim myFile.c, causing all the problems you described.
The standard idiom is something like:
if ((pid = fork()) < 0) {
// Handle fork error
}
else if (pid == 0) {
// Child process
execvp(...);
}
else {
// Parent process
w = waitpid(pid, ...);
}
Since the return value for fork is 0 for the child, after fork succeeds, the test (pid == 0) will be true for the child, so execvp will be called.
For the parent, fork returns the pid of the child, so the check (pid == 0), which still get executed, is false, so the else condition is executed, causing the parent to wait for the child.
Both parent and child in your program get execvp():
if ((pid = fork()) < 0)
errorExit (EXIT_FAILURE);
execvp(argv[0],argv);
You should check, if you are in parent with pid != 0, and if you are in child otherwise.
You should look at the return value of fork, after a successful fork you will have two running processes at the same position in your program. The child process will get a return value of 0, the parent will get a return value which is the pid of the child. Most likely you want to do different things in the child process and the parent process.
You might also want to think again about how execvp is called. Do you really want to give "./test" as the first argument to execvp?

Resources