#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"

#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;

#ifdef __DJGPP__
static char *spawn_make_response_file (char **argv);
static void reset_console (void);
#endif

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)
{
  SHELL_VAR *lastarg_var;

  maybe_make_export_env ();

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

#if NOFORK_COPY_ON_CHANGE == 0
  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 ();
}

int
nofork_save_all_environment (OLDENVBUF *envp)
{
  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);

  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
