//
// process.c
//
// Copyright (C) 2008 Till Kamppeter <till.kamppeter@gmail.com>
// Copyright (C) 2008 Lars Karlitski (formerly Uebernickel) <lars@karlitski.net>
//
// This file is part of foomatic-rip.
//
// Licensed under Apache License v2.0.  See the file "LICENSE" for more
// information.
//

#include "foomaticrip.h"
#include "process.h"
#include <unistd.h>
#include "util.h"
#include <sys/wait.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>


int kidgeneration = 0;

struct process
{
  char name[64];
  pid_t pid;
  int isgroup;
};

#define MAX_CHILDS 4
struct process procs[MAX_CHILDS] =
{
    { "", -1, 0 },
    { "", -1, 0 },
    { "", -1, 0 },
    { "", -1, 0 }
};


void
add_process(const char *name,
	    int pid,
	    int isgroup)
{
  int i;
  for (i = 0; i < MAX_CHILDS; i++)
  {
    if (procs[i].pid == -1)
    {
      strlcpy(procs[i].name, name, 64);
      procs[i].pid = pid;
      procs[i].isgroup = isgroup;
      return;
    }
  }
  rip_die(EXIT_PRNERR_NORETRY_BAD_SETTINGS,
	  "Didn't think there would be that many child processes... Exiting.\n");
}


int
find_process(int pid)
{
  int i;
  for (i = 0; i < MAX_CHILDS; i++)
    if (procs[i].pid == pid)
      return (i);
  return (-1);
}


void
clear_proc_list()
{
  int i;
  for (i = 0; i < MAX_CHILDS; i++)
    procs[i].pid = -1;
}


void
kill_all_processes()
{
  int i;

  for (i = 0; i < MAX_CHILDS; i++)
  {
    if (procs[i].pid == -1)
      continue;
    _log("Killing %s\n", procs[i].name);
    kill(procs[i].isgroup ? -procs[i].pid : procs[i].pid, 15);
    sleep(1 << (3 - kidgeneration));
    kill(procs[i].isgroup ? -procs[i].pid : procs[i].pid, 9);
  }
  clear_proc_list();
}


static pid_t
_start_process(const char *name,
	       int (*proc_func)(FILE *, FILE *, void *),
	       void *user_arg,
	       FILE **pipe_in,
	       FILE **pipe_out,
	       int createprocessgroup)
{
  pid_t pid;
  int pfdin[2], pfdout[2];
  int ret;
  FILE *in, *out;

  if (pipe_in)
    if (pipe(pfdin) < 0)
      return (-1);
  if (pipe_out)
    if (pipe(pfdout) < 0)
      return (-1);

  _log("Starting process \"%s\" (generation %d)\n", name, kidgeneration +1);

  pid = fork();
  if (pid < 0)
  {
    _log("Could not fork for %s\n", name);
    if (pipe_in)
    {
      close(pfdin[0]);
      close(pfdin[1]);
    }
    if (pipe_out)
    {
      close(pfdout[0]);
      close(pfdout[1]);
    }
    return (-1);
  }

  if (pid == 0)
  {
    // Child

    // Reset sigpipe behavior to default for all children
    signal(SIGPIPE, SIG_DFL);

    if (pipe_in)
    {
      close(pfdin[1]);
      in = fdopen(pfdin[0], "r");
    }
    else
      in = NULL;

    if (pipe_out)
    {
      close(pfdout[0]);
      out = fdopen(pfdout[1], "w");
    }
    else
      out = NULL;

    if (createprocessgroup)
      setpgid(0, 0);

    kidgeneration ++;

    // The subprocess list is only valid for the parent. Clear it.
    clear_proc_list();

    ret = proc_func(in, out, user_arg);
    exit(ret);
  }

  // Parent
  if (pipe_in)
  {
    close(pfdin[0]);
    *pipe_in = fdopen(pfdin[1], "w");
    if (!*pipe_in)
      _log("fdopen: %s\n", strerror(errno));
  }
  if (pipe_out)
  {
    close(pfdout[1]);
    *pipe_out = fdopen(pfdout[0], "r");
    if (!*pipe_out)
      _log("fdopen: %s\n", strerror(errno));
  }

  // Add the child process to the list of open processes (to be able to kill
  // them in case of a signal.
  add_process(name, pid, createprocessgroup);

  return (pid);
}


int
exec_command(FILE *in,
	     FILE *out,
	     void *cmd)
{
  if (in && dup2(fileno(in), fileno(stdin)) < 0)
    rip_die(EXIT_PRNERR_NORETRY_BAD_SETTINGS,
	    "%s: Could not dup stdin\n", (const char *)cmd);
  if (out && dup2(fileno(out), fileno(stdout)) < 0)
    rip_die(EXIT_PRNERR_NORETRY_BAD_SETTINGS,
	    "%s: Could not dup stdout\n", (const char *)cmd);

  execl(get_modern_shell(), get_modern_shell(), "-e", "-c",
	(const char *)cmd, (char *)NULL);

  _log("Error: Executing \"%s -c %s\" failed (%s).\n", get_modern_shell(),
       (const char *)cmd, strerror(errno));
  return (EXIT_PRNERR_NORETRY_BAD_SETTINGS);
}


pid_t
start_system_process(const char *name,
		     const char *command,
		     FILE **fdin,
		     FILE **fdout)
{
  return (_start_process(name, exec_command, (void*)command, fdin, fdout, 1));
}


pid_t
start_process(const char *name,
	      int (*proc_func)(FILE *, FILE *, void *),
	      void *user_arg,
	      FILE **fdin,
	      FILE **fdout)
{
  return (_start_process(name, proc_func, user_arg, fdin, fdout, 0));
}


int
wait_for_process(int pid)
{
  int i;
  int status;

  i = find_process(pid);
  if (i < 0)
  {
    _log("No such process \"%d\"", pid);
    return (-1);
  }

  waitpid(procs[i].pid, &status, 0);
  if (WIFEXITED(status))
    _log("%s exited with status %d\n", procs[i].name, WEXITSTATUS(status));
  else if (WIFSIGNALED(status))
    _log("%s received signal %d\n", procs[i].name, WTERMSIG(status));

  // remove from process list
  procs[i].pid = -1;
  return (status);
}


int
run_system_process(const char *name,
		   const char *command)
{
  int pid = start_system_process(name, command, NULL, NULL);
  return (wait_for_process(pid));
}
