#include <config.h>

#if !defined (HAVE_WORKING_FORK)

#include <ctype.h>
#include <errno.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dpmi.h>
#include <sys/system.h>

#include "nofork.h"
#include "dospath.h"

#include "command.h"
#include "general.h"
#include "error.h"
#include "variables.h"
#include "subst.h"
#include "externs.h"
#include "builtins/common.h"
#include "hashcmd.h"
#include "flags.h"
#include "bashjmp.h"

#define SAVESTRING(p)  ((p != NULL) ? savestring (p) : NULL)
#define FREE_AND_CLEAR(p)  (free (p), p = NULL)

#define CURRENT_PID ((pid_t) 1)

pid_t nofork_current_pid = CURRENT_PID;
int nofork_wait_status = -1;
OLDENVBUF *current_environment = NULL;

static void
save_jmp_buf(jmp_buf save, jmp_buf now)
{
  memcpy((char *)save, (char *)now, sizeof(jmp_buf));
}

static void
restore_jmp_buf(jmp_buf save, jmp_buf now)
{
  memcpy((char *)now, (char *)save, sizeof(jmp_buf));
}

extern jmp_buf top_level, subshell_top_level;
extern int interactive, interactive_shell, login_shell;
extern int subshell_environment;
extern int subshell_exit_builtin, exit_immediately_on_error;
extern int dollar_dollar_pid;
extern int array_needs_making;
extern SHELL_VAR *ifs_var;

static void
save_current_directory (OLDENVBUF *envp)
{
  envp->pwd    = SAVESTRING (get_string_value ("PWD"));
  envp->curdir = getcwd (NULL, PATH_MAX);
}

static void
restore_current_directory(OLDENVBUF *envp)
{
  /* change old directory */
  if (envp->curdir != NULL)
  {
    chdir(envp->curdir);
    if (envp->pwd != NULL)
      set_working_directory(envp->pwd);
    else
      set_working_directory(envp->curdir);
    FREE_AND_CLEAR (envp->curdir);
  }
  else if (envp->pwd != NULL)
  {
    chdir(envp->pwd);
    set_working_directory(envp->pwd);
  }

  if (envp->pwd)
    FREE_AND_CLEAR(envp->pwd);
}

extern WORD_LIST *subst_assign_varlist;

static void
save_global_variables(OLDENVBUF *envp)
{
  envp->interactive               = interactive;
  envp->interactive_shell         = interactive_shell;
  envp->login_shell               = login_shell;
  envp->subshell_environment      = subshell_environment;
  envp->subshell_exit_builtin     = subshell_exit_builtin;
  envp->exit_immediately_on_error = exit_immediately_on_error;
  envp->variable_context          = variable_context;
  envp->dollar_dollar_pid         = dollar_dollar_pid;
  envp->subst_assign_varlist      = subst_assign_varlist;
  envp->echo_command_at_execute   = echo_command_at_execute;
  subst_assign_varlist = NULL;
}

static void
restore_global_variables(OLDENVBUF *envp)
{
  interactive               = envp->interactive;
  interactive_shell         = envp->interactive_shell;
  login_shell               = envp->login_shell;
  subshell_environment      = envp->subshell_environment;
  subshell_exit_builtin     = envp->subshell_exit_builtin;
  exit_immediately_on_error = envp->exit_immediately_on_error;
  variable_context          = envp->variable_context;
  dollar_dollar_pid         = envp->dollar_dollar_pid;
  subst_assign_varlist      = envp->subst_assign_varlist;
  echo_command_at_execute   = envp->echo_command_at_execute;
}


static VAR_CONTEXT *
copy_var_context(VAR_CONTEXT *fvc)
{
  VAR_CONTEXT *vc;

  vc = (VAR_CONTEXT *)xmalloc(sizeof(VAR_CONTEXT));
  vc->name = fvc->name ? savestring(fvc->name) : (char *)NULL;
  vc->scope = fvc->scope;
  vc->flags = fvc->flags & ~(VC_COPYONCHANGE);

  vc->table = hash_copy(fvc->table, (sh_string_func_t *)copy_variable);
  vc->up = vc->down = (VAR_CONTEXT *)NULL;

  return vc;
}

static void
save_all_var_contexts(OLDENVBUF *envp)
{
  VAR_CONTEXT *vc;
  VAR_CONTEXT *vc_copy, *sv, *vc_copy_prev;

  vc = shell_variables;
  sv = vc_copy = copy_var_context(vc);

  while (vc->down)
  {
    vc_copy_prev = vc_copy;
    vc = vc->down;
    vc_copy = copy_var_context(vc);
    vc_copy_prev->down = vc_copy;
    vc_copy->up = vc_copy_prev;
  }

#if NOFORK_COPY_ON_CHANGE
  envp->global_variables = vc_copy;
  envp->shell_variables = sv;
#else
  envp->shell_variables = shell_variables;
  envp->global_variables = global_variables;
  shell_variables = sv;
  global_variables = vc_copy;
#endif
}

