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(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", booth_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(struct ticket_config *tk, char *prog)
70 {
71 if (set_booth_env(tk)) {
72 _exit(1);
73 }
74 closefiles(); /* don't leak open files */
75 tk_log_debug("running handler %s", prog);
76 execv(prog, tk_test.argv);
77 tk_log_error("%s: execv failed (%s)", prog, strerror(errno));
78 _exit(1);
79 }
80
81 static int
82 prog_filter(const struct dirent *dp)
83 {
84 return (*dp->d_name != '.');
85 }
86
87 static pid_t curr_pid;
88 static int ignore_status;
89
90 static int
91 test_exit_status(struct ticket_config *tk, char *prog, int status, int log_msg)
92 {
93 int rv = -1;
94
95 if (WIFEXITED(status)) {
96 rv = WEXITSTATUS(status);
97 } else if (WIFSIGNALED(status)) {
98 rv = 128 + WTERMSIG(status);
99 }
100 if (rv) {
101 if (log_msg) {
102 tk_log_warn("handler \"%s\" failed: %s",
103 prog, interpret_rv(status));
104 tk_log_warn("we are not allowed to acquire ticket");
105 }
106 } else {
107 tk_log_debug("handler \"%s\" exited with success",
108 prog);
109 }
110 return rv;
111 }
112
113 static void
114 reset_test_state(struct ticket_config *tk)
115 {
116 tk_test.pid = 0;
117 set_progstate(tk, EXTPROG_IDLE);
118 }
119
120 int
121 tk_test_exit_status(struct ticket_config *tk)
122 {
123 int rv;
124
125 rv = test_exit_status(tk, tk_test.path, tk_test.status, !tk_test.is_dir);
126 reset_test_state(tk);
127 return rv;
128 }
129
130 void
131 wait_child(int sig)
132 {
133 int i, status;
134 struct ticket_config *tk;
135
136 /* use waitpid(2) and not wait(2) in order not to interfere
137 * with popen(2)/pclose(2) and system(2) used in pacemaker.c
138 */
139 _FOREACH_TICKET(i, tk) {
140 if (tk_test.path && tk_test.pid > 0 &&
141 (tk_test.progstate == EXTPROG_RUNNING ||
142 tk_test.progstate == EXTPROG_IGNORE) &&
143 waitpid(tk_test.pid, &status, WNOHANG) == tk_test.pid) {
144 if (tk_test.progstate == EXTPROG_IGNORE) {
145 /* not interested in the outcome */
146 reset_test_state(tk);
147 } else {
148 tk_test.status = status;
149 set_progstate(tk, EXTPROG_EXITED);
150 }
151 }
152 }
153 }
154
155 /* the parent may want to have us stop processing scripts, say
156 * when the ticket gets revoked
157 */
158 static void
159 ignore_rest(int sig)
160 {
161 signal(SIGTERM, SIG_IGN);
162 ignore_status = 1;
163 if (curr_pid > 0) {
164 (void)kill(curr_pid, SIGTERM);
165 }
166 }
167
168 void
169 ext_prog_timeout(struct ticket_config *tk)
170 {
171 tk_log_warn("handler timed out");
172 }
173
174 int
175 is_ext_prog_running(struct ticket_config *tk)
176 {
177 if (!tk_test.path)
178 return 0;
179 return (tk_test.pid > 0 && tk_test.progstate == EXTPROG_RUNNING);
180 }
181
182 void
183 ignore_ext_test(struct ticket_config *tk)
184 {
185 if (is_ext_prog_running(tk)) {
186 (void)kill(tk_test.pid, SIGTERM);
187 set_progstate(tk, EXTPROG_IGNORE);
188 } else if (tk_test.progstate == EXTPROG_EXITED) {
189 /* external prog exited, but the status not yet examined;
190 * we're not interested in checking the status anymore */
191 reset_test_state(tk);
192 }
193 }
194
195 static void
196 process_ext_dir(struct ticket_config *tk)
197 {
198 char prog[FILENAME_MAX+1];
199 int rv, n_progs, i, status;
|
(1) Event var_decl: |
Declaring variable "proglist" without initializer. |
| Also see events: |
[uninit_use_in_call] |
200 struct dirent **proglist, *dp;
201
202 signal(SIGTERM, (__sighandler_t)ignore_rest);
203 signal(SIGCHLD, SIG_DFL);
204 signal(SIGUSR1, SIG_DFL);
205 signal(SIGINT, SIG_DFL);
|
(2) Event cond_true: |
Condition "debug_level", taking true branch. |
206 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] |
207 n_progs = scandir(tk_test.path, &proglist, prog_filter, alphasort);
208 if (n_progs == -1) {
209 tk_log_error("%s: scandir failed (%s)", tk_test.path, strerror(errno));
210 _exit(1);
211 }
212 for (i = 0; i < n_progs; i++) {
213 if (ignore_status)
214 break;
215 dp = proglist[i];
216 if (strlen(dp->d_name) + strlen(tk_test.path) + 1 > FILENAME_MAX) {
217 tk_log_error("%s: name exceeds max length (%s)",
218 tk_test.path, dp->d_name);
219 _exit(1);
220 }
221 strcpy(prog, tk_test.path);
222 strcat(prog, "/");
223 strcat(prog, dp->d_name);
224 switch(curr_pid=fork()) {
225 case -1:
226 log_error("fork: %s", strerror(errno));
227 _exit(1);
228 case 0: /* child */
229 run_ext_prog(tk, prog);
230 break; /* run_ext_prog effectively noreturn */
231 default: /* parent */
232 while (waitpid(curr_pid, &status, 0) != curr_pid)
233 ;
234 curr_pid = 0;
235 if (!ignore_status) {
236 rv = test_exit_status(tk, prog, status, 1);
237 if (rv)
238 _exit(rv);
239 } else {
240 /*
241 * To make ignore_rest function signal safe log_info
242 * must be removed from signal function. Information
243 * about signal delivery is important so put it here.
244 */
245 log_info("external programs handler caught TERM, ignoring "
246 "status of external test programs");
247 }
248 }
249 }
250 _exit(0);
251 }
252
253 /* run some external program
254 * return codes:
255 * RUNCMD_ERR: executing program failed (or some other failure)
256 * RUNCMD_MORE: program forked, results later
257 */
258 int
259 run_handler(struct ticket_config *tk)
260 {
261 int rv = 0;
262 pid_t pid;
263 struct stat stbuf;
264
265 if (!tk_test.path)
266 return 0;
267
268 if (stat(tk_test.path, &stbuf)) {
269 tk_log_error("%s: stat failed (%s)", tk_test.path, strerror(errno));
270 return RUNCMD_ERR;
271 }
272 tk_test.is_dir = (stbuf.st_mode & S_IFDIR);
273
274 switch(pid=fork()) {
275 case -1:
276 log_error("fork: %s", strerror(errno));
277 return RUNCMD_ERR;
278 case 0: /* child */
279 if (tk_test.is_dir) {
280 process_ext_dir(tk);
281 } else {
282 run_ext_prog(tk, tk_test.path);
283 }
284 default: /* parent */
285 tk_test.pid = pid;
286 set_progstate(tk, EXTPROG_RUNNING);
287 rv = RUNCMD_MORE; /* program runs */
288 }
289
290 return rv;
291 }
292