/* init -- Start up essential servers and user processes.
   Copyright (C) 1993, 1994 Free Software Foundation, Inc.

This file is part of the GNU Hurd.

The GNU Hurd 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, or (at your option)
any later version.

The GNU Hurd 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 the GNU Hurd; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* Written by Roland McGrath.  */

#include <hurd.h>
#include <mach.h>
#include <string.h>
#include "startup_S.h"
#include "startup_reply.h"
#include <sys/reboot.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <mach/notify.h>
#include <signal.h>
#include <hurd/fsys.h>
#include <device/device.h>
#include <sys/file.h>
#include <hurd/paths.h>
#include <mach/mig_errors.h>
#include <sys/wait.h>
#include <ttyent.h>
#include <unistd.h>
#include <ctype.h>
#include <hurd/process.h>
#include <sys/utsname.h>


#if __GNUC__ > 2 || __GNUC_MINOR__ >= 5
#define noreturn	__attributes__ ((volatile))
#else
#define noreturn	volatile
#endif

char *hurd_release = "unreleased";

/* Program to run for single user.  */
const char *shell = "/bin/sh";

/* Shell script to run for multi-user setup.  */
const char *rcfile = "/etc/rc";

#define	SINGLEUSER	shell
#define	MULTIUSER	multiuser_command
char *multiuser_command;	/* Set in main.  */

/* host_reboot flags for when we crash.  */
#define CRASH_FLAGS	RB_AUTOBOOT

/* XXX these should be in <hurd/paths.h> */
#define _HURD_PROC	_HURD "proc"
#define _HURD_AUTH	_HURD "auth"

const struct option options[] =
  {
    { "single-user",		no_argument,		NULL, 's' },
    { "prompt-for-servers",	no_argument,		NULL, 'p' },
    { "naughty-bits",		optional_argument,	NULL, 'n' },
    { "rcfile",			required_argument,	NULL, 'f' },
    { "shell",			required_argument,	NULL, 'S' },
    { NULL, }
  };

/* This structure keeps track of each registered essential task.  */
struct ess_task
  {
    struct ess_task *next;
    task_t task_port;
    char *name;
  };

/* This structure keeps track of each notified task.  */
struct ntfy_task
  {
    mach_port_t notify_port;
    struct ntfy_task *next;
  };

/* Many ways to die.  */
noreturn void crash_system (struct ess_task *et, thread_t losing_thread);
noreturn void panic (const char *message, error_t error);
noreturn void reboot_system (int), reboot_mach (int);
noreturn void crash (void), crash_mach (void);

static void initialize_version_info (void);

int read_ttys (void);
void reap_child (void), kill_users (void);

pid_t start_command (char **argv);
char **parse_command (const char *command, char **add_argv);

mach_port_t host_priv, device_master;

int prompt_for_servers;		/* -p flag.  */

unsigned int naughty_bits = 0xdeadbeef;	/* -n value.  */

/* What mode init is in.  */
enum { singleuser, multiuser, stopped } mode;

/* PID of the child running the singleuser
   shell or the multiuser startup script.  */
pid_t child;


/* These are linked lists of all of the registered items.  */
struct ess_task *ess_tasks;
struct ntfy_task *ntfy_tasks;

/* Set when the proc and auth servers call.  */
mach_port_t procserver, authserver;
mach_port_t procreply, authreply;
mach_msg_type_name_t procreplytype, authreplytype;
process_t bootfs_proc, authserver_proc;

/* Ports we serve.  */
mach_port_t request_portset, startup, fsys;

char **global_argv;


#define BOOT(flags)	((flags & RB_HALT) ? "halt" : "reboot")

/* Server routines.  */

error_t
S_startup_reboot (startup_t port,
		  mach_port_t refport,
		  int flags)
{
  const char *auth;

  if (refport == mach_task_self ())
    auth = "init task";
  else if (refport == host_priv)
    auth = "host privileged";
  else if (refport == device_master)
    auth = "device master";
  else
    return EPERM;

  printf ("init: %s (%s, %#x)\n", BOOT(flags), auth, flags);

  reboot_system (flags);
}

