18

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

Josh Correia
  • 3,807
  • 3
  • 33
  • 50
xx77aBs
  • 4,678
  • 9
  • 53
  • 77

3 Answers3

34

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.

Josh Correia
  • 3,807
  • 3
  • 33
  • 50
Nemo
  • 70,042
  • 10
  • 116
  • 153
  • Thank you very much. I think that's answer to my problem. I'll try it ASAP ;) – xx77aBs Jul 24 '11 at 07:15
  • It's working, I just added correct include and this line: setpgid(rar.pid(), 0); – xx77aBs Jul 24 '11 at 07:30
  • No problem. Note that `setpgid` is fraught with race conditions, and using it correctly is a bit delicate. I have updated my answer to be more complete. – Nemo Jul 24 '11 at 15:09
  • Thank you for additional info ;) – xx77aBs Jul 24 '11 at 22:57
  • typo in last if. EACCES and not EACCESS – NaN Nov 23 '11 at 19:04
  • This use of `assert` is extremely wrong! `assert` is conditionally enabled or disabled and when disabled, it eliminates it's argument altogether. You should be using `if(1!...) abort()` or your own macro wrapping it. – Jan Hudec Jan 02 '13 at 17:40
  • @Jan - The `/* or whatever */` was meant to imply "whatever your error-handling policy is". Whether this use of `assert` is "wrong" depends entirely on that policy. (Where I work, we never compile with `NDEBUG`, so `assert` is usable for "this should never happen" cases.) – Nemo Jan 02 '13 at 17:45
  • @Nemo: The "/* or whatever */" was right. But the important calls wrapped in `assert` were not. It so happens that in Linux you usually don't have control over whether the code will be compiled with `NDEBUG` or not as it's rather easy to call `configure CPPFLAGS="-O3 -fomit-frame-pointer -DNDEBUG"` – Jan Hudec Jan 02 '13 at 17:53
  • @Jan - I disagree with your edit, so I reverted it. Your comments are still here for anybody who stumbles across this in the future. – Nemo Jan 02 '13 at 17:54
  • @Jan: Using `assert` for "impossible" cases is perfectly normal and perfectly correct. (And you will note this code only uses `assert` for such impossible cases; the `fork` that could actually fail is checked explicitly.) Your downvote is based on your personal opinion, not technical fact nor standard practice. I added `/* or whatever */` to the `assert` cases to make you happy, even though it is not strictly necessary. Fair enough? – Nemo Jan 02 '13 at 18:03
  • @Nemo: Yes, but provided that: 1) the expression in argument of `assert` has no side effects (violated here) and 2) the condition may only be true if there is a bug in the program (that's not the case of `setpgid` result). – Jan Hudec Jan 02 '13 at 18:41
  • 1
    @Jan: Well, I disagree about `assert` being appropriate for expressions that are "only true if there is a bug in your program". In this context, `setpgid(0,0)` can fail only if there is a bug in the C library or the kernel, and checking such "impossible" cases with `assert` is perfectly reasonable. But you have a good point about side-effects. I thought `assert(x)` was guaranteed to evaluate `x`, but I was wrong. I have reverted to your version. – Nemo Jan 02 '13 at 20:01
  • Won't this be problematic if the child is somehow prioritized over the parent and hangs because the unblocking of the signals is only done in the parent? – Sarien Aug 16 '17 at 16:22
  • @Sarien: Regardless of priorities, both processes are guaranteed to make progress "eventually". Hitting Ctrl-C while the signal is blocked will cause it to be queued and delivered once it is "eventually" unblocked. – Nemo Aug 18 '17 at 19:51
  • @Nemo Okay, but still it might run for a long time without being able to receive signals, right? – Sarien Aug 19 '17 at 09:39
0

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.

Arnold Spence
  • 21,942
  • 7
  • 74
  • 67
  • Thank you for this page, I wasn't aware of it. I'm just calling quit and wait on my threads, and then exiting the application. It works for everything except RAR, so I don't think it's the problem. But I will read this page. Thanks again – xx77aBs Jul 24 '11 at 07:14
0

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.

hagello
  • 2,843
  • 2
  • 27
  • 37