#include #include #include #include #include #include #include #include #include #include #include #include "list.h" #include "logging.h" #include "process.h" #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) static LIST_HEAD(child_proc_list); struct child_process * childproc_alloc(char *const argv[], const char *pwd) { /* count args */ int cnt = 0; while (argv[cnt++] != NULL); /* alloc struct child_process and array of char **argv */ int size = sizeof(struct child_process) + sizeof(char *) * cnt; struct child_process *child = malloc(size); if (child == NULL) { log_print(LOG_ERROR, "create_child_process(): malloc()"); return NULL; } memset(child, 0, sizeof(struct child_process)); /* argv points to first byte after struct child_process */ child->argv = (char **)(child +1); int i; for (i = 0; i < ARRAY_SIZE(child->fd); i++) child->fd[i] = -1; /* copy argv */ for (i = 0; i < cnt; i++) child->argv[i] = (argv[i] != NULL) ? strdup(argv[i]) : NULL; /* copy pwd */ if (pwd != NULL) child->pwd = strdup(pwd); return child; } int childproc_free(struct child_process *child) { /* child already running, return error */ if (child->pid != 0) { log_print(LOG_ERROR, "childproc_free(): process [pid:%d] already running", child->pid); return -1; } int i; for (i = 0; child->argv[i] != NULL; i++) free(child->argv[i]); if (child->pwd != NULL) free(child->pwd); free(child); return 0; } pid_t childproc_fork(struct child_process *child, void (*exit_cb)(struct child_process *child, int exit_code, void *privdata), void *privdata) { struct stat stat_buf; if (stat(child->argv[0], &stat_buf) != 0) { log_print(LOG_ERROR, "childproc_fork(): stat()"); return -1; /* not a regular file, or not executable */ } else if (!S_ISREG(stat_buf.st_mode) || !(stat_buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) { log_print(LOG_ERROR, "childproc_fork(): stat()"); return -1; } /* exit callback */ child->exit_cb = exit_cb; child->privdata = privdata; /* * fd[0] - if -1 pipe() and dup2() read-side as child stdin, store write-side for parent * else dup2() this fd as child stdin * fd[1] - if -1 pipe() and dup2() write-side as child stdout, store read-side for parent * else dup2() this fd as child stdout * fd[2] - if -1 pipe() and dup2() write-side as child stderr, store read-side for parent * else dup2() this fd as child stderr */ int child_pipes[3][2]; if (child->fd[STDIN_FILENO] == -1) { if (pipe(child_pipes[STDIN_FILENO]) < 0) { log_print(LOG_ERROR, "childproc_fork(): pipe(STDIN_FILENO)"); return -1; } } else { child_pipes[STDIN_FILENO][0] = child->fd[STDIN_FILENO]; child_pipes[STDIN_FILENO][1] = -1; } if (child->fd[STDOUT_FILENO] == -1) { if (pipe(child_pipes[STDOUT_FILENO]) < 0) { log_print(LOG_ERROR, "childproc_fork(): pipe(STDOUT_FILENO)"); return -1; } } else { child_pipes[STDOUT_FILENO][0] = -1; child_pipes[STDOUT_FILENO][1] = child->fd[STDOUT_FILENO]; } if (child->fd[STDERR_FILENO] == -1) { if (pipe(child_pipes[STDERR_FILENO]) < 0) { log_print(LOG_ERROR, "childproc_fork(): pipe(STDERR_FILENO)"); return -1; } } else { child_pipes[STDERR_FILENO][0] = -1; child_pipes[STDERR_FILENO][1] = child->fd[STDERR_FILENO]; } /* add this to list now, so sigchld_handler can find it */ list_add_tail(&child->list, &child_proc_list); child->pid = fork(); if (child->pid == 0) { /* child */ close(child_pipes[STDIN_FILENO][1]); close(child_pipes[STDOUT_FILENO][0]); close(child_pipes[STDERR_FILENO][0]); if (dup2(child_pipes[STDIN_FILENO][0], STDIN_FILENO) < 0) { perror("dup2(STDIN_FILENO)"); exit(1); } if (dup2(child_pipes[STDOUT_FILENO][1], STDOUT_FILENO) < 0) { perror("dup2(STDOUT_FILENO)"); exit(1); } if (dup2(child_pipes[STDERR_FILENO][1], STDERR_FILENO) < 0) { perror("dup2(STDERR_FILENO)"); exit(1); } close(child_pipes[STDIN_FILENO][0]); close(child_pipes[STDOUT_FILENO][1]); close(child_pipes[STDERR_FILENO][1]); if (child->pwd != NULL && chdir(child->pwd) < 0) { perror("chdir()"); exit(1); } execv(child->argv[0], child->argv); perror("execv()"); exit(1); } else if (child->pid < 0) { /* fork error */ log_print(LOG_ERROR, "childproc_fork(): fork()"); return -1; } else { /* parent */ if (child->fd[STDIN_FILENO] == -1) { close(child_pipes[STDIN_FILENO][0]); child->fd[STDIN_FILENO] = child_pipes[STDIN_FILENO][1]; } if (child->fd[STDOUT_FILENO] == -1) { close(child_pipes[STDOUT_FILENO][1]); child->fd[STDOUT_FILENO] = child_pipes[STDOUT_FILENO][0]; } if (child->fd[STDERR_FILENO] == -1) { close(child_pipes[STDERR_FILENO][1]); child->fd[STDERR_FILENO] = child_pipes[STDERR_FILENO][0]; } return child->pid; } } void childproc_cleanup(void) { struct child_process *child, *tmp; list_for_each_entry_safe(child, tmp, &child_proc_list, list) { int status = 0; int ret = waitpid(child->pid, &status, WNOHANG); if (ret == -1) { log_print(LOG_WARN, "childproc_cleanup(): waitpid(%d)", child->pid); continue; } else if (ret != 0 && WIFEXITED(status)) { list_del(&child->list); if (child->exit_cb != NULL) child->exit_cb(child, WEXITSTATUS(status), child->privdata); child->pid = 0; childproc_free(child); } } }