Files @ ceee73453f47
Branch filter:

Location: DistRen/src/common/execio.c

LordOfWar
deleted reference to status_report_generator()
/*
  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 <http://www.gnu.org/licenses/>.
*/

#include "execio.h"

#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <malloc.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>

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);

      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! */
    }
}

/*
  returns 1 if child has exited, 
  returns 0 if child is still alive
 */
int _execio_checkpid(struct execio *eio)
{
  int childstatus;
  
  waitpid(eio->child, &childstatus, WNOHANG);
  /* perror()? */

  return WIFEXITED(childstatus);
}


int execio_read(struct execio *eio, void *buf, size_t len, size_t *bytesread)
{
  /*
    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
   */
  (*bytesread) = read(eio->pipe_read, buf, len);
  if(!*bytesread)
    {
      /* 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;
}