error_t
S_startup_essential_task (startup_t port,
			  task_t task,
			  char *name)
{
  static void notice_server_version (const char *, const char *, const char *);
  static int procserver_essential = 0; /* Nonzero if proc has called us.  */
  struct ess_task *et;
  mach_port_t prev;

  /* The proc server is single-threaded, so don't call it while
     it is waiting for us to reply.  */
  if (!procserver_essential)
    {
      /* The commented-out code here is wrong; the owner is for
	 the protection of the process and does not indicate what
	 the process can do.  This call needs instead to take
	 the privileged host port as proof that the caller can be
	 trusted. */
#if 0
      /* Fetch the idblock for this task.  */
      if ((err = proc_task2pid (procserver, task, &pid)) ||
	  (err = proc_getprocinfo (procserver, pid, &info)))
	return err;
      if (info.owner != 0)
	return EPERM;
#endif
    }

  /* Record this task as essential.  */
  et = malloc (sizeof (struct ess_task));
  if (et == NULL)
    return ENOMEM;
  et->task_port = task;
  et->name = strdup (name);
  if (et->name == NULL)
    {
      free (et);
      return ENOMEM;
    }
  et->next = ess_tasks;
  ess_tasks = et;
  
  /* Dead-name notification on the task port will tell us when it dies.  */
  mach_port_request_notification (mach_task_self (), task, 
				  MACH_NOTIFY_DEAD_NAME, 1, startup, 
				  MACH_MSG_TYPE_MAKE_SEND_ONCE, &prev);
  if (prev)
    mach_port_deallocate (mach_task_self (), prev);
  /* Taking over the exception port will give us a better chance
     if the task tries to get wedged on a fault.  */
  task_set_special_port (task, TASK_EXCEPTION_PORT, startup);

  /* The proc server is magical.  */
  if (!procserver_essential && !memcmp (name, "proc", sizeof ("proc")))
    procserver_essential = 1;

  return 0;
}


/* Register a port to be notified on shutdown.  */

error_t
S_startup_request_notification (startup_t port,
				mach_port_t their_port)
{
  struct ntfy_task *nt;
  mach_port_t prev;

  mach_port_request_notification (mach_task_self (), their_port, 
				  MACH_NOTIFY_DEAD_NAME, 1, startup, 
				  MACH_MSG_TYPE_MAKE_SEND_ONCE, &prev);
  if (prev)
    mach_port_deallocate (mach_task_self (), prev);

  nt = malloc (sizeof (struct ntfy_task));
  nt->notify_port = port;
  nt->next = ntfy_tasks;
  ntfy_tasks = nt;
  return 0;
}


/* Called whenever we get a dead name notification.  */

error_t
mach_notify_dead_name (mach_port_t notify,
		       mach_port_t name)
{
  struct ntfy_task *nt, *pnt;
  struct ess_task *et;
  
  for (et = ess_tasks; et != NULL; et = et->next)
    if (et->task_port == name)
      /* An essential task has died.  */
      crash_system (et, MACH_PORT_NULL);

  for (nt = ntfy_tasks, pnt = NULL; nt != NULL; pnt = nt, nt = nt->next)
    if (nt->notify_port == name)
      {
	/* Someone who wanted to be notified is gone.  */
	mach_port_deallocate (mach_task_self (), name);
	if (pnt != NULL)
	  pnt->next = nt->next;
	else
	  ntfy_tasks = nt->next;
	free (nt);
	return 0;
      }

  return 0;
}


/* Called when an essential task takes an exception.  */

error_t
catch_exception_raise (mach_port_t exc_port,
		       thread_t thread,
		       task_t task,
		       int exc,
		       int code,
		       int subcode)
{
  struct ess_task *et;
  
  /* XXX what about programs that use exceptions; e.g. ufs? */

  for (et = ess_tasks; et; et = et->next)
    if (et->task_port == task)
      {
#if 0
	/* XXX need to time out on replies */
	char dumpfile[PATH_MAX];
	file_t dump;
	sprintf (dumpfile, "/crash/core.%s.%p", et->name, task);
	dump = path_lookup (dumpfile, FS_LOOKUP_WRITE|FS_LOOKUP_CREATE, 0600);
	if (dump == MACH_PORT_NULL)
	  perror (dumpfile);
	else
	  {
	    error_t err = core_dump_task (dump, task, dump,
					  exc, code, getenv ("GNUTARGET"));
	    if (err)
	      {
		hurd_perror ("core_dump_task", err);
		remove (dumpfile);
	      }
	  }
#endif

	crash_system (et, thread);
      }

  return 0;
}


