I am trying to build a shell, and I've managed to code most of the functionality in, however I have a small problem.
Say I type firefox &. Firefox will open up as a background process. The & activates a BG flag that makes the parent not wait for the child process.
Then I type gedit. Gedit will open as a foreground process. Meaning that currently the parent is waiting for the process to close.
At this point, the parent has two processes - firefox and gedit. Firefox hasn't been waited on, and is currently in the background, whereas we are currently waiting for Gedit to finish. So far so good.
However, if I decide to send a SIGINT signal by pressing ctrl-c, both firefox and gedit will close. Not good, only gedit should be closing.
Here is my signal handler function:
pid_t suspended_process[10];
int last_suspended = -1;
void signal_handler(int signo){
pid_t process = currentpid();
// Catches interrupt signal
if(signo == SIGINT){
int success = kill(process, SIGINT);
}
// Catches suspend signal
else if(signo == SIGTSTP){
int success = kill(process, SIGTSTP);
resuspended = 1;
suspended_process[last_suspended+1] = process;
last_suspended++;
}
}
And here's the part in fork-exec code that either waits on a process, or keeps on going.
else if(pid > 0){ //Parent
current_pid = pid;
// Waits if background flag not activated.
if(BG == 0){
// WUNTRACED used to stop waiting when suspended
waitpid(current_pid, &status, WUNTRACED);
if(WIFEXITED(status)){
setExitcode(WEXITSTATUS(status));
}
else if(WIFSIGNALED(status)){
printf("Process received SIGNAL %d\n", WTERMSIG(status));
}
}
}
This also happens if I suspend a process beforehand. For example, I run firefox and then press ctrl-z to suspend it. Then I run gedit and press ctrl-c to close it. Right after, if I press fg to restore the suspended firefox, it closes immediately.
I cannot find a way to only send the SIGINT signal to the foreground process, it always sends the signal to ALL children other than the parent, no matter if they are in the background, or suspended.
Just in case, this is the function that initialises the signal handler:
void init_handler(){
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
// If conditions for signal handling.
// Also creates 2 signal handlers in memory for the SIGINT and SIGTSTP
if(sigaction(SIGINT, &sa, NULL) == -1)
printf("Couldn't catch SIGINT - Interrupt Signal\n");
if(sigaction(SIGTSTP, &sa, NULL) == -1)
printf("Couldn't catch SIGTSTP - Suspension Signal\n");
}
This is rather simple, but it isn't done with signals. Instead you must use a feature called process groups. Each single job (executable or pipeline or so) will be a separate process group. You can create process groups with setpgid (or on some systems with setpgrp). You can simply set the process group of the child process after fork but before exec, and then store the process group id of this job into the job table.
Now, the process group that is in the foreground is set as the active process group for the terminal (the /dev/tty of the shell) with tcsetpgrp - this is the process group that will receive CTRL+C. Those process groups that belong to the same session, but not to the group set to foreground with tcsetpgrp will be completely oblivious to CTRL+C.
With the help of Antti, I managed to find the problem. I added a single line to the fork-exec code:
else if(pid > 0){ //Parent
current_pid = pid;
if(setpgid(pid, pid) == 0) perror("setpid");
// Waits if background flag not activated.
if(BG == 0){
// WUNTRACED used to stop waiting when suspended
waitpid(current_pid, &status, WUNTRACED);
if(WIFEXITED(status)){
setExitcode(WEXITSTATUS(status));
}
else if(WIFSIGNALED(status)){
printf("Process received SIGNAL %d\n", WTERMSIG(status));
}
}
}
if(setpgid(pid, pid) == 0) perror("setpid");
From what I could gather, setpgid sets the process group ID of a process. Meaning that in the line above, I am setting the pgid of the process with the pid pid to pid.
I might be wrong, I still don't fully understand the process, but the reason why this works, is that the SIGINT signal is only sent to the process with pgid pid. Meaning before, since I wasn't setting the pgid of each process, they all had the same pgid, hence they'd all receive the signal. However, once I set the pgid for each process, if I press CTRL-C in the middle of a foreground process, it only exits that running process.
At least that's from what I could gather. I still don't fully understand tcsetpgrp, especially what I could set as the first parameter, which is the file descriptor. Adding this line right after setpgid:
tcsetpgrp(STDIN_FILENO, pid)
Simply launches the entire program in the background whenever I exec a command. Instead of running firefox and it shows up, I run firefox and the whole program gets stopped (according to what the terminal says at least). I have no idea why that happens.
Still, thanks to Antti!
Related
So I am trying to create a basic terminal, and my problem is this: if I use the following code to execute most "normal" terminal commands (e.g. ls, cat, etc), there are no issues. It sets the process group and works perfectly. However, when I try to execute a command such as vim ., it appears that the process immediately stops. If I remove the call to setpgrp(), the command works as expected (and executes with the pgid of the controlling terminal).
Here is my code:
pid_t normal_cmd(char **argv, int bg) {
int pid = fork(), status;
if(pid < 0)
unix_error("Could not fork child process");
else if(!pid) { //child (this is the problematic area)
setpgrp();
status = execvp(argv[0], argv);
if(status < 0)
unix_error("Could not exec child process");
}
else { //parent
if(!bg) {
addjob(jobs, pid, FG);
pause(); //waitpid() is in SIGCHLD handler
struct job_t *cj = getjobpid(jobs, pid);
if(cj && cj->state != ST)
deletejob(jobs, pid);
}
else
addjob(jobs, pid, BG);
}
return pid;
}
Any idea why changing the process group would cause vim to fail here?
vim will try to read from the controlling terminal, and because its process group is not the foreground process group, it will receive a SIGTTIN signal which suspends it. ls and other "normal" commands aren't suspended because they don't read from stdin (which is the terminal in this case).
The setpgrp() call has the effect of creating a new process group with the calling process as its leader -- and the new process group is not the foreground process group on the terminal until you make it so with tcsetpgrp() or ioctl(TIOCSPGRP).
You can read more about job control here. Especially this, which explains why a program in the background receives a SIGTTIN if it tries to read from the the tty, but not a SIGTTOU if it tries to write to it.
I have one simple program that's using Qt Framework.
It uses QProcess to execute RAR and compress some files. In my program I am catching SIGINT and doing something in my code when it occurs:
signal(SIGINT, &unix_handler);
When SIGINT occurs, I check if RAR process is done, and if it isn't I will wait for it ... The problem is that (I think) RAR process also gets SIGINT that was meant for my program and it quits before it has compressed all files.
Is there a way to run RAR process so that it doesn't receive SIGINT when my program receives it?
Thanks
If you are generating the SIGINT with Ctrl+C on a Unix system, then the signal is being sent to the entire process group.
You need to use setpgid or setsid to put the child process into a different process group so that it will not receive the signals generated by the controlling terminal.
[Edit:]
Be sure to read the RATIONALE section of the setpgid page carefully. It is a little tricky to plug all of the potential race conditions here.
To guarantee 100% that no SIGINT will be delivered to your child process, you need to do something like this:
#define CHECK(x) if(!(x)) { perror(#x " failed"); abort(); /* or whatever */ }
/* Block SIGINT. */
sigset_t mask, omask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
CHECK(sigprocmask(SIG_BLOCK, &mask, &omask) == 0);
/* Spawn child. */
pid_t child_pid = fork();
CHECK(child_pid >= 0);
if (child_pid == 0) {
/* Child */
CHECK(setpgid(0, 0) == 0);
execl(...);
abort();
}
/* Parent */
if (setpgid(child_pid, child_pid) < 0 && errno != EACCES)
abort(); /* or whatever */
/* Unblock SIGINT */
CHECK(sigprocmask(SIG_SETMASK, &omask, NULL) == 0);
Strictly speaking, every one of these steps is necessary. You have to block the signal in case the user hits Ctrl+C right after the call to fork. You have to call setpgid in the child in case the execl happens before the parent has time to do anything. You have to call setpgid in the parent in case the parent runs and someone hits Ctrl+C before the child has time to do anything.
The sequence above is clumsy, but it does handle 100% of the race conditions.
What are you doing in your handler? There are only certain Qt functions that you can call safely from a unix signal handler. This page in the documentation identifies what ones they are.
The main problem is that the handler will execute outside of the main Qt event thread. That page also proposes a method to deal with this. I prefer getting the handler to "post" a custom event to the application and handle it that way. I posted an answer describing how to implement custom events here.
Just make the subprocess ignore SIGINT:
child_pid = fork();
if (child_pid == 0) {
/* child process */
signal(SIGINT, SIG_IGN);
execl(...);
}
man sigaction:
During an execve(2), the dispositions of handled signals are reset to the default;
the dispositions of ignored signals are left unchanged.
I'm coding a basic shell in C, and I'm working on suspending a child process right now.
I think my signal handler is correct, and my child process is suspending, but after that, the terminal should return to the parent process and that's not happening.
The child is suspended, but my shell isn't registering any input or output anymore. tcsetpgrp() doesn't seem to be helping.
Here's my signal handler in my shell code for SIGTSTP:
void suspend(int sig) {
pid_t pid;
sigset_t mask;
//mpid is the pgid of this shell.
tcsetpgrp(STDIN_FILENO, mpid);
tcsetpgrp(STDOUT_FILENO, mpid);
sigemptyset(&mask);
sigaddset(&mask, SIGTSTP);
sigprocmask(SIG_UNBLOCK, &mask, NULL);
signal(SIGTSTP, SIG_DFL);
//active.pid is the pid of the child currently in the fg.
if (active.pid != 0) {
kill(active.pid, SIGTSTP);
}
else{
//if this code is being run in the child, child calls SIGTSTP on itself.
pid = getpid();
if (pid != 0 && pid != mpid){
kill(pid, SIGTSTP);
}
}
signal(SIGTSTP, suspend);
}
Can anyone tell me what I'm doing wrong?
Am I suspending my shell along with the child, and do I need to return stdin and stdout to the shell somehow? How would I do this?
Thanks!
It's an old question but still I think I found an answer.
You didn't write your parent's code but I'm assuming its looks something like:
int main(){
pid_t pid = fork();
if(pid == 0) //child process
//call some program
else //parent process
wait(&status); //or waitpid(pid, &status, 0)
//continue with the program
}
the problem is with the wait() or waitpid(), it's look like if you run your program on OS like Ubuntu after using Ctrl+Z your child process is getting the SIGTSTP but the wait() function in the parent process is still waiting!
The right way of doing that is to replace the wait() in the parent with pause(), and make another handler that catch SIGCHLD. For example:
void sigHandler(int signum){
switch(signum){
case SIGCHLD:
// note that the last argument is important for the wait to work
waitpid(-1, &status, WNOHANG);
break;
}
}
In this case after the child process receive Ctrl+Z the parent process also receive SIGCHLD and the pause() return.
tcsetpgrp is to specify what is the foreground job. When your shell spawns a job in foreground (without &), it should create a new process group and make that the foreground job (of the controlling terminal, not whatever's on STDIN). Then, upon pressing CTRL-Z, that job will get the TSTP. It's the terminal that suspends the job, not your shell. Your shell shouldn't trap TSTP or send TSTP to anyone.
It should just wait() for the job it has spawned and detect when it has been stopped (and claim back the foreground group and mark the job as suspended internally). Your fg command would make the job's pgid the foreground process group again and send a SIGCONT to it and wait for it again, while bg would just send the SIGCONT
i used folk with signals for make process pause and resume with ctrl+c
video while is running : link
Code:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void reverse_handler(int sig);
_Bool isPause=0;
_Bool isRunning=1;
int main()
{
int ppid;
int counter=0;
//make parent respond for ctrl+c (pause,resume).
signal(SIGINT,reverse_handler);
while(isRunning){
while(isPause==0)
{
/*code exec while process is resuming */
printf("\nc:%d",counter++);
fflush(stdout);
sleep(1);
}
//close parent after child is alive.
if((ppid=fork())==0){ exit(0); }
//make child respond for ctrl+c (pause,resume).
signal(SIGINT,reverse_handler);
//keep child alive and listening.
while(isPause==1){ /*code exec while process is pausing */ sleep(1); }
}
return 0;
}
//if process is pause made it resume and vice versa.
void reverse_handler(int sig){
if(isPause==0){
printf("\nPaused");
fflush(stdout);
isPause=1;
}
else if(isPause==1){
printf("\nresuming");
fflush(stdout);
isPause=0;
}
}
i hope that's be useful.
please comment me if there's any questions
I might be late to answer the question here but this is what worked when I was stuck with the same problem. According to the man pages for tcsetpgrp()
The function tcsetpgrp() makes the process group with process group ID
pgrp the foreground process group on the terminal associated to fd,
which must be the controlling terminal of the calling process, and
still be associated with its session. Moreover, pgrp must be a
(nonempty) process group belonging to the same session as the calling
process.
If tcsetpgrp() is called by a member of a background process group in
its session, and the calling process is not blocking or ignoring
SIGTTOU, a SIGTTOU signal is sent to all members of this background
process group.
So, what worked for me was ignoring the signal SIGTTOU in the shell program, before I created the processes that would come to the foreground. If I do not ignore this signal, then the kernel will send this signal to my shell program and suspend it.
I need to intercept reboot or shutdown. The prgram is like this:
void sig_handler(int sig) {
if (sig == SIGTERM) {
/* do something */
}
}
int main() {
....
signal(SIGTERM, sig_handler);
/* daemon */
pid = fork();
if (pid > 0) exit(EXIT_SUCCESS);
// I didn't do setsid() to retain process group id.
....
}
This works when I tested by 'kill -15 '. However, when I tried 'reboot' or 'shutdown' command, it never received the signal. The init man page says:
"When init is requested to change the runlevel, it sends the warning signal SIGTERM to all processes that are undefined in the new runlevel. It then waits 5 seconds before forcibly terminating these processes via the SIGKILL signal. Note that init assumes that all these processes (and their descendants) remain in the same process group which init originally created for them. If any process changes its process group affiliation it will not receive these signals. Such processes need to be terminated separately."
How to tell init daemon to send SIGTERM to this program? My guess is I should set process group id to something init knows, but how can I do that?
If your program is not started by the init system, it won't be managed by the init system. Launch it from an init.d script to get the benefits described.
I'm creating background processes in C using fork().
When I created one of these processes, I add its pid to an array so I can keep track of background processes.
pid = fork();
if(pid == -1)
{
printf("error: fork()\n");
}
else if(pid == 0)
{
execvp(*args, args);
exit(0);
}
else
{
// add process to tracking array
addBGroundProcess(pid, args[0]);
}
I have a handler for reaping zombies
void childHandler(int signum)
{
pid_t pid;
int status;
/* loop as long as there are children to process */
while (1) {
/* get zombie pids */
pid = waitpid(-1, &status, WNOHANG);
if (pid == -1)
{
if (errno == EINTR)
{
continue;
}
break;
}
else if (pid == 0)
{
break;
}
/* Remove this child from tracking array */
if (pid != mainPid)
cleanUpChild(pid);
}
}
When I create a background process, the handler is executing and attempting to clean up the child before I can even make the call to addBGroundProcess.
I'm using commands like emacs& which should not be exiting immediately.
What am I missing?
Thanks.
You're right, there is a race condition there. I suggest that you block the delivery of SIGCHLD using the sigprocmask function. When you have added the new PID to your data structure, unblock the signal again. When a signal is blocked, if that signal is received, the kernel remembers that it needs to deliver that signal, and when the signal is unblocked, it's delivered.
Here's what I mean, specifically:
sigset_t mask, prevmask;
//Initialize mask with just the SIGCHLD signal
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
sigprocmask(SIG_BLOCK, &mask, &prevmask); /*block SIGCHLD, get previous mask*/
pid = fork();
if(pid == -1)
{
printf("error: fork()\n");
}
else if(pid == 0)
{
execvp(*args, args);
exit(0);
}
else
{
// add process to tracking array
addBGroundProcess(pid, args[0]);
// Unblock SIGCHLD again
sigprocmask(SIG_SETMASK, &prevmask, NULL);
}
Also, I think there's a possibility that execvp could be failing. (It's good to handle this in general, even if it's not happening in this case.) It depends exactly how it's implemented, but I don't think that you're allowed to put a & on the end of a command to get it to run in the background. Running emacs by itself is probably what you want in this case anyway, and putting & on the end of a command line is a feature provided by the shell.
Edit: I saw your comments about how you don't want emacs to run in the current terminal session. How do you want it to run, exactly - in a separate X11 window, perhaps? If so, there are other ways of achieving that.
A fairly easy way of handling execvp's failure is to do this:
execvp(*args, args);
perror("execvp failed");
_exit(127);
Your code just catches the exit of the child process it fork'ed, which is not to say that another process wasn't fork'ed by that child first. I'm guessing that emacs in your case is doing another fork() on itself for some reason, and then allowing the initial process to exit (that's a trick daemons will do).
The setsid() function might also be worth looking at, although without writing up some code myself to check it I'm not sure if that's relevant here.
You should not be using the shell with & to run background processes. If you do that, they come out as grandchildren which you cannot track and wait on. Instead you need to either mimic what the shell does to run background processes in your own code, or it would probably work just as well to close the terminal (or rather stdin/out/err) and open /dev/null in its place in the child processes so they don't try to write to the terminal or take control of it.