#if defined(__linux__)
#define _GNU_SOURCE /* for pipe2() */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/wait.h>

#ifdef USE_LIBEDIT
#include <editline/readline.h>
#endif
#ifdef USE_EDITLINE
#include <editline.h>
#endif
#ifdef USE_READLINE
#include <readline/readline.h>
#include <readline/history.h>
#endif
#ifdef USE_NLLINE
#include <nlline.h>
#endif
#if defined(USE_GETLINE) || defined(USE_FGETS)
#include "readline.h"
#define readline(prompt) nlsh_readline(prompt)
#define add_history(line) nlsh_add_history(line)
#endif

#ifdef USE_NLLIBC
#include <nllibc.h>
#endif

#include "args.h"
#include "val.h"

static pid_t rootpid = -1;
static pid_t rootpgid = -1;
static pid_t current = 0;

static int sigint_handled = 0;
static int sigchld_handled = 0;
static int sigtstp_handled = 0;

static void signal_reset(void)
{
  struct sigaction sa;

  memset(&sa, 0, sizeof(sa));
  sa.sa_handler = SIG_DFL;

  sigaction(SIGINT , &sa, NULL);
  sigaction(SIGCHLD, &sa, NULL);
  sigaction(SIGTSTP, &sa, NULL);
  sigaction(SIGTTIN, &sa, NULL);
  sigaction(SIGTTOU, &sa, NULL);
}

static int exec_builtin_command(char *argv[])
{
  int argc;
  val_entry_t v;

  argc = get_argc(argv);
  if (argc < 1)
    goto noexec;

  if (!strcmp(argv[0], "env")) {
    for (v = val_start(); v; v = val_next(v)) {
      printf("%s=%s\n", val_name(v), val_val(v));
    }
    goto exec;
  }

noexec:
  return -1;

exec:
  return 0;
}

static int exec_command(char *argv[], char *envp[], char *path[])
{
  int n, i, flags, fd, out, save[3];
  char **args = NULL;
  char *outstr = NULL, *command = NULL, *errfile = NULL;
  enum {
    EXEC_ERR_UNKNOWN = 0,
    EXEC_ERR_NOFILE,
    EXEC_ERR_FILEERR,
    EXEC_ERR_NOCOMMAND,
  } e = EXEC_ERR_UNKNOWN;

  for (i = 0; i < 3; i++)
    save[i] = -1;

  while (1) {
    n = search_argv(argv, "<");
    if (n < 0)
      break;
    args = extract_argv(argv, n, 2);
    if (get_argc(args) < 2) {
      e = EXEC_ERR_NOFILE;
      goto err;
    }
    fd = open(args[1], O_RDONLY);
    if (fd < 0) {
      e = EXEC_ERR_FILEERR;
      errfile = args[1];
      goto err;
    }
    if (save[0] < 0)
      save[0] = dup(0);
    close(0);
    dup2(fd, 0);
    close(fd);
    free_argv(args);
    args = NULL;
  }

  while (1) {
    n = search_argv(argv, ">");
    if (n < 0) n = search_argv(argv, "2>");
    if (n < 0) n = search_argv(argv, ">>");
    if (n < 0) n = search_argv(argv, "2>>");
    if (n < 0)
      break;
    args = extract_argv(argv, n, 2);
    if (get_argc(args) < 2) {
      e = EXEC_ERR_NOFILE;
      goto err;
    }
    if (!strcmp(args[0], ">>") || !strcmp(args[0], "2>>")) {
      flags = O_WRONLY | O_CREAT | O_APPEND;
    } else {
      flags = O_WRONLY | O_CREAT | O_TRUNC;
    }
    if (!strcmp(args[0], "2>") || !strcmp(args[0], "2>>")) {
      out = 2;
    } else {
      out = 1;
    }

    fd = open(args[1], flags, 0644);
    if (fd < 0) {
      e = EXEC_ERR_FILEERR;
      errfile = args[1];
      goto err;
    }
    if (save[out] < 0)
      save[out] = dup(out);
    close(out);
    dup2(fd, out);
    close(fd);
    free_argv(args);
    args = NULL;
  }

  do {
    n = search_argv(argv, "1>&2");
    if (n < 0) n = search_argv(argv, "2>&1");
    if (n < 0)
      break;
    args = extract_argv(argv, n, 1);
    if (!strcmp(args[0], "1>&2")) {
      if (save[1] < 0)
	save[1] = dup(1);
      close(1);
      dup2(2, 1);
    } else {
      if (save[2] < 0)
	save[2] = dup(2);
      close(2);
      dup2(1, 2);
    }
    free_argv(args);
    args = NULL;
  } while (n >= 0);

  if (get_argc(argv) == 0) {
    goto err;
  }

  if (exec_builtin_command(argv) != -1)
    _exit(0);

  if ((strchr("./", argv[0][0]) == NULL) && (path != NULL)) {
    for (i = 0; path[i]; i++) {
      command = malloc(strlen(path[i]) + strlen(argv[0]) + 2);
      if (command == NULL)
	goto err;
      sprintf(command, "%s/%s", path[i], argv[0]);
      fd = open(command, O_RDONLY);
      if (fd >= 0) {
	close(fd);
	break;
      }
      free(command);
      command = NULL;
    }
  }
  if (command == NULL)
    command = strdup(argv[0]);
  if (command == NULL)
    goto err;

  execve(command, argv, envp);

  switch (errno) {
  case ENOENT:
  default:
    e = EXEC_ERR_NOCOMMAND;
    errfile = command;
    break;
  }

err:
  for (i = 0; i < 3; i++) {
    if (save[i] >= 0) {
      dup2(save[i], i);
      close(save[i]);
    }
  }

  switch (e) {
  case EXEC_ERR_NOFILE:    outstr = "Missing name for redirect.\n"; break;
  case EXEC_ERR_FILEERR:   outstr = "No such file or directory.\n"; break;
  case EXEC_ERR_NOCOMMAND: outstr = "Command not found.\n"; break;
  case EXEC_ERR_UNKNOWN:
  default:
    outstr = "Unknown error.\n";
    break;
  }
  if (outstr) {
    if (errfile != NULL) {
      write(2, errfile, strlen(errfile));
      write(2, ": ", 2);
    }
    write(2, outstr, strlen(outstr));
  }

  if (args)
    free_argv(args);

  if (command != NULL)
    free(command);

  return -1;
}