/* Called when users do `kill -SIG 1'.  */

error_t
S_sig_post (mach_port_t msgport,
	    int signo,
	    mach_port_t refport)
{
  if (refport != mach_task_self () &&
      refport != host_priv &&
      refport != device_master)
    return EPERM;

  switch (signo)
    {
    case SIGHUP:
    case SIGCONT:		/* Why not?  */
      /* Re-read /etc/ttys.  */
      mode = multiuser;
      read_ttys ();
      break;

    case SIGTSTP:
    case SIGSTOP:		/* Why not?  */
      if (mode == multiuser)
	mode = stopped;
      break;

    case SIGCHLD:
      reap_child ();
      break;

    default:
      mode = singleuser;
      kill_users ();
      break;
    }

  return 0;
}

/* Main program; starts up proc and auth servers,
   and generally makes the world safe for normal programs.  */

/* The RPC server demultiplexer.  */

static int
request_server (mach_msg_header_t *inp,
		mach_msg_header_t *outp)
{
  extern int notify_server (), msg_server (), startup_server ();
  
  return (notify_server (inp, outp) ||
	  msg_server (inp, outp) ||
	  startup_server (inp, outp));
}

/* Read a string from stdin into BUF.  */

static int
getstring (char *buf, size_t bufsize)
{
  if (fgets (buf, bufsize, stdin) != NULL && buf[0] != '\0')
    {
      size_t len = strlen (buf);
      if (buf[len - 1] == '\n' || buf[len - 1] == '\r')
	buf[len - 1] = '\0';
      return 1;
    }
  return 0;
}

/* Run SERVER, giving it INIT_PORT_MAX initial ports from PORTS.  */

static void
run (char *server, mach_port_t *ports)
{
  error_t err;
  char buf[BUFSIZ];
  char *prog = server;

  if (prompt_for_servers)
    {
      printf ("Server file name (default %s): ", server);
      if (getstring (buf, sizeof (buf)))
	prog = buf;
    }

  while (1)
    {
      file_t file;

      file = path_lookup (prog, O_EXEC, 0);
      if (file == MACH_PORT_NULL)
	perror (prog);
      else
	{
	  err = file_exec (file, MACH_PORT_NULL, EXEC_NEWTASK,
			   NULL, 0, /* No args.  */
			   NULL, 0, /* No env.  */
			   NULL, MACH_MSG_TYPE_COPY_SEND, 0, /* No dtable.  */
			   ports, MACH_MSG_TYPE_COPY_SEND, INIT_PORT_MAX,
			   NULL, 0, /* No info in init ints.  */
			   NULL, 0, NULL, 0);
	  if (!err)
	    break;

#ifdef notyet
	  hurd_perror (prog, err);
#endif
	}

      printf ("File name for server %s (or nothing to reboot): ", server);
      if (getstring (buf, sizeof (buf)))
	prog = buf;
      else
	crash ();
    }

  printf ("started %s\n", prog);
}

