I am trying to implement the behaviour of Ctrl+Z
in a mini C Shell. My code currently receives the SIGTSTP
signal and correctly suspends the process, however I can't seem to get it to move to the background and put the shell process back in the foreground.
I have tried my best to change the process group of the child that is suspended so that my waitpid()
loop can continue (because waitpid(-1, ...)
should only find children with the same process group as the parent, right?), however it always gets stuck waiting for the suspended process to end.
The while loop gets to the point where it prints Continuing
and then the waitpid()
never ends, unless I kill the child process externally.
Ctrl+C
works fine.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <assert.h>
#include <string.h>
int main()
{
char inbuf[256];
pid_t pid;
struct sigaction act;
for ( ; ; )
{
tcsetpgrp(fileno(stdin), getpgrp());
act.sa_handler = SIG_IGN;
assert(sigaction(SIGINT, &act, NULL) == 0);
assert(sigaction(SIGTSTP, &act, NULL) == 0);
printf("> ");
gets(inbuf);
pid = fork();
switch (pid)
{
case -1:// error
perror("fork");
exit(EXIT_FAILURE);
break;
case 0: // child
setpgrp();
tcsetpgrp(fileno(stdin), getpgid(pid));
act.sa_handler = SIG_DFL;
assert(sigaction(SIGINT, &act, NULL) == 0);
assert(sigaction(SIGTSTP, &act, NULL) == 0);
execlp( inbuf, inbuf, (char *)0 );
printf("execlp failed\n");
exit(EXIT_FAILURE);
break;
default:// parent
setpgid(pid, pid);
int status = 0;
signal(SIGTTOU, SIG_IGN);
signal(SIGTSTP, SIG_IGN);
tcsetpgrp(fileno(stdin), getpgid(pid));
int parent_group = getpgid(pid);
while (1) {
pid = waitpid(-1, &status, WUNTRACED);
printf("PID: %d\n", pid);
if (pid < 1) {
printf("Breaking\n");
break;
}
if (WIFSTOPPED(status)) {
printf("STOPPED: %d!\n", pid);
// I feel like I should be sending the process, pid, to the background here
//tcsetpgrp(fileno(stdin), parent_group);
//setpgid(pid, pid);
}
printf("Continuing\n");
}
}
memset(inbuf, '\0', sizeof(inbuf));
}
return 0;
}