diff --git a/lib/libc/gen/sysctl.3 b/lib/libc/gen/sysctl.3 --- a/lib/libc/gen/sysctl.3 +++ b/lib/libc/gen/sysctl.3 @@ -511,6 +511,7 @@ .It Dv KERN_PROC_NFDS Ta "Integer" .It Dv KERN_PROC_SIGFASTBLK Ta "Integer" .It Dv KERN_PROC_VM_LAYOUT Ta "struct kinfo_vm_layout" +.It Dv KERN_PROC_KQUEUE Ta "struct kinfo_knote []" .El .Pp .Bl -tag -compact @@ -584,6 +585,15 @@ location, if active. .It Dv KERN_PROC_VM_LAYOUT Fills a structure describing process virtual address space layout. +.It Dv KERN_PROC_KQUEUE +Fills an array of structures describing events registered with +the specified kqueue. +The next two node's values are the +.Va pid +and +.Va kqfd , +the process ID of the process, and the file descriptor of the kqueue +in that process, to query. .El .It Li KERN_PS_STRINGS Reports the location of the process diff --git a/lib/libprocstat/Symbol.map b/lib/libprocstat/Symbol.map --- a/lib/libprocstat/Symbol.map +++ b/lib/libprocstat/Symbol.map @@ -50,6 +50,8 @@ }; FBSD_1.8 { + procstat_get_kqueue_info; procstat_getrlimitusage; + procstat_freekqinfo; procstat_freerlimitusage; }; \ No newline at end of file diff --git a/lib/libprocstat/libprocstat.h b/lib/libprocstat/libprocstat.h --- a/lib/libprocstat/libprocstat.h +++ b/lib/libprocstat/libprocstat.h @@ -110,6 +110,7 @@ struct kinfo_kstack; struct kinfo_proc; struct kinfo_vmentry; +struct kinfo_knote; struct procstat; struct ptrace_lwpinfo; struct rlimit; @@ -204,6 +205,7 @@ #endif void procstat_freeenvv(struct procstat *procstat); void procstat_freegroups(struct procstat *procstat, gid_t *groups); +void procstat_freekqinfo(struct procstat *procstat, struct kinfo_knote *kni); void procstat_freekstack(struct procstat *procstat, struct kinfo_kstack *kkstp); void procstat_freeprocs(struct procstat *procstat, struct kinfo_proc *p); @@ -219,6 +221,8 @@ struct kinfo_proc *kp, int mmapped); struct kinfo_proc *procstat_getprocs(struct procstat *procstat, int what, int arg, unsigned int *count); +struct kinfo_knote *procstat_get_kqueue_info(struct procstat *procstat, + struct kinfo_proc *kp, int kqfd, unsigned int *count, char *errbuf); int procstat_get_pipe_info(struct procstat *procstat, struct filestat *fst, struct pipestat *pipe, char *errbuf); int procstat_get_pts_info(struct procstat *procstat, struct filestat *fst, diff --git a/lib/libprocstat/libprocstat.c b/lib/libprocstat/libprocstat.c --- a/lib/libprocstat/libprocstat.c +++ b/lib/libprocstat/libprocstat.c @@ -2817,3 +2817,69 @@ { free(resusage); } + +static struct kinfo_knote * +procstat_get_kqueue_info_sysctl(pid_t pid, int kqfd, unsigned int *cntp, + char *errbuf) +{ + int error, name[5]; + struct kinfo_knote *val; + size_t len; + + name[0] = CTL_KERN; + name[1] = KERN_PROC; + name[2] = KERN_PROC_KQUEUE; + name[3] = pid; + name[4] = kqfd; + + len = 0; + error = sysctl(name, nitems(name), NULL, &len, NULL, 0); + if (error == -1) { + snprintf(errbuf, _POSIX2_LINE_MAX, + "KERN_PROC_KQUEUE.pid<%d>.kq<%d> (size q) failed: %s", + pid, kqfd, strerror(errno)); + return (NULL); + } + val = malloc(len); + if (val == NULL) { + snprintf(errbuf, _POSIX2_LINE_MAX, "no memory"); + return (NULL); + } + + error = sysctl(name, nitems(name), val, &len, NULL, 0); + if (error == -1) { + snprintf(errbuf, _POSIX2_LINE_MAX, + "KERN_PROC_KQUEUE.pid<%d>.kq<%d> failed: %s", + pid, kqfd, strerror(errno)); + free(val); + return (NULL); + } + *cntp = len / sizeof(*val); + return (val); +} + +struct kinfo_knote * +procstat_get_kqueue_info(struct procstat *procstat, + struct kinfo_proc *kp, int kqfd, unsigned int *count, char *errbuf) +{ + switch (procstat->type) { + case PROCSTAT_KVM: + warnx("kvm method is not supported"); + return (NULL); + case PROCSTAT_SYSCTL: + return (procstat_get_kqueue_info_sysctl(kp->ki_pid, kqfd, + count, errbuf)); + case PROCSTAT_CORE: + warnx("core method is not supported"); + return (NULL); + default: + warnx("unknown access method: %d", procstat->type); + return (NULL); + } +} + +void +procstat_freekqinfo(struct procstat *procstat __unused, struct kinfo_knote *v) +{ + free(v); +} diff --git a/sys/kern/kern_event.c b/sys/kern/kern_event.c --- a/sys/kern/kern_event.c +++ b/sys/kern/kern_event.c @@ -64,6 +64,7 @@ #include #include #include +#include #include #include #include @@ -2829,3 +2830,103 @@ fdrop(fp, td); return (error); } + +static int +sysctl_kern_proc_kqueue_report_one(struct proc *p, struct sysctl_req *req, + struct kqueue *kq, struct knote *kn) +{ + struct kinfo_knote kin; + int error; + + if (kn->kn_status == KN_MARKER) + return (0); +#if 0 + if (kn_in_flux(kn) && (kn->kn_status & KN_SCAN) == 0) + return (0); +#endif + memset(&kin, 0, sizeof(kin)); + memcpy(&kin.knt_event, &kn->kn_kevent, sizeof(struct kevent)); + kin.knt_status = kn->kn_status; + kn_enter_flux(kn); + KQ_UNLOCK_FLUX(kq); + if (kn->kn_fop->f_userdump != NULL) + (void)kn->kn_fop->f_userdump(p, kn, &kin); + error = SYSCTL_OUT(req, &kin, sizeof(kin)); + maybe_yield(); + KQ_LOCK(kq); + kn_leave_flux(kn); + return (error); +} + +static int +sysctl_kern_proc_kqueue(SYSCTL_HANDLER_ARGS) +{ + struct thread *td; + struct proc *p; + struct file *fp; + struct kqueue *kq; + struct knote *kn; + int error, i, *name; + + name = (int *)arg1; + if ((u_int)arg2 != 2) + return (EINVAL); + + error = pget((pid_t)name[0], PGET_HOLD | PGET_CANDEBUG, &p); + if (error != 0) + return (error); +#ifdef COMPAT_FREEBSD32 + if (SV_CURPROC_FLAG(SV_ILP32)) { + /* XXXKIB */ + error = EOPNOTSUPP; + goto out1; + } +#endif + + td = curthread; + error = fget_remote(td, p, name[1] /* kqfd */, &fp); + if (error != 0) + goto out1; + if (fp->f_type != DTYPE_KQUEUE) { + error = EINVAL; + goto out2; + } + + kq = fp->f_data; + if (req->oldptr == NULL) { + error = SYSCTL_OUT(req, NULL, sizeof(struct kinfo_knote) * + kq->kq_knlistsize * 11 / 10); + goto out2; + } + + KQ_LOCK(kq); + for (i = 0; i < kq->kq_knlistsize; i++) { + SLIST_FOREACH(kn, &kq->kq_knlist[i], kn_link) { + error = sysctl_kern_proc_kqueue_report_one(p, req, + kq, kn); + if (error != 0) + goto out3; + } + } + if (kq->kq_knhashmask == 0) + goto out3; + for (i = 0; i <= kq->kq_knhashmask; i++) { + SLIST_FOREACH(kn, &kq->kq_knhash[i], kn_link) { + error = sysctl_kern_proc_kqueue_report_one(p, req, + kq, kn); + if (error != 0) + goto out3; + } + } +out3: + KQ_UNLOCK_FLUX(kq); +out2: + fdrop(fp, td); +out1: + PRELE(p); + return (error); +} + +static SYSCTL_NODE(_kern_proc, KERN_PROC_KQUEUE, kq, + CTLFLAG_RD | CTLFLAG_MPSAFE, + sysctl_kern_proc_kqueue, "KQueue events"); diff --git a/sys/kern/sys_eventfd.c b/sys/kern/sys_eventfd.c --- a/sys/kern/sys_eventfd.c +++ b/sys/kern/sys_eventfd.c @@ -25,27 +25,25 @@ * SUCH DAMAGE. */ -#include #include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include #include #include #include #include -#include -#include -#include +#include +#include +#include +#include +#include #include #include -#include #include -#include +#include +#include +#include #include diff --git a/sys/kern/sys_pipe.c b/sys/kern/sys_pipe.c --- a/sys/kern/sys_pipe.c +++ b/sys/kern/sys_pipe.c @@ -175,21 +175,26 @@ static int filt_pipenotsup(struct knote *kn, long hint); static int filt_piperead(struct knote *kn, long hint); static int filt_pipewrite(struct knote *kn, long hint); +static int filt_pipedump(struct proc *p, struct knote *kn, + struct kinfo_knote *kin); static const struct filterops pipe_nfiltops = { .f_isfd = 1, .f_detach = filt_pipedetach_notsup, .f_event = filt_pipenotsup + /* no userdump */ }; static const struct filterops pipe_rfiltops = { .f_isfd = 1, .f_detach = filt_pipedetach, - .f_event = filt_piperead + .f_event = filt_piperead, + .f_userdump = filt_pipedump, }; static const struct filterops pipe_wfiltops = { .f_isfd = 1, .f_detach = filt_pipedetach, - .f_event = filt_pipewrite + .f_event = filt_pipewrite, + .f_userdump = filt_pipedump, }; /* @@ -1900,3 +1905,14 @@ return (0); } + +static int +filt_pipedump(struct proc *p, struct knote *kn, + struct kinfo_knote *kin) +{ + struct pipe *pipe = kn->kn_hook; + + kin->knt_extdata = KNOTE_EXTDATA_PIPE; + kin->knt_pipe.knt_pipe_ino = pipe->pipe_ino; + return (0); +} diff --git a/sys/kern/vfs_subr.c b/sys/kern/vfs_subr.c --- a/sys/kern/vfs_subr.c +++ b/sys/kern/vfs_subr.c @@ -79,6 +79,7 @@ #include #include #include +#include #include #include #include @@ -6483,7 +6484,7 @@ .f_isfd = 0, .f_attach = filt_fsattach, .f_detach = filt_fsdetach, - .f_event = filt_fsevent + .f_event = filt_fsevent, }; static int @@ -6559,20 +6560,26 @@ static int filt_vfswrite(struct knote *kn, long hint); static int filt_vfsvnode(struct knote *kn, long hint); static void filt_vfsdetach(struct knote *kn); +static int filt_vfsdump(struct proc *p, struct knote *kn, + struct kinfo_knote *kin); + static const struct filterops vfsread_filtops = { .f_isfd = 1, .f_detach = filt_vfsdetach, - .f_event = filt_vfsread + .f_event = filt_vfsread, + .f_userdump = filt_vfsdump, }; static const struct filterops vfswrite_filtops = { .f_isfd = 1, .f_detach = filt_vfsdetach, - .f_event = filt_vfswrite + .f_event = filt_vfswrite, + .f_userdump = filt_vfsdump, }; static const struct filterops vfsvnode_filtops = { .f_isfd = 1, .f_detach = filt_vfsdetach, - .f_event = filt_vfsvnode + .f_event = filt_vfsvnode, + .f_userdump = filt_vfsdump, }; static void @@ -6721,6 +6728,41 @@ return (res); } +static int +filt_vfsdump(struct proc *p, struct knote *kn, struct kinfo_knote *kin) +{ + struct vattr va; + struct vnode *vp; + char *fullpath, *freepath; + int error; + + kin->knt_extdata = KNOTE_EXTDATA_VNODE; + + vp = kn->kn_fp->f_vnode; + kin->knt_vnode.knt_vnode_type = vntype_to_kinfo(vp->v_type); + + va.va_fsid = VNOVAL; + vn_lock(vp, LK_SHARED | LK_RETRY); + error = VOP_GETATTR(vp, &va, curthread->td_ucred); + VOP_UNLOCK(vp); + if (error != 0) + return (error); + kin->knt_vnode.knt_vnode_fsid = va.va_fsid; + kin->knt_vnode.knt_vnode_fileid = va.va_fileid; + + freepath = NULL; + fullpath = "-"; + error = vn_fullpath(vp, &fullpath, &freepath); + if (error == 0) { + strlcpy(kin->knt_vnode.knt_vnode_fullpath, fullpath, + sizeof(kin->knt_vnode.knt_vnode_fullpath)); + } + if (freepath != NULL) + free(freepath, M_TEMP); + + return (0); +} + int vfs_read_dirent(struct vop_readdir_args *ap, struct dirent *dp, off_t off) { diff --git a/sys/sys/event.h b/sys/sys/event.h --- a/sys/sys/event.h +++ b/sys/sys/event.h @@ -262,12 +262,17 @@ #define EVENT_REGISTER 1 #define EVENT_PROCESS 2 +struct kinfo_knote; +struct proc; + struct filterops { int f_isfd; /* true if ident == filedescriptor */ int (*f_attach)(struct knote *kn); void (*f_detach)(struct knote *kn); int (*f_event)(struct knote *kn, long hint); void (*f_touch)(struct knote *kn, struct kevent *kev, u_long type); + int (*f_userdump)(struct proc *p, struct knote *kn, + struct kinfo_knote *kin); }; /* diff --git a/sys/sys/sysctl.h b/sys/sys/sysctl.h --- a/sys/sys/sysctl.h +++ b/sys/sys/sysctl.h @@ -1041,6 +1041,7 @@ #define KERN_PROC_SIGFASTBLK 44 /* address of fastsigblk magic word */ #define KERN_PROC_VM_LAYOUT 45 /* virtual address space layout info */ #define KERN_PROC_RLIMIT_USAGE 46 /* array of rlim_t */ +#define KERN_PROC_KQUEUE 47 /* array of struct kinfo_knote */ /* * KERN_IPC identifiers diff --git a/sys/sys/user.h b/sys/sys/user.h --- a/sys/sys/user.h +++ b/sys/sys/user.h @@ -38,6 +38,7 @@ #ifndef _KERNEL /* stuff that *used* to be included by user.h, or is now needed */ #include +#include #include #include #include @@ -665,6 +666,27 @@ uintptr_t kvm_spare[12]; }; +#define KNOTE_EXTDATA_NONE 0 +#define KNOTE_EXTDATA_VNODE 1 +#define KNOTE_EXTDATA_PIPE 2 + +struct kinfo_knote { + struct kevent knt_event; + int knt_status; + int knt_extdata; + union { + struct knt_vnode_t { + int knt_vnode_type; + uint64_t knt_vnode_fsid; + uint64_t knt_vnode_fileid; + char knt_vnode_fullpath[PATH_MAX]; + } knt_vnode ; + struct knt_pipe_t { + ino_t knt_pipe_ino; + } knt_pipe; + }; +}; + #ifdef _KERNEL /* Flags for kern_proc_out function. */ #define KERN_PROC_NOTHREADS 0x1 diff --git a/sys/vm/sg_pager.c b/sys/vm/sg_pager.c --- a/sys/vm/sg_pager.c +++ b/sys/vm/sg_pager.c @@ -34,6 +34,7 @@ */ #include +#include #include #include #include diff --git a/usr.bin/procstat/Makefile b/usr.bin/procstat/Makefile --- a/usr.bin/procstat/Makefile +++ b/usr.bin/procstat/Makefile @@ -11,6 +11,7 @@ procstat_cred.c \ procstat_cs.c \ procstat_files.c \ + procstat_kqueue.c \ procstat_kstack.c \ procstat_penv.c \ procstat_ptlwpinfo.c \ diff --git a/usr.bin/procstat/procstat.h b/usr.bin/procstat/procstat.h --- a/usr.bin/procstat/procstat.h +++ b/usr.bin/procstat/procstat.h @@ -64,6 +64,7 @@ void procstat_cs(struct procstat *prstat, struct kinfo_proc *kipp); void procstat_env(struct procstat *prstat, struct kinfo_proc *kipp); void procstat_files(struct procstat *prstat, struct kinfo_proc *kipp); +void procstat_kqueues(struct procstat *prstat, struct kinfo_proc *kipp); void procstat_kstack(struct procstat *prstat, struct kinfo_proc *kipp); void procstat_rlimitusage(struct procstat *procstat, struct kinfo_proc *kipp); diff --git a/usr.bin/procstat/procstat.1 b/usr.bin/procstat/procstat.1 --- a/usr.bin/procstat/procstat.1 +++ b/usr.bin/procstat/procstat.1 @@ -159,16 +159,24 @@ .Va pid list can be used to limit the display of the locks, mostly because some types of locks do not have local (or any) owning processes. +.It Ar argument(s) | Fl c +Display command line arguments for the process. +.Pp +Substring commands are accepted. +.It Ar auxv | Fl x +Display ELF auxiliary vector for the process. .It Ar basic Print basic process statistics (this is the default). .It Ar binary | Fl b Display binary information for the process. .Pp Substring commands are accepted. -.It Ar argument(s) | Fl c -Display command line arguments for the process. +.It Ar credential(s) | Fl s +Display security credential information for the process. .Pp Substring commands are accepted. +.It Ar cpuset | Ar cs | Fl S +Display the cpuset information for the thread. .It Ar environment | Fl e Display environment variables for the process. .Pp @@ -179,24 +187,8 @@ If the .Fl C subcommand flag is used then additional capability information is printed. -.It Ar signal(s) | Fl i -Display signal pending and disposition information for the process. -.Pp -If the -.Fl n -subcommand option is used, the signal numbers are shown instead of signal -names. -.Pp -Substring commands are accepted. -.It Ar tsignal(s) | Fl j -Display signal pending and blocked information for the process's threads. -.Pp -If the -.Fl n -subcommand option is used, the signal numbers are shown instead of signal -names. -.Pp -Substring commands are accepted. +.It Ar kqueue(s) +Display the events registered in the process kqueues. .It Ar kstack | Fl k Display the stacks of kernel threads in the process, excluding stacks of threads currently running on a CPU and threads with stacks swapped to disk. @@ -205,10 +197,18 @@ .Fl v subcommand option is used (or the command flag is repeated), function offsets as well as function names are printed. -.It Ar rlimit | Fl l -Display resource limits for the process. +.It Ar pargs +Display arguments for the process. +.It Ar penv +Display environment variables for the process. .It Ar ptlwpinfo | Fl L Display LWP info for the process pertaining to its signal driven exit. +.It Ar pwdx +Display current working directory for the process. +.It Ar rlimit | Fl l +Display resource limits for the process. +.It Ar rlimitusage +Display the usage of the resource limits for the process. .It Ar rusage | Fl r Display resource usage information for the process. .Pp @@ -220,24 +220,28 @@ statistics. The second field in the table will list the thread ID to which the row of information corresponds. -.It Ar credential(s) | Fl s -Display security credential information for the process. +.It Ar signal(s) | Fl i +Display signal pending and disposition information for the process. +.Pp +If the +.Fl n +subcommand option is used, the signal numbers are shown instead of signal +names. .Pp Substring commands are accepted. -.It Ar cpuset | Ar cs | Fl S -Display the cpuset information for the thread. .It Ar thread(s) | Fl t Display thread information for the process. +.It Ar tsignal(s) | Fl j +Display signal pending and blocked information for the process's threads. +.Pp +If the +.Fl n +subcommand option is used, the signal numbers are shown instead of signal +names. +.Pp +Substring commands are accepted. .It Ar vm | Fl v Display virtual memory mappings for the process. -.It Ar auxv | Fl x -Display ELF auxiliary vector for the process. -.It Ar pargs -Display arguments for the process. -.It Ar penv -Display environment variables for the process. -.It Ar pwdx -Display current working directory for the process. .El .Pp All options generate output in the format of a table, the first field of diff --git a/usr.bin/procstat/procstat.c b/usr.bin/procstat/procstat.c --- a/usr.bin/procstat/procstat.c +++ b/usr.bin/procstat/procstat.c @@ -103,6 +103,8 @@ PS_CMP_PLURAL }, { "file", "files", "[-C]", &procstat_files, &cmdopt_files, PS_CMP_PLURAL }, + { "kqueue", "kqueues", NULL, &procstat_kqueues, &cmdopt_none, + PS_CMP_PLURAL }, { "kstack", "kstack", "[-v]", &procstat_kstack, &cmdopt_verbose, PS_CMP_NORMAL }, { "pargs", "args", NULL, &procstat_pargs, &cmdopt_none, diff --git a/usr.bin/procstat/procstat_kqueue.c b/usr.bin/procstat/procstat_kqueue.c new file mode 100644 --- /dev/null +++ b/usr.bin/procstat/procstat_kqueue.c @@ -0,0 +1,311 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2025 The FreeBSD Foundation + * + * This software was developed by Konstantin Belousov + * under sponsorship from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#define _KERNEL +#include +#undef _KERNEL +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "procstat.h" + +static const char kqs[] = "kqueues"; +static const char kq[] = "kqueue"; + +#define FILT_ELEM(name) [-EVFILT_##name] = #name, +static const char *filter_names[] = { + [0] = "invalid", + FILT_ELEM(READ) + FILT_ELEM(WRITE) + FILT_ELEM(AIO) + FILT_ELEM(VNODE) + FILT_ELEM(PROC) + FILT_ELEM(SIGNAL) + FILT_ELEM(TIMER) + FILT_ELEM(PROCDESC) + FILT_ELEM(FS) + FILT_ELEM(LIO) + FILT_ELEM(USER) + FILT_ELEM(SENDFILE) + FILT_ELEM(EMPTY) +}; +#undef FILT_ELEM + +#define PK_FLAG_ELEM(prefix, fname) { .flag = prefix##fname, .name = #fname } +#define PK_FLAG_LAST_ELEM() { .flag = -1, .name = 0 } +struct pk_elem { + unsigned int flag; + const char *name; +}; + +static const struct pk_elem kn_status_names[] = { + PK_FLAG_ELEM(KN_, ACTIVE), + PK_FLAG_ELEM(KN_, QUEUED), + PK_FLAG_ELEM(KN_, DISABLED), + PK_FLAG_ELEM(KN_, DETACHED), + PK_FLAG_ELEM(KN_, MARKER), + PK_FLAG_ELEM(KN_, KQUEUE), + PK_FLAG_ELEM(KN_, SCAN), + PK_FLAG_LAST_ELEM(), +}; + +static const struct pk_elem ev_flags_names[] = { + PK_FLAG_ELEM(EV_, ONESHOT), + PK_FLAG_ELEM(EV_, CLEAR), + PK_FLAG_ELEM(EV_, RECEIPT), + PK_FLAG_ELEM(EV_, DISPATCH), + PK_FLAG_ELEM(EV_, DROP), + PK_FLAG_ELEM(EV_, FLAG1), + PK_FLAG_ELEM(EV_, FLAG2), + PK_FLAG_LAST_ELEM(), +}; + +static char * +procstat_kqueue_flags(const struct pk_elem *names, unsigned flags) +{ + char *bres, *res; + const struct pk_elem *pl; + size_t len; + int i; + bool first; + + first = true; + len = 0; + for (i = 0;; i++) { + pl = &names[i]; + if (pl->flag == (unsigned)-1) + break; + if ((flags & pl->flag) != 0) { + if (first) + first = false; + else + len += sizeof(","); + len += strlen(pl->name); + } + } + len++; + + res = malloc(len); + first = true; + res[0] = '\0'; + for (i = 0;; i++) { + pl = &names[i]; + if (pl->flag == (unsigned)-1) + break; + if ((flags & pl->flag) != 0) { + if (first) + first = false; + else + strlcat(res, ",", len); + strlcat(res, pl->name, len); + } + } + + if (strlen(res) == 0) + return (res); + asprintf(&bres, "(%s)", res); + free(res); + return (bres); +} + +static const struct pk_elem rw_filter_names[] = { + PK_FLAG_ELEM(NOTE_, LOWAT), + PK_FLAG_ELEM(NOTE_, FILE_POLL), + PK_FLAG_LAST_ELEM(), +}; + +static const struct pk_elem user_filter_names[] = { + PK_FLAG_ELEM(NOTE_, FFAND), + PK_FLAG_ELEM(NOTE_, FFOR), + PK_FLAG_ELEM(NOTE_, TRIGGER), + PK_FLAG_LAST_ELEM(), +}; + +static const struct pk_elem vnode_filter_names[] = { + PK_FLAG_ELEM(NOTE_, DELETE), + PK_FLAG_ELEM(NOTE_, WRITE), + PK_FLAG_ELEM(NOTE_, EXTEND), + PK_FLAG_ELEM(NOTE_, ATTRIB), + PK_FLAG_ELEM(NOTE_, LINK), + PK_FLAG_ELEM(NOTE_, RENAME), + PK_FLAG_ELEM(NOTE_, REVOKE), + PK_FLAG_ELEM(NOTE_, OPEN), + PK_FLAG_ELEM(NOTE_, CLOSE), + PK_FLAG_ELEM(NOTE_, CLOSE_WRITE), + PK_FLAG_ELEM(NOTE_, READ), + PK_FLAG_LAST_ELEM(), +}; + +static const struct pk_elem proc_filter_names[] = { + PK_FLAG_ELEM(NOTE_, EXIT), + PK_FLAG_ELEM(NOTE_, FORK), + PK_FLAG_ELEM(NOTE_, EXEC), + PK_FLAG_ELEM(NOTE_, TRACK), + PK_FLAG_ELEM(NOTE_, TRACKERR), + PK_FLAG_ELEM(NOTE_, CHILD), + PK_FLAG_LAST_ELEM(), +}; + +static const struct pk_elem timer_filter_names[] = { + PK_FLAG_ELEM(NOTE_, SECONDS), + PK_FLAG_ELEM(NOTE_, MSECONDS), + PK_FLAG_ELEM(NOTE_, USECONDS), + PK_FLAG_ELEM(NOTE_, NSECONDS), + PK_FLAG_ELEM(NOTE_, ABSTIME), + PK_FLAG_LAST_ELEM(), +}; + +#define FILT_ELEM(name) [-EVFILT_##name] = "EVFILT_"#name, +static const struct pk_elem *filter_pk_names[] = { + [0] = NULL, + [-EVFILT_READ] = rw_filter_names, + [-EVFILT_WRITE] = rw_filter_names, + [-EVFILT_AIO] = rw_filter_names, + [-EVFILT_VNODE] = vnode_filter_names, + [-EVFILT_PROC] = proc_filter_names, + [-EVFILT_SIGNAL] = NULL, + [-EVFILT_TIMER] = timer_filter_names, + [-EVFILT_PROCDESC] = proc_filter_names, + [-EVFILT_FS] = NULL, + [-EVFILT_LIO] = rw_filter_names, + [-EVFILT_USER] = user_filter_names, + [-EVFILT_SENDFILE] = NULL, + [-EVFILT_EMPTY] = NULL, +}; + +static char * +procstat_kqueue_fflags(int filter, unsigned fflags) +{ + const struct pk_elem *names; + + names = NULL; + if (filter < 0 && -filter < (int)nitems(filter_pk_names)) + names = filter_pk_names[-filter]; + if (names == NULL) + return (strdup("")); + return (procstat_kqueue_flags(names, fflags)); +} + +static const char * +procstat_kqueue_get_filter_name(int filter) +{ + filter = -filter; + if (filter < 0 || filter >= (int)nitems(filter_names)) + filter = 0; + return (filter_names[filter]); +} + +static void +procstat_kqueue(struct procstat *procstat, struct kinfo_proc *kipp, int fd) +{ + struct kinfo_knote *kni, *knis; + char *flags, *fflags, *status; + unsigned int count, i; + char errbuf[_POSIX2_LINE_MAX]; + + errbuf[0] = '\0'; + knis = procstat_get_kqueue_info(procstat, kipp, fd, &count, errbuf); + if (knis == NULL) { + warnx("%s\n", errbuf); + return; + } + + for (i = 0; i < count; i++) { + kni = &knis[i]; + flags = procstat_kqueue_flags(ev_flags_names, + kni->knt_event.flags); + fflags = procstat_kqueue_fflags(kni->knt_event.filter, + kni->knt_event.fflags); + status = procstat_kqueue_flags(kn_status_names, + kni->knt_status); + xo_open_instance(kq); + xo_emit("{dk:process_id/%7d} ", kipp->ki_pid); + xo_emit("{:kqueue_fd/%10d} ", fd); + xo_emit("{:filter/%8s} ", procstat_kqueue_get_filter_name( + kni->knt_event.filter)); + xo_emit("{:ident/%10d} ", kni->knt_event.ident); + xo_emit("{:flags/%#10x%s} ", kni->knt_event.flags, flags); + xo_emit("{:fflags/%#10x%s} ", kni->knt_event.fflags, fflags); + xo_emit("{:data/%#10jx} ", (uintmax_t)kni->knt_event.data); + xo_emit("{:udata/%10p} ", (uintmax_t)kni->knt_event.udata); + xo_emit("{:ext0/%#10jx} ", (uintmax_t)kni->knt_event.ext[0]); + xo_emit("{:ext1/%#10jx} ", (uintmax_t)kni->knt_event.ext[1]); + xo_emit("{:ext2/%#10jx} ", (uintmax_t)kni->knt_event.ext[2]); + xo_emit("{:ext3/%#10jx} ", (uintmax_t)kni->knt_event.ext[3]); + xo_emit("{:status/%#10x%s}\n", (uintmax_t)kni->knt_status, + status); + free(flags); + free(fflags); + free(status); + xo_close_instance(kq); + } + + procstat_freekqinfo(procstat, knis); +} + +void +procstat_kqueues(struct procstat *procstat, struct kinfo_proc *kipp) +{ + struct filestat_list *fl; + struct filestat *f; + + if ((procstat_opts & PS_OPT_NOHEADER) == 0) + xo_emit("{T:/%7s %10s %8s %10s %10s %10s %10s %10s " + "%10s %10s %10s %10s %10s}\n", + "PID", "KQFD", "FILTER", "IDENT", "FLAGS", "FFLAGS", + "DATA", "UDATA", "EXT0", "EXT1","EXT2","EXT3", "STATUS"); + + xo_emit("{ek:process_id/%d}", kipp->ki_pid); + + fl = procstat_getfiles(procstat, kipp, 0); + if (fl == NULL) + return; + xo_open_list(kqs); + STAILQ_FOREACH(f, fl, next) { + if (f->fs_type != PS_FST_TYPE_KQUEUE) + continue; + xo_emit("{ek:kqueue/%d}", f->fs_fd); + xo_open_list(kq); + procstat_kqueue(procstat, kipp, f->fs_fd); + xo_close_list(kq); + } + xo_close_list(kqs); + procstat_freefiles(procstat, fl); +}