int
main (int argc, char **argv)
{
  static void initialize_version_info (void);
  error_t err;
  mach_port_t bootport;
  mach_port_t ports[INIT_PORT_MAX];
  size_t i;
  char c;
  device_t consdev;

  global_argv = argv;

  /* The filesystem has gotten us running somehow;
     we need to get from it some crucial information.  */

  if ((err = task_get_special_port (mach_task_self (),
				    TASK_BOOTSTRAP_PORT, &bootport)) ||
      (err = fsys_getpriv (bootport, &host_priv, &device_master)))
    crash_mach ();

  /* Open a stream on the console so we can print error messages.  */
  if (err = device_open (device_master, D_WRITE, "console", &consdev))
    crash_mach ();
  stdin = mach_open_devstream (consdev, "w+");
  if (stdin == NULL)
    crash_mach ();
  stdout = stderr = stdin;
  setbuf (stdout, NULL);	/* Make it unbuffered.  */

  initialize_version_info ();

  /* Allocate the receive right for our message port.  */
  if (err = mach_port_allocate (mach_task_self (),
				MACH_PORT_RIGHT_RECEIVE, &startup))
    panic ("mach_port_allocate", err);

  /* Set up the set of ports we will pass to the programs we exec.  */
  for (i = 0; i < INIT_PORT_MAX; ++i)
    switch (i)
      {
      case INIT_PORT_CRDIR:
	ports[i] = getcrdir ();
	break;
      case INIT_PORT_CWDIR:
	ports[i] = getcwdir ();
	break;
      case INIT_PORT_BOOTSTRAP:
	ports[i] = startup;
	break;
      default:
	ports[i] = MACH_PORT_NULL;
	break;
      }

  mode = multiuser;
  /* `+' in optstring means to ignore options after the first non-option.
     We want them to be arguments to rc.  */
  while ((c = getopt_long (argc, argv, "+spS:f:n::", options, NULL)) != EOF)
    switch (c)
      {
      case 's':
	mode = singleuser;
	break;
      case 'p':
	prompt_for_servers = 1;
	break;
      case 'S':
	shell = optarg;
	break;
      case 'f':
	rcfile = optarg;
	break;
      case 'n':
	naughty_bits = optarg == NULL ? 0 : strtol (optarg, NULL, 0);
	break;
      }

  /* Start the proc and auth servers.  */
  run (_HURD_PROC, ports);
  run (_HURD_AUTH, ports);

  /* Listen on our message port for signals and startup protocol requests.
     When we have heard from the auth and proc servers, we call
     `launch_system' (below).  */
  while (1)
    {
      if (err = mach_msg_server (request_server, vm_page_size, startup))
	panic ("mach_msg_server", err);
      puts ("init: mach_msg_server returned"); /* XXX use syslog */
    }
}

error_t
launch_system (void)
{
  mach_port_t old;
  
  /* Reply to the proc and auth servers.  */
  startup_procinit_reply (procreply, procreplytype, 0, authserver, host_priv,
			  device_master);
  startup_authinit_reply (authreply, authreplytype, 0, authserver_proc);

  /* Give the library our auth and proc server ports.  */
  _hurd_port_init (&_hurd_ports[INIT_PORT_AUTH], authserver);
  _hurd_port_init (&_hurd_ports[INIT_PORT_PROC], procserver);

  /* Tell the proc server our msgport and where our args and
     environment are.  */
  proc_setmsgport (procserver, startup, &old);
  if (old)
    mach_port_deallocate (mach_task_self (), old);
  proc_setprocargs (procserver, (int) global_argv, (int) environ);

  /* Give the bootstrap FS its proc and auth ports.  */
  {
    fsys_t fsys;

    if (errno = file_getcontrol (getcrdir (), &fsys))
      perror ("file_getcontrol (root)");
    else
      {
	if (errno = fsys_init (fsys, bootfs_proc, authserver))
	  perror ("fsys_init");
	mach_port_deallocate (mach_task_self (), fsys);
      }
  }
  
  /* Open /dev/console.  Since we have no descriptor table to begin with,
     it will be fd 0; dup it onto 1 and 2 as well, so the console is stdin,
     stdout, and stderr for our children.  Using `open' takes care of all
     the ctty magic.  */
  if (open ("/dev/console", O_RDWR) != 0)
    perror ("open: /dev/console");
  dup (0);
  dup (1);

  multiuser_command = malloc (strlen (shell) + 1 + strlen (rcfile) + 1);
  if (! multiuser_command)
    panic ("Can't malloc command line", errno);
  strcpy (multiuser_command, shell);
  strcat (multiuser_command, " ");
  strcat (multiuser_command, rcfile);

  {
    char buf[BUFSIZ];
    const char *cmd = mode == singleuser ? SINGLEUSER : MULTIUSER;
    if (prompt_for_servers)
      {
	printf ("Command to run for %s-user startup (default %s): ",
		mode == singleuser ? "single" : "multi", cmd);
	if (getstring (buf, sizeof (buf)))
	  cmd = buf;
      }
    child = start_command (parse_command (cmd, &global_argv[optind]));
    if (child < 0)
      panic ("Can't fork", errno);
  }
  return 0;
}