static void wait_log(pid_t pid, int eno, int status)
{
  char *outstr;
  int sig;

/* #define WAIT_LOG */
#ifdef WAIT_LOG
#define WAIT_WRITE(str) write(2, str, strlen(str));
#else
#define WAIT_WRITE(str) do { /* none */ } while (0)
#endif

  if (pid < 0) outstr = "wait4() < 0";
  else if (pid == 0) outstr = "wait4() == 0";
  else outstr = "wait4() > 0";
  WAIT_WRITE(outstr);

  if (pid < 0) {
    switch (errno) {
    case ECHILD: outstr = " (ECHILD)";  break;
    case EFAULT: outstr = " (EFAULT)";  break;
    case EINTR:  outstr = " (EINTR)";   break;
    case EINVAL: outstr = " (EINVAL)";  break;
    default:     outstr = " (UNKNOWN)"; break;
    }
    WAIT_WRITE(outstr);
  } else if (pid == 0) {
    /* WNOHANG is used and no stopped, continued or exited children */
  } else {
    sig = -1;
    if (WIFCONTINUED(status) != 0) {
      outstr = ", Cont.";
    } else if (WIFEXITED(status) != 0) {
      outstr = ", Exit."; /* WEXITSTATUS(status) */
    } else if (WIFSIGNALED(status) != 0) {
      outstr = ", Signaled.";
      sig = WTERMSIG(status);
    } else if (WIFSTOPPED(status) != 0) {
      outstr = ", Stopped.";
      sig = WSTOPSIG(status);
    } else {
      outstr = ", Unknown.";
    }
    WAIT_WRITE(outstr);

    switch (sig) {
    case -1: outstr = NULL; break;
    case SIGHUP:  outstr = " (SIGHUP)";  break;
    case SIGINT:  outstr = " (SIGINT)";  break;
    case SIGQUIT: outstr = " (SIGQUIT)"; break;
    case SIGPIPE: outstr = " (SIGPIPE)"; break;
    case SIGTERM: outstr = " (SIGTERM)"; break;
    case SIGSTOP: outstr = " (SIGSTOP)"; break;
    case SIGTSTP: outstr = " (SIGTSTP)"; break;
    case SIGCONT: outstr = " (SIGCONT)"; break;
    case SIGCHLD: outstr = " (SIGCHLD)"; break;
    case SIGTTIN: outstr = " (SIGTTIN)"; break;
    case SIGTTOU: outstr = " (SIGTTOU)"; break;
    default: outstr = " (Unknown Signal)"; break;
    }
    if (outstr)
      WAIT_WRITE(outstr);
  }

  WAIT_WRITE("\n");
}

