/*
char *startchild(name,cin,cout)
char *name;
FILE **cin, **cout;
  name: procedure name (either in search path or full path specified)
  cin: pointer to place into which startchild returns the stream pointer to
	opened file which will be child's stdin.  If NULL,
	nothing opened or returned
  cout: same as cin, but for child's stdout

  In parent context, returns NULL if things worked; pointer to (dynamically
	allocated) error string otherwise.
  In child context, we have new process if things worked; a message to stdout
	and an exit w/non-zero status (errno if non-0; 1 else-too bad if
	errno=1!) otherwise.
*/
#define STARTCHILD_ID "startchild version 1.2a  23 Apr 2004"
/*
  23 Apr 04.  Mods to "version-returning function".			
		a) change from local to global to ensure it can't be
		   "optimized out"							
		b) don't want "version" in any function name since such       
		   names appear when grep'ping for "version"		        
  20 Feb 04.  4 digit year in ID string.  Include .h file version in id
  11 Feb 04.  Change name; entries back to startchild
	      Get &x defn from core.h.  Might also fix some compiler warnings
		[Begin 1.2a]
  27 Jan 99.  Avoid opening & returning streams if they are given as NULL
	      Test more (hopefully all!) system calls
	      Return a pointer to an error string instead of errno.
		[Begin 1.2]
  25 Jan 99.  Return errno in case of parent error instead of exiting.
	Rename startchild startchild_defgb to reflect this calling sequence
	change.  WJS
		[Begin 1.1]
  15 May 98.  Type startchild so compiler is happy
  26 Apr 97.  Change error string to match JGOFS protocol.  WJS
  14 Mar 97.  Write in 1 printf instead of 2 (got first line but not
	2nd-hope this helps & doesn't eliminate both instead!)
	      Exit w/errno instead of 1 if errno non-zero.  WJS
   3 Jun 96.  Change error string so defgb can recognize it as error.  WJS
  30 May 96.  Mod to child process behavior after failed exec.  WJS
	1) print error info to stdout instead of stderr and
	flush it, thus feeding message to parent
	2) _exit()
		[Original source startchild.c from defexec stuff, I think]
*/

#include "core.h"

/*	Next set of defns seem to be "system" related.  In particular,	*/
/*	time.h and sys/time.h seem to be different beasts, w/the latter	*/
/*	relevant only here (and hence it should NOT be in core.h)	*/
/*	(Hmm... also seems to be in jdb.c... who knows?)		*/
#if SOL || HP
#include <sys/time.h>
#include <sys/resource.h>
#endif

int errno_sav;

/************************************************************************/

char *startchild_return_vers()
/*  Dummy routine.  Exists only to force .h file version string into	*/
/*  this module.  Note string must not be global or we'll have con-	*/
/*  flicts if another routine similarly includes the version string	*/
{
  static char version[] = STARTCHILD_ID"/"COREH_VERSION;
  return version;
}

void child_error(s,image)
char *s,*image;
{
    /*  JGOFS_ERR_PREFIX is &x (defined in core.h)			*/
  printf("%s startchild %s error in child process\n%s: %s\n",
				JGOFS_ERR_PREFIX,s,image,strerror(errno));
  fflush(NULL);
  if (errno == 0) errno = 1;
  _exit(errno);
}

char *parent_error(s)
char *s;
{
  static char *err_string;
  char *err_ptr;
  char *msg = ". errno msg: ";
  int len_s,len_msg;

  err_ptr = (errno > 0) ? strerror(errno) : "[None-errno not set]";
  len_s = strlen(s);
  len_msg = strlen(msg);

  if (  (err_string = (char *)malloc(len_s + len_msg + strlen(err_ptr) + 1))
		    == NULL  						    ) {
    err_string = (errno > 0) ? err_ptr : s;
    errno = errno_sav;
    return err_string;
  }

  strcpy (err_string,s);
  strcpy (err_string+len_s,msg);
  strcpy (err_string+len_s+len_msg,err_ptr);

  errno = errno_sav;
  return err_string;
}

char *startchild(name,cin,cout)
char *name;
FILE **cin, **cout;
{
  static int pipeto[2],pipefrom[2];
  int i,j,numfds,childpid;
  char *args[20];
#if SOL || HP
  struct rlimit rlp;
#endif

  errno_sav = errno;
  errno = -1;	/* Set to a know value in case functions don't set it	*/

  if (pipe(pipeto) || pipe(pipefrom) <0)
    return parent_error("pipe problem");

  switch (childpid = fork()) {

    case -1:  /*  fork error  */
      return parent_error("Could not fork");

    case 0:   /*  child process */
	/*  Break up input string into argument vector			*/
      i = 0;
      args[i++] = strtok(name," ");
      while (  (args[i]=strtok(NULL," ")) != NULL  )
	i++;
				/*
				    fprintf(stderr,"got here after fork\n");
				    fprintf(stderr,"%s\n",name);
				*/

      if (dup2(pipeto[0],0) < 0) child_error("dup2-pipeto",args[0]);
      if (dup2(pipefrom[1],1) < 0) child_error("dup2-pipefrom",args[0]);

	/*  Close all open files except stdin, stdout, and stderr.  Do	*/
	/*  this by trying to close every legal file descriptor >= 3,	*/
	/*  ignoring BADF, which means that particular descriptor was	*/
	/*  not pointing to an open file (? or is otherwise illegal)	*/
#if SOL || HP
      if (getrlimit(RLIMIT_NOFILE,&rlp) != 0) child_error("getrlimit",args[0]);
      numfds = rlp.rlim_cur;
#else
      numfds = getdtablesize();
#endif
      for (i=3; i<numfds; i++) {
	j = close(i);
	if ( ! ((j==0) || (errno==EBADF)) ) child_error("close",args[0]);
      }
      errno = -1;	/*  Reset to known value			*/

		    /*
		      i = 0;
		      while(args[i])fprintf(stderr,"arg %d = %s\n",i,args[i++]);
		    */

	/*  Overlay forked copy of calling program with program child	*/
	/*  should be running						*/
      execvp(args[0],args);
	/*  Error if we return from execvp				*/
      child_error("execvp",args[0]);

    default:  /*  parent process  */

	/*  Need to deal with pipes.  First close descriptors child is	*/
	/*  using.  Then connect file descriptors we want to use with	*/
	/*  streams sent to this routine, or close the descriptors	*/

	  /*  Close child's descriptors					*/
      if (close(pipeto[0]) != 0)
	return parent_error("Problem closing pipeto[0]");
      if (close(pipefrom[1]) != 0)
	return parent_error("Problem closing pipefrom[1]");
						/*    ptoch = pipeto[1];*/
						/*    pfrch = pipefrom[0];*/

	  /*  Deal with pipe that will write to child's stdin		*/
      if (cin != NULL) {
	if (  (*cin = fdopen(pipeto[1],"w")) == NULL  )
	  return parent_error("Problem connecting pipeto[1] to stream");
	setbuf(*cin,NULL);
      } else {
        if (close(pipeto[1]) != 0)
	  return parent_error("Problem closing pipeto[1]");
      }

	  /*  Deal with pipe that will read from child's stdout		*/
      if (cout != NULL) {
	if (  (*cout = fdopen(pipefrom[0],"r")) == NULL  )
	  return parent_error("Problem connecting pipefrom[0] to stream");
						/*    setbuf(frch,NULL);*/
      } else
	if (close(pipefrom[0]) != 0)
	  return parent_error("Problem closing pipefrom[0]");

      break;
    }

  errno = errno_sav;
  return NULL;
}
