Subject: multiple copies of wait4()
To: None <tech-kern@netbsd.org>
From: David Laight <david@l8s.co.uk>
List: tech-kern
Date: 10/24/2002 16:03:46
The source tree contains what are (effectively) 5 different
copies of the code for wait4().  This routine contains the
non-trivial code to free up a process.
Not all the copies actually match properly because changes 
have not been applied to all of them.

The code is in:
sys_wait4() (kern/kern_exit.c)
irix_sys_waitsys (compat/irix/irix_signal.c)
netbsd32_wait4 (compat/netbsd32/netbsd32_wait.c)
svr4_sys_waitsys (compat/svr4/svr4_misc.c)
svr4_32_sys_waitsys (compat/svr4_32/svr4_32_misc.c)

Although these compat options (probably) aren't that common
the cost of commoning the code is minimal.

Replacing sys_wait4() with the following code allows the
emulations to avoid knowing the internals of process allocation.

(The code for the emulations is now similar to sys_wait4, but
not given here to save bandwidth.)

int
sys_wait4(struct proc *parent, void *v, register_t *retval)
{
	struct sys_wait4_args /* {
		syscallarg(int)			pid;
		syscallarg(int *)		status;
		syscallarg(int)			options;
		syscallarg(struct rusage *)	rusage;
	} */ *uap = v;
	struct proc *child;
	int error, status;

	if (SCARG(uap, pid) == 0)
		SCARG(uap, pid) = -parent->p_pgid;

	if (SCARG(uap, options) & ~(WUNTRACED|WNOHANG|WALTSIG|WALLSIG))
		return (EINVAL);

	error = find_stopped_child( parent, SCARG(uap,pid), SCARG(uap,options),
				    &child);
	if (error)
		return error;
	if (!child) {
		*retval = 0;
		return 0;
	}

	*retval = child->p_pid;
	if (child->p_stat == SZOMB) {
		if (SCARG(uap, status)) {
			status = child->p_xstat;
			error = copyout(&status, SCARG(uap, status),
					sizeof(status));
			if (error)
				return (error);
		}
		if (SCARG(uap, rusage)) {
			error = copyout(child->p_ru, SCARG(uap, rusage),
					sizeof(struct rusage));
			if (error)
				return (error);
		}

		/* Finally finished with old proc entry.  */
		proc_free(child);
		return 0;
	}

	/* child state must be SSTOP */

	if (SCARG(uap, status)) {
		status = W_STOPCODE(child->p_xstat);
		error = copyout(&status, SCARG(uap, status), sizeof(status));
		if (error)
			return error;
	}
	return 0;
}

/* Find a stopped child (for 'wait4' and it's friends in the compat world) */

int
find_stopped_child(struct proc *parent, pid_t pid, int options,
	struct proc **child_p)
{
	struct proc	*child;
	int		c_found, error;

	do {
		c_found = 0;
		LIST_FOREACH(child, &parent->p_children, p_sibling) {
			if (pid != WAIT_ANY && child->p_pid != pid &&
			    child->p_pgid != -pid)
				continue;
			/*
			 * Wait for processes with p_exitsig != SIGCHLD
			 * processes only if WALTSIG is set; wait for
			 * processes with p_exitsig == SIGCHLD only if
			 * WALTSIG is clear.
			 */
			if (!(options & WALLSIG) &&
			    (options & WALTSIG ? child->p_exitsig == SIGCHLD
						: P_EXITSIG(child) != SIGCHLD))
				continue;

			c_found = 1;
			if (child->p_stat == SZOMB && !(options & WNOZOMBIE)) {
				*child_p = child;
				return 0;
			}

			if (child->p_stat == SSTOP &&
			    !(child->p_flag & P_WAITED) &&
			    (child->p_flag & P_TRACED || options & WUNTRACED)) {
				if (!(options & WNOWAIT))
					child->p_flag |= P_WAITED;
				*child_p = child;
				return 0;
			}
		}

		if (c_found == 0)
			return ECHILD;
		if (options & WNOHANG) {
			*child_p = 0;
			return 0;
		}

		error = tsleep(parent, PWAIT | PCATCH, "wait", 0);
	} while (!error);
	return error;
}

/* Free a process after parent has taken all the state info... */

void
proc_free( struct proc *p )
{
	struct proc *parent = p->p_pptr;
	int s;

	/*
	 * If we got the child via ptrace(2) or procfs, and
	 * the parent is different (meaning the process was
	 * attached, rather than run as a child), then we need
	 * to give it back to the old parent, and send the
	 * parent the exit signal.  The rest of the cleanup
	 * will be done when the old parent waits on the child.
	 */
	if ((p->p_flag & P_TRACED) && p->p_opptr != parent) {
		parent = p->p_opptr;
		p->p_opptr = NULL;
		if (!parent)
			parent = initproc;
		proc_reparent(p, parent);
		p->p_flag &= ~(P_TRACED|P_WAITED|P_FSTRACE);
		if (p->p_exitsig != 0)
			psignal(parent, P_EXITSIG(p));
		wakeup(parent);
		return;
	}

	scheduler_wait_hook(parent, p);
	p->p_xstat = 0;
	ruadd(&parent->p_stats->p_cru, p->p_ru);
	pool_put(&rusage_pool, p->p_ru);

	/* At this point we are going to start freeing the final resources.
	   If anyone tries to access the proc structure after here they
	   will get a shock - bits are missing.
	   Attempt to make it hard!
	*/

	leavepgrp(p);
	s = proclist_lock_write();
	LIST_REMOVE(p, p_list);         /* probably off zombproc */
	LIST_REMOVE(p, p_sibling);
	proclist_unlock_write(s);

	/* There shouldn't be any references left (unless someone
	   is sleeping with a ptr to us). */

	/*
	 * Decrement the count of procs running with this uid.
	 */
	(void)chgproccnt(p->p_cred->p_ruid, -1);

	/*
	 * Free up credentials.
	 */
	if (--p->p_cred->p_refcnt == 0) {
		crfree(p->p_cred->pc_ucred);
		pool_put(&pcred_pool, p->p_cred);
	}

	/*
	 * Release reference to text vnode
	 */
	if (p->p_textvp)
		vrele(p->p_textvp);

	pool_put(&proc_pool, p);
	nprocs--;
}

	David

-- 
David Laight: david@l8s.co.uk