/*  Copyright (C) 1993,1994 by the author(s).
 
 This software is published in the hope that it will be useful, but
 WITHOUT ANY WARRANTY for any part of this software to work correctly
 or as described in the manuals. See the ShapeTools Public License
 for details.

 Permission is granted to use, copy, modify, or distribute any part of
 this software but only under the conditions described in the ShapeTools 
 Public License. A copy of this license is supposed to have been given
 to you along with ShapeTools in a file named LICENSE. Among other
 things, this copyright notice and the Public License must be
 preserved on all copies.
 */
/*
 * call.c - call program
 *
 * Authors: Uli Pralle (Uli.Pralle@cs.tu-berlin.de)
 *          Juergen Nickelsen (Juergen.Nickelsen@cs.tu-berlin.de)
 *
 * $Header: call.c[4.0] Thu Jun 24 17:43:24 1993 andy@cs.tu-berlin.de frozen $
 */

#include <sys/wait.h>

#include "config.h"
#include "sttk.h"

LOCAL int doCall (progName, argv)
     char *progName;
     char **argv;
{
  Wait_t status;
  pid_t  pid, offered_pid;
  extern char **environ;

  if (!strrchr (progName, '/')) {
    if ((progName = stFindProgram (progName)) == NULL)
      return 0;
  }

  pid = fork ();

  if (pid == -1) {
    /* ERROR: Can't fork */
    return 0;
  }

  if (!pid) {
    /* child */
    /* close open files ? */
    execve (progName, argv, environ);
    /* ERROR: Can't exec */
    _exit (1);
  }
  else {
    /* parent */
    /* here we must redefine actual interrupt handler. */
    while ( ((offered_pid = wait (&status)) != pid) && (offered_pid != -1))
      ;				/* nothing to do */

    if (offered_pid == -1) {
      return 0;
    }
    /* here we must reconstruct old sighandl */
    if (WEXITSTATUS(status))
      return 0;
    else
      return 1;
  }
  return 0;
}

/*=====================
 * call editor
 *=====================*/

EXPORT int stCallEditor (editor, file, contents, newcontents)
     char *editor, *file, *contents;
     char **newcontents;
{
  /*
   * Calls editor "editor" with file "file" and returns its
   * contents after the editor session in "newcontents".
   * Return value is the length of the new text.
   *
   * On failure, 0 is returned to indicate the error. newcontents
   * is *not* updated and points to nowhere. On error the file
   * will be removed.
   *
   * If "contents" points to a valid text, this text is put (not appended)
   * into the temporary file before editor starts. Text must be
   * NULL terminated, otherwise strange things will happen...
   */
  
  FILE *fid;
  char *new;
  int length;
  struct stat sbuf;
  char *argv[10];

  *newcontents = NULL;		/* don't get confused on error. */

  stRegisterFile (file);
  if ((fid = fopen (file, "w")) == NULL) { /* create a file */
    stUnRegisterFile (file);
    /* ERROR: Can't open temporary file */
    return -1;
  }

  if (contents && *contents) {
    length = strlen (contents);
    
    if (fwrite (contents, sizeof (char), length, fid) != length) {
      /* ERROR: lost bytes while writing to file */
      fclose (fid);
      unlink (file);
      stUnRegisterFile (file);
      return -1;
    }
  }
  
  fclose (fid);
  
  argv[0] = editor;
  argv[1] = file;
  argv[2] = (char *) NULL;
  sprintf (stMessage, "starting up %s %s...\n", editor, file);
  stLog (stMessage, ST_LOG_MSG);

  if (! doCall (editor, argv)) {
    /* ERROR: Editor exited abnormally */
    unlink (file);
    stUnRegisterFile (file);
    return -1;
  }

  if (stat (file, &sbuf) == -1) {
    /* ERROR: Can't stat temporary file */
    return -1;
  }
    
  if ((fid = fopen (file, "r")) == NULL) {
    /* ERROR: Can't reopen temporary file */
    unlink (file);
    stUnRegisterFile (file);
    return -1;
  }
  unlink (file);
  stUnRegisterFile (file);
  length = sbuf.st_size;
  if ((new = malloc ((unsigned)(sizeof (char) * (length + 1)))) == NULL) {
    /* ERROR: Can't malloc for new */
    fclose (fid);
    return -1;
  }
  
  if (!fread (new, sizeof (char), (size_t)length, fid)) {
    /* ERROR: lost bytes while reading from file */
    fclose (fid);
    return -1;
  }
  new[length] = '\0';

  fclose (fid);
  *newcontents = new;
  return length;
}