static int wait_child(pid_t wpid, int log)
{
  pid_t pid, rpid = 0;
  int status, eno, done = 0;

  while (!done) {
    errno = 0;
    status = 0;
    pid = wait4(-wpid, &status, WCONTINUED|WUNTRACED, NULL);
    eno = errno;

    if (pid < 0) {
      switch (eno) {
      case ECHILD:
	done = 1;
	break;
      case EINTR:
	break;
      default:
	break;
      }
    } else if (pid > 0) {
      if (WIFCONTINUED(status) != 0) {
	/* Continued */
      } else if (WIFEXITED(status) != 0) {
	/* Exited */
      } else if (WIFSIGNALED(status) != 0) {
	switch (WTERMSIG(status)) {
	case SIGINT: /* Ctrl+C */
	  break;
	default:
	  break;
	}
      } else if (WIFSTOPPED(status) != 0) {
	switch (WSTOPSIG(status)) {
	case SIGTSTP: /* Ctrl+Z */
	  rpid = -pid;
	  done = 1;
	  break;
	default:
	  break;
	}
      } else {
	/* Unknown */
      }
    }

    if (log)
      wait_log(pid, eno, status);
  }

  return rpid;
}

static pid_t exec_argv(char *exec[], char *argv[], char *envp[], char *path[])
{
  int n;
  pid_t pid, first_child = 0;
  int fildes[2], bg = 0, ischild = 0, haschild = 0, pipe_in = -1;

  while (get_argc(argv) > 0) {
    if (!strcmp(argv[0], "(")) {
      delete_argv(argv, 0, 1);
      n = rsearch_argv(argv, ")");
      if (n < 0) {
	exec = extract_argv(argv, 0, -1);
      } else {
	delete_argv(argv, n, 1);
	for (; argv[n]; n++) {
	  if (!strcmp(argv[n], "|"))
	    break;
	}
	if (argv[n] == NULL) {
	  exec = extract_argv(argv, 0, -1);
	} else {
	  exec = extract_argv(argv, 0, n);
	  delete_argv(argv, 0, 1);
	}
      }
    } else {
      n = search_argv(argv, "|");
      if (n < 0) {
	exec = extract_argv(argv, 0, -1);
      } else {
	exec = extract_argv(argv, 0, n);
	delete_argv(argv, 0, 1);
      }
    }

    if (get_argc(exec) == 0)
      break;

    n = rsearch_argv(exec, "&");
    if (n >= 0) {
      delete_argv(exec, n, 1);
      bg = 1;
    }

    fildes[0] = -1;
    fildes[1] = -1;

    if (get_argc(argv) > 0) {
      if (pipe2(fildes, 0) < 0)
	break;
    }

    pid = fork();
    if (pid < 0)
      break;
    if (pid == 0) {
      /* child process */

      ischild = 1;
      haschild = 0;

      setpgid(0, first_child);

      signal_reset();

      free_argv(argv);
      argv = NULL;

      if (pipe_in >= 0) {
	close(0);
	dup2(pipe_in, 0);
	close(pipe_in);
	pipe_in = -1;
      }
      if (fildes[0] >= 0) {
	close(fildes[0]);
      }
      if (fildes[1] >= 0) {
	close(1);
	dup2(fildes[1], 1);
	close(fildes[1]);
      }

      if ((search_argv(exec, "|") >= 0) || (search_argv(exec, "(") >= 0)) {
	argv = exec;
	exec = NULL;
	continue;
      }

      exec_command(exec, envp, path);
      break;
    }

    /* parent process */

    haschild = 1;

    if (first_child == 0)
      first_child = pid;

    /* To wait child by wait4() and delay of child process to setpgid() */
    setpgid(pid, first_child);

    if (pipe_in >= 0) {
      close(pipe_in);
      pipe_in = -1;
    }
    if (fildes[0] >= 0)
      pipe_in = fildes[0];
    if (fildes[1] >= 0)
      close(fildes[1]);

    free_argv(exec);
    exec = NULL;
  }

  if (pipe_in >= 0)
    close(pipe_in);

  free_argv(exec);
  free_argv(argv);

  if (!haschild) {
    pid = -1;
  } else {
    if (bg) {
      pid = first_child;
    } else {
      if (!ischild) {
	tcsetpgrp(0, first_child);
      }

      pid = wait_child(first_child, !ischild);

      if (!ischild) {
	tcsetpgrp(0, rootpgid);
      }
    }
  }

  if (ischild) {
    _exit(0);
  }

  return pid;
}

