1 /*****************************************************************************
2 * Written by Chris Dunlap <cdunlap@llnl.gov>.
3 * Copyright (C) 2007-2018 Lawrence Livermore National Security, LLC.
4 * Copyright (C) 2001-2007 The Regents of the University of California.
5 * UCRL-CODE-2002-009.
6 *
7 * This file is part of ConMan: The Console Manager.
8 * For details, see <https://dun.github.io/conman/>.
9 *
10 * ConMan is free software: you can redistribute it and/or modify it under
11 * the terms of the GNU General Public License as published by the Free
12 * Software Foundation, either version 3 of the License, or (at your option)
13 * any later version.
14 *
15 * ConMan is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
18 * for more details.
19 *
20 * You should have received a copy of the GNU General Public License along
21 * with ConMan. If not, see <http://www.gnu.org/licenses/>.
22 *****************************************************************************/
23
24
25 #if HAVE_CONFIG_H
26 # include <config.h>
27 #endif /* HAVE_CONFIG_H */
28
29 #include <assert.h>
30 #include <errno.h>
31 #include <limits.h>
32 #include <signal.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <sys/socket.h>
37 #include <sys/stat.h>
38 #include <time.h>
39 #include <unistd.h>
40 #include "list.h"
41 #include "log.h"
42 #include "server.h"
43 #include "tpoll.h"
44 #include "util.h"
45 #include "util-file.h"
46 #include "util-str.h"
47
48
49 static int search_exec_path(const char *path, const char *src,
50 char *dst, int dstlen);
51 static int disconnect_process_obj(obj_t *process);
52 static int connect_process_obj(obj_t *process);
53 static int check_process_prog(obj_t *process);
54 static void reset_process_delay(obj_t *process);
55
56 extern tpoll_t tp_global; /* defined in server.c */
57
58
is_process_dev(const char * dev,const char * cwd,const char * exec_path,char ** path_ref)59 int is_process_dev(const char *dev, const char *cwd,
60 const char *exec_path, char **path_ref)
61 {
62 char buf[PATH_MAX];
63 int n;
64 struct stat st;
65
66 assert(dev != NULL);
67
68 if (!strchr(dev, '/')
69 && (search_exec_path(exec_path, dev, buf, sizeof(buf)) == 0)) {
70 dev = buf;
71 }
72 else if ((dev[0] != '/') && (cwd != NULL)) {
73 n = snprintf(buf, sizeof(buf), "%s/%s", cwd, dev);
74 if ((n < 0) || ((size_t) n >= sizeof(buf))) {
75 return(0);
76 }
77 dev = buf;
78 }
79 if (stat(dev, &st) < 0) {
80 return(0);
81 }
82 if (!S_ISREG(st.st_mode)) {
83 return(0);
84 }
85 if (access(dev, X_OK) < 0) {
86 return(0);
87 }
88 if (path_ref) {
89 *path_ref = create_string(dev);
90 }
91 return(1);
92 }
93
94
search_exec_path(const char * path,const char * src,char * dst,int dstlen)95 static int search_exec_path(const char *path, const char *src,
96 char *dst, int dstlen)
97 {
98 char path_buf[PATH_MAX];
99 char file_buf[PATH_MAX];
100 char *p;
101 char *q;
102 struct stat st;
103 int n;
104
105 assert((path == NULL) || (strlen(path) < PATH_MAX));
106 assert(src != NULL);
107 assert(strchr(src, '/') == NULL);
108 assert(dst != NULL);
109 assert(dstlen >= 0);
110
111 if (!path) {
112 return(-1);
113 }
114 if (strlcpy(path_buf, path, sizeof(path_buf)) >= sizeof(path_buf)) {
115 return(-1);
116 }
117 for (p = path_buf; p && *p; p = q) {
118 if ((q = strchr(p, ':'))) {
119 *q++ = '\0';
120 }
121 else {
122 q = strchr(p, '\0');
123 }
124 if (stat(p, &st) < 0) {
125 continue;
126 }
127 if (!S_ISDIR(st.st_mode)) {
128 continue;
129 }
130 n = snprintf(file_buf, sizeof(file_buf), "%s/%s", p, src);
131 if ((n < 0) || ((size_t) n >= sizeof(file_buf))) {
132 continue;
133 }
134 if (access(file_buf, X_OK) < 0) {
135 continue;
136 }
137 if ((dst != NULL) && (dstlen > 0)) {
138 if (strlcpy(dst, file_buf, dstlen) >= (size_t) dstlen) {
139 return(1);
140 }
141 }
142 return(0);
143 }
144 return(-1);
145 }
146
147
create_process_obj(server_conf_t * conf,char * name,List args,char * errbuf,int errlen)148 obj_t * create_process_obj(server_conf_t *conf, char *name, List args,
149 char *errbuf, int errlen)
150 {
151 /* Creates a new process device object and adds it to the master objs list.
152 * Note: an external process will later be fork/exec'd based on the argv
153 * by main:open_objs:reopen_obj:open_process_obj().
154 * Returns the new object, or NULL on error.
155 */
156 ListIterator i;
157 obj_t *process;
158 process_obj_t *auxp;
159 int num_args;
160 int n;
161 char *arg;
162
163 assert(conf != NULL);
164 assert((name != NULL) && (name[0] != '\0'));
165 assert((args != NULL) && (list_count(args) > 0));
166
167 /* Check for duplicate console names.
168 */
169 i = list_iterator_create(conf->objs);
170 while ((process = list_next(i))) {
171 if (is_console_obj(process) && !strcmp(process->name, name)) {
172 if ((errbuf != NULL) && (errlen > 0)) {
173 snprintf(errbuf, errlen,
174 "console [%s] specifies duplicate console name", name);
175 }
176 break;
177 }
178 }
179 list_iterator_destroy(i);
180 if (process != NULL) {
181 return(NULL);
182 }
183 process = create_obj(conf, name, -1, CONMAN_OBJ_PROCESS);
184 auxp = &(process->aux.process);
185
186 auxp->timer = -1;
187 auxp->delay = PROCESS_MIN_TIMEOUT;
188 auxp->pid = -1;
189 auxp->tStart = 0;
190 auxp->logfile = NULL;
191 auxp->state = CONMAN_PROCESS_DOWN;
192 num_args = list_count(args);
193 auxp->argv = calloc(num_args + 1, sizeof(char *));
194 if (!auxp->argv) {
195 out_of_memory();
196 }
197 for (n = 0; (n < num_args) && (arg = list_pop(args)); n++) {
198 auxp->argv[n] = arg;
199 }
200 auxp->argv[n] = (char *) NULL;
201 if ((arg = strrchr(auxp->argv[0], '/'))) {
202 auxp->prog = arg + 1;
203 }
204 else {
205 auxp->prog = auxp->argv[0];
206 }
207 /* Add obj to the master conf->objs list.
208 */
209 list_append(conf->objs, process);
210
211 return(process);
212 }
213
214
open_process_obj(obj_t * process)215 int open_process_obj(obj_t *process)
216 {
217 /* (Re)opens the specified 'process' obj.
218 * Returns 0 if the child process is successfully opened;
219 * o/w, sets a reconnect timer and returns -1.
220 */
221 process_obj_t *auxp;
222 int rc = 0;
223
224 assert(process != NULL);
225 assert(is_process_obj(process));
226
227 auxp = &(process->aux.process);
228
229 if (auxp->timer >= 0) {
230 (void) tpoll_timeout_cancel(tp_global, auxp->timer);
231 auxp->timer = -1;
232 }
233
234 if (auxp->state == CONMAN_PROCESS_UP) {
235 rc = disconnect_process_obj(process);
236 }
237 else if (auxp->state == CONMAN_PROCESS_DOWN) {
238 rc = connect_process_obj(process);
239 }
240
241 if (rc < 0) {
242 DPRINTF((15, "Retrying [%s] connection to prog=\"%s\" in %ds\n",
243 process->name, auxp->argv[0], auxp->delay));
244
245 auxp->timer = tpoll_timeout_relative(tp_global,
246 (callback_f) open_process_obj, process, auxp->delay * 1000);
247
248 auxp->delay = (auxp->delay == 0)
249 ? PROCESS_MIN_TIMEOUT
250 : MIN(auxp->delay * 2, PROCESS_MAX_TIMEOUT);
251 }
252 return(rc);
253 }
254
255
disconnect_process_obj(obj_t * process)256 static int disconnect_process_obj(obj_t *process)
257 {
258 /* Closes the existing connection with the specified 'process' obj.
259 * Always returns -1 to signal a reconnect.
260 */
261 process_obj_t *auxp;
262 time_t tNow;
263 char *delta_str;
264
265 assert(process != NULL);
266 assert(process->aux.process.pid > 0);
267 assert(process->aux.process.tStart > 0);
268 assert(process->aux.process.state != CONMAN_PROCESS_DOWN);
269
270 auxp = &(process->aux.process);
271
272 if (process->fd >= 0) {
273 tpoll_clear(tp_global, process->fd, POLLIN | POLLOUT);
274 (void) close(process->fd);
275 process->fd = -1;
276 }
277 if (time(&tNow) == (time_t) -1) {
278 log_err(errno, "time() failed");
279 }
280 delta_str = create_time_delta_string(auxp->tStart, tNow);
281
282 /* Notify linked objs when transitioning from an UP state.
283 */
284 assert(auxp->state == CONMAN_PROCESS_UP);
285 write_notify_msg(process, LOG_INFO,
286 "Console [%s] disconnected from \"%s\" (pid %d) after %s",
287 process->name, auxp->prog, auxp->pid, delta_str);
288 free(delta_str);
289
290 (void) kill(auxp->pid, SIGKILL);
291 auxp->pid = -1;
292 auxp->tStart = 0;
293 auxp->state = CONMAN_PROCESS_DOWN;
294 return (-1);
295 }
296
297
connect_process_obj(obj_t * process)298 static int connect_process_obj(obj_t *process)
299 {
300 /* Opens a connection to the specified 'process' obj.
301 * Returns 0 if the connection is successfully completed; o/w, returns -1.
302 */
303 process_obj_t *auxp;
304 int fd_pair[2] = {-1,-1};
305 pid_t pid;
306
307 assert(process != NULL);
308 assert(process->fd == -1);
309 assert(process->aux.process.pid == -1);
310 assert(process->aux.process.state != CONMAN_PROCESS_UP);
311
312 auxp = &(process->aux.process);
313
314 if (check_process_prog(process) < 0) {
315 goto err;
316 }
317 if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd_pair) < 0) {
318 write_notify_msg(process, LOG_WARNING,
319 "Console [%s] connection failed: socketpair error: %s",
320 process->name, strerror(errno));
321 goto err;
322 }
323 set_fd_nonblocking(fd_pair[0]);
324 set_fd_nonblocking(fd_pair[1]);
325 set_fd_closed_on_exec(fd_pair[0]);
326 set_fd_closed_on_exec(fd_pair[1]);
327
328 if ((pid = fork()) < 0) {
329 write_notify_msg(process, LOG_WARNING,
330 "Console [%s] connection failed: fork error: %s",
331 process->name, strerror(errno));
332 goto err;
333 }
334 else if (pid == 0) {
335 if (close(fd_pair[0]) < 0) {
336 log_err(errno, "close() of child fd_pair failed");
337 }
338 if (dup2(fd_pair[1], STDIN_FILENO) < 0) {
339 log_err(errno, "dup2() of child stdin failed");
340 }
341 if (dup2(fd_pair[1], STDOUT_FILENO) < 0) {
342 log_err(errno, "dup2() of child stdout failed");
343 }
344 if (dup2(fd_pair[1], STDERR_FILENO) < 0) {
345 log_err(errno, "dup2() of child stderr failed");
346 }
347 execv(auxp->argv[0], auxp->argv);
348 _exit(127);
349 }
350 if (close(fd_pair[1]) < 0) {
351 log_err(errno, "close() of parent fd_pair failed");
352 }
353 if (time(&(auxp->tStart)) == (time_t) -1) {
354 log_err(errno, "time() failed");
355 }
356 process->fd = fd_pair[0];
357 auxp->pid = pid;
358 process->gotEOF = 0;
359 auxp->state = CONMAN_PROCESS_UP;
360 tpoll_set(tp_global, process->fd, POLLIN);
361
362 /* Require the connection to be up for a minimum length of time before
363 * resetting the reconnect-delay back to zero.
364 */
365 auxp->timer = tpoll_timeout_relative(tp_global,
366 (callback_f) reset_process_delay, process, PROCESS_MIN_TIMEOUT * 1000);
367
368 /* Notify linked objs when transitioning into an UP state.
369 */
370 write_notify_msg(process, LOG_INFO,
371 "Console [%s] connected to \"%s\" (pid %d)",
372 process->name, auxp->prog, auxp->pid);
373 DPRINTF((9, "Opened [%s] process: fd=%d/%d prog=\"%s\" pid=%d.\n",
374 process->name, fd_pair[0], fd_pair[1], auxp->argv[0], auxp->pid));
375
376 return(0);
377
378 err:
379 if (fd_pair[0] >= 0) {
380 (void) close(fd_pair[0]);
381 }
382 if (fd_pair[1] >= 0) {
383 (void) close(fd_pair[1]);
384 }
385 return(-1);
386 }
387
388
check_process_prog(obj_t * process)389 static int check_process_prog(obj_t *process)
390 {
391 /* Checks whether the 'process' executable will likely exec.
392 * Returns 0 if all checks pass; o/w, returns -1.
393 */
394 process_obj_t *auxp;
395 struct stat st;
396
397 assert(process != NULL);
398
399 auxp = &(process->aux.process);
400
401 if (stat(auxp->argv[0], &st) < 0) {
402 write_notify_msg(process, LOG_WARNING,
403 "Console [%s] connection failed: \"%s\" stat error: %s",
404 process->name, auxp->prog, strerror(errno));
405 return(-1);
406 }
407 if (!S_ISREG(st.st_mode)) {
408 write_notify_msg(process, LOG_WARNING,
409 "Console [%s] connection failed: \"%s\" not a regular file",
410 process->name, auxp->prog);
411 return(-1);
412 }
413 if (access(auxp->argv[0], X_OK) < 0) {
414 write_notify_msg(process, LOG_WARNING,
415 "Console [%s] connection failed: \"%s\" not executable",
416 process->name, auxp->prog);
417 return(-1);
418 }
419 return(0);
420 }
421
422
reset_process_delay(obj_t * process)423 static void reset_process_delay(obj_t *process)
424 {
425 /* Resets the process obj's reconnect-delay after the connection has been up
426 * for the minimum length of time. This protects against spinning on
427 * reconnects where the process immediately terminates.
428 */
429 process_obj_t *auxp;
430
431 assert(process != NULL);
432 assert(is_process_obj(process));
433
434 auxp = &(process->aux.process);
435
436 /* Reset the timer ID since this routine is only invoked by a timer.
437 */
438 auxp->timer = -1;
439
440 DPRINTF((15, "Reset [%s] reconnect delay\n", process->name));
441 auxp->delay = 0;
442 return;
443 }
444