/* System calls relating to processes and protection
   Copyright (C) 1991 Free Software Foundation

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 1, 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 Michael I. Bushnell.  */

#include "mach.h"
#include <cthreads.h>
#include <gnu/types.h>
#include <gnu/param.h>
#include <gnu/errno.h>
#include <gnu/resource.h>
#include <gnu/wait.h>
#include <gnu/signal.h>
#include <hurd/hurd_types.h>

#include "process.h"
#include "auth.h"
#include "io.h"

#include "alix.h"
#include <machine/alix_machdep.h>
#include "alix_proc.h"
#include "alix_io.h"
#include "alix_auth.h"

/* Missing: execve, setgroups, ptrace?, getlogin, setlogin, vfork */

/* SYSCALL: Change the current 32 bit host id for the machine using
   proc_sethostid.  */
int
sethostid (void *ap, int *ret1, int *ret2)
{
  struct 
    {
      int hostid;
    }
  *args = ap;

  return h2ae(proc_sethostid (procserver, args->hostid));
}

/* SYSCALL: Get the current 32 bit host id for the machine using
   proc_gethostid.  */
int 
gethostid (void *ap, int *ret1, int *ret2)
{
  return h2ae(proc_gethostid (procserver, ret1));
}

/* SYSCALL: Set the hostname for the machine using proc_sethostname.  */
int
sethostname (void *ap, int *ret1, int *ret2)
{
  struct 
    {
      char *namep;
      int len;
    }
  *args = ap;
  char name[args->len];
  int err;

  if (err = copyin (args->namep, name, args->len))
    return err;

  return h2ae(proc_sethostname (procserver, name, args->len));
}

/* SYSCALL: Get the hostname for the machine using proc_gethostname.  */
int
gethostname (void *ap, int *ret1, int *ret2)
{
  struct
    {
      char *bufp;
      int buflen;
    }
  *args = ap;
  error_t err;
  u_int len = MAXHOSTNAMELEN;
  char ourbuf[len];

  err = proc_gethostname (procserver, ourbuf, &len);
  if (len > args->buflen)
    len = args->buflen;
  *ret1 = len;
  if (!err)
    err = copyout (ourbuf, args->bufp, len);
  return h2ae (err);
}

/* Get the pid and ppid from the proc server.  The proclock must be held.  */
int
fetchpids ()
{
  int err;
  
  err = proc_getpids (procserver, &pid, &ppid, &orphaned);
  if (!err)
    pidvalid = 1;
  return err;
}

/* SYSCALL: Get the process id.  */
int
getpid (void *ap, int *ret1, int *ret2)
{
  int err;
  
  mutex_lock (&proclock);
  if (!pidvalid)
    if (err = fetchpids ())
      {
	mutex_unlock (&proclock);
	return h2ae (err);
      }
  
  *ret1 = pid;
#ifdef COMPAT_43
  *ret2 = ppid;
#endif
  mutex_unlock (&proclock);
  return 0;
}

/* SYSCALL: Get the parent process ID.  */
int
getppid (void *ap, int *ret1, int *ret2)
{
  int err;
  mutex_lock (&proclock);
  if (!pidvalid)
    if (err = fetchpids ())
      {
	mutex_unlock (&proclock);
	return h2ae (err);
      }
  
  *ret1 = ppid;
  mutex_unlock (&proclock);
  return 0;
}

/* SYSCALL: Return the port to the process server.  */
int
getproc (void *ap, int *ret1, int *ret2)
{
  mutex_lock (&proclock);
  mach_port_mod_refs (mach_task_self (), procserver, MACH_PORT_RIGHT_SEND, 1);
  *ret1 = procserver;
  mutex_unlock (&proclock);
  return 0;
}

/* SYSCALL: Set the port to the process server.  */
int
setproc (void *ap, int *ret1, int *ret2)
{
  struct
    {
      process_t proc;
    }
  *args = ap;
  
  mach_port_mod_refs (mach_task_self (), args->proc, MACH_PORT_RIGHT_SEND, 1);
  mutex_lock (&proclock);
  pidvalid = 0;
  procserver = args->proc;
  mutex_unlock (&proclock);
  return 0;
}

