1
0
Files
irix-657m-src/irix/kern/os/proc/ppgrp.c
2022-09-29 17:59:04 +03:00

894 lines
20 KiB
C

/**************************************************************************
* *
* Copyright (C) 1989-1996 Silicon Graphics, Inc. *
* *
* These coded instructions, statements, and computer programs contain *
* unpublished proprietary information of Silicon Graphics, Inc., and *
* are protected by Federal copyright law. They may not be disclosed *
* to third parties or copied or duplicated in any form, in whole or *
* in part, without the prior written consent of Silicon Graphics, Inc. *
* *
**************************************************************************/
#ident "$Id: ppgrp.c,v 1.54 1998/01/10 02:40:01 ack Exp $"
#include "sys/types.h"
#include "sys/param.h"
#include "sys/systm.h"
#include "sys/conf.h"
#include "sys/errno.h"
#include "sys/ksignal.h"
#include "sys/cred.h"
#include "pproc_private.h"
#include "ksys/vsession.h"
#include "sys/sema.h"
#include "sys/var.h"
#include "sys/debug.h"
#include "sys/sysinfo.h"
#include "sys/kmem.h"
#include "sys/cmn_err.h"
#include "ksys/pid.h"
#include "ksys/childlist.h"
#include <values.h>
#include <ksys/vproc.h>
#include <ksys/vpgrp.h>
#include "vpgrp_private.h"
#include "pproc.h"
/* prototypes for static functions */
static int proc_contributesjcl(proc_t *, pid_t, pid_t);
static int anystop(pgrp_t *, pid_t);
typedef int (*pgrpscanfunc_t)(struct proc *, long);
/*
* process group - Each process in the system is a member of a process group
* that is identified by a process group ID. This grouping
* permits the signaling of related processes. New processes
* join the process group of their creators.
*
* orphaned process group - A process group in which the parent of every
* member is either itself a member of the group or
* is not a member of the group's session.
* The point being that job control status for an
* orphaned process group could not be reported to
* any other process in the same (or any) session.
*
* locking:
* Each pgroup has its own mutex lock, which protects its own
* chain, member count and various fields. There is also a
* list access mode lock, which in read mode represents the
* number of pending signal operations against the processesr
* in the process group, and in write mode represents the number
* of list updates in progress or oustanding. Note that in write
* mode, the pgrp lock must remain held while performing an
* update to the list; whereas, in read mode, the pgrp lock
* can be dropped (after switching to read mode) while the
* list chain is traversed.
*
* A process wishing to change its (or another's) process group
* acquires the target's p_who mrlock in update mode -- this
* avoids simultaneous pgroup (or session) changes; any
* process may dereference p_vpgrp while holding either p_who
* in access mode, or the p_siglock spinlock -- p_siglock must
* be held when p_vpgrp, p_pgid or p_sid are changed.
*
* The physical pgrp structure is reached from the virtual pgrp via
* behavior chain reference. The pgrp's behavior descriptor is embedded
* in the pgrp structure. This behavior descriptor points to the pgrp_t
* itself and back to the associated vpgrp_t. Vpgrp structures are hashed
* by pgid for lookups.
------------
| |<------------------------------
| | |
| |-- VPGRP_TO_FIRST_BHV()- |
------------ | |
vpgrp_t | |
| |
first behavior <---------------------- |
... |
previous behavior |
| |
v |
-------------- |
| pg_bhv |-- BHV_TO_VPGRP()------------
--------------
: | :
: v : ----------- ----------- -----------
-------------- | | | | | |
| pg_chain |----->|p_pgroup | |p_pgroup | |p_pgroup |
|------------| |---------| |---------| |---------|
| pg_sid | |p_pgflink|---->|p_pgflink|---->... |p_pgflink|--
|------------| |---------| |---------| |---------| |
| pg_memcnt | --|p_pgblink|<----|p_pgblink| ...<----|p_pgblink| ---
|------------| | |---------| |---------| |---------|
| pg_jclcnt | --- | ... | | ... | | ... |
|------------| |---------| |---------| |---------|
| pg_lockmode| | ... | | ... | | ... |
|------------| |---------| |---------| |---------|
| pg_lock | proc proc proc
--------------
pgrp_t
*/
#define JCL_CONTRIBUTOR(_ch_pgid, _ch_sid, _p_pgid, _p_sid) \
(_ch_pgid != _p_pgid && _ch_sid == _p_sid)
/*
* Return 1 if the parent process of process cp prevents
* process group pgid in session sid from being orphaned.
*/
static int
proc_contributesjcl(proc_t *cp, pid_t pgid, pid_t sid)
{
vp_get_attr_t pattr;
vproc_t *pvp;
pvp = VPROC_LOOKUP_STATE(cp->p_ppid, ZNO);
if (pvp == NULL)
return 0;
VPROC_GET_ATTR(pvp, VGATTR_JCL, &pattr);
VPROC_RELE(pvp);
/*
* Now the acid test:
* is our parent in the same session but in a different process group.
*/
return JCL_CONTRIBUTOR(pgid, sid, pattr.va_pgid, pattr.va_sid);
}
/*
* pgroupscan - examine every element on the process group chain,
* and call the named routine for each one. if the routine
* returns non-zero, stop the scan and return. returns non-zero
* if aborted by the called routine, zero otherwise.
*
* The caller has the specific pgrp held.
*/
int
pgroupscan(
pgrp_t *pg,
pgrpscanfunc_t pfunc,
long arg)
{
proc_t *tp;
int tmp = 0;
/* REFERENCED */
vpgrp_t *vpg = BHV_TO_VPGRP(&pg->pg_bhv);
ASSERT(PGRPLIST_IS_READLOCKED(pg));
for (tp = pg->pg_chain; tp != NULL; tp = tp->p_pgflink) {
if (tmp = (*pfunc)(tp, arg))
break;
}
return(tmp);
}
/* ARGSUSED */
static int
anyustop(uthread_t *ut, void *annoy)
{
return (((ut->ut_flags & UT_STOP) && (ut->ut_whystop == JOBCONTROL))
|| sigsetismember(&ut->ut_sig, &stopdefault));
}
/*
* anystop - any members of passed process group stopped?
*
* Called with pgroup list lock held.
*/
static int
anystop(
pgrp_t *pg,
pid_t exclude_pid)
{
proc_t *mp;
for (mp = pg->pg_chain; mp; mp = mp->p_pgflink) {
if (mp->p_pid == exclude_pid)
continue;
if ((mp->p_flag & SJSTOP) ||
uthread_apply(&mp->p_proxy, UT_ID_NULL, anyustop, 0))
return 1;
}
return(0);
}
sequence_num_t
ppgrp_sigseq(
bhv_desc_t *bdp)
{
pgrp_t *pg = BHV_TO_PPGRP(bdp);
return pg->pg_sigseq;
}
void
ppgrp_set_sigseq(
bhv_desc_t *bdp,
sequence_num_t sequence)
{
pgrp_t *pg = BHV_TO_PPGRP(bdp);
pg->pg_sigseq = sequence;
}
int
ppgrp_sig_wait(
bhv_desc_t *bdp,
sequence_num_t sequence)
{
pgrp_t *pg = BHV_TO_PPGRP(bdp);
int retval = 1;
PGRP_LOCK(pg);
ASSERT(pg->pg_memcnt > 0);
/*
* XXX - punt for PGRP_SIGSEQ_LT, returning non-zero is functionally
* correct. This op is on the verge of extinction anyhow.
* If the list is in read mode, we'll assume that a pgrp signal
* is being delivered and so we will return non-zero and force
* waitsys to retry.
*/
if (!PGRPLIST_IS_READLOCKED(pg)) {
if (PGRP_SIGSEQ_GT(pg,sequence))
retval = -1;
else if (PGRP_SIGSEQ_GEQ(pg,sequence))
retval = 0;
}
PGRP_UNLOCK(pg);
return retval;
}
/*
* Foreach child, update child's pgrp's orphan status as a result
* of parent's new state.
*/
void
link_children(
proc_t *p,
pid_t pgid,
pid_t sid)
{
child_pidlist_t **list;
child_pidlist_t *cpid;
vproc_t *vpr;
mutex_lock(&p->p_childlock, PZERO);
list = &p->p_childpids;
while (cpid = *list) {
vpr = VPROC_LOOKUP_STATE(cpid->cp_pid, ZYES);
ASSERT(vpr != NULL);
VPROC_PGRP_LINKAGE(vpr, pgid, sid);
list = &cpid->cp_next;
VPROC_RELE(vpr);
}
mutex_unlock(&p->p_childlock);
}
void
ppgrp_setattr(
bhv_desc_t *bdp,
int *is_batchp)
{
pgrp_t *pg = BHV_TO_PPGRP(bdp);
if (is_batchp != NULL)
pg->pg_batch = *is_batchp;
}
/* Physical behavior for local process (sub-)groups. */
/*
* Get process group attributes.
* A NULL pointer can be supplied if a particular attribute is not needed.
*/
void
ppgrp_getattr(
bhv_desc_t *bdp,
pid_t *sidp,
int *is_orphanedp,
int *is_batchp)
{
pgrp_t *pg = BHV_TO_PPGRP(bdp);
vpgrp_t *vpg = BHV_TO_VPGRP(bdp);
if (is_orphanedp != NULL)
*is_orphanedp = (pg->pg_jclcnt == 0);
if (sidp != NULL)
*sidp = vpg->vpg_sid;
if (is_batchp != NULL)
*is_batchp = pg->pg_batch;
}
/*
* Perform process group orphan action.
* This is called only on the server cell.
*/
void
ppgrp_orphan(
bhv_desc_t *bdp,
int is_orphaned,
int is_exit)
{
vpgrp_t *vpg = BHV_TO_VPGRP(bdp);
int is_stopped;
ASSERT(BHV_POSITION(VPGRP_TO_FIRST_BHV(vpg)) != VPGRP_POSITION_DC);
if (is_orphaned && is_exit) {
VPGRP_ANYSTOP(vpg, 0, &is_stopped);
if (is_stopped)
VPGRP_SENDSIG(vpg, SIGHUP, VPG_SENDCONT, 0, NULL, NULL);
}
return;
}
int
ppgrp_join_begin(
bhv_desc_t *bdp)
{
pgrp_t *pg = BHV_TO_PPGRP(bdp);
vpgrp_t *vpg = BHV_TO_VPGRP(bdp);
PGRP_LOCK(pg);
/*
* May have lost a race with a leaving member and the pgrp
* is now marked for destruction.
*/
if (!VPGRP_IS_VALID(vpg)) {
PGRP_UNLOCK(pg);
return 1;
}
if (++pg->pg_memcnt == 1)
VPGRP_MEMBERSHIP(vpg, 1, vpg->vpg_sid);
PGRP_UNLOCK(pg);
return 0;
}
/*
* Add a (local) process to a process group
*/
void
ppgrp_join_end(
bhv_desc_t *bdp,
proc_t *p,
int attach)
{
pgrp_t *pg = BHV_TO_PPGRP(bdp);
vpgrp_t *vpg = BHV_TO_VPGRP(bdp);
PGRP_LOCK(pg);
/*
* Get the pgrp list for update.
* If some action's currently directed at the entire process group,
* wait until the action's over.
*/
PGRPLIST_WRITELOCK(pg);
if (pg->pg_chain == NULL) {
pg->pg_jclcnt = 0;
}
p->p_pgblink = NULL;
p->p_pgflink = pg->pg_chain;
if (pg->pg_chain)
pg->pg_chain->p_pgblink = p;
pg->pg_chain = p;
/*
* Release the list "lock". We still have the pgrp lock, though.
*/
PGRPLIST_UNLOCK(pg);
if (attach) {
/*
* A process joins a new process group (or makes
* new group unto itself).
*/
if (proc_contributesjcl(p, vpg->vpg_pgid, vpg->vpg_sid)) {
p_flagset(p, SPGJCL);
/*
* If creating a new process group or the existing group
* is detached, this process may be attaching the group.
* Need to hold local membership and drop the pgrp lock
* while doing this, though.
*/
if (++pg->pg_jclcnt == 1) {
PGRPLIST_READLOCK(pg); /* Get read */
PGRP_UNLOCK(pg);
VPGRP_ORPHAN(vpg, 0, 0);
PGRP_LOCK(pg);
ASSERT(PGRPLIST_IS_READLOCKED(pg));
PGRPLIST_UNLOCK(pg);
}
}
PGRP_UNLOCK(pg);
/*
* If the process in question is a parent, its new pgrp can
* change the orphan status of its children pgrps.
*/
link_children(p, vpg->vpg_pgid, vpg->vpg_sid);
} else
PGRP_UNLOCK(pg);
}
/*
* Remove a process from a process group
* If it is the last member of the process group, deallocate the pgroup.
* If it is not the last member of the process group, and this process
* will cause the process group to be orphaned, detach the process group
*/
void
ppgrp_leave(
bhv_desc_t *bdp,
proc_t *p,
int exitting)
{
/* REFERENCED */
pid_t pgid;
pgrp_t *pg = BHV_TO_PPGRP(bdp);
vpgrp_t *vpg = BHV_TO_VPGRP(bdp);
ASSERT(ismrlocked(&p->p_who, MR_UPDATE));
/*
* The p_who mrlock stops others (other processes or other threads
* within the p
*/
pgid = p->p_pgid;
ASSERT(pgid != -1);
ASSERT(pgid == vpg->vpg_pgid);
/*
* Must have pgrp_lock to dequeue pg from hash list.
*/
PGRP_LOCK(pg);
PGRPLIST_WRITELOCK(pg);
/*
* Remove p from this process group
*/
if (p->p_pgblink == NULL) {
/*
* p is first on q
*/
pg->pg_chain = p->p_pgflink;
} else {
p->p_pgblink->p_pgflink = p->p_pgflink;
}
if (p->p_pgflink != NULL)
p->p_pgflink->p_pgblink = p->p_pgblink;
p->p_pgflink = p->p_pgblink = NULL;
if (--pg->pg_memcnt == 0)
VPGRP_MEMBERSHIP(vpg, 0, p->p_sid);
PGRPLIST_UNLOCK(pg);
if (p->p_flag & SPGJCL) {
p_flagclr(p, SPGJCL);
ASSERT(pg->pg_jclcnt > 0);
if (--pg->pg_jclcnt == 0) {
/*
* The pgrp previously not (locally) orphaned
* but this process leaving changes that.
* Hold the membership and invoke another
* vop to check global state and do the
* right POSIX stuff.
*/
if (exitting) {
PGRPLIST_READLOCK(pg);
PGRP_UNLOCK(pg);
VPGRP_ORPHAN(vpg, 1, exitting);
PGRP_LOCK(pg);
PGRPLIST_UNLOCK(pg);
} else {
VPGRP_ORPHAN(vpg, 1, exitting);
}
}
}
PGRP_UNLOCK(pg);
}
/*
* Re-evaluate the orphan status of a process group for which
* a parent process of a given member is exiting.
*/
void
ppgrp_detach(
bhv_desc_t *bdp,
proc_t *p)
{
pgrp_t *pg = BHV_TO_PPGRP(bdp);
vpgrp_t *vpg = BHV_TO_VPGRP(bdp);
ASSERT(pg->pg_memcnt > 0);
/*
* Fast check for pgrp already orphaned - if the local
* pgrp is orphaned, there will be no change resulting
* from this detach.
* Otherwise, we must use the VPGRP operation to perform
* an orphan check.
*/
PGRP_LOCK(pg);
if (pg->pg_jclcnt == 0) {
ASSERT(!(p->p_flag & SPGJCL));
PGRP_UNLOCK(pg);
return;
}
if (p->p_flag & SPGJCL) {
p_flagclr(p, SPGJCL);
ASSERT(pg->pg_jclcnt > 0);
if (--pg->pg_jclcnt == 0) {
/*
* The pgrp previously not (locally) orphaned
* but this process leaving changes that.
* Hold the membership and invoke another
* vop to check global state and do the
* right POSIX stuff.
*/
PGRPLIST_READLOCK(pg);
PGRP_UNLOCK(pg);
VPGRP_ORPHAN(vpg, 1, 1);
PGRP_LOCK(pg);
PGRPLIST_UNLOCK(pg);
}
}
PGRP_UNLOCK(pg);
}
/*
* Re-evaluate the orphan status of a process group for which
* the parent of a member has changed pgid/sid. This may result
* in either orphaning or non-orphaning the group. Note that
* if the group is orphaned in this case we don't go doing the
* POSIX SIGHUP/SIGCONT stuff.
*/
void
ppgrp_linkage(
bhv_desc_t *bdp,
proc_t *p,
pid_t parent_pgid,
pid_t parent_sid)
{
pgrp_t *pg = BHV_TO_PPGRP(bdp);
vpgrp_t *vpg = BHV_TO_VPGRP(bdp);
int contribution_change;
ASSERT(pg->pg_memcnt > 0);
contribution_change = (JCL_CONTRIBUTOR(p->p_pgid, p->p_sid,
parent_pgid, parent_sid) ? 1 : 0)
- ((p->p_flag & SPGJCL) ? 1 : 0);
switch (contribution_change) {
case 0:
break;
case 1:
/* We're now a contributor and may non-orphan the group */
PGRP_LOCK(pg);
p_flagset(p, SPGJCL);
if (++pg->pg_jclcnt == 1) {
PGRPLIST_READLOCK(pg);
PGRP_UNLOCK(pg);
VPGRP_ORPHAN(vpg, 0, 0);
PGRP_LOCK(pg);
PGRPLIST_UNLOCK(pg);
}
PGRP_UNLOCK(pg);
break;
case -1:
/* We're no longer a contributor and may orphan the group */
PGRP_LOCK(pg);
p_flagclr(p, SPGJCL);
ASSERT(pg->pg_jclcnt > 0);
if (--pg->pg_jclcnt == 0) {
PGRPLIST_READLOCK(pg);
PGRP_UNLOCK(pg);
VPGRP_ORPHAN(vpg, 1, 0);
PGRP_LOCK(pg);
PGRPLIST_UNLOCK(pg);
}
PGRP_UNLOCK(pg);
break;
}
}
/*
* Check for any stopped members locally.
*/
void
ppgrp_anystop(
bhv_desc_t *bdp,
pid_t exclude_pid,
int *is_stopped)
{
pgrp_t *pg = BHV_TO_PPGRP(bdp);
/* REFERENCED */
vpgrp_t *vpg = BHV_TO_VPGRP(bdp);
if (pg->pg_chain == NULL) {
*is_stopped = 0;
return;
}
PGRP_LOCK(pg);
*is_stopped = anystop(pg, exclude_pid);
PGRP_UNLOCK(pg);
}
/*
* Send signal (at last!) to process, if not no-ctty.
*/
static int
sigpgrpf(
proc_t *p,
long arg)
{
sig_arg_t *argp = (sig_arg_t *) arg;
int error;
if (!(p->p_flag & SNOCTTY)) {
/* Hold to prevent reap */
if (VPROC_HOLD_STATE(PROC_TO_VPROC(p), ZYES))
return(0); /* just missed it! */
error = pproc_sendsig(&p->p_bhv, argp->sig, argp->opts,
argp->sid, argp->credp, argp->infop);
if (error)
argp->error = error;
VPROC_RELE(PROC_TO_VPROC(p));
}
return(0);
}
/* ARGSUSED */
static int
sendcont(
proc_t *p,
long arg)
{
sig_arg_t *argp = (sig_arg_t *) arg;
int error;
/* Hold to prevent reap */
if (VPROC_HOLD_STATE(PROC_TO_VPROC(p), ZYES))
return(0); /* missed it! */
error = pproc_sendsig(&p->p_bhv, SIGHUP, SIG_ISKERN, 0, NULL, NULL);
error = pproc_sendsig(&p->p_bhv, SIGCONT, SIG_ISKERN, 0, NULL, NULL);
if (error)
argp->error = error;
VPROC_RELE(PROC_TO_VPROC(p));
return(0);
}
/*
* Signal members of a process group
*/
int
ppgrp_sendsig(
bhv_desc_t *bdp,
int sig,
int options,
pid_t sid,
cred_t *credp,
k_siginfo_t *infop)
{
pgrp_t *pg = BHV_TO_PPGRP(bdp);
pgrpscanfunc_t sigfunc;
sig_arg_t arg;
ASSERT(pg);
ASSERT(sig > 0);
if (pg->pg_chain == NULL)
return 0;
/* Initialize pgroupscan function argument block */
arg.sig = sig;
arg.opts = options;
arg.sid = sid;
arg.credp = credp;
arg.infop = infop;
arg.error = 0;
/*
* Here we must set the hold-bit in the process group
* structure, so no process group members can exit,
* and no prospective members can join --
* release hold via pgrp_rele, below.
*
* This prevents the race condition between
* sigtoproc and waitsys, and others.
*/
PGRP_LOCK(pg);
PGRPLIST_READLOCK(pg);
PGRP_SIGSEQ_INC(pg); /* increment signal sequence */
PGRP_UNLOCK(pg);
if (options & VPG_SENDCONT)
sigfunc = (pgrpscanfunc_t) sendcont;
else
sigfunc = (pgrpscanfunc_t) sigpgrpf;
/*
* Scan through members of the process group.
*/
pgroupscan(pg, sigfunc, (long) &arg);
PGRP_LOCK(pg);
ASSERT(PGRPLIST_IS_READLOCKED(pg));
PGRPLIST_UNLOCK(pg);
PGRP_UNLOCK(pg);
return arg.error;
}
/*
* This routine is not is the ops table. It is called by the
* distribution layer to obtain physical state information,
*/
void
ppgrp_getstate(
bhv_desc_t *bdp,
int *nmembers,
int *is_orphaned)
{
pgrp_t *pg = BHV_TO_PPGRP(bdp);
PGRP_LOCK(pg);
*nmembers = pg->pg_memcnt;
*is_orphaned = (pg->pg_jclcnt == 0);
PGRP_UNLOCK(pg);
}
/* Communication structure between ppgrp_nice and pgrp_nice_f,
* since pgroupscan allows only 1 argument.
*/
typedef struct {
int flags; /* GET_NICE or SET_NICE */
int nice; /* Nice value to set, or, for
* getpriority, the lowest nice
* value of the pgroup. */
int cnt; /* number of procs found */
cred_t *scred; /* Credentials of caller */
} pg_nice_s;
static int
pgrp_nice_f(
proc_t *p,
long arg)
{
int error;
int nice;
pg_nice_s *ns = (pg_nice_s *)arg;
if (ns->flags & VPG_GET_NICE) {
error = proc_get_nice(p, &nice, ns->scred);
if (error == 0) {
if (nice < ns->nice)
ns->nice = nice;
ns->cnt++;
}
return error;
}
/* Set priority
* proc_set_nice rewrites the input nice value with the
* old nice value - hence we pass a temp, which has to be
* reinitialized for each proc_set_nice call.
*/
nice = ns->nice;
/* XXX sfc - this is now wrong - need to iterate over
* all cells with threads for this proc to set nice.
*/
error = proc_set_nice(p, &nice, ns->scred, 0);
if (error == 0)
ns->cnt++;
return error;
}
/* Support for the BSD setpriority/getpriority system calls */
int
ppgrp_nice(
bhv_desc_t *bdp,
int flags,
int *nice,
int *count,
cred_t *scred)
{
pgrp_t *pg = BHV_TO_PPGRP(bdp);
pg_nice_s nice_s;
int error;
ASSERT( (flags & (VPG_GET_NICE|VPG_SET_NICE)) == VPG_GET_NICE || \
(flags & (VPG_GET_NICE|VPG_SET_NICE)) == VPG_SET_NICE);
nice_s.flags = flags;
nice_s.cnt = 0;
nice_s.scred = scred;
/* getpriority returns the highest priority (lowest numerical
* nice value) enjoyed by any of the specified processes.
* To implement this:
* initialize 'nice' to MAXINT - pgrp_nice_f will keep track
* of highest value, and return that in nice.
*/
if (flags & VPG_GET_NICE)
nice_s.nice = MAXINT;
else
nice_s.nice = *nice;
/* prevent our proc list from changing while we scan */
PGRP_LOCK(pg);
PGRPLIST_READLOCK(pg);
PGRP_UNLOCK(pg);
error = pgroupscan(pg, pgrp_nice_f, (long)&nice_s);
PGRP_LOCK(pg);
PGRPLIST_UNLOCK(pg);
PGRP_UNLOCK(pg);
if (error)
return error;
if (flags & VPG_GET_NICE)
/* No error - return 'best' priority */
*nice = nice_s.nice;
/* Number of processes found - so caller can determine if ESRCH. */
*count = nice_s.cnt;
/* No errors */
return 0;
}
int
ppgrp_setbatch(
bhv_desc_t *bdp)
{
pgrp_t *pg = BHV_TO_PPGRP(bdp);
vpgrp_t *vpg = BHV_TO_VPGRP(bdp);
/* Last minute check that we're the pgrp leader alone in our pgrp */
if (!(pg->pg_memcnt == 1 && vpg->vpg_pgid == current_pid()))
return EPERM;
pg->pg_batch = 1;
return 0;
}
int
ppgrp_clearbatch(
bhv_desc_t *bdp)
{
pgrp_t *pg = BHV_TO_PPGRP(bdp);
pg->pg_batch = 0;
return 0;
}