1 /*
2 * Copyright (C) 2014 Philipp Marek <philipp.marek@linbit.com>
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This software is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19 #include <stdlib.h>
20 #include <string.h>
21 #include <errno.h>
22 #include <arpa/inet.h>
23 #include <signal.h>
24 #include <sys/wait.h>
25 #include <inttypes.h>
26 #include <dirent.h>
27 #include <stdio.h>
28 #include <assert.h>
29 #include <time.h>
30 #include "ticket.h"
31 #include "config.h"
32 #include "inline-fn.h"
33 #include "log.h"
34 #include "pacemaker.h"
35 #include "booth.h"
36 #include "handler.h"
37
38 static int
39 set_booth_env(const struct booth_config *conf, struct ticket_config *tk)
40 {
41 int rv;
42 char expires[16];
43
44 sprintf(expires, "%" PRId64, (int64_t)wall_ts(&tk->term_expires));
45 rv = setenv("BOOTH_TICKET", tk->name, 1) ||
46 setenv("BOOTH_LOCAL", local->addr_string, 1) ||
47 setenv("BOOTH_CONF_NAME", conf->name, 1) ||
48 setenv("BOOTH_CONF_PATH", cl.configfile, 1) ||
49 setenv("BOOTH_TICKET_EXPIRES", expires, 1);
50
51 if (rv) {
52 log_error("Cannot set environment: %s", strerror(errno));
53 }
54 return rv;
55 }
56
57 static void
58 closefiles(void)
59 {
60 int fd;
61
62 /* close all descriptors except stdin/out/err */
63 for (fd = getdtablesize() - 1; fd > STDERR_FILENO; fd--) {
64 close(fd);
65 }
66 }
67
68 static void
69 run_ext_prog(const struct booth_config *conf, struct ticket_config *tk,
70 char *prog)
71 {
72 if (set_booth_env(conf, tk)) {
73 _exit(1);
74 }
75 closefiles(); /* don't leak open files */
76 tk_log_debug("running handler %s", prog);
77 execv(prog, tk_test.argv);
78 tk_log_error("%s: execv failed (%s)", prog, strerror(errno));
79 _exit(1);
80 }
81
82 static int
83 prog_filter(const struct dirent *dp)
84 {
85 return (*dp->d_name != '.');
86 }
87
88 static pid_t curr_pid;
89 static int ignore_status;
90
91 static int
92 test_exit_status(struct ticket_config *tk, char *prog, int status, int log_msg)
93 {
94 int rv = -1;
95
96 if (WIFEXITED(status)) {
97 rv = WEXITSTATUS(status);
98 } else if (WIFSIGNALED(status)) {
99 rv = 128 + WTERMSIG(status);
100 }
101 if (rv) {
102 if (log_msg) {
103 tk_log_warn("handler \"%s\" failed: %s",
104 prog, interpret_rv(status));
105 tk_log_warn("we are not allowed to acquire ticket");
106 }
107 } else {
108 tk_log_debug("handler \"%s\" exited with success",
109 prog);
110 }
111 return rv;
112 }
113
114 static void
115 reset_test_state(struct ticket_config *tk)
116 {
117 tk_test.pid = 0;
118 set_progstate(tk, EXTPROG_IDLE);
119 }
120
121 int
122 tk_test_exit_status(struct ticket_config *tk)
123 {
124 int rv;
125
126 rv = test_exit_status(tk, tk_test.path, tk_test.status, !tk_test.is_dir);
127 reset_test_state(tk);
128 return rv;
129 }
130
131 void
132 wait_child(struct booth_config *conf, int sig)
133 {
134 int i, status;
135 struct ticket_config *tk;
136
137 /* use waitpid(2) and not wait(2) in order not to interfere
138 * with popen(2)/pclose(2) and system(2) used in pacemaker.c
139 */
140 FOREACH_TICKET(conf, i, tk) {
141 if (tk_test.path && tk_test.pid > 0 &&
142 (tk_test.progstate == EXTPROG_RUNNING ||
143 tk_test.progstate == EXTPROG_IGNORE) &&
144 waitpid(tk_test.pid, &status, WNOHANG) == tk_test.pid) {
145 if (tk_test.progstate == EXTPROG_IGNORE) {
146 /* not interested in the outcome */
147 reset_test_state(tk);
148 } else {
149 tk_test.status = status;
150 set_progstate(tk, EXTPROG_EXITED);
151 }
152 }
153 }
154 }
155
156 /* the parent may want to have us stop processing scripts, say
157 * when the ticket gets revoked
158 */
159 static void
160 ignore_rest(int sig)
161 {
162 signal(SIGTERM, SIG_IGN);
163 ignore_status = 1;
164 if (curr_pid > 0) {
165 (void)kill(curr_pid, SIGTERM);
166 }
167 }
168
169 void
170 ext_prog_timeout(struct ticket_config *tk)
171 {
172 tk_log_warn("handler timed out");
173 }
174
175 int
176 is_ext_prog_running(struct ticket_config *tk)
177 {
178 if (!tk_test.path)
179 return 0;
180 return (tk_test.pid > 0 && tk_test.progstate == EXTPROG_RUNNING);
181 }
182
183 void
184 ignore_ext_test(struct ticket_config *tk)
185 {
186 if (is_ext_prog_running(tk)) {
187 (void)kill(tk_test.pid, SIGTERM);
188 set_progstate(tk, EXTPROG_IGNORE);
189 } else if (tk_test.progstate == EXTPROG_EXITED) {
190 /* external prog exited, but the status not yet examined;
191 * we're not interested in checking the status anymore */
192 reset_test_state(tk);
193 }
194 }
195
196 static void
197 process_ext_dir(const struct booth_config *conf, struct ticket_config *tk)
198 {
199 char prog[FILENAME_MAX+1];
200 int rv, n_progs, i, status;
|
(1) Event var_decl: |
Declaring variable "proglist" without initializer. |
| Also see events: |
[uninit_use_in_call] |
201 struct dirent **proglist, *dp;
202
203 signal(SIGTERM, (__sighandler_t)ignore_rest);
204 signal(SIGCHLD, SIG_DFL);
205 signal(SIGUSR1, SIG_DFL);
206 signal(SIGINT, SIG_DFL);
|
(2) Event cond_true: |
Condition "debug_level", taking true branch. |
207 tk_log_debug("running programs in directory %s", tk_test.path);
|
(3) Event uninit_use_in_call: |
Using uninitialized value "proglist" when calling "scandir". |
| Also see events: |
[var_decl] |
208 n_progs = scandir(tk_test.path, &proglist, prog_filter, alphasort);
209 if (n_progs == -1) {
210 tk_log_error("%s: scandir failed (%s)", tk_test.path, strerror(errno));
211 _exit(1);
212 }
213 for (i = 0; i < n_progs; i++) {
214 if (ignore_status)
215 break;
216 dp = proglist[i];
217 if (strlen(dp->d_name) + strlen(tk_test.path) + 1 > FILENAME_MAX) {
218 tk_log_error("%s: name exceeds max length (%s)",
219 tk_test.path, dp->d_name);
220 _exit(1);
221 }
222 strcpy(prog, tk_test.path);
223 strcat(prog, "/");
224 strcat(prog, dp->d_name);
225 switch(curr_pid=fork()) {
226 case -1:
227 log_error("fork: %s", strerror(errno));
228 _exit(1);
229 case 0: /* child */
230 run_ext_prog(conf, tk, prog);
231 break; /* run_ext_prog effectively noreturn */
232 default: /* parent */
233 while (waitpid(curr_pid, &status, 0) != curr_pid)
234 ;
235 curr_pid = 0;
236 if (!ignore_status) {
237 rv = test_exit_status(tk, prog, status, 1);
238 if (rv)
239 _exit(rv);
240 } else {
241 /*
242 * To make ignore_rest function signal safe log_info
243 * must be removed from signal function. Information
244 * about signal delivery is important so put it here.
245 */
246 log_info("external programs handler caught TERM, ignoring "
247 "status of external test programs");
248 }
249 }
250 }
251 _exit(0);
252 }
253
254 /* run some external program
255 * return codes:
256 * RUNCMD_ERR: executing program failed (or some other failure)
257 * RUNCMD_MORE: program forked, results later
258 */
259 int
260 run_handler(const struct booth_config *conf, struct ticket_config *tk)
261 {
262 int rv = 0;
263 pid_t pid;
264 struct stat stbuf;
265
266 if (!tk_test.path)
267 return 0;
268
269 if (stat(tk_test.path, &stbuf)) {
270 tk_log_error("%s: stat failed (%s)", tk_test.path, strerror(errno));
271 return RUNCMD_ERR;
272 }
273 tk_test.is_dir = (stbuf.st_mode & S_IFDIR);
274
275 switch(pid=fork()) {
276 case -1:
277 log_error("fork: %s", strerror(errno));
278 return RUNCMD_ERR;
279 case 0: /* child */
280 if (tk_test.is_dir) {
281 process_ext_dir(conf, tk);
282 } else {
283 run_ext_prog(conf, tk, tk_test.path);
284 }
285 default: /* parent */
286 tk_test.pid = pid;
287 set_progstate(tk, EXTPROG_RUNNING);
288 rv = RUNCMD_MORE; /* program runs */
289 }
290
291 return rv;
292 }
293