/* Callbacks from initial system servers.  */

/* Called by the proc server when it starts up.  */

error_t
S_startup_procinit (startup_t server,
		    mach_port_t reply,
		    mach_msg_type_name_t reply_porttype,
		    process_t proc, process_t fs_proc, process_t auth_proc,
		    auth_t *auth,
		    mach_port_t *priv, mach_port_t *dev)
{
  if (procserver)
    /* Only one proc server.  */
    return EPERM;

  procserver = proc;

  bootfs_proc = fs_proc;
  authserver_proc = auth_proc;

  procreply = reply;
  procreplytype = reply_porttype;

  /* Save the reply port until we get startup_authinit.  */
  if (authserver)
    launch_system ();

  return MIG_NO_REPLY;
}

/* Called by the auth server when it starts up.  */

error_t
S_startup_authinit (startup_t server,
		    mach_port_t reply,
		    mach_msg_type_name_t reply_porttype,
		    mach_port_t auth)
{
  if (authserver)
    /* Only one auth server.  */
    return EPERM;

  authserver = auth;

  /* Save the reply port until we get startup_procinit.  */
  authreply = reply;
  authreplytype = reply_porttype;

  if (procserver)
    launch_system ();

  return MIG_NO_REPLY;
}

/* Maintainence of children.  */

struct proc
  {
    struct proc *next;
    char *name;			/* Terminal name.  */
    char *command1;		/* argv of first command to execute.  */
    char *command2;		/* argv of second command to execute.  */
    char **argv1, **argv2;
    pid_t pid1, pid2;		/* PIDs of running children.  */
    int checked;		/* Used in read_ttys.  */
  };

struct proc *procs;

/* Called when a child has died.  */
void
reap_child (void)
{
  int status;
  pid_t pid = waitpid (WAIT_ANY, &status, WNOHANG);
  struct proc *p;

  if (pid == -1)
    {
      /* XXX Should use syslog.  Should crash?  */
      perror ("init: wait");
      return;
    }
  else if (pid == 0)
    {
      puts ("init: bogus SIGCHLD");
      return;
    }

  if (pid == child)
    {
      /* The singleuser shell or the multiuser startup script has died.  */

      char buf[BUFSIZ];
      const char *command = 0;

      switch (mode)
	{
	case singleuser:
	  if (WIFSIGNALED (status) && WTERMSIG (status) == SIGKILL)
	    {
	      puts ("Couldn't exec single-user shell.");
	      printf ("Enter command to run instead (or nothing to reboot): ");
	      if (!getstring (buf, sizeof (buf)))
		crash ();
	      command = buf;
	    }
	  else
	    {
	      /* The singleuser shell exited.
		 Run the multiuser startup script now.  */
	      mode = multiuser;
	      command = MULTIUSER;
	    }
	  break;
	  
	case multiuser:
	case stopped:
	  if (WIFEXITED (status) && WEXITSTATUS (status) == 0)
	    {
	      /* The multiuser startup script exited successfully.
		 Start up programs from /etc/ttys now.  */
	      child = -1;
	      if (mode == stopped || (read_ttys () && procs != NULL))
		return;
	    }
	  
	  /* The multiuser startup script failed,
	     or reading /etc/ttys failed.
	     Run a singleuser shell now.  */
	  mode = singleuser;
	  command = SINGLEUSER;
	  break;
	}

      child = start_command (parse_command (command, NULL));
      if (child < 0)
	panic ("Can't fork", errno);

      return;
    }

  /* XXX want to notice commands that are failing repeatedly.  */

  /* Restart the commands that died.  */
  for (p = procs; p != NULL; p = p->next)
    {
      if (p->pid1 == pid)
	{
	  p->pid1 = mode == multiuser ? start_command (p->argv1) : -1;
	  break;
	}
      if (p->pid2 == pid)
	{
	  p->pid2 = mode == multiuser ? start_command (p->argv2) : -1;
	  break;
	}
    }
}


