/*
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 "execio.h"
#include
#include
#ifndef _WIN32
#include
#endif
#include
#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 --;
}
/* stderr is the only stream we haven't confiscated atm - just for fun - I will confiscate it later, though, to support parsing error messages */
fprintf(stderr, "closed %d/%d fds before execing \"%s\"\n", counter2, maxfds, progname);
/*
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, 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, 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
*/
fprintf(stderr, "execio_close: running waitpid\n");
waitpid(eio->child, &childstatus, 0);
free(eio);
return 0;
}