/* evlist/child.c * * Device wrapper for child process. * * Copyright (C) 2003, 2004 * Andy Goth * * This code is available under the GNU General Public License; see COPYING. */ #include #include #include #include #include #include #include #include #include #include "evlist/evlist.h" #define SH_PATH "/bin/sh" static int child_cleanup (device_t* dev); static int child_open (device_t* dev); static int child_close (device_t* dev); typedef struct child { int mode; char** envp; pid_t pid; } child_t; int child_init(device_t* dev, char* cmd, char** envp, int mode) { char* cmd_dup; child_t* state; int i, len; char** env_ptrs; int env_ptrs_len = 0; char* env_data; int env_data_len = 0; /* Duplicate envp. */ for (; envp[env_ptrs_len] != NULL; ++env_ptrs_len) { env_data_len += strlen(envp[env_ptrs_len]) + 1; } ++env_ptrs_len; if ((env_ptrs = malloc(sizeof(char**) * env_ptrs_len)) == NULL) goto error1; if ((env_data = malloc(sizeof(char ) * env_data_len)) == NULL) goto error2; env_data_len = 0; for (i = 0; i < env_ptrs_len; ++i) { if (i < env_ptrs_len - 1) { /* Element points to an environment variable. */ len = strlen(envp[i]) + 1; env_ptrs[i] = &env_data[env_data_len]; memcpy(env_ptrs[i], envp[i], len); env_data_len += len; } else { /* Environment list is terminated by a NULL pointer. */ env_ptrs[i] = NULL; } } /* Duplicate cmd. */ if ((cmd_dup = strdup(cmd)) == NULL) goto error3; /* Allocate space for the state struct. */ if ((state = malloc(sizeof(child_t))) == NULL) goto error4; state->mode = mode; state->envp = env_ptrs; state->pid = -1; /* Configure the low-level portion of the device structure. */ dev->path = cmd_dup ; dev->hw_state = state ; dev->hw_cleanup = child_cleanup ; dev->hw_open = child_open ; dev->hw_close = child_close ; dev->hw_readable = device_readable; dev->hw_writable = device_writable; return 0; error4: free(cmd_dup ); error3: free(env_data); error2: free(env_ptrs); error1: return -1; } static int child_cleanup(device_t* dev) { child_t* state = dev->hw_state; free(state->envp[0]); free(state->envp); free(state); free(dev->path); return 0; } static int child_open(device_t* dev) { child_t* state = dev->hw_state; int mode = state->mode; pid_t pid = -1; int i, ret, saved_errno; int c_fd_in, c_fd_out; /* Child fd's. */ int p_fd_in, p_fd_out; /* Parent fd's. */ int pipes[2][2]; int fd_mask = 0; char* args[4] = {SH_PATH, "-c", dev->path, NULL}; /* Theoretically, this line could be moved to child_init(). */ if (mode != O_RDONLY && mode != O_WRONLY && mode != O_RDWR) return -1; /* Open pipes between fork, child, and parent. * * mode = O_RDONLY, O_WRONLY, O_RDWR: * pipes[0][0] = fork --> parent fd readable by parent * pipes[0][1] = fork --> parent fd writable by fork * * mode = O_RDONLY, O_RDWR: * p_fd_in = pipes[1][0] = child --> parent fd readable by parent * c_fd_out = pipes[1][1] = child --> parent fd writable by child * * mode = O_WRONLY: * c_fd_in = pipes[1][0] = parent --> child fd readable by child * p_fd_out = pipes[1][1] = parent --> child fd writable by parent * * mode = O_RDWR: * c_fd_in = pipes[2][0] = parent --> child fd readable by child * p_fd_out = pipes[2][1] = parent --> child fd writable by parent */ for (i = 0; i < (mode == O_RDWR ? 3 : 2); ++i) { if (pipe(pipes[i]) == -1) { perror("pipe"); goto error; } else { fd_mask |= (3 << (i * 2)); } } /* Prepare to connect file descriptors. */ switch (mode) { case O_RDONLY: c_fd_out = pipes[1][1]; p_fd_in = pipes[1][0]; /* child-->parent */ p_fd_out = -1; c_fd_in = STDIN_FILENO; /* stdin-->child */ break; case O_WRONLY: c_fd_out = STDOUT_FILENO; p_fd_in = -1; /* child-->stdout */ p_fd_out = pipes[1][1]; c_fd_in = pipes[1][0]; /* parent-->child */ break; case O_RDWR: c_fd_out = pipes[1][1]; p_fd_in = pipes[1][0]; /* child-->parent */ p_fd_out = pipes[2][1]; c_fd_in = pipes[2][0]; /* parent-->child */ } /* Fork a child. According to Mr. Gil Carrick, this is child abuse. :^) */ switch (pid = fork()) { case -1: /* Failure. */ perror("fork"); goto error; case 0: /* Child process. XXX: Should I worry about passing leftover fd's to * the child? Do I religiously set FD_CLOEXEC? Or do I try closing * everything right here? */ if (close(pipes[0][0]) == -1) goto child_error; if (fcntl(pipes[0][1], F_SETFD, FD_CLOEXEC) == -1) goto child_error; if (c_fd_in != STDIN_FILENO) { if (close(STDIN_FILENO) == -1) goto child_error; if (dup2(c_fd_in, STDIN_FILENO) == -1) goto child_error; } if (c_fd_out != STDOUT_FILENO) { if (close(STDOUT_FILENO) == -1) goto child_error; if (dup2(c_fd_out, STDOUT_FILENO) == -1) goto child_error; } /* Child's stderr remains connected to parent's stderr. */ execve(SH_PATH, args, state->envp); /* If execve() returns -1 (or returns at all!), it's an error. Fall * through to child_error to handle it. */ child_error: write(pipes[0][1], &errno, sizeof(int)); exit(EXIT_FAILURE); default:; /* Parent. Fall through. */ } /* Check status of child. */ state->pid = pid; close(pipes[0][1]); fd_mask &= ~2; switch (read(pipes[0][0], &saved_errno, sizeof(int))) { case -1: /* Bah! */ perror("read"); goto error; case 0: /* Child closed pipe--- execve() succeeded. Fall through. */ break; default: /* Child ate flaming death. */ errno = saved_errno; perror("fork"); goto error; } close(pipes[0][0]); fd_mask &= ~1; /* Connect file descriptors to the event loop. */ switch (mode) { case O_RDONLY: device_attach_fd(dev, p_fd_in, O_RDONLY); close(c_fd_out); break; case O_WRONLY: device_attach_fd(dev, p_fd_out, O_WRONLY); close(c_fd_in); break; case O_RDWR: device_attach_fd(dev, p_fd_in, O_RDONLY); device_attach_fd(dev, p_fd_out, O_WRONLY); close(c_fd_in); close(c_fd_out); } return 0; error: saved_errno = errno; /* Close previously-open pipe fd's. */ for (i = 0; i < 6; ++i) { if (fd_mask & (1 << i)) { if (close(pipes[i / 2][i % 2]) == -1) perror("close"); } } /* Kill the child process. */ if (pid != -1) { if (kill(pid, SIGKILL) == -1) perror("kill"); if (waitpid(pid, NULL, 0) == -1) perror("waitpid"); } errno = saved_errno; return -1; } static int child_close(device_t* dev) { child_t* state = dev->hw_state; if (kill(state->pid, SIGTERM) == -1) perror("kill"); /* XXX: what if SIGTERM doesn't work? */ if (waitpid(state->pid, NULL, 0) == -1) perror("waitpid"); state->pid = -1; device_detatch_fd(dev, O_RDWR); /* TODO: use wait4() to get handy statistics in case the calling * program wants them. */ return 0; } /* vim: set ts=4 sts=4 sw=4 tw=80 et: */