static int
snarfcmds (const struct ttyent *t, struct proc *p)
{
  if (t->ty_status & TTY_ON)
    {
      /* Start the new commands.  */
      
      if (t->ty_window != NULL)
	{
	  p->command1 = strdup (t->ty_window);
	  if (p->command1 == NULL)
	    {
	      perror ("reading /etc/ttys: strdup");
	      return 0;
	    }
	  p->argv1 = parse_command (p->command1, NULL);
	  if (p->argv1 == NULL)
	    return 0;
	  p->pid1 = start_command (p->argv1);
	}
      
      if (t->ty_getty != NULL)
	{
	  p->command2 = strdup (t->ty_window);
	  if (p->command2 == NULL)
	    {
	      perror ("reading /etc/ttys: strdup");
	      return 0;
	    }
	  p->argv2 = parse_command (p->command2, NULL);
	  if (p->argv2 == NULL)
	    return 0;
	  p->pid2 = start_command (p->argv2);
	}
    }
  return 1;
}

static inline void
killchild (pid_t pid, const char *name)
{
  if (pid != -1 && kill (pid, SIGHUP)) /* XXX ? */
    printf ("init: kill failed for PID %d on %s: %s",
	    pid, name, strerror (errno));
}


/* Read /etc/ttys and start and/or kill things.  */
int
read_ttys (void)
{
  struct proc *p, *lastp;
  struct ttyent *t;

  if (setttyent ())
    {
      perror ("setttyent");
      return 0;
    }

  for (p = procs; p != NULL; p = p->next)
    p->checked = 0;

  while ((t = getttyent ()) != NULL)
    {
      for (p = procs; p != NULL; p = p->next)
	if (!strcmp (p->name, t->ty_name))
	  {
	    if (t->ty_window == NULL ||
		(p->command1 == NULL && t->ty_window != NULL) ||
		strcmp (p->command1, t->ty_window))
	      {
		/* Kill the old command.  */
		if (p->command1 != NULL)
		  {
		    free (p->command1);
		    free (p->argv1[0]);
		    free (p->argv1);
		    p->command1 = NULL;
		  }
		killchild (p->pid1, p->name);
		p->pid1 = -1;
	      }

	    if (t->ty_getty == NULL ||
		(p->command2 == NULL && t->ty_getty != NULL) ||
		strcmp (p->command2, t->ty_getty))
	      {
		/* Kill the old command.  */
		if (p->command2 != NULL)
		  {
		    free (p->command2);
		    free (p->argv2[0]);
		    free (p->argv2);
		    p->command2 = NULL;
		  }
		killchild (p->pid2, p->name);
		p->pid2 = -1;
	      }

	    p->checked = snarfcmds (t, p);
	    break;
	  }

      if (p == NULL)
	{
	  /* A new entry.  */
	  p = malloc (sizeof (*p));
	  if (p == NULL)
	    perror ("reading /etc/ttys: malloc");
	  else
	    {
	      p->name = strdup (t->ty_name);
	      if (p->name == NULL)
		{
		  perror ("reading /etc/ttys: strdup");
		  free (p);
		}
	      else
		{
		  p->command1 = p->command2 = NULL;
		  p->pid1 = p->pid2 = -1;
		  p->checked = snarfcmds (t, p);
		  p->next = procs;
		  procs = p;
		}
	    }
	}
    }

  p = procs;
  lastp = NULL;
  while (p != NULL)
    {
      struct proc *next = p->next;
      if (!p->checked)
	{
	  /* This entry no longer exists.
	     Kill the processes and remove the entry from the chain.  */

	  killchild (p->pid1, p->name);
	  killchild (p->pid2, p->name);

	  free (p->name);
	  free (p->command1);
	  free (p->argv1[0]);
	  free (p->argv1);
	  free (p->command2);
	  free (p->argv2[0]);
	  free (p->argv2);
	  free (p);

	  if (lastp == NULL)
	    procs = next;
	  else
	    lastp->next = next;
	}
      p = next;
    }

  return 1;
}

