/*
 * Copyright (c) 2002, The EROS Group, LLC and Johns Hopkins
 * University. All rights reserved.
 * 
 * This software was developed to support the EROS secure operating
 * system project (http://www.eros-os.org). The latest version of
 * the OpenCM software can be found at http://www.opencm.org.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following
 *    disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 * 
 * 3. Neither the name of the The EROS Group, LLC nor the name of
 *    Johns Hopkins University, nor the names of its contributors
 *    may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/fcntl.h>
#include <sys/wait.h>
#include <opencm.h>

static void
maybe_grow_argv(SubProcess *proc)
{
  if (proc->narg == proc->maxarg) {
    proc->maxarg += 64;
    proc->argv = GC_REALLOC(proc->argv, sizeof(char *) * proc->maxarg);
  }
}

SubProcess *
subprocess_create(void)
{
  SubProcess *proc = GC_MALLOC(sizeof(SubProcess));

  proc->narg = 0;
  proc->maxarg = 0;
  proc->argv = 0;
  
  return proc;
}

void
subprocess_AddArg(SubProcess *proc, const char *arg)
{
  maybe_grow_argv(proc);

  proc->argv[proc->narg++] = xstrdup(arg);
}

#ifdef __unix__
int
subprocess_Run(SubProcess *proc,
	       const char *input, const char *output, const char *errput,
	       unsigned flags)
{
  pid_t pid;
  int fin = 0, fout = 1, ferr = 2;

  log_trace(DBG_SUBPROC, "Curdir is %s\n", path_current_directory());

  /* Add the trailing null argument for execvp */
  maybe_grow_argv(proc);

  proc->argv[proc->narg++] = 0;

  TRY {
    /* Set up the requested input/output channels: */
    if (input) {
      fin = open(input, O_RDONLY);
      if (fin < 0)
	THROW(ExSubprocess, 
	      format("Unable to redirect command input from: %s", input));
    }
  
    if (output) {
      fout = open(output, O_WRONLY | O_CREAT);
      if (fout < 0)
	THROW(ExSubprocess, 
	      format("Unable to redirect command output to %s", output));
    }
  
    if (errput) {
      ferr = open(errput, O_WRONLY);
      if (fout < 0)
	THROW(ExSubprocess, 
	      format("Unable to redirect command errors to %s", errput));
    }
  }
  DEFAULT(ex) {
    if (fin > 0)
      close(fin);
    if (fout > 0)
      close(fout);
    if (ferr > 0)
      close(ferr);
  }
  END_CATCH;
  
  {
    int i;
    for (i = 0; i < proc->narg; i++)
      log_trace(DBG_SUBPROC, "arg %d: %s\n", i, proc->argv[i]);
  }

  /* Below this point all file descriptors are known valid. */

  pid = fork();

  if (pid == 0) {
    /* This is the child */

    if (fin != 0) {
      dup2(fin, 0);
      close(fin);
    }

    if (fout != 1) {
      dup2(fout, 1);
      close(fout);
    }

    if (ferr != 2) {
      dup2(ferr, 2);
      close(ferr);
    }

    if (flags & SPF_DISCONNECT)
      os_background();

    /* Operating descriptors are set up. Now exec the requested
       command. */

    (void) execvp (proc->argv[0], proc->argv);
    syslog_error("Cannot exec %s\n", proc->argv[0]);
    _exit (127);
  }
  else {
    /* parent */
    int status;
    pid_t w;

    if (fin != 0)
      close(fin);
    if (fout != 1)
      close(fout);
    if (ferr != 2)
      close(ferr);
    
    if (flags & SPF_DISCONNECT)
      return 0;

    for (;;) {
      w = waitpid(pid, &status, 0);
      
      if (w == -1 && errno != EINTR) {
	log_trace(DBG_SUBPROC, "Return status: %d errno %d\n", status, errno);
	THROW(ExSubprocess, 
	      format("Wait on child (pid=%d) failed", pid));
      }

      if (w == pid) {
        if(!WIFEXITED(status)) /* child exited abnormally */
          THROW(ExSubprocess, "Child process exited abnormally");
        if(WIFSIGNALED(status))
          THROW(ExSubprocess,
                format("Child process died due to signal %d", WTERMSIG(status)));
	log_trace(DBG_SUBPROC, 
		  "Return pid %d, status %d, errno %d\n", w, status, errno);
	return WEXITSTATUS(status);
      }
    }
  }
}
#endif
