/* $Id$ */ /*** This file is part of ivam2. ivam2 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. ivam2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ivam2; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ***/ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include "exec.h" #include "main.h" #define CHILD_BUF_SIZE 1024 struct process_info { pid_t pid; int dead; int stderr_pipe; process_exit_cb_t cb; void *user; struct process_info *next; char *read_buf; size_t read_buf_len; }; static struct process_info *procs = NULL; static struct process_info *find_process(pid_t pid) { struct process_info *p = procs; while (p) { if (p->pid == pid) return p; p = p->next; } return NULL; } static void *oop_read_cb(oop_source *source, int fd, oop_event event, void *user); static void close_child_pipe(struct process_info *p) { assert(p); if (p->read_buf_len && p->read_buf) { *(p->read_buf + p->read_buf_len) = 0; daemon_log(LOG_INFO, "child(%lu): %s", (unsigned long) p->pid, p->read_buf); p->read_buf_len = 0; } if (p->stderr_pipe >= 0) { assert(event_source && event_source->cancel_fd); event_source->cancel_fd(event_source, p->stderr_pipe, OOP_READ); close(p->stderr_pipe); p->stderr_pipe = -1; } } static void free_process_info(struct process_info *p) { assert (p); close_child_pipe(p); if (p->read_buf) free(p->read_buf); free(p); } static void remove_process(pid_t pid) { struct process_info *p; if (!procs) return; if (procs->pid == pid) { p = procs; procs = procs->next; free(p); return; } p = procs; while (p->next) { if (p->next->pid == pid) { struct process_info *r; r = p->next; p->next = p->next->next; free_process_info(r); } p = p->next; } return; } static void *oop_sigchld_cb(oop_source *source, int sig, void *user) { pid_t pid; int status; struct process_info *p; assert(source && sig == SIGCHLD); if ((pid = waitpid(-1, &status, WUNTRACED)) <= 0) { daemon_log(LOG_ERR, "wait() failed: %s", strerror(errno)); return OOP_HALT; } if (WIFSTOPPED(status)) { daemon_log(LOG_ERR, "Child process stopped!"); return OOP_CONTINUE; } if (!(p = find_process(pid))) { daemon_log(LOG_WARNING, "Got SIGCHLD for unknown process, reaping"); return OOP_CONTINUE; } assert(p && !p->dead); p->dead = 1; if (p->cb) p->cb(pid, status, p->user); if (p->stderr_pipe < 0) remove_process(pid); return OOP_CONTINUE; } static void *oop_read_cb(oop_source *source, int fd, oop_event event, void *user) { struct process_info *p; ssize_t s; char *c, *b, *start; size_t i; assert(source && event == OOP_READ); p = (struct process_info*) user; assert(p && p->stderr_pipe == fd && fd >= 0); if (!p->read_buf) { p->read_buf = malloc(CHILD_BUF_SIZE); p->read_buf_len = 0; } assert(p->read_buf); start = p->read_buf+p->read_buf_len; if ((s = read(fd, start, CHILD_BUF_SIZE-p->read_buf_len-1)) < 0) { daemon_log(LOG_ERR, "Failed to read from child pipe: %s", strerror(errno)); return OOP_HALT; } if (s == 0) { /* EOF */ close_child_pipe(p); if (p->dead) remove_process(p->pid); return OOP_CONTINUE; } /* Escape */ for (c = start, i = 0; i < s; i++, c++) if (*c != '\r' && *c != '\n' && (*c < 32 || *c == 127)) *c = '.'; p->read_buf_len += s; for (c = b = p->read_buf, i = 0; i < p->read_buf_len; i++, c++) { if (*c == '\r' || *c == '\n') { if (c > b) { *c = 0; daemon_log(LOG_INFO, "child: %s", b); } b = c+1; } } if (b != p->read_buf) memcpy(p->read_buf, b, p->read_buf_len = c-b); return OOP_CONTINUE; } static void pipe_close(int fds[]) { if (fds[0] >= 0) close(fds[0]); if (fds[1] >= 0) close(fds[1]); } pid_t child_process_create(const char *file, char *const argv[], int *ifd, int *ofd, process_exit_cb_t cb, void *user, int pipe_hack) { pid_t pid; int stdin_fds[2] = { -1, -1 }; int stdout_fds[2] = { -1, -1 }; int stderr_fds[2] = { -1, -1 }; daemon_log(LOG_INFO, "Executing child process '%s'.", file); if (ofd && pipe(stdin_fds) < 0) { daemon_log(LOG_ERR, "pipe() failed: %s", strerror(errno)); goto fail; } if (ifd && pipe(stdout_fds) < 0) { daemon_log(LOG_ERR, "pipe() failed: %s", strerror(errno)); goto fail; } if (pipe(stderr_fds) < 0) { daemon_log(LOG_ERR, "pipe() failed: %s", strerror(errno)); goto fail; } if ((pid = fork()) < 0) { daemon_log(LOG_ERR, "fork() failed: %s", strerror(errno)); goto fail; } if (pid) { /* parent */ struct process_info *p = malloc(sizeof(struct process_info)); assert(p); memset(p, 0, sizeof(struct process_info)); p->pid = pid; p->cb = cb; p->user = user; p->next = procs; p->stderr_pipe = stderr_fds[0]; close(stderr_fds[1]); if (ifd) { *ifd = stdout_fds[0]; close(stdout_fds[1]); } if (ofd) { *ofd = stdin_fds[1]; close(stdin_fds[0]); } assert(event_source && event_source->on_fd); event_source->on_fd(event_source, p->stderr_pipe, OOP_READ, oop_read_cb, p); procs = p; return pid; } else { /* child */ int fd, efd; close(stderr_fds[0]); if (ifd) { if (pipe_hack) { /* This might get a problem in the future, when more tahn 127 fds have been allocated */ struct stat s; assert(fstat(128, &s) < 0 && errno == EBADF); if (dup2(stdout_fds[0], 128) < 0) { daemon_log(LOG_ERR, "dup2() failed: %s", strerror(errno)); exit(1); } } close(stdout_fds[0]); } if (ofd) close(stdin_fds[1]); for (fd = 0; fd <= 127; fd++) { if (fd != stderr_fds[1] && (!ifd || fd != stdout_fds[1]) && (!ofd || fd != stdin_fds[0])) close(fd); } if (ofd) { if (stdin_fds[0] != 0) { if (dup2(stdin_fds[0], 0) < 0) { daemon_log(LOG_ERR, "dup2() failed: %s", strerror(errno)); exit(1); } close(stdin_fds[0]); } } else { close(0); if (open("/dev/null", O_RDONLY) != 0) { daemon_log(LOG_ERR, "open(\"/dev/null\") != 0: %s", strerror(errno)); exit(1); } } efd = ifd ? stdout_fds[1] : stderr_fds[1]; if (efd != 1) { if (dup2(efd, 1) < 0) { daemon_log(LOG_ERR, "dup2() failed: %s", strerror(errno)); exit(1); } if (ifd) close(stdout_fds[1]); } if (stderr_fds[1] != 2){ if (dup2(stderr_fds[1], 2) < 0) { daemon_log(LOG_ERR, "dup2() failed: %s", strerror(errno)); exit(1); } close(stderr_fds[1]); } umask(0077); execvp(file, argv); daemon_log(LOG_ERR, "exec('%s', ...) failed: %s", file, strerror(errno)); exit(1); } fail: pipe_close(stdin_fds); pipe_close(stdout_fds); pipe_close(stderr_fds); return (pid_t) -1; } int child_process_init(void) { assert(!procs); assert(event_source && event_source->on_signal); event_source->on_signal(event_source, SIGCHLD, oop_sigchld_cb, NULL); return 0; } void child_process_done(void) { assert(event_source && event_source->cancel_signal); event_source->cancel_signal(event_source, SIGCHLD, oop_sigchld_cb, NULL); while (procs) { struct process_info *p = procs; procs = procs->next; free_process_info(p); } } int child_process_kill(pid_t pid) { struct process_info *p = find_process(pid); assert (p); if (kill(p->pid, SIGTERM) < 0 && errno != ESRCH) { daemon_log(LOG_ERR, "Failed to kill() process: %s", strerror(errno)); return -1; } return 0; }