static int ismatch_wildcard(char *wildcard, char *filename)
{
  int i, j;

  for (i = 0;; i++) {
    switch (wildcard[i]) {
    case '*':
      for (j = 0;; j++) {
	if (ismatch_wildcard(wildcard + i + 1, filename + i + j))
	  return 1;
	if (filename[i + j] == '\0')
	  break;
      }
      return 0;
    case '?':
      break;
    default:
      if (wildcard[i] != filename[i])
	return 0;
      break;
    }
    if (wildcard[i] == '\0')
      break;
  }

  return 1;
}

static char **_expand_wildcard(char *argv[], int pos,
			       char *dirname, char *pathname)
{
  char *filename = NULL, *p;
  DIR *dirp = NULL;
  struct dirent *dirent;
  int len;

  if (dirname == NULL) {
    if (pathname[0] == '/') {
      dirname = "";
      pathname++;
    } else
      dirname = NULL;
  }

  filename = strdup(pathname);
  if (filename == NULL)
    goto ret;
  pathname = strchr(filename, '/');
  if (pathname != NULL) {
    *pathname = '\0';
    pathname++;
  }

  dirp = opendir(dirname ? (dirname[0] ? dirname : "/") : ".");
  if (dirp == NULL)
    goto ret;

  while (1) {
    dirent = readdir(dirp);
    if (dirent == NULL)
      break;
    if ((!strcmp(dirent->d_name, ".") || !strcmp(dirent->d_name, "..")) &&
	(filename[0] == '*'))
      continue;
    if (ismatch_wildcard(filename, dirent->d_name)) {
      len = dirname ? (strlen(dirname) + 1) : 0;
      p = malloc(len + strlen(dirent->d_name) + 1);
      if (p == NULL)
	goto ret;
      if (dirname) {
	strcpy(p, dirname);
	p[len - 1] = '/';
      }
      strcpy(p + len, dirent->d_name);
      if (pathname != NULL) {
	argv = _expand_wildcard(argv, pos, p, pathname);
      } else {
	argv = insert_argv(argv, pos, p);
      }
      free(p);
    }
  }

ret:
  if (filename != NULL)
    free(filename);
  if (dirp)
    closedir(dirp);

  return argv;
}

static char **expand_wildcard(char *argv[])
{
  int argc, i;

  argc = get_argc(argv);

  for (i = argc - 1; i > 0; i--) {
    if (!strchr(argv[i], '*') && !strchr(argv[i], '?'))
      continue;
    argv = _expand_wildcard(argv, i + 1, NULL, argv[i]);
    delete_argv(argv, i, 1);
  }

  return argv;
}

static int exec_shell_command(char *argv[])
{
  int argc;
  char **args;
  char *dirname;
  pid_t pid = 0;

  argc = get_argc(argv);
  if (argc < 1)
    goto noexec;

  args = make_argv_equal(argv[0]);
  if (args) {
    if (get_argc(args) > 1) {
      val_add(args[0], args[1]);
      free_argv(args);
      goto exec;
    }
    free_argv(args);
  }

  if (!strcmp(argv[0], "bg")) {
    if ((current == 0) || (current > 0)) {
      write(2, "bg: No current job.\n", 20);
    } else {
      pid = -current;
      kill(pid, SIGCONT);
    }
    goto exec;
  }

  if (!strcmp(argv[0], "fg")) {
    if (current == 0) {
      write(2, "fg: No current job.\n", 20);
    } else {
      pid = (current > 0) ? current : -current;

      tcsetpgrp(0, pid);

      if (current < 0)
	kill(pid, SIGCONT);
      pid = wait_child(pid, 1);

      tcsetpgrp(0, rootpgid);

      if (pid == 0)
	pid = 1;
    }
    goto exec;
  }

  if (!strcmp(argv[0], "cd")) {
    if (argc > 1) {
      dirname = argv[1];
    } else {
      dirname = val_get("HOME");
    }
    if (dirname != NULL) {
      if (chdir(dirname) < 0) {
	write(2, dirname, strlen(dirname));
	write(2, ": No such file or directory.\n", 29);
      }
      goto exec;
    }
  }

noexec:
  return -1;

exec:
  return pid;
}