#ifdef COMPAT_43 
/* SYSCALL: Wait for a child process to exit using proc_wait. */
int
wait0 (int *ret1, int *ret2)
{
  struct rusage unused;
  return h2ae (proc_wait (procserver, -1, ret2, 0, &unused, (pid_t *)ret1));
}

/* SYSCALL: Wait for a child process to exit or stop, and get resource
   usage, using proc_wait.  */
int
wait3 (int options,
       struct rusage *rup,
       int *ret1,
       int *ret2)
{
  int err;
  struct rusage ru;
  
  err = h2ae (proc_wait (procserver, -1, ret2, options, &ru,
			 (pid_t *)ret1));
  if (!err)
    err = copyout (&ru, args->rup, sizeof (struct rusage));
  return err;
}
#endif

/* SYSCALL: Wait for a potentially specified child process to exit or
   stop, using proc_wait.  */
int
wait4 (void *ap, int *ret1, int *ret2)
{
  struct 
    {
      int pid;
      int *statusp;
      int options;
      struct rusage *rup;
    }
  *args = ap;
  
  int status;
  int err;
  struct rusage ru;

  err =  h2ae (proc_wait (procserver, args->pid, &status, args->options,
			  &ru, (pid_t *)ret1));

  if (!err)
    err = copyout (&status, args->statusp, sizeof (int));
  if (!err)
    err = copyout (&ru, args->rup, sizeof (struct rusage));
  return err;
}

/* SYSCALL: Terminate the current process, returning status to the
   parent through proc_exit.  */
int
exit (void *ap, int *ret1, int *ret2)
{
  struct 
    {
      int status;
    }
  *args = ap;

  proc_exit (procserver, W_EXITCODE (args->status, 0));
  task_terminate (mach_task_self ());
  return 0;			/* yeah, like right... */
}

/* SYSCALL: Duplicate the running process.  */
int
fork (void *ap, int *ret1, int *ret2, int *regs)
{
  task_t task;
  thread_t emul_thread;
  thread_t user_thread;
  int i;
  mach_port_t newproc;
  mach_port_t newsigport;
  int err;
  pid_t newpid, newpgrp;
  int neworph;

  /* New signal port */
  mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, &newsigport);

  /* Create the new task */
  task_create (mach_task_self (), 1, &task);

  /* Register new process */
  err = proc_register (procserver, task, newsigport, &newproc);
  if (err)
    {
      task_terminate (task);
      mach_port_mod_refs (mach_task_self (), newsigport, 
			  MACH_PORT_RIGHT_RECEIVE, -1);
      return h2ae (err);
    }
  proc_getpids (newproc, &newpid, &newpgrp, &neworph);

  /* Create the new threads */
  thread_create (task, &emul_thread);
  thread_create (task, &user_thread);
  
  set_newemul_thread (task, emul_thread);
  
  set_newuser_thread (task, user_thread, regs, newpid);
  
  /* Duplicate file descriptors */
  mutex_lock (&iolock);
  for (i = 0; i < dtablesize; i++)
    if (dtable[i].ioserver)
      mach_port_insert_right (task, dtable[i].ioserver, dtable[i].ioserver,
			      MACH_MSG_TYPE_COPY_SEND);
  mach_port_insert_right (task, cwdir, cwdir, MACH_MSG_TYPE_COPY_SEND);
  mach_port_insert_right (task, crdir, crdir, MACH_MSG_TYPE_COPY_SEND);
  mach_port_insert_right (task, ccdir, ccdir, MACH_MSG_TYPE_COPY_SEND);
  mutex_unlock (&iolock);
  
  /* Authorization */
  mutex_lock (&authlock);
  mach_port_insert_right (task, authserver, authserver, 
			  MACH_MSG_TYPE_COPY_SEND);
  mutex_unlock (&authlock);
  
  /* Process structure */
  mutex_lock (&proclock);
  mach_port_insert_right (task, procserver, newproc, 
			  MACH_MSG_TYPE_MOVE_SEND);
  mach_port_insert_right (task, sigthread_port, newsigport,
			  MACH_MSG_TYPE_MOVE_RECEIVE);
  mutex_unlock (&proclock);
  
  /* Set child running */
  thread_resume (emul_thread);
  thread_resume (user_thread);
  mach_port_deallocate (mach_task_self (), user_thread);
  mach_port_deallocate (mach_task_self (), emul_thread);
  mach_port_deallocate (mach_task_self (), task);

  *ret1 = newpid;
  *ret2 = 0;

  return 0;
}

