1 // +------------------------------------------------------------------+
2 // | ____ _ _ __ __ _ __ |
3 // | / ___| |__ ___ ___| | __ | \/ | |/ / |
4 // | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
5 // | | |___| | | | __/ (__| < | | | | . \ |
6 // | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
8 // | Copyright Mathias Kettner 2014 mk@mathias-kettner.de |
9 // +------------------------------------------------------------------+
11 // This file is part of Check_MK.
12 // The official homepage is at http://mathias-kettner.de/check_mk.
14 // check_mk is free software; you can redistribute it and/or modify it
15 // under the terms of the GNU General Public License as published by
16 // the Free Software Foundation in version 2. check_mk is distributed
17 // in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
18 // out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
19 // PARTICULAR PURPOSE. See the GNU General Public License for more de-
20 // ails. You should have received a copy of the GNU General Public
21 // License along with GNU Make; see the file COPYING. If not, write
22 // to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
23 // Boston, MA 02110-1301 USA.
34 static pid_t g_pid
= 0;
35 static int g_timeout
= 0;
36 static int g_signum
= SIGTERM
;
38 static void out(const char *buf
) {
39 size_t bytes_to_write
= strlen(buf
);
40 while (bytes_to_write
> 0) {
41 ssize_t written
= write(STDERR_FILENO
, buf
, bytes_to_write
);
43 if (errno
== EINTR
) continue;
47 bytes_to_write
-= written
;
51 static void exit_with(const char *message
, int err
, int status
) {
61 static void version() {
63 "waitmax version 1.1\n"
64 "Copyright Mathias Kettner 2008\n"
65 "This is free software; see the source for copying conditions. "
67 "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR "
72 static void usage(int status
) {
74 "Usage: waitmax [-s SIGNUM] MAXTIME PROGRAM [ARGS...]\n"
76 "Execute PROGRAM as a subprocess. If PROGRAM does not exit before "
78 "seconds, it will be killed with SIGTERM or an alternative signal.\n"
80 " -s, --signal SIGNUM kill with SIGNUM on timeout\n"
81 " -h, --help this help\n"
82 " -V, --version show version an exit\n",
86 static void kill_group(pid_t pid
, int signum
) {
87 /* The child might have become a process group leader itself, so send the
88 signal directly to it. */
91 /* Guard against harakiri... */
92 signal(signum
, SIG_IGN
);
94 /* Send the signal to all processes in our fresh process group. */
98 static void signalhandler(int signum
) {
99 if (signum
== SIGALRM
) {
100 /* The child took too long, so remember that we timed out and send the
101 configured signal instead of SIGALRM. */
106 /* Are we the child process or has the child not been execvp'd yet? */
107 if (g_pid
== 0) exit(signum
+ 128);
109 /* Send the configure signal to our process group. */
110 kill_group(g_pid
, signum
);
112 /* Make sure the children actually react on the signal. */
113 if (signum
!= SIGKILL
&& signum
!= SIGCONT
) kill_group(g_pid
, SIGCONT
);
116 static void setup_signal_handlers() {
118 sigemptyset(&sa
.sa_mask
);
119 sa
.sa_handler
= signalhandler
;
120 sa
.sa_flags
= SA_RESTART
; /* just to be sure... */
121 sigaction(g_signum
, &sa
, NULL
);
122 sigaction(SIGALRM
, &sa
, NULL
);
123 sigaction(SIGHUP
, &sa
, NULL
);
124 sigaction(SIGINT
, &sa
, NULL
);
125 sigaction(SIGQUIT
, &sa
, NULL
);
126 sigaction(SIGTERM
, &sa
, NULL
);
128 /* Guard against a background child doing I/O on the tty. */
129 sa
.sa_handler
= SIG_IGN
;
130 sigaction(SIGTTIN
, &sa
, NULL
);
131 sigaction(SIGTTOU
, &sa
, NULL
);
133 /* Make sure that waitpid won't fail. */
134 sa
.sa_handler
= SIG_DFL
;
135 sigaction(SIGCHLD
, &sa
, NULL
);
138 static void unblock_signal(int signum
) {
139 sigset_t signals_to_unblock
;
140 sigemptyset(&signals_to_unblock
);
141 sigaddset(&signals_to_unblock
, signum
);
142 if (sigprocmask(SIG_UNBLOCK
, &signals_to_unblock
, NULL
) == -1)
143 exit_with("sigprocmask failed", errno
, 1);
146 static struct option long_options
[] = {{"version", no_argument
, 0, 'V'},
147 {"help", no_argument
, 0, 'h'},
148 {"signal", required_argument
, 0, 's'},
151 int main(int argc
, char **argv
) {
152 /* Note: setenv calls malloc, and 'diet' warns about that. */
153 if (getenv("POSIXLY_CORRECT") == NULL
) putenv("POSIXLY_CORRECT=true");
155 while ((ret
= getopt_long(argc
, argv
, "Vhs:", long_options
, NULL
)) != -1) {
166 g_signum
= atoi(optarg
);
167 if (g_signum
< 1 || g_signum
> 32)
168 exit_with("Signalnumber must be between 1 and 32.", 0, 1);
177 if (optind
+ 1 >= argc
) usage(1);
179 int maxtime
= atoi(argv
[optind
]);
180 if (maxtime
<= 0) usage(1);
182 /* Create a new process group with ourselves as the process group
183 leader. This way we can send a signal to all subprocesses later (unless
184 some non-direct descendant creates its own process group). Doing this in
185 the parent process already simplifies things, because we don't have to
186 worry about foreground/background groups then. */
189 /* Setting up signal handlers before forking avoids race conditions with the
191 setup_signal_handlers();
194 if (g_pid
== -1) exit_with("fork() failed", errno
, 1);
197 /* Restore tty behavior in the child. */
199 sigemptyset(&sa
.sa_mask
);
200 sa
.sa_flags
= SA_RESTART
; /* just to be sure... */
201 sa
.sa_handler
= SIG_DFL
;
202 sigaction(SIGTTIN
, &sa
, NULL
);
203 sigaction(SIGTTOU
, &sa
, NULL
);
205 execvp(argv
[optind
+ 1], argv
+ optind
+ 1);
206 exit_with(argv
[optind
+ 1], errno
, 253);
209 /* Make sure SIGALRM is not blocked (e.g. by parent). */
210 unblock_signal(SIGALRM
);
214 while (waitpid(g_pid
, &status
, 0) == -1) {
215 if (errno
!= EINTR
) exit_with("waitpid() failed", errno
, 1);
218 if (WIFEXITED(status
)) return WEXITSTATUS(status
);
219 if (WIFSIGNALED(status
)) return g_timeout
? 255 : 128 + WTERMSIG(status
);
220 exit_with("Program did neither exit nor was signalled.", 0, 254);
221 return 0; /* Make GCC happy. */