/*=====================
 * call command
 *=====================*/

/*
 *	int callCmd(char *command_processor, char *commandstring) ;
 *	Call command_processor with commandstring. Commandstring is sent
 *	to command_processor's stdin. If Commandstring is not terminated
 *	with newline, a newline is added.
 *	For return codes see sttk.h.
 *
 *	int stCallCmdErrno ;
 *	Contains additional status information if callCmd fails.
 */


#include <errno.h>

extern int errno ;		/* to obtain error information after
				 * failed syscalls */

/* definition of return codes in sttk.h */

#define DELIM_CHARS   " \t"	/* delimiter characters for breaking
				 * command_processor into argv */

EXPORT int stCallCmdErrno ;	/* to export additional status information */

static int sigpipe_occurred ;	/* flag: true if SIGPIPE was caught */
static int sigusr_occurred ;	/* flag: true if SIGUSR[12] was caught */

				/* function to send commandstring to child */
static int write_to_child() ;

/* catch SIGUSR[12] and set flag */
static Sigret_t sigusr_handler(signal)
int signal ;
{
    sigusr_occurred = signal ;
}

EXPORT int stCallCmd (command_processor, commandstring)
char *command_processor ;
char *commandstring ;
{
    char   **argv ;		/* built from command_processor */
    char   *cmdproc2 ;		/* copy of command_processor for stStrtok */
    int    numtok ;		/* number of tokens in command_processor */
    int    i ;			/* loop counter */
    pid_t  child_pid ;		/* PID of forked process */
    Wait_t child_status ;	/* exit status of child process */
    int    pipefd[2] ;		/* file descriptors for pipe to child */
    int    write_result ;	/* result of write_to_child() */
    int    wait_result ;	/* return value of wait(2) */
    int	   commandflag ;	/* do we have a commandstring? */
    static Sigret_t (*old_sigusr1hand)() ; /* old signal handler for SIGUSR1 */
    static Sigret_t (*old_sigusr2hand)() ; /*  ... and SIGUSR2 */

    /* do we have a command string? */
    commandflag = commandstring != NULL && *commandstring != 0 ;

    /* no signals or errors yet */
    sigpipe_occurred = 0 ;
    sigusr_occurred = 0 ;
    stCallCmdErrno = 0 ;

    /* copy command_processor for stStrtok */
    if ((cmdproc2 = malloc(strlen(command_processor) + 1)) == NULL) {
	stCallCmdErrno = ENOMEM ;
	return NO_MORE_CORE ;
    }
    strcpy(cmdproc2, command_processor) ;


    if (command_processor == NULL || *command_processor == 0
	    || stStrtok(cmdproc2) == NULL) {
	return CMDPROC_EMPTY ;
    }

    /* allocate and set up argv for child process */
    for (numtok = 1; stStrtok(NULL); numtok++)
	;

    /* copy command_processor for stStrtok */
    if ((cmdproc2 = malloc(strlen(command_processor) + 1)) == NULL) {
	stCallCmdErrno = ENOMEM ;
	return NO_MORE_CORE ;
    }
    strcpy(cmdproc2, command_processor) ;

    argv = (char **) calloc(numtok + 1, sizeof(char *)) ;
    if (argv == NULL) {
	stCallCmdErrno = ENOMEM ;
	return NO_MORE_CORE ;
    }
    argv[0] = stStrtok(cmdproc2) ;
    for (i = 1; i < numtok; i++) {
	argv[i] = stStrtok(NULL) ;
    }
    argv[i] = NULL ;


    if (commandflag) {
	/* set up pipe to child's stdin */
	if (pipe(pipefd) != 0) {
	    stCallCmdErrno = errno ;
	    return PIPE_FAILED ;
	}
    }
    
    /* set up signal handler for SIGUSR1 and SIGUSR2 */
    old_sigusr1hand = signal(SIGUSR1, sigusr_handler) ;
    old_sigusr2hand = signal(SIGUSR2, sigusr_handler) ;

    /* and now let things go */
    switch (child_pid = vfork()) {
      case -1:			/* aehem... */
	stCallCmdErrno = errno ;
	free((char *) argv) ;
	signal(SIGUSR1, old_sigusr1hand) ;
	signal(SIGUSR2, old_sigusr2hand) ;
	return FORK_FAILED ;
	
      case 0:			/* child process */
	if (commandflag) {
	    /* set up pipe as stdin */
	    close(pipefd[1]) ;
	    close(0) ;
	    dup(pipefd[0]) ;
	    close(pipefd[0]) ;
	}

	signal(SIGUSR1, old_sigusr1hand) ;
	signal(SIGUSR2, old_sigusr2hand) ;

	/* execute program */
	execvp(argv[0], argv) ;

	/* this failed, so notify parent */
	stCallCmdErrno = errno ; /* this only works with vfork(2) */
	kill(getppid(), errno == ENOENT ? SIGUSR1 : SIGUSR2) ;
	_exit(0) ;		/* Don't flush parent's buffers! */
	break ;
	
      default:			/* parent process */
	close(pipefd[0]) ;

	/* write commandstring to child process */
	if (commandflag) {
	    write_result = write_to_child(pipefd[1], commandstring) ;
	} else {
	    write_result = OK ;
	}
	close(pipefd[1]) ;

	/* check termination of child process */
	while ((wait_result = wait(&child_status)) != child_pid) {
	    if (wait_result == -1) {
		stCallCmdErrno = errno ;
		signal(SIGUSR1, old_sigusr1hand) ; /* reset signal handlers */
		signal(SIGUSR2, old_sigusr2hand) ;
		return WAIT_ERROR ;
	    }
	}


	signal(SIGUSR1, old_sigusr1hand) ; /* reset signal handlers */
	signal(SIGUSR2, old_sigusr2hand) ;

	/* execvp(3) may have failed:
	 * if we can use vfork(2), stCallCmdErrno contains the
	 * reason of exec's failure */
	switch (sigusr_occurred) {
	  case SIGUSR1:
	    return NO_PROGRAM ;
	  case SIGUSR2:
	    return EXEC_FAILED ;
	}

	/* write to pipe may have had problems */
	if (write_result != OK) {
	    stCallCmdErrno = write_result ;
	    return WRITE_FAILED ;
	}

	/* report child's exit status or reason of death */
	if (WIFEXITED(child_status)) {
	    return WEXITSTATUS(child_status) ;
	} else {
	    /* the only case where stCallCmdErrno can't be interpreted
	     * like errno */
	    memcpy(&stCallCmdErrno, &child_status, sizeof(int)) ;
	    return CHILD_KILLED ;
	}
    }
    /* not reached, but to make gcc happy: */
    return 0 ;
}

