diff --git a/sys/kern/kern_sig.c b/sys/kern/kern_sig.c --- a/sys/kern/kern_sig.c +++ b/sys/kern/kern_sig.c @@ -68,6 +68,7 @@ #include #include #include +#include #include #include #include @@ -102,6 +103,14 @@ SDT_PROBE_DEFINE3(proc, , , signal__discard, "struct thread *", "struct proc *", "int"); +static SLIST_HEAD(, coredumper) coredumpers = \ + SLIST_HEAD_INITIALIZER(coredumpers); +static struct rmlock coredump_rmlock; +RM_SYSINIT(coredump_lock, &coredump_rmlock, "coredump_lock"); + +static coredumper_handle_fn coredump_vnode; +COREDUMP_HANDLER(coredump_vnode, NULL, coredump_vnode); + static int coredump(struct thread *); static int killpg1(struct thread *td, int sig, int pgid, int all, ksiginfo_t *ksi); @@ -4131,16 +4140,15 @@ return (0); } -/* - * Dump a process' core. The main routine does some - * policy checking, and creates the name of the coredump; - * then it passes on a vnode and a size limit to the process-specific - * coredump routine if there is one; if there _is not_ one, it returns - * ENOSYS; otherwise it returns the error from the process-specific routine. + /* + * The vnode dumper is the traditional coredump handler. Our policy and limits + * are generally checked already, so it creates the coredump name and passes on + * a vnode and a size limit to the process-specific coredump routine if there is + * one. If there _is not_ one, it returns ENOSYS; otherwise it returns the + * error from the process-specific routine. */ - static int -coredump(struct thread *td) +coredump_vnode(struct thread *td, off_t limit) { struct proc *p = td->td_proc; struct ucred *cred = td->td_ucred; @@ -4153,33 +4161,9 @@ int error, error1, jid, locked, ppid, sig; char *name; /* name of corefile */ void *rl_cookie; - off_t limit; char *fullpath, *freepath = NULL; struct sbuf *sb; - PROC_LOCK_ASSERT(p, MA_OWNED); - MPASS((p->p_flag & P_HADTHREADS) == 0 || p->p_singlethread == td); - - if (!do_coredump || (!sugid_coredump && (p->p_flag & P_SUGID) != 0) || - (p->p_flag2 & P2_NOTRACE) != 0) { - PROC_UNLOCK(p); - return (EFAULT); - } - - /* - * Note that the bulk of limit checking is done after - * the corefile is created. The exception is if the limit - * for corefiles is 0, in which case we don't bother - * creating the corefile at all. This layout means that - * a corefile is truncated instead of not being created, - * if it is larger than the limit. - */ - limit = (off_t)lim_cur(td, RLIMIT_CORE); - if (limit == 0 || racct_get_available(p, RACCT_CORE) == 0) { - PROC_UNLOCK(p); - return (EFBIG); - } - ppid = p->p_oppid; sig = p->p_sig; jid = p->p_ucred->cr_prison->pr_id; @@ -4293,6 +4277,107 @@ return (error); } +void +coredumper_register(struct coredumper *cd) +{ + + rm_wlock(&coredump_rmlock); + SLIST_INSERT_HEAD(&coredumpers, cd, cd_entry); + rm_wunlock(&coredump_rmlock); +} + +void +coredumper_unregister(struct coredumper *cd) +{ + + rm_wlock(&coredump_rmlock); + SLIST_REMOVE(&coredumpers, cd, coredumper, cd_entry); + rm_wunlock(&coredump_rmlock); +} + +/* + * Dump a process' core. The main routine does some policy checking, then does + * a search for an appropriate coredumper to use. It is expected that the + * dumper implementations will call into the process images' dumper routines + * if there is one, and return ENOSYS if there is not. We don't handle the + * latter bit here in case one wants to add a pluggable coredumper that can + * encode a dump of those processes in some other way that's helpful to them. + * + * By default, we will use coredump_vnode() above if a custom coredump module + * has not been loaded. + */ +static int +coredump(struct thread *td) +{ + struct coredumper *iter, *chosen; + struct proc *p = td->td_proc; + struct rm_priotracker tracker; + off_t limit; + int error, priority; + + PROC_LOCK_ASSERT(p, MA_OWNED); + MPASS((p->p_flag & P_HADTHREADS) == 0 || p->p_singlethread == td); + + if (!do_coredump || (!sugid_coredump && (p->p_flag & P_SUGID) != 0) || + (p->p_flag2 & P2_NOTRACE) != 0) { + PROC_UNLOCK(p); + return (EFAULT); + } + + /* + * Note that the bulk of limit checking is done after + * the corefile is created. The exception is if the limit + * for corefiles is 0, in which case we don't bother + * creating the corefile at all. This layout means that + * a corefile is truncated instead of not being created, + * if it is larger than the limit. + */ + limit = (off_t)lim_cur(td, RLIMIT_CORE); + if (limit == 0 || racct_get_available(p, RACCT_CORE) == 0) { + PROC_UNLOCK(p); + return (EFBIG); + } + + rm_rlock(&coredump_rmlock, &tracker); + priority = -1; + chosen = NULL; + SLIST_FOREACH(iter, &coredumpers, cd_entry) { + if (iter->cd_probe == NULL) { + /* + * If we haven't found anything of a higher priority + * yet, we'll call this a GENERIC. Ideally, we want + * coredumper modules to include a probe function. + */ + if (priority < 0) { + priority = COREDUMPER_GENERIC; + chosen = iter; + } + + continue; + } + + error = (*iter->cd_probe)(td); + if (error < 0) + continue; + + /* + * Higher priority than previous options. + */ + if (error > priority) { + priority = error; + chosen = iter; + } + } + rm_runlock(&coredump_rmlock, &tracker); + + /* Currently, we always have the vnode dumper built in. */ + MPASS(chosen != NULL); + error = ((*chosen->cd_handle)(td, limit)); + PROC_LOCK_ASSERT(p, MA_NOTOWNED); + + return (error); +} + /* * Nonexistent system call-- signal process (may want to handle it). Flag * error in case process won't see signal immediately (blocked or ignored). diff --git a/sys/sys/exec.h b/sys/sys/exec.h --- a/sys/sys/exec.h +++ b/sys/sys/exec.h @@ -38,6 +38,7 @@ #define _SYS_EXEC_H_ #include +#include /* * Before ps_args existed, the following structure, found at the top of @@ -90,6 +91,45 @@ struct compressor *comp; }; +typedef int coredumper_probe_fn(struct thread *); +/* + * Some arbitrary values for coredumper probes to return. The highest priority + * we can find wins. It's somewhat expected that a coredumper may want to bid + * differently based on the process in question. Note that probe functions will + * be called with the proc lock held, so they must not sleep. + */ +#define COREDUMPER_NOMATCH (-1) /* Decline to touch it */ +#define COREDUMPER_GENERIC (0) /* I handle coredumps */ +#define COREDUMPER_SPECIAL (50) /* Special handler */ +#define COREDUMPER_HIGH_PRIORITY (100) /* High-priority handler */ + +/* + * The handle functions will be called with the proc lock held, and should + * return with the proc lock dropped. + */ +typedef int coredumper_handle_fn(struct thread *, off_t); + +struct coredumper { + SLIST_ENTRY(coredumper) cd_entry; + const char *cd_name; + coredumper_probe_fn *cd_probe; + coredumper_handle_fn *cd_handle; +}; + +void coredumper_register(struct coredumper *); +void coredumper_unregister(struct coredumper *); + +#define COREDUMP_HANDLER(name, probe, handle) \ + static struct coredumper name##_coredumper = { \ + .cd_name = #name, \ + .cd_probe = probe, \ + .cd_handle = handle, \ + }; \ + SYSINIT(name##_register, SI_SUB_EXEC, SI_ORDER_ANY, \ + coredumper_register, &name##_coredumper); \ + SYSUNINIT(name##_unregister, SI_SUB_EXEC, SI_ORDER_ANY, \ + coredumper_unregister, &name##_coredumper); \ + struct image_params; struct execsw {