static void
restore_all_var_contexts(OLDENVBUF *envp)
{
  delete_all_contexts(shell_variables);
  dispose_var_context(global_variables);

  global_variables = envp->global_variables;
  shell_variables = envp->shell_variables;
  delete_all_variables(shell_functions);
  hash_dispose(shell_functions);
  shell_functions = envp->shell_functions;
}

void
copy_var_contexts_on_change(void)
{
  if (current_environment == NULL)
    return;

  save_all_var_contexts(current_environment);
  global_variables->flags &= ~(VC_COPYONCHANGE);
}


static void
save_shell_variables(OLDENVBUF *envp)
{
#if NOFORK_COPY_ON_CHANGE
  SHELL_VAR *lastarg_var;
#endif

  maybe_make_export_env();

  envp->global_variables = NULL;
  envp->shell_variables = NULL;
  global_variables->flags |= VC_COPYONCHANGE;

#if NOFORK_COPY_ON_CHANGE || 1
  /* Must be copied to be restored later because the shell variables
     may be modified by the subshell call. */
  save_all_var_contexts(envp);
#endif

  envp->shell_functions = shell_functions;
  shell_functions = hash_copy(envp->shell_functions, (sh_string_func_t *)copy_variable);

  envp->temporary_env = temporary_env;
  if (temporary_env)
    temporary_env = hash_copy(envp->temporary_env, (sh_string_func_t *)copy_variable);

#if NOFORK_COPY_ON_CHANGE
  lastarg_var = find_variable("_");
  if (lastarg_var)
    envp->lastarg = savestring(value_cell(lastarg_var));
#endif

  envp->rest_of_args = list_rest_of_args();
}

static void
restore_shell_variables(OLDENVBUF *envp)
{
  SHELL_VAR *temp_ifs;

  if (envp->global_variables)
    restore_all_var_contexts(envp);
  else
    global_variables->flags &= ~(VC_COPYONCHANGE);

  if (temporary_env)
  {
    delete_all_variables(temporary_env);
    hash_dispose(temporary_env);
  }
  temporary_env = envp->temporary_env;

  temp_ifs = find_variable("IFS");
  setifs(temp_ifs);

#if NOFORK_COPY_ON_CHANGE
  bind_variable("_", envp->lastarg);
  free(envp->lastarg);
#endif

  remember_args(envp->rest_of_args, 1);

  array_needs_making = 1;
  maybe_make_export_env();
}

extern int return_catch_flag;

int
nofork_save_all_environment(OLDENVBUF *envp)
{
  save_jmp_buf(envp->return_catch, return_catch);
  envp->return_catch_flag = return_catch_flag;
  save_jmp_buf(envp->top_level, top_level);
  save_jmp_buf(envp->subshell_top_level, subshell_top_level);
  nofork_save_std_fds(envp->fds);
  save_current_directory(envp);
  save_global_variables(envp);
  save_shell_variables(envp);

  envp->prev_environment = current_environment;
  current_environment = envp;

  return 0;
}

int
nofork_restore_all_environment(OLDENVBUF *envp)
{
  restore_shell_variables(envp);
  restore_global_variables(envp);
  restore_current_directory(envp);
  nofork_restore_std_fds(envp->fds);
  restore_jmp_buf(envp->top_level, top_level);
  restore_jmp_buf(envp->subshell_top_level, subshell_top_level);

  restore_jmp_buf(envp->return_catch, return_catch);
  return_catch_flag = envp->return_catch_flag;

  current_environment = envp->prev_environment;

  return 0;
}

#include <sys/param.h>
#if !defined (MAXPID)
#define MAXPID ((pid_t) 30000)
#endif

static pid_t last_pid = CURRENT_PID;

pid_t
nofork_make_child_pid(void)
{
  if (++last_pid > MAXPID)
    last_pid = CURRENT_PID + 1;

  return last_pid;
}

void
nofork_save_std_fds(int fds[3])
{
  int i;

  /* save stdin/out/err */
  for (i = 0; i < 3; i++)
#if defined (F_DUPFD)
    if ((fds[i] = fcntl(i, F_DUPFD, 20)) < 0)
#else
    if ((fds[i] = dup(i)) < 0)
#endif
      internal_error("Cannot duplicate fd %d: %s", i, strerror(errno));
}

void
nofork_restore_std_fds(int fds[3])
{
  int i;

  /* restore stdin/out/err */
  for (i = 0; i < 3; i++)
    if (fds[i] >= 0)
    {
      if (dup2(fds[i], i) < 0)
        internal_error("cannot duplicate fd %d to fd %d: %s", fds[i], i, strerror(errno));
      close (fds[i]);
      fds[i] = -1;
    }
}

#endif /* !HAVE_WORKING_FORK */
