1 /*
2 * Copyright 2012 Red Hat, Inc.
3 *
4 * This copyrighted material is made available to anyone wishing to use,
5 * modify, copy, or redistribute it subject to the terms and conditions
6 * of the GNU General Public License v2 or (at your option) any later version.
7 */
8
9 #include <inttypes.h>
10 #include <unistd.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <stdint.h>
14 #include <stddef.h>
15 #include <poll.h>
16 #include <fcntl.h>
17 #include <string.h>
18 #include <errno.h>
19 #include <limits.h>
20 #include <time.h>
21 #include <stdarg.h>
22 #include <signal.h>
23 #include <sys/time.h>
24 #include <sys/types.h>
25 #include <sys/wait.h>
26 #include <sys/prctl.h>
27 #include <grp.h>
28
29 #include "dlm_daemon.h"
30
31
32 static int _log_stderr;
33
34 #define log_helper(fmt, args...) \
35 do { \
36 if (_log_stderr) \
37 fprintf(stderr, "%llu " fmt "\n", (unsigned long long)monotime(), ##args); \
38 } while (0)
39
40
41 /*
42 * Restrict the commands that can be run.
43 */
44
45 #define CMD_ID_LVCHANGE_REFRESH 1
46 #define CMD_ID_LVS 2
47
48 static int _get_cmd_id(char **av, int av_count)
49 {
50 if ((av_count >= 3) &&
51 !strcmp(av[0], "lvm") &&
52 !strcmp(av[1], "lvchange") &&
53 !strcmp(av[2], "--refresh")) {
54 return CMD_ID_LVCHANGE_REFRESH;
55 }
56
57 if ((av_count >= 2) &&
58 !strcmp(av[0], "lvm") &&
59 !strcmp(av[1], "lvs")) {
60 return CMD_ID_LVS;
61 }
62
63 return 0;
64 }
65
66 /*
67 * Keep track of running pids mainly because when the process
68 * exits we get the pid, and need to look up the uuid from the
69 * pid to return the uuid/pid/result back to the main daemon.
70 */
71
72 #define MAX_RUNNING 32
73
74 struct running {
75 char uuid[RUN_UUID_LEN];
76 int pid;
77 int cmd_id;
78 };
79
80 static struct running running_cmds[MAX_RUNNING];
81 static int running_count;
82
83 static int _save_running_cmd(char *uuid, int pid, int cmd_id)
84 {
85 int i;
86
87 for (i = 0; i < MAX_RUNNING; i++) {
88 if (!running_cmds[i].pid) {
89 running_cmds[i].pid = pid;
90 running_cmds[i].cmd_id = cmd_id;
91 memcpy(running_cmds[i].uuid, uuid, RUN_UUID_LEN);
92 running_count++;
93 return 0;
94 }
95 }
96 log_helper("too many running commands");
97 return -1;
98 }
99
100 static struct running *_get_running_cmd(int pid)
101 {
102 int i;
103
104 for (i = 0; i < MAX_RUNNING; i++) {
105 if (running_cmds[i].pid == pid)
106 return &running_cmds[i];
107 }
108 return NULL;
109 }
110
111 static struct running *_get_running_uuid(char *uuid)
112 {
113 int i;
114
115 for (i = 0; i < MAX_RUNNING; i++) {
116 if (!strcmp(running_cmds[i].uuid, uuid))
117 return &running_cmds[i];
118 }
119 return NULL;
120 }
121
122 static void _clear_running_cmd(struct running *running)
123 {
124 running_count--;
125 running->pid = 0;
126 running->cmd_id = 0;
127 memset(running->uuid, 0, RUN_UUID_LEN);
128 }
129
130 /* runs in child process that was forked by helper */
131
132 static void exec_command(char *cmd_str, int out_fd)
133 {
134 char cmd_buf[16];
135 char arg[ONE_ARG_LEN];
136 char *av[MAX_AV_COUNT + 1]; /* +1 for NULL */
137 int av_count = 0;
138 int i, rv, arg_len, cmd_len, cmd_id;
139
140 for (i = 0; i < MAX_AV_COUNT + 1; i++)
141 av[i] = NULL;
142
143 if (!cmd_str[0])
144 return;
145
146 /* this should already be done, but make sure */
147 cmd_str[RUN_COMMAND_LEN - 1] = '\0';
148
149 memset(&arg, 0, sizeof(arg));
150 arg_len = 0;
151 cmd_len = strlen(cmd_str);
152
153 for (i = 0; i < cmd_len; i++) {
154 if (!cmd_str[i])
155 break;
156
157 if (av_count == MAX_AV_COUNT)
158 break;
159
160 if (cmd_str[i] == '\\') {
161 if (i == (cmd_len - 1))
162 break;
163 i++;
164
165 if (cmd_str[i] == '\\') {
166 arg[arg_len++] = cmd_str[i];
167 continue;
168 }
169 if (isspace(cmd_str[i])) {
170 arg[arg_len++] = cmd_str[i];
171 continue;
172 } else {
173 break;
174 }
175 }
176
177 if (isalnum(cmd_str[i]) || ispunct(cmd_str[i])) {
178 arg[arg_len++] = cmd_str[i];
179 } else if (isspace(cmd_str[i])) {
180 if (arg_len)
181 av[av_count++] = strdup(arg);
182
183 memset(arg, 0, sizeof(arg));
184 arg_len = 0;
185 } else {
186 break;
187 }
188 }
189
190 if ((av_count < MAX_AV_COUNT) && arg_len) {
191 av[av_count++] = strdup(arg);
192 }
193
194 /*
195 for (i = 0; i < MAX_AV_COUNT + 1; i++) {
196 if (!av[i])
197 break;
198
199 log_helper("command av[%d] \"%s\"", i, av[i]);
200 }
201 */
202
203 cmd_id = _get_cmd_id(av, av_count);
204
205 /* tell the parent the command we have identified to run */
206 memset(cmd_buf, 0, sizeof(cmd_buf));
207 snprintf(cmd_buf, sizeof(cmd_buf), "cmd_id %d", cmd_id);
208 rv = write(out_fd, cmd_buf, sizeof(cmd_buf));
209 if (rv < 0)
210 log_helper("write cmd_buf from child error %d", rv);
211 close(out_fd);
212
213 /* if we return before exec, the child does exit(1) (failure) */
214 if (!cmd_id)
215 return;
216
217 execvp(av[0], av);
218 }
219
220 static int read_request(int fd, struct run_request *req)
221 {
222 int rv;
223 retry:
|
(1) Event string_null_source: |
Function "read" does not terminate string "req". [Note: The source code implementation of the function has been overridden by a builtin model.] |
224 rv = read(fd, req, sizeof(struct run_request));
|
(2) Event cond_false: |
Condition "rv == -1", taking false branch. |
225 if (rv == -1 && errno == EINTR)
|
(3) Event if_end: |
End of if statement. |
226 goto retry;
227
|
(4) Event cond_false: |
Condition "rv != 1160UL /* sizeof (struct run_request) */", taking false branch. |
228 if (rv != sizeof(struct run_request))
|
(5) Event if_end: |
End of if statement. |
229 return -1;
230 return 0;
231 }
232
233 static int send_status(int fd)
234 {
235 struct run_reply rep;
236 int rv;
237
238 memset(&rep, 0, sizeof(rep));
239
240 rv = write(fd, &rep, sizeof(rep));
241
242 if (rv == sizeof(rep))
243 return 0;
244 return -1;
245 }
246
247 static int send_result(struct running *running, int fd, int pid, int result)
248 {
249 struct run_reply rep;
250 int rv;
251
252 memset(&rep, 0, sizeof(rep));
253
254 rep.header.type = DLM_MSG_RUN_REPLY;
255
256 memcpy(rep.uuid, running->uuid, RUN_UUID_LEN);
257 rep.info.local_pid = pid;
258 rep.info.local_result = result;
259
260 rv = write(fd, &rep, sizeof(rep));
261
262 if (rv == sizeof(rep))
263 return 0;
264 return -1;
265 }
266
267 #define HELPER_STATUS_INTERVAL 30
268 #define STANDARD_TIMEOUT_MS (HELPER_STATUS_INTERVAL*1000)
269 #define RECOVERY_TIMEOUT_MS 1000
270
271 /* run by the child helper process forked by dlm_controld in setup_helper */
272
273 int run_helper(int in_fd, int out_fd, int log_stderr)
274 {
275 struct pollfd pollfd;
276 struct run_request req;
277 struct running *running;
278 struct dlm_header *hd = (struct dlm_header *)&req;
279 char cmd_buf[16];
280 siginfo_t info;
281 unsigned int fork_count = 0;
282 unsigned int done_count = 0;
283 time_t now, last_send, last_good = 0;
284 int timeout = STANDARD_TIMEOUT_MS;
285 int rv, pid, cmd_id;
286
287 _log_stderr = log_stderr;
288
289 rv = setgroups(0, NULL);
|
(1) Event cond_true: |
Condition "rv < 0", taking true branch. |
290 if (rv < 0)
|
(2) Event cond_true: |
Condition "_log_stderr", taking true branch. |
291 log_helper("error clearing helper groups errno %i", errno);
292
293 memset(&pollfd, 0, sizeof(pollfd));
294 pollfd.fd = in_fd;
295 pollfd.events = POLLIN;
296
297 now = monotime();
298 last_send = now;
299 rv = send_status(out_fd);
|
(3) Event cond_true: |
Condition "!rv", taking true branch. |
300 if (!rv)
301 last_good = now;
302
303 openlog("dlm_controld", LOG_CONS | LOG_PID, LOG_LOCAL4);
304
305 while (1) {
306 rv = poll(&pollfd, 1, timeout);
|
(4) Event cond_true: |
Condition "rv == -1", taking true branch. |
|
(5) Event cond_true: |
Condition "*__errno_location() == 4", taking true branch. |
|
(8) Event cond_false: |
Condition "rv == -1", taking false branch. |
307 if (rv == -1 && errno == EINTR)
|
(6) Event continue: |
Continuing loop. |
|
(9) Event if_end: |
End of if statement. |
308 continue;
309
|
(10) Event cond_false: |
Condition "rv < 0", taking false branch. |
310 if (rv < 0)
|
(11) Event if_end: |
End of if statement. |
311 exit(0);
312
313 now = monotime();
314
|
(12) Event cond_true: |
Condition "now - last_good >= 30", taking true branch. |
|
(13) Event cond_true: |
Condition "now - last_send >= 2", taking true branch. |
315 if (now - last_good >= HELPER_STATUS_INTERVAL &&
316 now - last_send >= 2) {
317 last_send = now;
318 rv = send_status(out_fd);
|
(14) Event cond_true: |
Condition "!rv", taking true branch. |
319 if (!rv)
320 last_good = now;
321 }
322
323 memset(&req, 0, sizeof(req));
324
|
(15) Event cond_true: |
Condition "pollfd.revents & 1", taking true branch. |
325 if (pollfd.revents & POLLIN) {
|
(16) Event string_null_source: |
Function "read_request" does not terminate string "req". [details] |
| Also see events: |
[string_null] |
326 rv = read_request(in_fd, &req);
|
(17) Event cond_false: |
Condition "rv", taking false branch. |
327 if (rv)
|
(18) Event if_end: |
End of if statement. |
328 continue;
329
|
(19) Event cond_true: |
Condition "hd->type == DLM_MSG_RUN_REQUEST", taking true branch. |
330 if (hd->type == DLM_MSG_RUN_REQUEST) {
331 int cmd_pipe[2];
332
|
(20) Event cond_false: |
Condition "running_count >= 32", taking false branch. |
333 if (running_count >= MAX_RUNNING) {
334 log_helper("too many running commands");
335 exit(1);
|
(21) Event if_end: |
End of if statement. |
336 }
337
338 /*
339 * Child writes cmd_buf to cmd_pipe, parent reads
340 * cmd_buf from cmd_pipe. cmd_buf contains the
341 * string "cmd_id <num>" where <num> is CMD_ID_NUM
342 * identifying the command being run by the child.
343 */
344
|
(22) Event cond_false: |
Condition "pipe(cmd_pipe)", taking false branch. |
345 if (pipe(cmd_pipe))
|
(23) Event if_end: |
End of if statement. |
346 exit(1);
347
348 pid = fork();
|
(24) Event cond_false: |
Condition "!pid", taking false branch. |
349 if (!pid) {
350 close(cmd_pipe[0]);
351 exec_command(req.command, cmd_pipe[1]);
352 exit(1);
|
(25) Event if_end: |
End of if statement. |
353 }
354
355 close(cmd_pipe[1]);
356
357 memset(cmd_buf, 0, sizeof(cmd_buf));
358 cmd_id = 0;
359
360 rv = read(cmd_pipe[0], cmd_buf, sizeof(cmd_buf));
|
(26) Event cond_false: |
Condition "rv < 0", taking false branch. |
361 if (rv < 0)
|
(27) Event if_end: |
End of if statement. |
362 log_helper("helper read child cmd_id error %d", rv);
363
364 close(cmd_pipe[0]);
365
366 sscanf(cmd_buf, "cmd_id %d", &cmd_id);
367
368 _save_running_cmd(req.uuid, pid, cmd_id);
369
370 fork_count++;
371
|
(28) Event cond_true: |
Condition "_log_stderr", taking true branch. |
|
(29) Event string_null: |
Passing unterminated string "req.command" to "fprintf", which expects a null-terminated string. [Note: The source code implementation of the function has been overridden by a builtin model.] |
| Also see events: |
[string_null_source] |
372 log_helper("helper run %s pid %d cmd_id %d running %d fork_count %d done_count %d %s",
373 req.uuid, pid, cmd_id, running_count, fork_count, done_count, req.command);
374
375 } else if (hd->type == DLM_MSG_RUN_CANCEL) {
376
377 /* TODO: should we also send kill to the pid? */
378
379 if (!(running = _get_running_uuid(req.uuid)))
380 log_helper("no running cmd for cancel uuid");
381 else {
382 log_helper("cancel running cmd uuid %s pid %d", running->uuid, running->pid);
383 _clear_running_cmd(running);
384 }
385 }
386 }
387
388 if (pollfd.revents & (POLLERR | POLLHUP | POLLNVAL))
389 exit(0);
390
391 /* collect child exits until no more children exist (ECHILD)
392 or none are ready (WNOHANG) */
393
394 while (1) {
395 memset(&info, 0, sizeof(info));
396
397 rv = waitid(P_ALL, 0, &info, WEXITED | WNOHANG);
398
399 if ((rv < 0) && (errno == ECHILD)) {
400 /*
401 log_helper("helper no children exist fork_count %d done_count %d", fork_count, done_count);
402 */
403 timeout = STANDARD_TIMEOUT_MS;
404 }
405
406 else if (!rv && !info.si_pid) {
407 log_helper("helper no children ready fork_count %d done_count %d", fork_count, done_count);
408 timeout = RECOVERY_TIMEOUT_MS;
409 }
410
411 else if (!rv && info.si_pid) {
412 done_count++;
413
414 if (!(running = _get_running_cmd(info.si_pid))) {
415 log_helper("running cmd for pid %d result %d not found",
416 info.si_pid, info.si_status);
417 continue;
418 } else {
419 log_helper("running cmd for pid %d result %d done",
420 info.si_pid, info.si_status);
421 }
422
423 if (info.si_status) {
424 syslog(LOG_ERR, "%llu run error %s id %d pid %d status %d code %d",
425 (unsigned long long)monotime(),
426 running->uuid, running->cmd_id, running->pid,
427 info.si_status, info.si_code);
428 }
429
430 send_result(running, out_fd, info.si_pid, info.si_status);
431
432 _clear_running_cmd(running);
433 continue;
434 }
435
436 else {
437 log_helper("helper waitid rv %d errno %d fork_count %d done_count %d",
438 rv, errno, fork_count, done_count);
439 }
440
441 break;
442 }
|
(7) Event loop: |
Looping back. |
443 }
444
445 closelog();
446 return 0;
447 }
448