Skip to content
Snippets Groups Projects
exit.c 41.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (current->signal->flags & SIGNAL_GROUP_EXIT)
    		exit_code = current->signal->group_exit_code;
    	else if (!thread_group_empty(current)) {
    		struct signal_struct *const sig = current->signal;
    		struct sighand_struct *const sighand = current->sighand;
    		spin_lock_irq(&sighand->siglock);
    		if (sig->flags & SIGNAL_GROUP_EXIT)
    			/* Another thread got here before we took the lock.  */
    			exit_code = sig->group_exit_code;
    		else {
    			sig->group_exit_code = exit_code;
    			zap_other_threads(current);
    		}
    		spin_unlock_irq(&sighand->siglock);
    	}
    
    	do_exit(exit_code);
    	/* NOTREACHED */
    }
    
    /*
     * this kills every thread in the thread group. Note that any externally
     * wait4()-ing process will get the correct exit code - even if this
     * thread is not the thread group leader.
     */
    asmlinkage void sys_exit_group(int error_code)
    {
    	do_group_exit((error_code & 0xff) << 8);
    }
    
    
    static int eligible_child(pid_t pid, int options, struct task_struct *p)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	if (pid > 0) {
    		if (p->pid != pid)
    			return 0;
    	} else if (!pid) {
    		if (process_group(p) != process_group(current))
    			return 0;
    	} else if (pid != -1) {
    		if (process_group(p) != -pid)
    			return 0;
    	}
    
    	/*
    	 * Do not consider detached threads that are
    	 * not ptraced:
    	 */
    	if (p->exit_signal == -1 && !p->ptrace)
    		return 0;
    
    	/* Wait for all children (clone and not) if __WALL is set;
    	 * otherwise, wait for clone children *only* if __WCLONE is
    	 * set; otherwise, wait for non-clone children *only*.  (Note:
    	 * A "clone" child here is one that reports to its parent
    	 * using a signal other than SIGCHLD.) */
    	if (((p->exit_signal != SIGCHLD) ^ ((options & __WCLONE) != 0))
    	    && !(options & __WALL))
    		return 0;
    	/*
    	 * Do not consider thread group leaders that are
    	 * in a non-empty thread group:
    	 */
    
    	if (delay_group_leader(p))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return 2;
    
    	if (security_task_wait(p))
    		return 0;
    
    	return 1;
    }
    
    
    static int wait_noreap_copyout(struct task_struct *p, pid_t pid, uid_t uid,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			       int why, int status,
    			       struct siginfo __user *infop,
    			       struct rusage __user *rusagep)
    {
    	int retval = rusagep ? getrusage(p, RUSAGE_BOTH, rusagep) : 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	put_task_struct(p);
    	if (!retval)
    		retval = put_user(SIGCHLD, &infop->si_signo);
    	if (!retval)
    		retval = put_user(0, &infop->si_errno);
    	if (!retval)
    		retval = put_user((short)why, &infop->si_code);
    	if (!retval)
    		retval = put_user(pid, &infop->si_pid);
    	if (!retval)
    		retval = put_user(uid, &infop->si_uid);
    	if (!retval)
    		retval = put_user(status, &infop->si_status);
    	if (!retval)
    		retval = pid;
    	return retval;
    }
    
    /*
     * Handle sys_wait4 work for one task in state EXIT_ZOMBIE.  We hold
     * read_lock(&tasklist_lock) on entry.  If we return zero, we still hold
     * the lock and this task is uninteresting.  If we return nonzero, we have
     * released the lock and the system call should return.
     */
    
    static int wait_task_zombie(struct task_struct *p, int noreap,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			    struct siginfo __user *infop,
    			    int __user *stat_addr, struct rusage __user *ru)
    {
    	unsigned long state;
    	int retval;
    	int status;
    
    	if (unlikely(noreap)) {
    		pid_t pid = p->pid;
    		uid_t uid = p->uid;
    		int exit_code = p->exit_code;
    		int why, status;
    
    		if (unlikely(p->exit_state != EXIT_ZOMBIE))
    			return 0;
    		if (unlikely(p->exit_signal == -1 && p->ptrace == 0))
    			return 0;
    		get_task_struct(p);
    		read_unlock(&tasklist_lock);
    		if ((exit_code & 0x7f) == 0) {
    			why = CLD_EXITED;
    			status = exit_code >> 8;
    		} else {
    			why = (exit_code & 0x80) ? CLD_DUMPED : CLD_KILLED;
    			status = exit_code & 0x7f;
    		}
    		return wait_noreap_copyout(p, pid, uid, why,
    					   status, infop, ru);
    	}
    
    	/*
    	 * Try to move the task's state to DEAD
    	 * only one thread is allowed to do this:
    	 */
    	state = xchg(&p->exit_state, EXIT_DEAD);
    	if (state != EXIT_ZOMBIE) {
    		BUG_ON(state != EXIT_DEAD);
    		return 0;
    	}
    	if (unlikely(p->exit_signal == -1 && p->ptrace == 0)) {
    		/*
    		 * This can only happen in a race with a ptraced thread
    		 * dying on another processor.
    		 */
    		return 0;
    	}
    
    	if (likely(p->real_parent == p->parent) && likely(p->signal)) {
    
    		struct signal_struct *psig;
    		struct signal_struct *sig;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		/*
    		 * The resource counters for the group leader are in its
    		 * own task_struct.  Those for dead threads in the group
    		 * are in its signal_struct, as are those for the child
    		 * processes it has previously reaped.  All these
    		 * accumulate in the parent's signal_struct c* fields.
    		 *
    		 * We don't bother to take a lock here to protect these
    		 * p->signal fields, because they are only touched by
    		 * __exit_signal, which runs with tasklist_lock
    		 * write-locked anyway, and so is excluded here.  We do
    		 * need to protect the access to p->parent->signal fields,
    		 * as other threads in the parent group can be right
    		 * here reaping other children at the same time.
    		 */
    		spin_lock_irq(&p->parent->sighand->siglock);
    
    		psig = p->parent->signal;
    		sig = p->signal;
    		psig->cutime =
    			cputime_add(psig->cutime,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			cputime_add(p->utime,
    
    			cputime_add(sig->utime,
    				    sig->cutime)));
    		psig->cstime =
    			cputime_add(psig->cstime,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			cputime_add(p->stime,
    
    			cputime_add(sig->stime,
    				    sig->cstime)));
    		psig->cmin_flt +=
    			p->min_flt + sig->min_flt + sig->cmin_flt;
    		psig->cmaj_flt +=
    			p->maj_flt + sig->maj_flt + sig->cmaj_flt;
    		psig->cnvcsw +=
    			p->nvcsw + sig->nvcsw + sig->cnvcsw;
    		psig->cnivcsw +=
    			p->nivcsw + sig->nivcsw + sig->cnivcsw;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		spin_unlock_irq(&p->parent->sighand->siglock);
    	}
    
    	/*
    	 * Now we are sure this task is interesting, and no other
    	 * thread can reap it because we set its state to EXIT_DEAD.
    	 */
    	read_unlock(&tasklist_lock);
    
    	retval = ru ? getrusage(p, RUSAGE_BOTH, ru) : 0;
    	status = (p->signal->flags & SIGNAL_GROUP_EXIT)
    		? p->signal->group_exit_code : p->exit_code;
    	if (!retval && stat_addr)
    		retval = put_user(status, stat_addr);
    	if (!retval && infop)
    		retval = put_user(SIGCHLD, &infop->si_signo);
    	if (!retval && infop)
    		retval = put_user(0, &infop->si_errno);
    	if (!retval && infop) {
    		int why;
    
    		if ((status & 0x7f) == 0) {
    			why = CLD_EXITED;
    			status >>= 8;
    		} else {
    			why = (status & 0x80) ? CLD_DUMPED : CLD_KILLED;
    			status &= 0x7f;
    		}
    		retval = put_user((short)why, &infop->si_code);
    		if (!retval)
    			retval = put_user(status, &infop->si_status);
    	}
    	if (!retval && infop)
    		retval = put_user(p->pid, &infop->si_pid);
    	if (!retval && infop)
    		retval = put_user(p->uid, &infop->si_uid);
    	if (retval) {
    		// TODO: is this safe?
    		p->exit_state = EXIT_ZOMBIE;
    		return retval;
    	}
    	retval = p->pid;
    	if (p->real_parent != p->parent) {
    		write_lock_irq(&tasklist_lock);
    		/* Double-check with lock held.  */
    		if (p->real_parent != p->parent) {
    			__ptrace_unlink(p);
    			// TODO: is this safe?
    			p->exit_state = EXIT_ZOMBIE;
    			/*
    			 * If this is not a detached task, notify the parent.
    			 * If it's still not detached after that, don't release
    			 * it now.
    			 */
    			if (p->exit_signal != -1) {
    				do_notify_parent(p, p->exit_signal);
    				if (p->exit_signal != -1)
    					p = NULL;
    			}
    		}
    		write_unlock_irq(&tasklist_lock);
    	}
    	if (p != NULL)
    		release_task(p);
    	BUG_ON(!retval);
    	return retval;
    }
    
    /*
     * Handle sys_wait4 work for one task in state TASK_STOPPED.  We hold
     * read_lock(&tasklist_lock) on entry.  If we return zero, we still hold
     * the lock and this task is uninteresting.  If we return nonzero, we have
     * released the lock and the system call should return.
     */
    
    static int wait_task_stopped(struct task_struct *p, int delayed_group_leader,
    			     int noreap, struct siginfo __user *infop,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			     int __user *stat_addr, struct rusage __user *ru)
    {
    	int retval, exit_code;
    
    	if (!p->exit_code)
    		return 0;
    	if (delayed_group_leader && !(p->ptrace & PT_PTRACED) &&
    	    p->signal && p->signal->group_stop_count > 0)
    		/*
    		 * A group stop is in progress and this is the group leader.
    		 * We won't report until all threads have stopped.
    		 */
    		return 0;
    
    	/*
    	 * Now we are pretty sure this task is interesting.
    	 * Make sure it doesn't get reaped out from under us while we
    	 * give up the lock and then examine it below.  We don't want to
    	 * keep holding onto the tasklist_lock while we call getrusage and
    	 * possibly take page faults for user memory.
    	 */
    	get_task_struct(p);
    	read_unlock(&tasklist_lock);
    
    	if (unlikely(noreap)) {
    		pid_t pid = p->pid;
    		uid_t uid = p->uid;
    		int why = (p->ptrace & PT_PTRACED) ? CLD_TRAPPED : CLD_STOPPED;
    
    		exit_code = p->exit_code;
    		if (unlikely(!exit_code) ||
    
    		    unlikely(p->state & TASK_TRACED))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			goto bail_ref;
    		return wait_noreap_copyout(p, pid, uid,
    					   why, (exit_code << 8) | 0x7f,
    					   infop, ru);
    	}
    
    	write_lock_irq(&tasklist_lock);
    
    	/*
    	 * This uses xchg to be atomic with the thread resuming and setting
    	 * it.  It must also be done with the write lock held to prevent a
    	 * race with the EXIT_ZOMBIE case.
    	 */
    	exit_code = xchg(&p->exit_code, 0);
    	if (unlikely(p->exit_state)) {
    		/*
    		 * The task resumed and then died.  Let the next iteration
    		 * catch it in EXIT_ZOMBIE.  Note that exit_code might
    		 * already be zero here if it resumed and did _exit(0).
    		 * The task itself is dead and won't touch exit_code again;
    		 * other processors in this function are locked out.
    		 */
    		p->exit_code = exit_code;
    		exit_code = 0;
    	}
    	if (unlikely(exit_code == 0)) {
    		/*
    		 * Another thread in this function got to it first, or it
    		 * resumed, or it resumed and then died.
    		 */
    		write_unlock_irq(&tasklist_lock);
    bail_ref:
    		put_task_struct(p);
    		/*
    		 * We are returning to the wait loop without having successfully
    		 * removed the process and having released the lock. We cannot
    		 * continue, since the "p" task pointer is potentially stale.
    		 *
    		 * Return -EAGAIN, and do_wait() will restart the loop from the
    		 * beginning. Do _not_ re-acquire the lock.
    		 */
    		return -EAGAIN;
    	}
    
    	/* move to end of parent's list to avoid starvation */
    	remove_parent(p);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	write_unlock_irq(&tasklist_lock);
    
    	retval = ru ? getrusage(p, RUSAGE_BOTH, ru) : 0;
    	if (!retval && stat_addr)
    		retval = put_user((exit_code << 8) | 0x7f, stat_addr);
    	if (!retval && infop)
    		retval = put_user(SIGCHLD, &infop->si_signo);
    	if (!retval && infop)
    		retval = put_user(0, &infop->si_errno);
    	if (!retval && infop)
    		retval = put_user((short)((p->ptrace & PT_PTRACED)
    					  ? CLD_TRAPPED : CLD_STOPPED),
    				  &infop->si_code);
    	if (!retval && infop)
    		retval = put_user(exit_code, &infop->si_status);
    	if (!retval && infop)
    		retval = put_user(p->pid, &infop->si_pid);
    	if (!retval && infop)
    		retval = put_user(p->uid, &infop->si_uid);
    	if (!retval)
    		retval = p->pid;
    	put_task_struct(p);
    
    	BUG_ON(!retval);
    	return retval;
    }
    
    /*
     * Handle do_wait work for one task in a live, non-stopped state.
     * read_lock(&tasklist_lock) on entry.  If we return zero, we still hold
     * the lock and this task is uninteresting.  If we return nonzero, we have
     * released the lock and the system call should return.
     */
    
    static int wait_task_continued(struct task_struct *p, int noreap,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			       struct siginfo __user *infop,
    			       int __user *stat_addr, struct rusage __user *ru)
    {
    	int retval;
    	pid_t pid;
    	uid_t uid;
    
    	if (unlikely(!p->signal))
    		return 0;
    
    	if (!(p->signal->flags & SIGNAL_STOP_CONTINUED))
    		return 0;
    
    	spin_lock_irq(&p->sighand->siglock);
    	/* Re-check with the lock held.  */
    	if (!(p->signal->flags & SIGNAL_STOP_CONTINUED)) {
    		spin_unlock_irq(&p->sighand->siglock);
    		return 0;
    	}
    	if (!noreap)
    		p->signal->flags &= ~SIGNAL_STOP_CONTINUED;
    	spin_unlock_irq(&p->sighand->siglock);
    
    	pid = p->pid;
    	uid = p->uid;
    	get_task_struct(p);
    	read_unlock(&tasklist_lock);
    
    	if (!infop) {
    		retval = ru ? getrusage(p, RUSAGE_BOTH, ru) : 0;
    		put_task_struct(p);
    		if (!retval && stat_addr)
    			retval = put_user(0xffff, stat_addr);
    		if (!retval)
    			retval = p->pid;
    	} else {
    		retval = wait_noreap_copyout(p, pid, uid,
    					     CLD_CONTINUED, SIGCONT,
    					     infop, ru);
    		BUG_ON(retval == 0);
    	}
    
    	return retval;
    }
    
    
    static inline int my_ptrace_child(struct task_struct *p)
    {
    	if (!(p->ptrace & PT_PTRACED))
    		return 0;
    	if (!(p->ptrace & PT_ATTACHED))
    		return 1;
    	/*
    	 * This child was PTRACE_ATTACH'd.  We should be seeing it only if
    	 * we are the attacher.  If we are the real parent, this is a race
    	 * inside ptrace_attach.  It is waiting for the tasklist_lock,
    	 * which we have to switch the parent links, but has already set
    	 * the flags in p->ptrace.
    	 */
    	return (p->parent != p->real_parent);
    }
    
    static long do_wait(pid_t pid, int options, struct siginfo __user *infop,
    		    int __user *stat_addr, struct rusage __user *ru)
    {
    	DECLARE_WAITQUEUE(wait, current);
    	struct task_struct *tsk;
    	int flag, retval;
    
    	add_wait_queue(&current->signal->wait_chldexit,&wait);
    repeat:
    	/*
    	 * We will set this flag if we see any child that might later
    	 * match our criteria, even if we are not able to reap it yet.
    	 */
    	flag = 0;
    	current->state = TASK_INTERRUPTIBLE;
    	read_lock(&tasklist_lock);
    	tsk = current;
    	do {
    		struct task_struct *p;
    		struct list_head *_p;
    		int ret;
    
    		list_for_each(_p,&tsk->children) {
    
    			p = list_entry(_p, struct task_struct, sibling);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    			ret = eligible_child(pid, options, p);
    			if (!ret)
    				continue;
    
    			switch (p->state) {
    			case TASK_TRACED:
    
    				/*
    				 * When we hit the race with PTRACE_ATTACH,
    				 * we will not report this child.  But the
    				 * race means it has not yet been moved to
    				 * our ptrace_children list, so we need to
    				 * set the flag here to avoid a spurious ECHILD
    				 * when the race happens with the only child.
    				 */
    				flag = 1;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				if (!my_ptrace_child(p))
    					continue;
    				/*FALLTHROUGH*/
    			case TASK_STOPPED:
    				/*
    				 * It's stopped now, so it might later
    				 * continue, exit, or stop again.
    				 */
    				flag = 1;
    				if (!(options & WUNTRACED) &&
    				    !my_ptrace_child(p))
    					continue;
    				retval = wait_task_stopped(p, ret == 2,
    							   (options & WNOWAIT),
    							   infop,
    							   stat_addr, ru);
    				if (retval == -EAGAIN)
    					goto repeat;
    				if (retval != 0) /* He released the lock.  */
    					goto end;
    				break;
    			default:
    			// case EXIT_DEAD:
    				if (p->exit_state == EXIT_DEAD)
    					continue;
    			// case EXIT_ZOMBIE:
    				if (p->exit_state == EXIT_ZOMBIE) {
    					/*
    					 * Eligible but we cannot release
    					 * it yet:
    					 */
    					if (ret == 2)
    						goto check_continued;
    					if (!likely(options & WEXITED))
    						continue;
    					retval = wait_task_zombie(
    						p, (options & WNOWAIT),
    						infop, stat_addr, ru);
    					/* He released the lock.  */
    					if (retval != 0)
    						goto end;
    					break;
    				}
    check_continued:
    				/*
    				 * It's running now, so it might later
    				 * exit, stop, or stop and then continue.
    				 */
    				flag = 1;
    				if (!unlikely(options & WCONTINUED))
    					continue;
    				retval = wait_task_continued(
    					p, (options & WNOWAIT),
    					infop, stat_addr, ru);
    				if (retval != 0) /* He released the lock.  */
    					goto end;
    				break;
    			}
    		}
    		if (!flag) {
    			list_for_each(_p, &tsk->ptrace_children) {
    				p = list_entry(_p, struct task_struct,
    						ptrace_list);
    				if (!eligible_child(pid, options, p))
    					continue;
    				flag = 1;
    				break;
    			}
    		}
    		if (options & __WNOTHREAD)
    			break;
    		tsk = next_thread(tsk);
    
    		BUG_ON(tsk->signal != current->signal);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	} while (tsk != current);
    
    	read_unlock(&tasklist_lock);
    	if (flag) {
    		retval = 0;
    		if (options & WNOHANG)
    			goto end;
    		retval = -ERESTARTSYS;
    		if (signal_pending(current))
    			goto end;
    		schedule();
    		goto repeat;
    	}
    	retval = -ECHILD;
    end:
    	current->state = TASK_RUNNING;
    	remove_wait_queue(&current->signal->wait_chldexit,&wait);
    	if (infop) {
    		if (retval > 0)
    		retval = 0;
    		else {
    			/*
    			 * For a WNOHANG return, clear out all the fields
    			 * we would set so the user can easily tell the
    			 * difference.
    			 */
    			if (!retval)
    				retval = put_user(0, &infop->si_signo);
    			if (!retval)
    				retval = put_user(0, &infop->si_errno);
    			if (!retval)
    				retval = put_user(0, &infop->si_code);
    			if (!retval)
    				retval = put_user(0, &infop->si_pid);
    			if (!retval)
    				retval = put_user(0, &infop->si_uid);
    			if (!retval)
    				retval = put_user(0, &infop->si_status);
    		}
    	}
    	return retval;
    }
    
    asmlinkage long sys_waitid(int which, pid_t pid,
    			   struct siginfo __user *infop, int options,
    			   struct rusage __user *ru)
    {
    	long ret;
    
    	if (options & ~(WNOHANG|WNOWAIT|WEXITED|WSTOPPED|WCONTINUED))
    		return -EINVAL;
    	if (!(options & (WEXITED|WSTOPPED|WCONTINUED)))
    		return -EINVAL;
    
    	switch (which) {
    	case P_ALL:
    		pid = -1;
    		break;
    	case P_PID:
    		if (pid <= 0)
    			return -EINVAL;
    		break;
    	case P_PGID:
    		if (pid <= 0)
    			return -EINVAL;
    		pid = -pid;
    		break;
    	default:
    		return -EINVAL;
    	}
    
    	ret = do_wait(pid, options, infop, NULL, ru);
    
    	/* avoid REGPARM breakage on x86: */
    	prevent_tail_call(ret);
    	return ret;
    }
    
    asmlinkage long sys_wait4(pid_t pid, int __user *stat_addr,
    			  int options, struct rusage __user *ru)
    {
    	long ret;
    
    	if (options & ~(WNOHANG|WUNTRACED|WCONTINUED|
    			__WNOTHREAD|__WCLONE|__WALL))
    		return -EINVAL;
    	ret = do_wait(pid, options | WEXITED, NULL, stat_addr, ru);
    
    	/* avoid REGPARM breakage on x86: */
    	prevent_tail_call(ret);
    	return ret;
    }
    
    #ifdef __ARCH_WANT_SYS_WAITPID
    
    /*
     * sys_waitpid() remains for compatibility. waitpid() should be
     * implemented by calling sys_wait4() from libc.a.
     */
    asmlinkage long sys_waitpid(pid_t pid, int __user *stat_addr, int options)
    {
    	return sys_wait4(pid, stat_addr, options, NULL);
    }
    
    #endif