/* Fetch the ID block from the auth server, returning an error if we
   get one.  Must be called with the auth lock.  */
static error_t
getidblock ()
{
  int err;

  err = auth_getids (authserver, &idblock);
  if (!err)
    idblockvalid = 1;
  return err;
}  

/* SYSCALL: Return the current real user id */
int
getuid (void *ap, int *ret1, int *ret2)
{
  error_t err;
  
  mutex_lock (&authlock);
  
  if (!idblockvalid)
    if (err = getidblock ())
      {
	mutex_unlock (&authlock);
	return h2ae (err);
      }

  *ret1 = idblock.ruid;
#ifdef COMPAT_43
  *ret2 = idblock.euid;
#endif

  mutex_unlock (&authlock);
  
  return 0;
}

/* SYSCALL: Retrun the current effective user id.  */
int
geteuid (void *ap, int *ret1, int *ret2)
{
  error_t err;
  mutex_lock (&authlock);
  if (!idblockvalid)
    if (err = getidblock ())
      {
	mutex_unlock (&authlock);
	return h2ae (err);
      }
  *ret1 = idblock.euid;
  mutex_unlock (&authlock);
  return 0;
}
  
/* SYSCALL: Return the current real group id.  */
int
getgid (void *ap, int *ret1, int *ret2)
{
  int err;
  mutex_lock (&authlock);

  if (!idblockvalid)
    if (err = getidblock ())
      {
	mutex_unlock (&authlock);
	return h2ae (err);
      }
  
  *ret1 = idblock.rgid;
#ifdef COMPAT_43
  *ret2 = idblock.gidset[0];
#endif

  mutex_unlock (&authlock);
  return 0;
}

/* SYSCALL: Return the "effective group id" (which is really the first
   entry in the gidset.  */
int
getegid (void *ap, int *ret1, int *ret2)
{
  int err;
  mutex_lock (&authlock);
  
  if (!idblockvalid)
    if (err = getidblock ())
      {
	mutex_unlock (&authlock);
	return h2ae (err);
      }
  *ret1 = idblock.gidset[0];
  return 0;
}

/* SYSCALL: Return the current group set. -- XXX NOT POSIX COMPLIANT */
int
getgroups (void *ap, int *ret1, int *ret2)
{
  struct 
    {
      int gidsetsize;
      int *gidset;
    }
  *args = ap;
  int groups[NGROUPS];
  int i;
  int err;

  mutex_lock (&authlock);

  if (!idblockvalid)
    if (err = getidblock ())
      {
	mutex_unlock (&authlock);
	return h2ae (err);
      }

  if (args->gidsetsize == 0)
    {
      mutex_unlock (&authlock);
      *ret1 = idblock.ngroups;
      return 0;
    }

  if (args->gidsetsize < idblock.ngroups)
    {
      mutex_unlock (&authlock);
      return EINVAL;
    }
  
  for (i = 0; i < idblock.ngroups; i++)
    groups[i] = idblock.gidset[i];
  err = copyout (groups, args->gidset, idblock.ngroups * sizeof (int));
  if (!err)
    *ret1 = idblock.ngroups;
  return err;
}

