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