/* Session and process group manipulation 
   Copyright (C) 1992 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 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 Michael I. Bushnell.  */

error_t
proc_setsid (struct proc *p)
{
  struct session *sess;
  
  if ((p->p_pgrp && p->p_pgrp->pg_pgid == p->p_pid) || pgrp_find (p->p_pid))
    return EPERM;
  
  leave_pgrp (p);
  
  sess = malloc (sizeof (struct session));
  sess->s_leader = p;
  sess->s_fakecoll = MACH_PORT_NULL;
  sess->s_sessionid = MACH_PORT_NULL;
  sess->s_count = 0;

  p->p_pgrp = new_pgrp (p->p_pid, sess);
  
  join_pgrp (p);
  return 0;
}

error_t
proc_getsid (struct proc *callerp,
	     pid_t pid,
	     pid_t *sid)
{
  struct proc *p = pid_find (pid);
  if (!p)
    return ESRCH;
  if (!p->p_pgrp)
    *sid = 0;
  else
    *sid = p->p_pgrp->pg_session->s_leader->p_pid;
  return 0;
}

error_t
proc_getsidport (struct proc *p,
		 mach_port_t *sessport)
{
  if (!p->p_pgrp)
    *sessport = MACH_PORT_NULL;
  else
    {
      if (!p->p_pgrp->pg_session->s_sessionid)
	mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE,
			    &p->p_pgrp->pg_session->s_sessionid);
      *sessport = p->p_pgrp->pg_session->s_sessionid;
    }
  return 0;
}

error_t
proc_setpgrp (struct proc *callerp,
	      pid_t pid,
	      pid_t pgid)
{
  struct proc *p = pid_find (pid);
  struct pgrp *pg = pgrp_find (pgid);
  
  if (!p || (p != callerp & p->p_parent != callerp))
    return ESRCH;
  
  if (p->p_parent == callerp && p->p_exec)
    return EACCES;
  
  if (!p->p_pgrp)
    return EACCES;

  if (p->p_pgrp->pg_session->s_leader == p
      || p->p_pgrp->pg_session != callerp->p_pgrp->pg_session
      || ((pgid != p->p_pgrp->pg_pgid
	   && (!pg || pg->pg_session != callerp->p_pgrp->pg_session))))
    return EPERM;
  
  leave_pgrp (p);
  p->p_pgrp = pg ? pg : new_pgrp (pgid, p->p_pgrp->pg_session);
  join_pgrp (p);
  
  return 0;
}

error_t
proc_getpgrp (struct proc *callerp,
	      pid_t pid,
	      pid_t *pgid)
{
  struct proc *p = pid_find (pid);
  
  if (!p)
    return ESRCH;
  
  if (p->p_pgrp)
    *pgid = p->p_pgrp->pg_pgid;
  else
    *pgid = 0;
  
  return 0;
}

leave_pgrp (struct proc *p)
{
  struct pgrp *pg = p->p_pgrp;

  if (!pg)
    return;
  
  *p->p_gprevp = p->p_gnext;
  
  /* If we were the last member of our pgrp, free it */
  if (!pg->pg_plist)
    {
      if (pg->pg_fakecoll)
	mach_port_mod_refs (mach_task_self (), pg->pg_fakecoll,
			    MACH_PORT_RIGHT_RECEIVE, 1)
      if (!--pg->pg_session->s_count)
	{
	  /* Deallocate the session */
	  if (pg->pg_session->s_fakecoll)
	    mach_port_mod_refs (mach_task_self (), pg->pg_session->s_fakecoll,
				MACH_PORT_RIGHT_RECEIVE, 1);
	  if (pg->pg_session->s_sessionid)
	    mach_port_mod_refs (mach_task_self (), pg->pg_session->s_sessionid,
				MACH_PORT_RIGHT_RECEIVE, 1);
	  free (pg->pg_session);
	}
      free (pg);
    }
  else if (p->p_parent->p_pgrp != pg
	   && p->p_parent->p_pgrp->pg_session == pg->pg_session
	   && !--pg->pg_orphcnt)
    {
      /* We were the last process keeping this from being
	 an orphaned process group -- do the orphaning gook */
      struct proc *ip;
      int dosignal;
      
      for (ip = pg->pg_plist; ip; ip = ip->p_gnext)
	{
	  if (ip->p_stopped)
	    dosignal = 1;
	  proc_newids (ip->p_msgport, ip->p_task, ip->p_pid,
		       ip->p_parent->p_pid, 1);
	}
      if (dosignal)
	for (ip = pg->pg_plist; ip; ip = ip->p_gnext)
	  {
	    sig_post (ip->p_msgport, SIGHUP, ip->p_task);
	    sig_post (ip->p_msgport, SIGCONT, ip->p_task);
	  }
    }
}

void
join_pgrp (struct proc *p)
{
  struct pgrp *pg = p->p_pgrp;

  if (!pg)
    return;
  
  p->p_gnext = pg->p_plist;
  p->p_gprevp = &pg->p_plist;
  if (pg->p_plist)
    pg->p_plist->p_gprevp = &p->p_gnext;
  pg->p_plist = p;
  
  if (p->p_parent->p_pgrp != pg
      && p->p_parent->p_pgrp->pg_session == pg->pg_session)
    pg->p_orphcnt++;
}

struct pgrp *
new_pgrp (pid_t pgid,
	  struct session *sess)
{
  struct pgrp *pg;
  
  pg = malloc (sizeof (struct pgrp));
  pg->pg_plist = 0;
  pg->pg_fakecoll = MACH_PORT_NULL;
  pg->pg_pgid = pgid;
  ++(pg->pg_session = sess)->s_count;
  pg->pg_orphcnt = 0;
  
  addhash (&pghash, pg, &pg->pg_hashloc, pgid);
  return pg;
}

struct pgrp *
pgrp_find (pid_t pgid)
{
  return findhash (&pghash, pgid);
}