/* SYSCALL: Return the current authentication handle.  */
int 
getauth (void *ap, int *ret1, int *ret2)
{
  mutex_lock (&authlock);
  mach_port_mod_refs (mach_task_self (), authserver, MACH_PORT_RIGHT_SEND, 1);
  *ret1 = authserver;
  mutex_unlock (&authlock);
  return 0;
}

/* Reauthenticate a single I/O descriptor.  */
static void
reauth_io (io_t *portp)
{
  io_t newport;
  
  io_reauthenticate (*portp);
  auth_user_authenticate (authserver, *portp, &newport);
  mach_port_deallocate (mach_task_self (), *portp);
  *portp = newport;
}

void reauthenticate (auth_t newhandle)
{
  int i;
  mach_port_t unused;
  
  mach_port_deallocate (mach_task_self (), authserver);
  authserver = newhandle;

  mutex_unlock (&authlock);
  
  /* Reauthenticate all the file descriptors and the proc server */
  mutex_lock (&iolock);
  for (i = 0; i < dtablesize; i++)
    {
      if (dtable[i].ioserver)
	reauth_io (&dtable[i].ioserver);
    }
  
  reauth_io (&cwdir);
  reauth_io (&crdir);
  mutex_unlock (&iolock);

  mutex_lock (&proclock);
  proc_reauthenticate (procserver);
  auth_user_authenticate (authserver, procserver, &unused);
  mutex_unlock (&proclock);
}

/* The idblock has been set.  Try and reauthenticate, if it fails,
   just mark the idblock as invalid.  */
static int
try_reauthenticate ()
{
  auth_t newhandle;
  int err;
  
  err = auth_makeauth (authserver, idblock, &newhandle);
  if (err)
    {
      idblockvalid = 0;
      mutex_unlock (&authlock);
      return h2ae (err);
    }
  reauthenticate (newhandle);
  return 0;
}

/* SYSCALL: Set the real user id.  */
int
setuid (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int uid;
    }
  *args = ap;
  int err;

  mutex_lock (&authlock);
  if (!idblockvalid)
    if (err = getidblock ())
      {
	mutex_unlock (&authlock);
	return h2ae (err);
      }
  
  idblock.ruid = args->uid;
  idblock.euid = args->uid;
  idblock.svuid = args->uid;
  return try_reauthenticate ();
}

/* SYSCALL: Set the effective user id. */
int
seteuid (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int euid;
    }
  *args = ap;
  int err;

  mutex_lock (&authlock);
  if (!idblockvalid)
    if (err = getidblock ())
      {
	mutex_unlock (&authlock);
	return h2ae (err);
      }
  
  idblock.euid = args->euid;
  return try_reauthenticate ();
}

/* SYSCALL: Set the real group id.  */
int
setgid (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int gid;
    }
  *args = ap;
  int err;
  
  mutex_lock (&authlock);
  if (!idblockvalid)
    if (err = getidblock ())
      {
	mutex_unlock (&authlock);
	return h2ae (err);
      }
  
  if (idblock.ngroups < 1)
    idblock.ngroups = 1;
  idblock.rgid = args->gid;
  idblock.svgid = args->gid;
  idblock.gidset[0] = args->gid;
  return try_reauthenticate ();
}

/* SYSCALL: Set the "effective group id" (gidset[0]).  */
int
setegid (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int egid;
    }
  *args = ap;
  int err;

  mutex_lock (&authlock);
  if (!idblockvalid)
    if (err = getidblock ())
      {
	mutex_unlock (&authlock);
	return h2ae (err);
      }
  
  if (idblock.ngroups < 1)
    idblock.ngroups = 1;
  idblock.gidset[0] = args->egid;
  return try_reauthenticate ();
}

/* SYSCALL: Set a new authentication port.  */
int
setauth (void *ap, int *ret1, int *ret2)
{
  struct
    {
      auth_t auth;
    }
  *args = ap;
  
  mach_port_mod_refs (mach_task_self (), args->auth, MACH_PORT_RIGHT_SEND, 1);
  mutex_lock (&authlock);
  idblockvalid = 0;
  reauthenticate (args->auth);
  return 0;
}

