/*
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
#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)
{
/* 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 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], 0);
dup2(pipe_write[0], 1);
/*
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);
return 1; /* this line should never be reached because we exec -- unless if the exec returns something bad. Then we'd have to tell execio over the pipe about that somehow... */
/* in fact, maybe we should abort() here because if we returned, a monster of a distren client would exist! */
}
}
size_t execio_read(struct execio *eio, void *buf, size_t len)
{
int toreturn;
/*
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
*/
toreturn = read(eio->pipe_read, buf, len);
if(!toreturn)
/* 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 toreturn;
}
size_t execio_write(struct execio *eio, void *buf, size_t len)
{
int toreturn;
/*
TODO: errno handling
*/
toreturn = write(eio->pipe_write, buf, len);
if(!toreturn)
eio->state = EXECIO_STATE_ERROR;
return toreturn;
}
enum execio_state execio_state(struct execio *eio)
{
return eio->state;
}
int execio_close(struct execio *eio)
{
close(eio->pipe_read);
close(eio->pipe_write);
return 0;
}