/* Copyright 2008 Nathan Phillip Brink, Ethan Zonca This file is a part of DistRen. DistRen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. DistRen 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with DistRen. If not, see . */ #include "common/config.h" #include "common/execio.h" #include #include #ifndef _WIN32 #include #endif #include #include #include #include #include int execio_open(struct execio **rem, const char *progname, char *const argv[]) { /* pipe used to write to child */ int pipe_write[2]; /* pipe used to read from child */ int pipe_read[2]; pid_t child; /* for wait(2) if needed */ int childstatus; int counter; int counter2; int maxfds; /* create two pipes to facilitate communication with child */ if(pipe(pipe_write)) return 1; if(pipe(pipe_read)) { close(pipe_write[0]); close(pipe_write[1]); return 1; } /* parent */ child = fork(); if(child == -1) { close(pipe_write[0]); close(pipe_write[1]); close(pipe_read[0]); close(pipe_read[1]); return 1; } if(child) /* the parent proc: */ { /* close sides of pipe we won't use */ close(pipe_write[0]); close(pipe_read[1]); /* setup execio struct */ (*rem) = malloc(sizeof(struct execio)); if(!(*rem)) { /* we should tell the child we're dead - use wait and close our end of the pipes! */ close(pipe_write[1]); close(pipe_read[0]); /* we should probably pass of the wait() call to a thread that just does boring things like that. Especially for when the server tries to connect to other servers... */ /* maybe we should just kill instead of term the child */ kill(child, SIGTERM); /* the waitpid(2) seems to indicate that only when the child is terminated will this wait return. */ waitpid(child, &childstatus, 0); } (*rem)->pipe_write = pipe_write[1]; (*rem)->pipe_read = pipe_read[0]; (*rem)->state = 0; (*rem)->child = child; return 0; } /* child */ else { /* close unused pipes */ close(pipe_write[1]); close(pipe_read[0]); /* reset stdin, stdout, and stderr to the appropriate files. OK, not stderr :-) */ dup2(pipe_read[1], STDOUT_FILENO); dup2(pipe_write[0], STDIN_FILENO); /* close the fds that were dup'd */ close(pipe_read[1]); close(pipe_write[0]); /* close all other file descriptors. We want to keep 0, 1, and 2 open. We don't know that the last open() or pipe() always gives the highest fd number. However, I will assume that it does. Maybe this is a bad idea: */ counter = pipe_write[0]; if(counter < pipe_write[1]) counter = pipe_write[1]; if(counter < pipe_read[0]) counter = pipe_read[0]; if(counter < pipe_read[1]) counter = pipe_read[1]; counter2 = 0; maxfds = counter; while(counter > 2) { if(!close(counter)) counter2 ++; /* record how many descriptors we still had open :-) */ counter --; } /* now exec: execvp uses interpreter to find the file to exec */ execvp(progname, argv); fprintf(stderr, "uh-oh, ``%s'' didn't start for execio\n", progname); exit(1); } } /* returns 1 if child has exited, returns 0 if child is still alive */ int _execio_checkpid(struct execio *eio) { int childstatus; #ifdef _WIN32 waitpid(eio->child, &childstatus, 0); #else waitpid(eio->child, &childstatus, WNOHANG); #endif /* perror()? */ return WIFEXITED(childstatus); } int execio_read(struct execio *eio, const void *buf, size_t len, size_t *bytesread) { ssize_t ret; /* TODO: detect NULL eio? TODO: errno? update status of eio for execio_status/to be able to cleanup subproc?? whenever read() returns 0, it means EOF */ ret = read(eio->pipe_read, buf, len); if(ret == -1) { (*bytesread) = 0; perror("read"); switch(errno) { case EAGAIN: case EINTR: return 0; break; default: return 1; } } (*bytesread) = (size_t)ret; if(!ret) { /* should also be able to figure out if is bad fd and should set EXECIO_STATE_ERROR instead of _EOF */ eio->state = EXECIO_STATE_EOF; return 1; } return 0; } int execio_write(struct execio *eio, const void *buf, size_t len, size_t *bytesread) { errno = 0; (*bytesread) = write(eio->pipe_write, buf, len); if(!*bytesread) { switch(errno) { case EPIPE: /* the program closed the pipe (died) */ fprintf(stderr, "execio_write: the child program closed its stdin pipe\n"); eio->state = EXECIO_STATE_EOF; break; default: fprintf(stderr, "execio_write: unhandled error writing to an fd: \n"); perror("write"); eio->state = EXECIO_STATE_ERROR; break; } return 1; } return 0; } enum execio_state execio_state(struct execio *eio) { return eio->state; } int execio_close(struct execio *eio) { int childstatus; close(eio->pipe_read); close(eio->pipe_write); /* maybe we should just kill rather than term the child */ kill(eio->child, SIGTERM); /* the waitpid(2) seems to indicate that only when the child is terminated will this wait return. This are of code will probably need improving - the ability to seng SIGKILL after a timeout? So we'll output a debug line before running waitpid */ waitpid(eio->child, &childstatus, 0); free(eio); return 0; }