static int exec_line(char *line, char *envp[], char *path)
{
  char **argv, **path_argv;
  pid_t pid;

  argv = make_argv(line);

  argv = expand_wildcard(argv);

  pid = exec_shell_command(argv);
  if (pid != -1)
    return pid;

  path_argv = make_argv_punc(path);
  pid = exec_argv(NULL, argv, envp, path_argv);
  free_argv(path_argv);

  return pid;
}

void sigint_handler(int arg)
{
  sigint_handled++;
  return;
}

void sigchld_handler(int arg)
{
  sigchld_handled++;
  return;
}

void sigtstp_handler(int arg)
{
  sigtstp_handled++;
  return;
}

static void signal_init(void)
{
  struct sigaction sa;

  memset(&sa, 0, sizeof(sa));

  sa.sa_handler = sigint_handler;
  sigaction(SIGINT, &sa, NULL);

  sa.sa_handler = sigchld_handler;
  sigaction(SIGCHLD, &sa, NULL);

  sa.sa_handler = sigtstp_handler;
  sigaction(SIGTSTP, &sa, NULL);

  sa.sa_handler = SIG_IGN;
  sigaction(SIGTTIN, &sa, NULL);
  sigaction(SIGTTOU, &sa, NULL);
}

static void init(void)
{
  signal_init();
}

static char *make_prompt(void)
{
  char *p, *user, *host, *home;
  char cwd[256], hostname[256];
  static char *prompt = NULL;

  user = val_get("USER");
  if (user == NULL)
    user = val_get("LOGNAME");

  host = val_get("HOST");
  if (host == NULL)
    host = val_get("HOSTNAME");

  home = val_get("HOME");

  if (getcwd(cwd, sizeof(cwd)) == NULL)
    cwd[0] = 0;

  if (user == NULL)
    user = "user";

  if ((host != NULL) && (strlen(host) < sizeof(hostname))) {
    strcpy(hostname, host);
    p = strchr(hostname, '.');
    if (p)
      *p = '\0';
    host = hostname;
  } else {
    host = "host";
  }

  if ((home != NULL) && (strlen(home) > 1) &&
      !strncmp(cwd, home, strlen(home))) {
    memmove(cwd + 1, cwd + strlen(home), strlen(cwd) - strlen(home) + 1);
    cwd[0] = '~';
  }

  prompt = realloc(prompt, 32 + strlen(user) + strlen(host) + strlen(cwd));
  if (prompt != NULL) {
    sprintf(prompt, "%s@%s:%s>%% ", user, host, cwd);
  }

  return prompt ? prompt : "> ";
}

static char **file_search(char *dirname, char *filename, char **argv)
{
  DIR *dirp = NULL;
  char *p;
  struct dirent *dirent;

  dirp = opendir(dirname[0] ? dirname : "/");
  if (dirp == NULL)
    goto ret;

  while (1) {
    dirent = readdir(dirp);
    if (dirent == NULL)
      break;
    if (!strcmp(dirent->d_name, ".") || !strcmp(dirent->d_name, ".."))
      continue;
    if (!strncmp(filename, dirent->d_name, strlen(filename))) {
      if (dirent->d_type == DT_DIR) {
	p = malloc(strlen(dirent->d_name) + 2);
	if (p != NULL) {
	  strcpy(p, dirent->d_name);
	  strcat(p, "/");
	  argv = add_argv(argv, p);
	  free(p);
	} else {
	  argv = add_argv(argv, dirent->d_name);
	}
      } else {
	argv = add_argv(argv, dirent->d_name);
      }
      if (argv == NULL)
	goto err;
    }
  }

  goto ret;

err:
  if (argv != NULL) {
    free_argv(argv);
    argv = NULL;
  }

ret:
  if (dirp != NULL)
    closedir(dirp);

  return argv;
}

