/*
 * exec.c
 *
 * Copyright (C) 2004 Bastian Blank <waldi@debian.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * $LastChangedBy: bastian $
 * $LastChangedDate: 2004-09-19 12:03:45 +0200 (Sun, 19 Sep 2004) $
 * $LastChangedRevision: 628 $
 */

#define _GNU_SOURCE

#include <config.h>

#include <debian-installer.h>

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/poll.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include "execute.h"
#include "log.h"
#include "target.h"

const char *const execute_environment_target[] =
{
  "DEBIAN_FRONTEND=noninteractive",
  "PATH=/sbin:/usr/sbin:/bin:/usr/bin",
  NULL
};

static int internal_di_exec_child (const char *filename, const char *const argv[], const char *const envp[], pid_t pid, di_process_handler *child_prepare_handler, void *child_prepare_user_data, int fd_status, int fd_stdin, int fd_stdout, int fd_stderr)
{
  dup2 (fd_stdin, 0);
  dup2 (fd_stdout, 1);
  dup2 (fd_stderr, 2);

  close (fd_stdin);
  close (fd_stdout);
  close (fd_stderr);

  if (child_prepare_handler)
    if (child_prepare_handler (pid, child_prepare_user_data))
    {
      int status = 255;
      write (fd_status, &status, sizeof (int));
      _exit (255);
    }

  execve (filename, (char *const *) argv, (char *const *) envp);

  int status = -errno;
  write (fd_status, &status, sizeof (int));
  _exit (255);
}

static int internal_di_exec (const char *filename, const char *const argv[], const char *const envp[], execute_io_handler *stdout_handler, execute_io_handler *stderr_handler, void *io_user_data, di_process_handler *child_prepare_handler, void *child_prepare_user_data)
{
  char line[1024];
  pid_t pid;
  int fd_null, fds_status[2], fds_stdout[2] = { -1 }, fds_stderr[2] = { -1 }, nr = 0;
  int errno_saved;
  int ret = 0;

  fd_null = open ("/dev/null", O_RDWR);

  pipe (fds_status);
  fcntl (fds_status[1], F_SETFD, FD_CLOEXEC);

  if (stdout_handler)
  {
    pipe (fds_stdout);
    nr++;
  }
  else
    fds_stdout[0] = fd_null;

  if (stderr_handler)
  {
    pipe (fds_stderr);
    nr++;
  }
  else
    fds_stderr[0] = fd_null;

  pid = fork ();
  errno_saved = errno;

  if (pid <= 0)
  {
    close (fds_status[0]);
    close (fds_stdout[0]);
    close (fds_stderr[0]);
  }

  if (pid == 0)
    internal_di_exec_child (filename, argv, envp, pid, child_prepare_handler, child_prepare_user_data, fds_status[1], fd_null, fds_stdout[1], fds_stderr[1]);

  close (fd_null);

  if (pid < 0)
  {
    ret = -errno_saved;
    goto out;
  }

  close (fds_status[1]);
  close (fds_stdout[1]);
  close (fds_stderr[1]);

  fcntl (fds_stdout[0], F_SETFL, O_NONBLOCK);
  fcntl (fds_stderr[0], F_SETFL, O_NONBLOCK);

  struct pollfd pollfds[2];
  pollfds[0].events = POLLIN;
  pollfds[1].events = POLLIN;

  struct files
  {
    FILE *file;
    execute_io_handler *handler;
  }
  files[2];

  int i = 0;

  pollfds[0].fd = fds_status[0];

  errno_saved = -1;

  while (poll (pollfds, 1, -1) >= 0)
  {
    if (pollfds[0].revents & POLLIN)
    {
      int status;
      read (pollfds[0].fd, &status, sizeof (int));
      ret = status;
    }
    if (pollfds[0].revents & POLLHUP)
      break;
  }

  if (ret)
    goto out;

  if (nr == 0)
    goto wait;

  if (stdout_handler)
  {
    files[i].file = fdopen (fds_stdout[0], "r");
    files[i].handler = stdout_handler;
    pollfds[i].fd = fds_stdout[0];
    i++;
  }

  if (stderr_handler)
  {
    files[i].file = fdopen (fds_stderr[0], "r");
    files[i].handler = stderr_handler;
    pollfds[i].fd = fds_stderr[0];
    i++;
  }

  while (poll (pollfds, nr, -1) >= 0)
  {
    bool exit = false;

    for (i = 0; i < nr; i++)
    {
      if (pollfds[i].revents & POLLIN)
      {
        while (fgets (line, sizeof (line), files[i].file) != NULL)
          files[i].handler (line, strlen (line), io_user_data);
        exit = true;
      }
    }

    if (exit)
      continue;

    for (i = 0; i < nr; i++)
      if (pollfds[i].revents & POLLHUP)
        exit = true;

    if (exit)
      break;
  }

wait:
  if (!waitpid (pid, &ret, 0))
    ret = -1;

out:
  close (fds_status[0]);
  close (fds_stdout[0]);
  close (fds_stderr[0]);

  return ret;
}

int execute_io_log_handler (char *buf, size_t n __attribute__ ((unused)), void *user_data __attribute__ ((unused)))
{ 
  if (buf[n - 1] == '\n')
    buf[n - 1] = 0;
  log_text (DI_LOG_LEVEL_OUTPUT, "%s", buf);
  return 0;
}

static void execute_status (int status)
{
  log_text (DI_LOG_LEVEL_DEBUG, "Status: %d", status);

  if (status < 0)
    log_text (DI_LOG_LEVEL_CRITICAL, "Execution failed: %s", strerror (-status)); 
  else if (WIFEXITED (status))
    ;
  else if (WIFSIGNALED (status))
    log_text (DI_LOG_LEVEL_CRITICAL, "Execution failed with signal: %s", strsignal (WTERMSIG (status))); 
  else
    log_text (DI_LOG_LEVEL_CRITICAL, "Execution failed with unknown status: %d", status); 
} 

int execute_full (const char *const command, execute_io_handler *stdout_handler, execute_io_handler *stderr_handler, void *io_user_data)
{
  log_text (DI_LOG_LEVEL_DEBUG, "Execute \"%s\"", command);
  const char *const argv[] = { "sh", "-c", command, NULL };
  int ret = internal_di_exec ("/bin/sh", argv, (const char *const *) environ, stdout_handler, stderr_handler, io_user_data, NULL, NULL);
  execute_status (ret);
  return ret;
}

int execute_target_full (const char *const command, execute_io_handler *stdout_handler, execute_io_handler *stderr_handler, void *io_user_data)
{
  log_text (DI_LOG_LEVEL_DEBUG, "Execute \"%s\" in chroot", command);
  const char *const argv[] = { "sh", "-c", command, NULL };
  int ret = internal_di_exec ("/bin/sh", argv, execute_environment_target, stdout_handler, stderr_handler, io_user_data, di_exec_prepare_chroot, (void *) target_root);
  execute_status (ret);
  return ret;
}

