Signals can get tricky and there are lots of issues with your approach.
You should:
- kick off the timer (
alarm(5)
) in main
- do the sighandler registration and timer kick-off after you've spawned the child (or you somewhat risk running the signal handler in the child in between
fork
and execvp
)
- use
sigaction
rather than signal
to register the signal, as the former has clear portable semantics unlike the latter
- loop on
EINTR
around waitpid
(as signal interruptions will cause waitpid
to fail with EINTR
)
As for the handler, it'll need to
- use only async-signal-safe functions
- register another
alarm()
around read
- unblock
SIGALRM
for the alarm
around read
but not before you somehow mark yourself as being in your SIGALRM
signal handler already so the potential recursive entry of the handler can do a different thing (kill the child and exit)
(For the last point, you could do without signal-unblocking if you register the handler with .sa_flags = SA_NODEFER
, but that has the downside of opening up your application to stack-overflow caused by many externally sent (via kill) SIGALRMs. If you wanted to handle externally sent SIGALRM
s precisely, you could register the handler with .sa_flags=SA_SIGINFO
and use info->si_code
to differentiate between user-sends and alarm-sends of SIGALRM
, presumably aborting on externally-sent ones)
It could look something like this (based on your code):
#include <iostream>
#include <chrono>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>
//AS-safe raw io helper functions
ssize_t /* Write "n" bytes to a descriptor */
writen(int fd, const char *ptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
nleft = n;
while (nleft > 0) {
if ((nwritten = write(fd, ptr, nleft)) < 0) {
if (nleft == n)
return(-1); /* error, return -1 */
else
break; /* error, return amount written so far */
} else if (nwritten == 0) {
break;
}
nleft -= nwritten;
ptr += nwritten;
}
return(n - nleft); /* return >= 0 */
}
ssize_t writes(int fd, char const *str0) { return writen(fd,str0,strlen(str0)); }
ssize_t writes2(char const *str0) { return writes(2,str0); }
//AS-safe sigprockmask helpers (they're in libc too, but not specified as AS-safe)
int sigrelse(int sig){
sigset_t set; sigemptyset(&set); sigaddset(&set,sig);
return sigprocmask(SIG_UNBLOCK,&set,0);
}
int sighold(int sig){
sigset_t set; sigemptyset(&set); sigaddset(&set,sig);
return sigprocmask(SIG_BLOCK,&set,0);
}
#define INTERRUPT_TIME 5
using namespace std;
using namespace chrono;
int pid;
volatile sig_atomic_t recursing_handler_eh; //to differentiate recursive executions of signal_handler
void signal_handler(int signum) {
char ch;
if(!recursing_handler_eh){
kill(pid,SIGSTOP);
writes2("Programm B is interrupted. Please type enter within 5 seconds or the programm will be terminated\n");
alarm(5);
recursing_handler_eh = 1;
sigrelse(SIGALRM);
if (1!=read(0,&ch,1)) signal_handler(signum);
alarm(0);
sighold(SIGALRM);
writes2("Continuing");
kill(pid,SIGCONT);
recursing_handler_eh=0;
alarm(INTERRUPT_TIME);
return;
}
kill(pid,SIGTERM);
_exit(1);
}
int main(int argc, char* argv[]) {
//Usage
if(string(argv[1]) == "h" || string(argv[1]) == "help"){
cout << "usage" << endl;
return 0;
}
pid = fork();
if (pid == 0) {
cout << "Name of programm B: " << argv[1] << endl;
cout << "PID of programm B: " << getpid() << endl;
execvp(argv[1], &argv[1]);
} else if (pid < 0) { cerr << "error" <<endl; return 1; }
struct sigaction sa; sa.sa_handler = signal_handler; sigemptyset(&sa.sa_mask); sa.sa_flags=0; sigaction(SIGALRM, &sa,0);
//signal(SIGALRM, signal_handler);
alarm(INTERRUPT_TIME);
cout << "PID of programm A: " << getpid() << endl;
high_resolution_clock::time_point t1 = high_resolution_clock::now();
int r;
do r = waitpid(pid, nullptr, 0); while(r==-1 && errno==EINTR);
high_resolution_clock::time_point t2 = high_resolution_clock::now();
auto duration = duration_cast<milliseconds>(t2 - t1).count();
cout << "Computing time: " << duration << "ms" << endl;
return 0;
}
Not that the above will wait only for an enter key. To wait for any key, you'll need to put your terminal in raw/cbreak mode and restore the previous settings on exit (ideally on signal deaths too).