/* Start a command.  */

pid_t
start_command (char **argv)
{
  pid_t pid = fork ();
  if (pid == 0)
    {
      /* We are the child.  */
      execvp (argv[0], argv);
      perror (argv[0]);
      /* The proc server will report that we died with SIGKILL.
	 The parent will notice this and know our exec lost.  */
      task_terminate (mach_task_self ());
    }
  return pid;
}

/* Chop COMMAND into a vector of words, append ADD_ARGV, and return it.  */

char **
parse_command (const char *command, char **add_argv)
{
  int argc;
  char **argv;
  const char *p;
  char **a;

  argc = 0;
  p = command;
  do
    {
      while (isspace (*p))
	++p;
      ++argc;
      while (*p != '\0' && !isspace (*p))
	++p;
    } while (*p != '\0');

  if (add_argv != NULL)
    for (a = add_argv; *a != NULL; ++a)
      ++argc;

  argv = malloc ((argc + 1) * sizeof (char *));
  if (argv == NULL)
    {
      perror ("malloc");
      return NULL;
    }
  command = strdup (command);
  if (command == NULL)
    {
      perror ("strdup");
      free (argv);
      return NULL;
    }

  argc = 0;
  p = command;
  while (1)
    {
      while (isspace (*p))
	++p;
      argv[argc++] = p;
      while (*p != '\0' && !isspace (*p))
	++p;
      if (*p == '\0')
	break;
      else
	*p++ = '\0';
    }

  if (add_argv != NULL)
    for (a = add_argv; *a != NULL; ++a)
      argv[argc++] = *a;

  argv[argc] = NULL;

  return argv;
}

/* Bringing the system down.  */


noreturn void
panic (const char *msg, error_t err)
{
  const char *lossage = ((child != -1 || procs != NULL) ? "stampede" :
			 "unstable ecosystem");
  if (err)
    printf ("%s: %s: %s\n", lossage, msg, strerror (err));
  else
    printf ("%s: %s\n", lossage, msg);

  crash ();
}


/* Kill all running processes that are not registered as essential.  */
void
kill_users (void)
{
  pid_t *pids;
  unsigned int npids, i;

  if (errno = proc_getallpids (procserver, &pids, &npids))
    {
      perror ("init: Can't get PIDs from proc server");
      puts ("init: nobody killed");
      return;
    }

  for (i = 0; i < npids; ++i)
    {
      task_t task;

      if (errno = proc_pid2task (procserver, pids[i], &task))
	printf ("init: Couldn't get task port for PID %d: %s\n",
		pids[i], strerror (errno));
      else
	{
	  struct ess_task *e;

	  for (e = ess_tasks; e != NULL; e = e->next)
	    if (e->task_port == task)
	      break;
	  if (e == NULL)
	    {
	      mach_port_t msgport;
	      if (errno = proc_getmsgport (procserver, pids[i], &msgport))
		printf ("init: Couldn't get message port for PID %d: %s\n",
			pids[i], strerror (errno));
	      else
		{
		  sig_post (msgport, SIGHUP, task);
		  /* XXX need to time out on reply */
		}

	      if (errno = task_terminate (task))
		printf ("init: Couldn't terminate task with PID %d: %s\n",
			pids[i], strerror (errno));
	    }
	  else
	    printf ("init: not killing essential task with PID %d\n", pids[i]);
	  mach_port_deallocate (mach_task_self (), task);
	}
    }
      

  vm_deallocate (mach_task_self (), (vm_address_t) pids,
		 npids * sizeof (pid_t));
}

noreturn void
crash_system (struct ess_task *et, thread_t thread)
{
  time_t now = time ((time_t *) NULL);
  printf ("init: %s at %.25s: hurd going down on %s server: \
task %p thread %p\n",
	  (now & naughty_bits) ? "gang-bang" : "stampede",
	  asctime (gmtime (&now)),
	  et->name, (void *) et->task_port, (void *) thread);
  crash ();
}


/* Reboot the microkernel.  */
noreturn void
reboot_mach (int flags)
{
  printf ("init: %sing Mach (flags %#x)...\n", BOOT (flags), flags);
  while (errno = host_reboot (host_priv, flags))
    perror ("host_reboot");
  for (;;);
}