/* write command into fd, append newline if necessary */
static int write_to_child(fd, command)
int fd ;			/* input file descriptor of pipe to child */
char *command ;			/* command string */
{
    int	written ;		/* no. of bytes written to child */
    int	cmd_write ;		/* length of rest of commandstring to write */
    int w_stat ;		/* return value of write(2) */
    static Sigret_t (*old_sigpipehand)() ; /* old signal handler for SIGPIPE */

    /* block SIGPPIPE, errno will report it anyway */
    old_sigpipehand = signal(SIGPIPE, SIG_IGN) ;
    cmd_write = strlen(command) ;
    written = 0 ;
    /* write in pieces to avoid pipe constipation */
    while (cmd_write > 0) {
	if ((w_stat = write(fd, command + written,
			    cmd_write > BUFSIZ ? BUFSIZ : cmd_write)) == -1) {
	    stCallCmdErrno = errno ;
	    signal(SIGPIPE, old_sigpipehand) ;
	    return stCallCmdErrno;
	}
	written += w_stat ;
	cmd_write -= w_stat ;
    }
    /* append newline if necessary */
    if (command[written - 1] != '\n') {
	if (write(fd, "\n", 1) == -1) {
	    stCallCmdErrno = errno ;
	    signal(SIGPIPE, old_sigpipehand) ;
	    return errno ;
	}
    }
    signal(SIGPIPE, old_sigpipehand) ;
    return OK ;
}

