Changeset View
Standalone View
sys/kern/sys_procdesc.c
/*- | /*- | ||||||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD | * SPDX-License-Identifier: BSD-2-Clause-FreeBSD | ||||||||
* | * | ||||||||
* Copyright (c) 2009, 2016 Robert N. M. Watson | * Copyright (c) 2009, 2016 Robert N. M. Watson | ||||||||
* Copyright (c) 2019, 2021 Mariusz Zaborksi <oshogbo@FreeBSD.org> | |||||||||
* All rights reserved. | * All rights reserved. | ||||||||
* | * | ||||||||
* This software was developed at the University of Cambridge Computer | * This software was developed at the University of Cambridge Computer | ||||||||
* Laboratory with support from a grant from Google, Inc. | * Laboratory with support from a grant from Google, Inc. | ||||||||
* | * | ||||||||
* Portions of this software were developed by BAE Systems, the University of | * Portions of this software were developed by BAE Systems, the University of | ||||||||
* Cambridge Computer Laboratory, and Memorial University under DARPA/AFRL | * Cambridge Computer Laboratory, and Memorial University under DARPA/AFRL | ||||||||
* contract FA8650-15-C-7558 ("CADETS"), as part of the DARPA Transparent | * contract FA8650-15-C-7558 ("CADETS"), as part of the DARPA Transparent | ||||||||
Show All 39 Lines | |||||||||
* SIGKILL and reparent it to init so that there's a process to reap it | * SIGKILL and reparent it to init so that there's a process to reap it | ||||||||
* when it's done exiting. | * when it's done exiting. | ||||||||
* - If the process exits before the descriptor is closed, it will not | * - If the process exits before the descriptor is closed, it will not | ||||||||
* generate SIGCHLD on termination, or be picked up by waitpid(). | * generate SIGCHLD on termination, or be picked up by waitpid(). | ||||||||
* - The pdkill(2) system call may be used to deliver a signal to the process | * - The pdkill(2) system call may be used to deliver a signal to the process | ||||||||
* using its process descriptor. | * using its process descriptor. | ||||||||
* - The pdwait4(2) system call may be used to block (or not) on a process | * - The pdwait4(2) system call may be used to block (or not) on a process | ||||||||
* descriptor to collect termination information. | * descriptor to collect termination information. | ||||||||
* | |||||||||
* Open questions: | |||||||||
* | |||||||||
* - Will we want to add a pidtoprocdesc(2) system call to allow process | |||||||||
* descriptors to be created for processes without pdfork(2)? | |||||||||
*/ | */ | ||||||||
#include <sys/cdefs.h> | #include <sys/cdefs.h> | ||||||||
__FBSDID("$FreeBSD$"); | __FBSDID("$FreeBSD$"); | ||||||||
#include <sys/param.h> | #include <sys/param.h> | ||||||||
#include <sys/capsicum.h> | #include <sys/capsicum.h> | ||||||||
#include <sys/fcntl.h> | #include <sys/fcntl.h> | ||||||||
Show All 38 Lines | static struct fileops procdesc_ops = { | ||||||||
.fo_close = procdesc_close, | .fo_close = procdesc_close, | ||||||||
.fo_chmod = invfo_chmod, | .fo_chmod = invfo_chmod, | ||||||||
.fo_chown = invfo_chown, | .fo_chown = invfo_chown, | ||||||||
.fo_sendfile = invfo_sendfile, | .fo_sendfile = invfo_sendfile, | ||||||||
.fo_fill_kinfo = procdesc_fill_kinfo, | .fo_fill_kinfo = procdesc_fill_kinfo, | ||||||||
.fo_flags = DFLAG_PASSABLE, | .fo_flags = DFLAG_PASSABLE, | ||||||||
}; | }; | ||||||||
static void procdesc_close_pd(struct procdesc *pd); | |||||||||
static int | |||||||||
procdesc_alloc(int flags, bool waitok, struct procdesc **ppd) | |||||||||
kib: I like the Mark' attitude of passing M_WAITOK/M_NOWAIT flags instead of bool. | |||||||||
{ | |||||||||
struct procdesc *pd; | |||||||||
pd = malloc(sizeof(*pd), M_PROCDESC, M_ZERO | | |||||||||
(waitok ? M_WAITOK : M_NOWAIT)); | |||||||||
if (pd == NULL) | |||||||||
return (ENOMEM); | |||||||||
pd->pd_flags = 0; | |||||||||
if (flags & PD_DAEMON) | |||||||||
kibUnsubmitted Not Done Inline Actionsif ((flags & PD_DAEMON) != 0) kib: `if ((flags & PD_DAEMON) != 0)` | |||||||||
pd->pd_flags |= PDF_DAEMON; | |||||||||
*ppd = pd; | |||||||||
return (0); | |||||||||
} | |||||||||
static void | |||||||||
procdesc_install(struct proc *p, struct procdesc *pd) | |||||||||
{ | |||||||||
pd->pd_proc = p; | |||||||||
pd->pd_pid = p->p_pid; | |||||||||
p->p_procdesc = pd; | |||||||||
PROCDESC_LOCK_INIT(pd); | |||||||||
knlist_init_mtx(&pd->pd_selinfo.si_note, &pd->pd_lock); | |||||||||
/* | /* | ||||||||
* Process descriptors start out with two references: one from their | |||||||||
* struct file, and the other from their struct proc. | |||||||||
*/ | |||||||||
refcount_init(&pd->pd_refcount, 2); | |||||||||
} | |||||||||
/* | |||||||||
* Return a locked process given a process descriptor, or ESRCH if it has | * Return a locked process given a process descriptor, or ESRCH if it has | ||||||||
* died. | * died. | ||||||||
*/ | */ | ||||||||
int | int | ||||||||
procdesc_find(struct thread *td, int fd, cap_rights_t *rightsp, | procdesc_find(struct thread *td, int fd, cap_rights_t *rightsp, | ||||||||
struct proc **p) | struct proc **p) | ||||||||
{ | { | ||||||||
struct procdesc *pd; | struct procdesc *pd; | ||||||||
Show All 31 Lines | procdesc_pid(struct file *fp_procdesc) | ||||||||
KASSERT(fp_procdesc->f_type == DTYPE_PROCDESC, | KASSERT(fp_procdesc->f_type == DTYPE_PROCDESC, | ||||||||
("procdesc_pid: !procdesc")); | ("procdesc_pid: !procdesc")); | ||||||||
pd = fp_procdesc->f_data; | pd = fp_procdesc->f_data; | ||||||||
return (pd->pd_pid); | return (pd->pd_pid); | ||||||||
} | } | ||||||||
int | |||||||||
sys_pdopen(struct thread *td, struct pdopen_args *uap) | |||||||||
{ | |||||||||
int flag; | |||||||||
flag = uap->flag; | |||||||||
if ((flag & ~PD_ALLOWED_AT_PDGETPID) != 0) | |||||||||
return (EINVAL); | |||||||||
return (kern_pdopen(td, uap->pid, flag)); | |||||||||
Done Inline ActionsWhy read uap->flag second time ? kib: Why read uap->flag second time ? | |||||||||
} | |||||||||
static int | |||||||||
pdopen_find_proc(struct thread *td, pid_t pid, struct proc **pproc) | |||||||||
{ | |||||||||
struct proc *proc; | |||||||||
int error; | |||||||||
proc = pfind_any(pid); | |||||||||
kibUnsubmitted Not Done Inline ActionsSo Markj' note about not allowing open of zombies/exiting processes is not handled. Am I wrong? kib: So Markj' note about not allowing open of zombies/exiting processes is not handled. Am I wrong? | |||||||||
if (proc == NULL) | |||||||||
return (ESRCH); | |||||||||
error = p_cangetpd(td, proc); | |||||||||
if (error != 0) { | |||||||||
PROC_UNLOCK(proc); | |||||||||
return (error); | |||||||||
} | |||||||||
*pproc = proc; | |||||||||
return (0); | |||||||||
} | |||||||||
int | |||||||||
kern_pdopen(struct thread *td, pid_t pid, int flag) | |||||||||
{ | |||||||||
struct proc *proc; | |||||||||
struct procdesc *procdesc; | |||||||||
struct file *fp; | |||||||||
Done Inline ActionsWe should avoid creating a new process descriptor for a zombie or process that has P_WEXIT set. markj: We should avoid creating a new process descriptor for a zombie or process that has P_WEXIT set. | |||||||||
int fd, error; | |||||||||
sx_slock(&proctree_lock); | |||||||||
kibUnsubmitted Not Done Inline ActionsWhat for do you need proctree_lock there? kib: What for do you need proctree_lock there? | |||||||||
error = pdopen_find_proc(td, pid, &proc); | |||||||||
if (error != 0) { | |||||||||
return (error); | |||||||||
} | |||||||||
procdesc = NULL; | |||||||||
if (proc->p_procdesc == NULL) { | |||||||||
/* | /* | ||||||||
* Process descriptor does not exists. | |||||||||
* If there is no memory we have to release lock and retry | |||||||||
* allocation. Then we will fetch a new process structure | |||||||||
* (it may be a different process with same pid), or the | |||||||||
* procdesc might be created by different process in the | |||||||||
* mean time. | |||||||||
*/ | |||||||||
error = procdesc_alloc(0, false, &procdesc); | |||||||||
if (error != 0) { | |||||||||
Not Done Inline ActionsThis may leak. markj: This may leak. | |||||||||
PROC_UNLOCK(proc); | |||||||||
sx_sunlock(&proctree_lock); | |||||||||
(void)procdesc_alloc(0, true, &procdesc); | |||||||||
sx_slock(&proctree_lock); | |||||||||
error = pdopen_find_proc(td, pid, &proc); | |||||||||
if (error != 0) { | |||||||||
free(procdesc, M_PROCDESC); | |||||||||
return (error); | |||||||||
} | |||||||||
} | |||||||||
if (proc->p_procdesc != NULL) { | |||||||||
free(procdesc, M_PROCDESC); | |||||||||
procdesc = NULL; | |||||||||
} else if ((proc->p_flag & P_WEXIT) == 0 && | |||||||||
proc->p_state != PRS_ZOMBIE) { | |||||||||
procdesc_install(proc, procdesc); | |||||||||
} else { | |||||||||
PROC_UNLOCK(proc); | |||||||||
sx_sunlock(&proctree_lock); | |||||||||
return (ECHILD); | |||||||||
} | |||||||||
} | |||||||||
kibUnsubmitted Not Done Inline ActionsIMO this is overcomplicated. Why not allocate a spare procdesc in advance, before any locks are taken, and free if not needed? I do not think that the cost of unused allocation is high, esp. when comparing with the locking dance there. kib: IMO this is overcomplicated. Why not allocate a spare procdesc in advance, before any locks… | |||||||||
oshogboAuthorUnsubmitted Done Inline ActionsMaybe we can allocate procdesc for all the process when created? oshogbo: Maybe we can allocate procdesc for all the process when created? | |||||||||
markjUnsubmitted Not Done Inline ActionsWhy is that preferable to pre-allocating at pdopen() time? It seems a bit undesirable to allocate mostly unused memory for each process. At least, if we go that route, I suspect the procdesc should be directly embedded in struct proc, not allocated separately. markj: Why is that preferable to pre-allocating at pdopen() time? It seems a bit undesirable to… | |||||||||
oshogboAuthorUnsubmitted Done Inline ActionsI'm not saying it preferable. I'm just wondering about best approach. 'm also afraid of "some memory" that won't be used but its sound a little bit cleaner to assume that the prcodesc just always exists and its a part of the process itself. Currently we have to separate few more cases when the procdesc exists, when doesn't, when its free. I just wonder if the "cleaner" approach wouldn't be just allocating it and keep while the process is leaving. oshogbo: I'm not saying it preferable. I'm just wondering about best approach. 'm also afraid of "some… | |||||||||
kibUnsubmitted Not Done Inline ActionsI looked a the procdesc structure, and I think that it probably would be better to embed it into struct proc. Not quite, because almost nothing from the struct would be left if we do so. I believe that what would we need are pd_flags, pd_selinfo, and pd_lock. But, pd_flags can be merged into p_flag2, and pd_lock replaced by e.g. process mutex, since we reuse the same structure. Then the only added field would be pd_selinfo which is of 72 bytes, which probably would go into the uma tail of unused bytes for the allocation. But if done correctly, this should remove a structure which lifetime is somewhat tricky. kib: I looked a the procdesc structure, and I think that it probably would be better to embed it… | |||||||||
markjUnsubmitted Not Done Inline Actions
And the p_procdesc field would be removed, saving 8 bytes. But actually we have good slab efficiency right now: vm.uma.PROC.keg.ipers: 3 (items per slab) vm.uma.PROC.keg.ppera: 1 (pages per slab) vm.uma.PROC.keg.rsize: 1336 With an embedded procdesc we can only fit 2 procs per page, it seems. But UMA will dynamically grow the slab size to improve efficiency, so I think it is still worth doing. markj: > Then the only added field would be pd_selinfo which is of 72 bytes, which probably would go… | |||||||||
/* | |||||||||
* We didn't create a new process descriptor. | |||||||||
* So let's just increase the refcount. | |||||||||
*/ | |||||||||
if (procdesc == NULL) { | |||||||||
procdesc = proc->p_procdesc; | |||||||||
refcount_acquire(&proc->p_procdesc->pd_refcount); | |||||||||
PROCDESC_LOCK(procdesc); | |||||||||
procdesc->pd_flags &= ~PDF_CLOSED; | |||||||||
PROCDESC_UNLOCK(procdesc); | |||||||||
} | |||||||||
PROC_UNLOCK(proc); | |||||||||
sx_sunlock(&proctree_lock); | |||||||||
error = procdesc_falloc(td, &fp, &fd, flag, NULL); | |||||||||
if (error != 0) { | |||||||||
procdesc_close_pd(procdesc); | |||||||||
return (error); | |||||||||
} | |||||||||
procdesc_finit(procdesc, fp); | |||||||||
fdrop(fp, td); | |||||||||
td->td_retval[0] = fd; | |||||||||
return (0); | |||||||||
} | |||||||||
/* | |||||||||
* Retrieve the PID associated with a process descriptor. | * Retrieve the PID associated with a process descriptor. | ||||||||
*/ | */ | ||||||||
int | int | ||||||||
kern_pdgetpid(struct thread *td, int fd, cap_rights_t *rightsp, pid_t *pidp) | kern_pdgetpid(struct thread *td, int fd, cap_rights_t *rightsp, pid_t *pidp) | ||||||||
{ | { | ||||||||
struct file *fp; | struct file *fp; | ||||||||
int error; | int error; | ||||||||
Show All 31 Lines | |||||||||
* by the fork code first, then the process is forked, and then we get a | * by the fork code first, then the process is forked, and then we get a | ||||||||
* chance to set up the process descriptor. Failure is not permitted at this | * chance to set up the process descriptor. Failure is not permitted at this | ||||||||
* point, so procdesc_new() must succeed. | * point, so procdesc_new() must succeed. | ||||||||
*/ | */ | ||||||||
void | void | ||||||||
procdesc_new(struct proc *p, int flags) | procdesc_new(struct proc *p, int flags) | ||||||||
{ | { | ||||||||
struct procdesc *pd; | struct procdesc *pd; | ||||||||
pd = malloc(sizeof(*pd), M_PROCDESC, M_WAITOK | M_ZERO); | (void)procdesc_alloc(flags, true, &pd); | ||||||||
Done Inline ActionsComment is outdated. kib: Comment is outdated. | |||||||||
Done Inline ActionsIn other parts of the kernel we go through significant pain to ensure that syscalls do not fail because of transient memory allocation failures; I think we should do the same here too. The M_NOWAIT caller can check for ENOMEM, drop locks, allocate with M_WAITOK, and retry the lookup before either assigning the procedesc to the process, or discovering that the race was lost. markj: In other parts of the kernel we go through significant pain to ensure that syscalls do not fail… | |||||||||
Done Inline ActionsTo expand on the reasoning a bit, it is quite difficult to write reliable system software if system calls are allowed to fail this way. Realistically, the only thing an application will do with ENOMEM is retry the call. If it forgets to do that, it now has a rare failure mode. Instead of forcing every application developer using pdopen() to deal with this, the kernel should do it. markj: To expand on the reasoning a bit, it is quite difficult to write reliable system software if… | |||||||||
pd->pd_proc = p; | procdesc_install(p, pd); | ||||||||
pd->pd_pid = p->p_pid; | |||||||||
p->p_procdesc = pd; | |||||||||
pd->pd_flags = 0; | |||||||||
if (flags & PD_DAEMON) | |||||||||
pd->pd_flags |= PDF_DAEMON; | |||||||||
PROCDESC_LOCK_INIT(pd); | |||||||||
knlist_init_mtx(&pd->pd_selinfo.si_note, &pd->pd_lock); | |||||||||
/* | |||||||||
* Process descriptors start out with two references: one from their | |||||||||
* struct file, and the other from their struct proc. | |||||||||
*/ | |||||||||
refcount_init(&pd->pd_refcount, 2); | |||||||||
} | } | ||||||||
Done Inline ActionsWhy not return the error code directly, as most other kernel functions do ? kib: Why not return the error code directly, as most other kernel functions do ? | |||||||||
/* | /* | ||||||||
* Create a new process decriptor for the process that refers to it. | * Create a new process decriptor for the process that refers to it. | ||||||||
*/ | */ | ||||||||
int | int | ||||||||
procdesc_falloc(struct thread *td, struct file **resultfp, int *resultfd, | procdesc_falloc(struct thread *td, struct file **resultfp, int *resultfd, | ||||||||
int flags, struct filecaps *fcaps) | int flags, struct filecaps *fcaps) | ||||||||
{ | { | ||||||||
Show All 24 Lines | procdesc_free(struct procdesc *pd) | ||||||||
* When the last reference is released, we assert that the descriptor | * When the last reference is released, we assert that the descriptor | ||||||||
* has been closed, but not that the process has exited, as we will | * has been closed, but not that the process has exited, as we will | ||||||||
* detach the descriptor before the process dies if the descript is | * detach the descriptor before the process dies if the descript is | ||||||||
* closed, as we can't wait synchronously. | * closed, as we can't wait synchronously. | ||||||||
*/ | */ | ||||||||
if (refcount_release(&pd->pd_refcount)) { | if (refcount_release(&pd->pd_refcount)) { | ||||||||
KASSERT(pd->pd_proc == NULL, | KASSERT(pd->pd_proc == NULL, | ||||||||
("procdesc_free: pd_proc != NULL")); | ("procdesc_free: pd_proc != NULL")); | ||||||||
KASSERT((pd->pd_flags & PDF_CLOSED), | KASSERT((pd->pd_flags & PDF_CLOSED) != 0, | ||||||||
("procdesc_free: !PDF_CLOSED")); | ("procdesc_free: !PDF_CLOSED")); | ||||||||
knlist_destroy(&pd->pd_selinfo.si_note); | knlist_destroy(&pd->pd_selinfo.si_note); | ||||||||
PROCDESC_LOCK_DESTROY(pd); | PROCDESC_LOCK_DESTROY(pd); | ||||||||
free(pd, M_PROCDESC); | free(pd, M_PROCDESC); | ||||||||
} | } | ||||||||
} | } | ||||||||
Done Inline Actions() != 0 kib: () != 0 | |||||||||
/* | /* | ||||||||
* procdesc_exit() - notify a process descriptor that its process is exiting. | * procdesc_exit() - notify a process descriptor that its process is exiting. | ||||||||
* We use the proctree_lock to ensure that process exit either happens | * We use the proctree_lock to ensure that process exit either happens | ||||||||
* strictly before or strictly after a concurrent call to procdesc_close(). | * strictly before or strictly after a concurrent call to procdesc_close(). | ||||||||
*/ | */ | ||||||||
int | int | ||||||||
procdesc_exit(struct proc *p) | procdesc_exit(struct proc *p) | ||||||||
{ | { | ||||||||
struct procdesc *pd; | struct procdesc *pd; | ||||||||
sx_assert(&proctree_lock, SA_XLOCKED); | sx_assert(&proctree_lock, SA_XLOCKED); | ||||||||
PROC_LOCK_ASSERT(p, MA_OWNED); | PROC_LOCK_ASSERT(p, MA_OWNED); | ||||||||
KASSERT(p->p_procdesc != NULL, ("procdesc_exit: p_procdesc NULL")); | KASSERT(p->p_procdesc != NULL, ("procdesc_exit: p_procdesc NULL")); | ||||||||
pd = p->p_procdesc; | pd = p->p_procdesc; | ||||||||
PROCDESC_LOCK(pd); | PROCDESC_LOCK(pd); | ||||||||
KASSERT((pd->pd_flags & PDF_CLOSED) == 0 || p->p_pptr == p->p_reaper, | |||||||||
("procdesc_exit: closed && parent not reaper")); | |||||||||
pd->pd_flags |= PDF_EXITED; | pd->pd_flags |= PDF_EXITED; | ||||||||
pd->pd_xstat = KW_EXITCODE(p->p_xexit, p->p_xsig); | pd->pd_xstat = KW_EXITCODE(p->p_xexit, p->p_xsig); | ||||||||
/* | /* | ||||||||
* If the process descriptor has been closed, then we have nothing | * If the process descriptor has been closed, then we have nothing | ||||||||
* to do; return 1 so that init will get SIGCHLD and do the reaping. | * to do; return 1 so that init will get SIGCHLD and do the reaping. | ||||||||
* Clean up the procdesc now rather than letting it happen during | * Clean up the procdesc now rather than letting it happen during | ||||||||
Show All 29 Lines | procdesc_reap(struct proc *p) | ||||||||
pd = p->p_procdesc; | pd = p->p_procdesc; | ||||||||
pd->pd_proc = NULL; | pd->pd_proc = NULL; | ||||||||
p->p_procdesc = NULL; | p->p_procdesc = NULL; | ||||||||
procdesc_free(pd); | procdesc_free(pd); | ||||||||
} | } | ||||||||
/* | /* | ||||||||
* procdesc_close() - last close on a process descriptor. If the process is | * procdesc_close_pd() - close a process descriptor in single filedesc. | ||||||||
* still running, terminate with SIGKILL (unless PDF_DAEMON is set) and let | * When we close the last process descriptor in all filedesc then: | ||||||||
* its reaper clean up the mess; if not, we have to clean up the zombie | * If the process was created by the pdfork, last closed process descriptor | ||||||||
* ourselves. | * means that there is no more handleres to process. We want to SIGKILL the | ||||||||
kibUnsubmitted Not Done Inline Actions
kib: | |||||||||
* process (unless PDF_DAEMON is set) and let its reaper clean up the mess. | |||||||||
* If the PDF_DAEMON is set we want to reparent it to the init process and allow | |||||||||
kibUnsubmitted Not Done Inline Actions
kib: | |||||||||
* it leave his own life. In case when the PDF_DAEMON flag is set or the pd was | |||||||||
* created because of the pdopen we keep the procdesc - some other process may | |||||||||
* obtain it. The procdesc finnaly will be clean by the exit1 function. | |||||||||
kibUnsubmitted Not Done Inline Actions
kib: | |||||||||
* If the process is already zombie and we close a last process descriptor, | |||||||||
* we have to clean up it ourselves. | |||||||||
*/ | */ | ||||||||
kibUnsubmitted Not Done Inline ActionsMost parts of this comment should end up in the man page. kib: Most parts of this comment should end up in the man page. | |||||||||
static int | static void | ||||||||
procdesc_close(struct file *fp, struct thread *td) | procdesc_close_pd(struct procdesc *pd) | ||||||||
{ | { | ||||||||
struct procdesc *pd; | |||||||||
struct proc *p; | struct proc *p; | ||||||||
KASSERT(fp->f_type == DTYPE_PROCDESC, ("procdesc_close: !procdesc")); | |||||||||
pd = fp->f_data; | |||||||||
fp->f_ops = &badfileops; | |||||||||
fp->f_data = NULL; | |||||||||
sx_xlock(&proctree_lock); | sx_xlock(&proctree_lock); | ||||||||
PROCDESC_LOCK(pd); | |||||||||
pd->pd_flags |= PDF_CLOSED; | |||||||||
PROCDESC_UNLOCK(pd); | |||||||||
Not Done Inline ActionsWhy moving this block ? kib: Why moving this block ? | |||||||||
Done Inline ActionsI assumed the process descriptor should be marked as closed only when we are going to really closed it. That mean that the reference drops to 1 or the process is zombie already. oshogbo: I assumed the process descriptor should be marked as closed only when we are going to really… | |||||||||
p = pd->pd_proc; | p = pd->pd_proc; | ||||||||
if (p == NULL) { | if (p == NULL) { | ||||||||
/* | /* | ||||||||
* This is the case where process' exit status was already | * This is the case where process' exit status was already | ||||||||
* collected and procdesc_reap() was already called. | * collected and procdesc_reap() was already called. | ||||||||
*/ | */ | ||||||||
procdesc_free(pd); | |||||||||
sx_xunlock(&proctree_lock); | sx_xunlock(&proctree_lock); | ||||||||
} else { | return; | ||||||||
} | |||||||||
PROC_LOCK(p); | PROC_LOCK(p); | ||||||||
AUDIT_ARG_PROCESS(p); | AUDIT_ARG_PROCESS(p); | ||||||||
if (refcount_load(&pd->pd_refcount) <= 2) { | |||||||||
PROCDESC_LOCK(pd); | |||||||||
pd->pd_flags |= PDF_CLOSED; | |||||||||
PROCDESC_UNLOCK(pd); | |||||||||
if (p->p_state == PRS_ZOMBIE) { | if (p->p_state == PRS_ZOMBIE) { | ||||||||
/* | /* | ||||||||
* If the process is already dead and just awaiting | * If the process is already dead and just awaiting | ||||||||
* reaping, do that now. This will release the | * reaping, do that now. This will release the | ||||||||
* process's reference to the process descriptor when it | * process's reference to the process descriptor when it | ||||||||
* calls back into procdesc_reap(). | * calls back into procdesc_reap(). | ||||||||
*/ | */ | ||||||||
proc_reap(curthread, p, NULL, 0); | proc_reap(curthread, p, NULL, 0); | ||||||||
} else { | |||||||||
/* | |||||||||
* If the process is not yet dead, we need to kill it, | |||||||||
* but we can't wait around synchronously for it to go | |||||||||
* away, as that path leads to madness (and deadlocks). | |||||||||
* First, detach the process from its descriptor so that | |||||||||
* its exit status will be reported normally. | |||||||||
*/ | |||||||||
pd->pd_proc = NULL; | |||||||||
p->p_procdesc = NULL; | |||||||||
procdesc_free(pd); | procdesc_free(pd); | ||||||||
return; | |||||||||
/* | } else if ((p->p_flag2 & P2_PDFORK) != 0) { | ||||||||
* Next, reparent it to its reaper (usually init(8)) so | p->p_flag2 &= ~P2_PDFORK; | ||||||||
* that there's someone to pick up the pieces; finally, | |||||||||
* terminate with prejudice. | |||||||||
*/ | |||||||||
p->p_sigparent = SIGCHLD; | p->p_sigparent = SIGCHLD; | ||||||||
if ((p->p_flag & P_TRACED) == 0) { | if ((p->p_flag & P_TRACED) == 0) { | ||||||||
proc_reparent(p, p->p_reaper, true); | proc_reparent(p, p->p_reaper, true); | ||||||||
} else { | } else { | ||||||||
proc_clear_orphan(p); | proc_clear_orphan(p); | ||||||||
p->p_oppid = p->p_reaper->p_pid; | p->p_oppid = p->p_reaper->p_pid; | ||||||||
proc_add_orphan(p, p->p_reaper); | proc_add_orphan(p, p->p_reaper); | ||||||||
} | } | ||||||||
if ((pd->pd_flags & PDF_DAEMON) == 0) | |||||||||
if ((pd->pd_flags & PDF_DAEMON) == 0) { | |||||||||
pd->pd_proc = NULL; | |||||||||
p->p_procdesc = NULL; | |||||||||
procdesc_free(pd); | |||||||||
kern_psignal(p, SIGKILL); | kern_psignal(p, SIGKILL); | ||||||||
PROC_UNLOCK(p); | |||||||||
sx_xunlock(&proctree_lock); | |||||||||
} | } | ||||||||
} | } | ||||||||
} | |||||||||
/* | |||||||||
* Release the file descriptor's reference on the process descriptor. | |||||||||
*/ | |||||||||
procdesc_free(pd); | procdesc_free(pd); | ||||||||
PROC_UNLOCK(p); | |||||||||
sx_xunlock(&proctree_lock); | |||||||||
} | |||||||||
static int | |||||||||
procdesc_close(struct file *fp, struct thread *td) | |||||||||
{ | |||||||||
struct procdesc *pd; | |||||||||
KASSERT(fp->f_type == DTYPE_PROCDESC, ("procdesc_close: !procdesc")); | |||||||||
pd = fp->f_data; | |||||||||
fp->f_ops = &badfileops; | |||||||||
fp->f_data = NULL; | |||||||||
Not Done Inline ActionsThe proc lock synchronizes access to pd_refcount; we can simply read the value before deciding what to do instead of introducing the new refcount_release_ret() KPI. Or, if you prefer to keep it, the refcount(9) man page should be updated. markj: The proc lock synchronizes access to pd_refcount; we can simply read the value before deciding… | |||||||||
procdesc_close_pd(pd); | |||||||||
return (0); | return (0); | ||||||||
} | } | ||||||||
static int | static int | ||||||||
procdesc_poll(struct file *fp, int events, struct ucred *active_cred, | procdesc_poll(struct file *fp, int events, struct ucred *active_cred, | ||||||||
struct thread *td) | struct thread *td) | ||||||||
{ | { | ||||||||
struct procdesc *pd; | struct procdesc *pd; | ||||||||
int revents; | int revents; | ||||||||
revents = 0; | revents = 0; | ||||||||
pd = fp->f_data; | pd = fp->f_data; | ||||||||
PROCDESC_LOCK(pd); | PROCDESC_LOCK(pd); | ||||||||
if (pd->pd_flags & PDF_EXITED) | if (pd->pd_flags & PDF_EXITED) | ||||||||
revents |= POLLHUP; | revents |= POLLHUP; | ||||||||
if (revents == 0) { | if (revents == 0) { | ||||||||
selrecord(td, &pd->pd_selinfo); | selrecord(td, &pd->pd_selinfo); | ||||||||
Done Inline ActionsThis reverts r339390. markj: This reverts r339390. | |||||||||
pd->pd_flags |= PDF_SELECTED; | pd->pd_flags |= PDF_SELECTED; | ||||||||
} | } | ||||||||
Done Inline ActionsConsider acquiring the (shared?) proctree lock in pdopen() instead. I believe that can be used to avoid the race without adding a new process flag. markj: Consider acquiring the (shared?) proctree lock in pdopen() instead. I believe that can be used… | |||||||||
PROCDESC_UNLOCK(pd); | PROCDESC_UNLOCK(pd); | ||||||||
return (revents); | return (revents); | ||||||||
} | } | ||||||||
static void | static void | ||||||||
procdesc_kqops_detach(struct knote *kn) | procdesc_kqops_detach(struct knote *kn) | ||||||||
{ | { | ||||||||
struct procdesc *pd; | struct procdesc *pd; | ||||||||
▲ Show 20 Lines • Show All 114 Lines • Show Last 20 Lines |
I like the Mark' attitude of passing M_WAITOK/M_NOWAIT flags instead of bool.