char **readline_completion_func(const char *line, int start, int end)
{
  char *pathname = NULL, *dirname = NULL, *filename, *complete = NULL;
  char **argv = NULL, **r = NULL, **path_argv = NULL, *p;
  int i, n, len, is_exec = 0;

  pathname = malloc(end - start + 1);
  if (pathname == NULL)
    goto ret;

  strncpy(pathname, line, end - start);
  pathname[end - start] = '\0';

  for (i = start; i >= 0; i--) {
    if ((i == 0) || (line[-start + i - 1] == '|'))
      is_exec = 1;
    else if (isspace(line[-start + i - 1]))
      continue;
    break;
  }

  p = strrchr(pathname, '/');
  if (p != NULL) {
    dirname = malloc(p - pathname + 1);
    if (dirname == NULL)
      goto ret;
    strncpy(dirname, pathname, p - pathname);
    dirname[p - pathname] = '\0';
    filename = p + 1;
    argv = file_search(dirname, filename, argv);
  } else {
    filename = pathname;
    if (!is_exec) {
      argv = file_search(".", filename, argv);
    } else {
      path_argv = make_argv_punc(val_get("PATH"));
      if (path_argv) {
	for (i = 0; path_argv[i]; i++)
	  argv = file_search(path_argv[i], filename, argv);
      }
    }
  }

  if ((argv == NULL) || (argv[0] == NULL))
    goto ret;

  len = dirname ? (strlen(dirname) + 1) : 0;
  complete = malloc(len + strlen(argv[0]) + 1);
  if (complete == NULL)
    goto ret;

  if (dirname) {
    strcpy(complete, dirname);
    complete[len - 1] = '/';
  }

  p = complete + len;
  strcpy(p, argv[0]);

  for (n = 0; argv[n]; n++) {
    for (i = 0; p[i]; i++) {
      if (argv[n][i] != p[i]) {
	p[i] = '\0';
	break;
      }
    }
  }

  argv = insert_argv(argv, 0, pathname);
  if (strcmp(pathname, complete) != 0) {
    argv = insert_argv(argv, 0, complete);
  }

ret:
  if (pathname != NULL)
    free(pathname);
  if (dirname != NULL)
    free(dirname);
  if (complete != NULL)
    free(complete);

  if (path_argv)
    free_argv(path_argv);

  if (argv != NULL) {
    n = get_argc(argv);
    r = malloc(sizeof(*r) * (n + 1));
    if (r != NULL) {
      for (i = 0; i < n; i++) {
	r[i] = strdup(argv[i]);
      }
      r[n] = NULL;
    }
    free_argv(argv);
  }

  return r;
}

int main(int argc, char *argv[], char *envp[])
{
  char *line, *outstr, *prompt;
  int i, finished = 0, status, sigint_handled_old;
  pid_t pid;

#if 0
  for (i = 0; envp[i]; i++)
    printf("envp[%d]: %s\n", i, envp[i]);
  for (i = 0; i < argc; i++)
    printf("argv[%d]: %s\n", i, argv[i]);
#endif

  rootpid = getpid();
  rootpgid = getpgid(0);

  init();

  for (i = 0; envp[i]; i++) {
    val_add_from_string(envp[i]);
  }

#if defined(USE_LIBEDIT) || defined(USE_EDITLINE) || defined(USE_READLINE) || defined(USE_NLLINE)
  rl_attempted_completion_function = readline_completion_func;
#endif

  while (!finished) {
    prompt = make_prompt();

    sigint_handled_old = sigint_handled;

    line = readline(prompt);

    while (1) {
      status = 0;
      pid = wait4(-1, &status, WNOHANG|WCONTINUED|WUNTRACED, NULL);
      if (pid <= 0)
	break;
      outstr = "Unknown.\n";
      if (WIFCONTINUED(status) != 0) {
	outstr = "Cont.\n";
	if ((current < 0) && (pid == -current))
	  current = -current;
      } else if (WIFEXITED(status) != 0) {
	outstr = "Exited.\n";
	if ((current != 0) && ((pid == current) || (pid == -current)))
	  current = 0;
      } else if (WIFSIGNALED(status) != 0) {
	outstr = "Received signal.\n";
#if 0
	switch (WTERMSIG(status)) {
	case SIGINT:
	default:
	  break;
	}
#endif
      } else if (WIFSTOPPED(status) != 0) {
	outstr = "Stopped.\n";
	if ((current > 0) && (pid == current))
	  current = -current;
#if 0
	switch (WSTOPSIG(status)) {
	case SIGTTIN:
	case SIGTSTP:
	default:
	  break;
	}
#endif
      }
      if (outstr)
	printf("%s [%d]\n", outstr, pid);
    }

    if (sigint_handled_old != sigint_handled) { /* break by Ctrl+C */
      if (!line) {
	write(1, "\n", 1); /* for libedit */
      } else {
	free(line);
      }
      continue;
    }

    if (line == NULL) /* exit by Ctrl+D */
      break;

    if (!strcmp(line, "exit"))
      finished = 1;
    else {
      pid = exec_line(line, envp, val_get("PATH"));
      if (pid != -1) {
	add_history(line);
      }
      if (pid == 1) {
	current = 0;
      } else if ((pid != -1) && (pid != 0)) {
	current = pid;
      }
    }

    free(line);
  }

  return 0;
}