noreturn void
crash_mach (void)
{
  reboot_mach (CRASH_FLAGS);
}

noreturn void
reboot_system (int flags)
{
  struct ntfy_task *n;

  for (n = ntfy_tasks; n != NULL; n = n->next)
    {
      error_t err;
      printf ("init: notifying %p\n", (void *) n->notify_port);
      /* XXX need to time out on reply */
      err = startup_dosync (n->notify_port);
      if (err && err != MACH_SEND_INVALID_DEST)
	printf ("init: %p complained: %s\n",
		(void *) n->notify_port,
		strerror (err));
    }

  reboot_mach (flags);
}

noreturn void
crash (void)
{
  reboot_system (CRASH_FLAGS);
}

/* Version information handling.

   A server registers its name, release, and version with
   startup_register_version.  These are used to construct the `version'
   element of `struct utsname', returned by uname.  The release is compared
   against `hurd_release', as compiled into init.  If it matches, it is
   omitted.  A typical version string for the system might be:

   "Mach 3.0 VERSION(MK75); init 0.0; proc 0.0; auth 0.0; boot ufs 0.0" */

const char version_string[] = "0.0"; /* Version of init.  */

static struct utsname uname_info;

error_t
S_startup_register_version (startup_t server,
			    task_t credentials,
			    const char *name, 
			    const char *release,
			    const char *version)
{
  static char *p = uname_info.version;
  static char *const end = &uname_info.version[sizeof (uname_info.version)];
  inline void add (const char *s)
    {
      if (p < end)
	{
	  size_t len = strlen (s);
	  if (end - 1 - p < len)
	    memcpy (p, s, len);
	  p += len;
	}
    }

  if (credentials != mach_task_self ())
    /* Must be privileged to register for uname.  */
    return EPERM;

  if (version == NULL || version[0] == '\0')
    {
      /* An empty version means to clear the list.  */
      p = uname_info.version;
      initialize_version_info ();
      if (release && release[0] != '\0')
	strncpy (release, uname_info.release, sizeof uname_info.release);
      return 0;
    }

  if (p > uname_info.version)
    add ("; ");

  if (name)
    {
      add (name);
      add (" ");
    }
  add (version);
  if (release && strcmp (release, hurd_release))
    {
      add (" {");
      add (release);
      add (" }");
    }

  if (p < end)
    *p++ = '\0';
  else
    {
      end[-1] = '\0';
#if 0
      syslog (LOG_NOTICE, "_UTSNAMELLEN (%u) too short; need more than %u",
	      sizeof (uname_info.version), p - uname_info.version);
#endif
    }
  return 0;
}

static void
initialize_version_info (void)
{
  kernel_version_t mach_version;
  char *p;
  struct host_basic_info info;
  size_t n = sizeof info;

  strcpy (uname_info.sysname, "GNU");
  strcpy (uname_info.release, hurd_release);

  host_info (mach_host_self (), HOST_BASIC_INFO, &info, &n);
#if 0
  sprintf (uname_info.machine, "%s %s",
	   mach_cpu_types[info.cpu_type], /* XXX */
	   mach_cpu_subtypes[info.cpu_type][info.cpu_subtype]);	/* XXX */
#else
  uname_info.machine[0] = '\0';
#endif  

  /* Get the microkernel version.  */
  host_kernel_version (mach_host_self (), mach_version);
  /* The version string returned by Mach is too long and hairy.
     The interesting part is before a colon.  */
  p = index (mach_version, ':');
  if (p)
    *p = '\0';
  S_startup_register_version (MACH_PORT_NULL, MACH_PORT_NULL,
			    NULL, NULL, mach_version);

  /* Notice our own version.  */
  S_startup_register_version (MACH_PORT_NULL, MACH_PORT_NULL,
			    "init", hurd_release, version_string);

  /* Other version info will be added by startup_register_version requests.  */
}

error_t
S_startup_uname (startup_t init,
		 struct utsname *uname)
{
  *uname = uname_info;

  return proc_gethostname (procserver, uname->nodename);
}