#ifdef COMPAT_43
/* SYSCALL: Set the real and effective uid's.  */
int
setreuid_compat43 (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int ruid;
      int euid;
    }
  *args = ap;
  int err;
  
  mutex_lock (&authlock);
  if (!idblockvalid)
    if (err = getidblock ())
      {
	mutex_unlock (&authlock);
	return h2ae (err);
      }
  
  if (args->ruid != -1)
    idblock.ruid = args->ruid;
  if (args->euid != -1)
    idblock.euid = args->euid;
  return try_reauthenticate ();
}

/* SYSCALL: Set the real and "effective" group ids.  */
int setregid_compat43 (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int rgid;
      int egid;
    }
  *args = ap;
  int err;
  
  mutex_lock (&authlock);
  if (!idblockvalid)
    if (err = getidblock ())
      {
	mutex_unlock (&authlock);
	return h2ae (err);
      }
  
  if (args->egid != -1)
    {
      if (idblock.ngroups < 1)
	idblock.ngroups = 1;
      idblock.gidset[0] = args->egid;
    }
  if (args->rgid != -1)
    idblock.rgid = args->rgid;
  return try_reauthenticate ();
}
#endif
	
/* SYSCALL: Set session id.  */
int
setsid (void *ap, int *ret1, int *ret2)
{
  int err;
  
  mutex_lock (&proclock);
  if (err = h2ae (proc_setsid (procserver)))
    {
      mutex_unlock (&proclock);
      return err;
    }
  
  pidvalid = 0;
  mutex_unlock (&proclock);
  return 0;
}

/* SYSCALL: Find out the process group for a specified process using
   proc_getpgrp.  */
int
getpgrp (void *ap, int *ret1, int *ret2)
{
  struct 
    {
      pid_t forpid;
    }
  *args = ap;
  error_t err;
  
  mutex_lock (&proclock);
  
  if (err = fetchpids ())
    {
      mutex_unlock (&proclock);
      return h2ae (err);
    }
  
  if (args->forpid == pid)
    {
      *ret1 = pgrp;
      mutex_unlock (&proclock);
      return 0;
    }
  else
    {
      mutex_unlock (&proclock);
      return h2ae (proc_getpgrp (procserver, args->forpid, (pid_t *)ret1));
    }
}

/* SYSCALL: Set the process group of a specified process using proc_setpgrp. */
int
setpgid (void *ap, int *ret1, int *ret2)
{
  struct 
    {
      pid_t forpid;
      pid_t newpgrp;
    }
  *args = ap;
  error_t err;
  
  err = proc_setpgrp (procserver, args->forpid, args->newpgrp);
  if (err)
    return h2ae (err);
  
  mutex_lock (&proclock);
  
  if (!pidvalid)
    return 0;

  if (args->forpid == pid || args->forpid == 0)
    pgrp = args->newpgrp;

  mutex_unlock (&proclock);
  return 0;
}

/* This is called by the process server when our parent changes or
   someone changes our pgrp.  */
error_t
proc_newids (sigthread_t sigt,
	     pid_t newppid,
	     pid_t newpgrp,
	     int neworph)
{
  mutex_lock (&proclock);
  ppid = newppid;
  pgrp = newpgrp;
  orphaned = neworph;
  pidvalid = 1;
  mutex_unlock (&proclock);
  return 0;
}

/* SYSCALL: Return a task port for a specified process using proc_pid2task. */
int
pid2task (void *ap, int *ret1, int *ret2)
{
  struct
    {
      pid_t inpid;
    }
  *args = ap;

  return h2ae (proc_pid2task (procserver, args->inpid, (mach_port_t *)ret1));
}

/* SYSCALL: Get the pid from a task port using proc_task2pid.  */
int
task2pid (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int task;
    }
  *args = ap;

  return h2ae (proc_task2pid (procserver, args->task, (pid_t *)ret1));
}

