aboutsummaryrefslogtreecommitdiffstats
path: root/system_cmds/lskq.tproj
diff options
context:
space:
mode:
authorCameron Katri <me@cameronkatri.com>2021-05-09 14:20:58 -0400
committerCameron Katri <me@cameronkatri.com>2021-05-09 14:20:58 -0400
commit5fd83771641d15c418f747bd343ba6738d3875f7 (patch)
tree5abf0f78f680d9837dbd93d4d4c3933bb7509599 /system_cmds/lskq.tproj
downloadapple_cmds-5fd83771641d15c418f747bd343ba6738d3875f7.tar.gz
apple_cmds-5fd83771641d15c418f747bd343ba6738d3875f7.tar.zst
apple_cmds-5fd83771641d15c418f747bd343ba6738d3875f7.zip
Import macOS userland
adv_cmds-176 basic_cmds-55 bootstrap_cmds-116.100.1 developer_cmds-66 diskdev_cmds-667.40.1 doc_cmds-53.60.1 file_cmds-321.40.3 mail_cmds-35 misc_cmds-34 network_cmds-606.40.1 patch_cmds-17 remote_cmds-63 shell_cmds-216.60.1 system_cmds-880.60.2 text_cmds-106
Diffstat (limited to 'system_cmds/lskq.tproj')
-rw-r--r--system_cmds/lskq.tproj/common.h167
-rw-r--r--system_cmds/lskq.tproj/lskq.1236
-rw-r--r--system_cmds/lskq.tproj/lskq.c963
3 files changed, 1366 insertions, 0 deletions
diff --git a/system_cmds/lskq.tproj/common.h b/system_cmds/lskq.tproj/common.h
new file mode 100644
index 0000000..959ac66
--- /dev/null
+++ b/system_cmds/lskq.tproj/common.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2015 Apple Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#ifndef _LSKQ_COMMON_H_
+#define _LSKQ_COMMON_H_
+
+#include <stdint.h>
+
+/*
+ * This file must be kept in sync with xnu headers
+ */
+
+/*
+ * bsd/sys/event.h
+ */
+__options_decl(kn_status_t, uint16_t /* 12 bits really */, {
+ KN_ACTIVE = 0x001, /* event has been triggered */
+ KN_QUEUED = 0x002, /* event is on queue */
+ KN_DISABLED = 0x004, /* event is disabled */
+ KN_DROPPING = 0x008, /* knote is being dropped */
+ KN_LOCKED = 0x010, /* knote is locked (kq_knlocks) */
+ KN_POSTING = 0x020, /* f_event() in flight */
+ KN_STAYACTIVE = 0x040, /* force event to stay active */
+ KN_DEFERDELETE = 0x080, /* defer delete until re-enabled */
+ KN_MERGE_QOS = 0x100, /* f_event() / f_* ran concurrently and overrides must merge */
+ KN_REQVANISH = 0x200, /* requested EV_VANISH */
+ KN_VANISHED = 0x400, /* has vanished */
+ KN_SUPPRESSED = 0x800, /* event is suppressed during delivery */
+});
+
+/*
+ * bsd/sys/eventvar.h
+ */
+__options_decl(kq_state_t, uint16_t, {
+ KQ_SEL = 0x0001, /* select was recorded for kq */
+ KQ_SLEEP = 0x0002, /* thread is waiting for events */
+ KQ_PROCWAIT = 0x0004, /* thread waiting for processing */
+ KQ_KEV32 = 0x0008, /* kq is used with 32-bit events */
+ KQ_KEV64 = 0x0010, /* kq is used with 64-bit events */
+ KQ_KEV_QOS = 0x0020, /* kq events carry QoS info */
+ KQ_WORKQ = 0x0040, /* KQ is bound to process workq */
+ KQ_WORKLOOP = 0x0080, /* KQ is part of a workloop */
+ KQ_PROCESSING = 0x0100, /* KQ is being processed */
+ KQ_DRAIN = 0x0200, /* kq is draining */
+ KQ_WAKEUP = 0x0400, /* kq awakened while processing */
+ KQ_DYNAMIC = 0x0800, /* kqueue is dynamically managed */
+ KQ_R2K_ARMED = 0x1000, /* ast notification armed */
+ KQ_HAS_TURNSTILE = 0x2000, /* this kqueue has a turnstile */
+});
+
+/*
+ * bsd/pthread/workqueue_internal.h
+ */
+__enum_decl(workq_tr_state_t, uint8_t, {
+ WORKQ_TR_STATE_IDLE = 0, /* request isn't in flight */
+ WORKQ_TR_STATE_NEW = 1, /* request is being initiated */
+ WORKQ_TR_STATE_QUEUED = 2, /* request is being queued */
+ WORKQ_TR_STATE_CANCELED = 3, /* request is canceled */
+ WORKQ_TR_STATE_BINDING = 4, /* request is preposted for bind */
+ WORKQ_TR_STATE_BOUND = 5, /* request is bound to a thread */
+});
+
+
+/*
+ * bsd/sys/signal.h
+ */
+static const char *
+sig_strs[] = {
+ [0] = "<UNKN>",
+ [1] = "SIGHUP",
+ [2] = "SIGINT",
+ [3] = "SIGQUIT",
+ [4] = "SIGILL",
+ [5] = "SIGTRAP",
+ [6] = "SIGABRT",
+ [7] = "SIGEMT",
+ [8] = "SIGFPE",
+ [9] = "SIGKILL",
+ [10] = "SIGBUS",
+ [11] = "SIGSEGV",
+ [12] = "SIGSYS",
+ [13] = "SIGPIPE",
+ [14] = "SIGALRM",
+ [15] = "SIGTERM",
+ [16] = "SIGURG",
+ [17] = "SIGSTOP",
+ [18] = "SIGTSTP",
+ [19] = "SIGCONT",
+ [20] = "SIGCHLD",
+ [21] = "SIGTTIN",
+ [22] = "SIGTTOU",
+ [23] = "SIGIO",
+ [24] = "SIGXCPU",
+ [25] = "SIGXFSZ",
+ [26] = "SIGVTALRM",
+ [27] = "SIGPROF",
+ [28] = "SIGWINCH",
+ [29] = "SIGINFO",
+ [30] = "SIGUSR1",
+ [31] = "SIGUSR2"
+};
+
+/*
+ * bsd/sys/event.h: EVFILT_*
+ */
+static const char *
+filt_strs[] = {
+ NULL,
+ "READ",
+ "WRITE",
+ "AIO",
+ "VNODE",
+ "PROC",
+ "SIGNAL",
+ "TIMER",
+ "MACHPORT",
+ "FS",
+ "USER",
+ "<inval>",
+ "VM",
+ "SOCK",
+ "MEMSTATUS",
+ "EXCEPT",
+ "CHANNEL",
+ "WORKLOOP",
+};
+
+/*
+ * bsd/sys/proc_info.h: PROX_FDTYPE_*
+ */
+static const char *
+fdtype_strs[] = {
+ "ATALK",
+ "VNODE",
+ "SOCKET",
+ "PSHM",
+ "PSEM",
+ "KQUEUE",
+ "PIPE",
+ "FSEVENTS",
+ "ATALK",
+ "POLICY",
+ "CHANNEL",
+ "NEXUS",
+};
+
+#endif /* _LSKQ_COMMON_H_ */
diff --git a/system_cmds/lskq.tproj/lskq.1 b/system_cmds/lskq.tproj/lskq.1
new file mode 100644
index 0000000..86e31d8
--- /dev/null
+++ b/system_cmds/lskq.tproj/lskq.1
@@ -0,0 +1,236 @@
+.\" Copyright (c) 2015, Apple Inc. All rights reserved.
+.\"
+.Dd Apr 20, 2015
+.Dt lskq 1
+.Os "Mac OS X"
+.Sh NAME
+.Nm lskq
+.Nd display process kqueue state
+.Sh SYNOPSIS
+.Nm lskq
+.Op Fl vhe
+.Op Fl p Ar <pid> | Fl a
+.Sh DESCRIPTION
+The
+.Nm lskq
+command enumerates kqueues and registered kevents of running processes.
+.Sh OPTIONS
+.Pp
+.Bl -tag -width xxx
+.It Fl p Ar <pid>
+Show kqueues of process
+.Ar <pid> .
+.It Fl a
+Show kqueues for all running processes. Requires root.
+.It Fl v
+Verbose: show opaque user data and filter-specific extension fields.
+.It Fl e
+Ignore empty kqueues.
+.It Fl r
+Print fields in raw hex.
+.It Fl h
+Show help and exit.
+.El
+.Sh OUTPUT
+.Nm lskq
+prints one line of output for each registered kevent, consisting of process,
+kqueue, and kevent information. For kqueues with no registered kevents, a single
+line is output with an ident of `-'. See
+.Xr kevent 2
+for field semantics. The output columns are:
+.Bl -tag -width xxxxxxxxxxxx
+.It command
+shortened process name.
+.It pid
+process identifier.
+.It kq
+file descriptor corresponding to kqueue, or ``wq'' for the special workq kqueue.
+.It kqst
+kqueue status bitmask.
+.Bl -tag -width xxxxxxx -compact
+.It Sy k
+kqueue is in a
+.Fn kevent*
+wait set (KQ_SLEEP).
+.It Sy s
+kqueue is in a
+.Fn select
+wait set (KQ_SEL).
+.It Sy 3 6 q
+Type of kevents on this kqueue: KEV32, KEV64, or KEV_QOS.
+.El
+.It ident
+kevent identifier. The meaning depends on the kevent filter specified. Where
+possible,
+.Nm lskq
+prints both numeric and symbolic names.
+.It filter
+kevent filter type (EVFILT_*).
+.It fdtype
+file descriptor type, for filters operating on file descriptors.
+.It fflags
+kevent filter flags bitmask. The meaning of each field depends on the filter type.
+.Bl -tag -width xxxxxxx -compact
+.Pp
+.It EVFILT_READ:
+.It Sy l
+NOTE_LOWAT
+.Pp
+.It EVFILT_MACHPORT:
+.It Sy r
+MACH_RCV_MSG
+.Pp
+.It EVFILT_VNODE:
+.It Sy d
+NOTE_DELETE
+.It Sy w
+NOTE_WRITE
+.It Sy e
+NOTE_EXTEND
+.It Sy a
+NOTE_ATTRIB
+.It Sy l
+NOTE_LINK
+.It Sy r
+NOTE_RENAME
+.It Sy v
+NOTE_REVOKE
+.It Sy u
+NOTE_FUNLOCK
+.Pp
+.It EVFILT_PROC:
+.It Sy x
+NOTE_EXIT
+.It Sy t
+NOTE_EXITSTATUS
+.It Sy d
+NOTE_EXIT_DETAIL
+.It Sy f
+NOTE_FORK
+.It Sy e
+NOTE_EXEC
+.It Sy s
+NOTE_SIGNAL
+.It Sy r
+NOTE_REAP
+.Pp
+.It EVFILT_TIMER:
+.It Sy s u n m
+NOTE_SECONDS, NOTE_USECONDS, NOTE_NSECONDS, NOTE_MACHTIME
+.It Sy a A
+NOTE_ABSOLUTE, NOTE_MACH_CONTINUOUS_TIME
+.It Sy c
+NOTE_CRITICAL
+.It Sy b
+NOTE_BACKGROUND
+.It Sy l
+NOTE_LEEWAY
+.Pp
+.It EVFILT_USER:
+.It Sy t
+NOTE_TRIGGER
+.It Sy a
+NOTE_FFAND
+.It Sy o
+NOTE_FFOR
+.Pp
+.It EVFILT_WORKLOOP:
+.It Sy t w i
+NOTE_WL_THREAD_REQUEST, NOTE_WL_SYNC_WAIT, NOTE_WL_SYNC_IPC
+.It Sy W
+NOTE_WL_SYNC_WAKE
+.It Sy q
+NOTE_WL_UPDATE_QOS
+.It Sy o
+NOTE_WL_DISCOVER_OWNER
+.It Sy e
+NOTE_WL_IGNORE_ESTALE
+.El
+.It flags
+kevent generic flags bitmask.
+.Bl -tag -width xxxxxxx -compact
+.It Sy a
+EV_ADD
+.It Sy n
+EV_ENABLE
+.It Sy d
+EV_DISABLE
+.It Sy x
+EV_DELETE
+.Pp
+.It Sy r
+EV_RECEIPT
+.It Sy 1
+EV_ONESHOT
+.It Sy c
+EV_CLEAR
+.It Sy s
+EV_DISPATCH
+.Pp
+.It Sy u
+EV_UDATA_SPECIFIC
+.It Sy p
+EV_FLAG0 (EV_POLL)
+.It Sy b
+EV_FLAG1 (EV_OOBAND)
+.It Sy o
+EV_EOF
+.It Sy e
+EV_ERROR
+.El
+.It evst
+kevent status bitmask.
+.Bl -tag -width xxxxxxx -compact
+.It Sy a
+KN_ACTIVE (event has triggered)
+.It Sy q
+KN_QUEUED (event has been added to the active list)
+.It Sy d
+KN_DISABLED (knote is disabled)
+.It Sy p
+KN_SUPPRESSED (event delivery is in flight)
+.It Sy s
+KN_STAYACTIVE (event is marked as always-enqueued on the active list)
+.Pp
+.It Sy d
+KN_DROPPING (knote is about to be dropped)
+.It Sy l
+KN_LOCKED (knote is locked)
+.It Sy P
+KN_POSTING (knote is being posted)
+.It Sy m
+KN_MERGE_QOS (knote is in override saturating mode)
+.Pp
+.It Sy D
+KN_DEFERDELETE (knote is waiting for deferred-delete ack)
+.It Sy v
+KN_REQVANISH
+.It Sy n
+KN_VANISHED
+.El
+.It qos
+The QoS requested for the knote.
+.It data
+Filter-specific data.
+.El
+.Pp
+If the
+.Fl v
+(verbose) option is specified, the opaque user-data field and further
+filter-specific extension fields are printed in raw hexadecimal.
+.Sh NOTES
+The output of
+.Nm lskq
+is not an atomic snapshot of system state. In cases where
+.Nm lskq
+is able to detect an inconsistency, a warning will be printed.
+.Pp
+Not all flags are symbolicated. Use
+.Fl r
+(raw mode) to inspect additional flags.
+.Sh SEE ALSO
+.Xr kqueue 2 ,
+.Xr kevent 2 ,
+.Xr ddt 1 ,
+.Xr lsof 8 ,
+.Xr lsmp 1
diff --git a/system_cmds/lskq.tproj/lskq.c b/system_cmds/lskq.tproj/lskq.c
new file mode 100644
index 0000000..a48bb26
--- /dev/null
+++ b/system_cmds/lskq.tproj/lskq.c
@@ -0,0 +1,963 @@
+/*
+ * Copyright (c) 2015-2016 Apple Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <strings.h>
+#include <assert.h>
+#include <errno.h>
+
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#include <sys/proc_info.h>
+#include <sys/param.h>
+#include <pthread/pthread.h>
+#include <mach/message.h>
+#define PRIVATE
+#include <libproc.h>
+#undef PRIVATE
+#include <os/assumes.h>
+#include <os/overflow.h>
+
+#include "common.h"
+
+#define ARRAYLEN(x) (sizeof((x))/sizeof((x[0])))
+
+/* command line options */
+static int verbose;
+static int all_pids;
+static int ignore_empty;
+static int raw;
+
+static char *self = "lskq";
+
+static inline const char *
+filt_name(int16_t filt)
+{
+ static char unkn_filt[32];
+ int idx = -filt;
+ if (idx >= 0 && idx < ARRAYLEN(filt_strs)) {
+ return filt_strs[idx];
+ } else {
+ snprintf(unkn_filt, sizeof(unkn_filt), "%i (?)", idx);
+ return unkn_filt;
+ }
+}
+
+static inline const char *
+fdtype_str(uint32_t type)
+{
+ static char unkn_fdtype[32];
+ if (type < ARRAYLEN(fdtype_strs)) {
+ return fdtype_strs[type];
+ } else {
+ snprintf(unkn_fdtype, sizeof(unkn_fdtype), "%i (?)", type);
+ return unkn_fdtype;
+ }
+}
+
+static char *
+fflags_build(struct kevent_extinfo *info, char *str, int len)
+{
+ unsigned ff = info->kqext_sfflags;
+
+ switch (info->kqext_kev.filter) {
+
+ case EVFILT_READ: {
+ snprintf(str, len, "%c ",
+ (ff & NOTE_LOWAT) ? 'l' : '-'
+ );
+ break;
+ }
+
+ case EVFILT_MACHPORT: {
+ snprintf(str, len, "%c ",
+ (ff & MACH_RCV_MSG) ? 'r' : '-'
+ );
+ break;
+ }
+
+ case EVFILT_VNODE: {
+ snprintf(str, len, "%c%c%c%c%c%c%c%c",
+ (ff & NOTE_DELETE) ? 'd' : '-',
+ (ff & NOTE_WRITE) ? 'w' : '-',
+ (ff & NOTE_EXTEND) ? 'e' : '-',
+ (ff & NOTE_ATTRIB) ? 'a' : '-',
+ (ff & NOTE_LINK) ? 'l' : '-',
+ (ff & NOTE_RENAME) ? 'r' : '-',
+ (ff & NOTE_REVOKE) ? 'v' : '-',
+ (ff & NOTE_FUNLOCK) ? 'u' : '-'
+ );
+ break;
+ }
+
+ case EVFILT_PROC: {
+/* NOTE_REAP is deprecated, but we still want to show if it's used */
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ snprintf(str, len, "%c%c%c%c%c%c%c",
+ (ff & NOTE_EXIT) ? 'x' : '-',
+ (ff & NOTE_EXITSTATUS) ? 't' : '-',
+ (ff & NOTE_EXIT_DETAIL)? 'd' : '-',
+ (ff & NOTE_FORK) ? 'f' : '-',
+ (ff & NOTE_EXEC) ? 'e' : '-',
+ (ff & NOTE_SIGNAL) ? 's' : '-',
+ (ff & NOTE_REAP) ? 'r' : '-'
+ );
+ break;
+#pragma clang diagnostic pop
+ }
+
+ case EVFILT_TIMER: {
+ snprintf(str, len, "%c%c%c%c%c ",
+ (ff & NOTE_SECONDS) ? 's' :
+ (ff & NOTE_USECONDS) ? 'u' :
+ (ff & NOTE_NSECONDS) ? 'n' :
+ (ff & NOTE_MACHTIME) ? 'm' : '?',
+ (ff & NOTE_ABSOLUTE) ? 'a' :
+ (ff & NOTE_MACH_CONTINUOUS_TIME) ? 'A' : '-',
+ (ff & NOTE_CRITICAL) ? 'c' : '-',
+ (ff & NOTE_BACKGROUND) ? 'b' : '-',
+ (ff & NOTE_LEEWAY) ? 'l' : '-'
+ );
+ break;
+ }
+
+ case EVFILT_USER:
+ snprintf(str, len, "%c%c%c ",
+ (ff & NOTE_TRIGGER) ? 't' : '-',
+ (ff & NOTE_FFAND) ? 'a' : '-',
+ (ff & NOTE_FFOR) ? 'o' : '-'
+ );
+ break;
+
+ case EVFILT_WORKLOOP:
+ snprintf(str, len, "%c%c%c%c%c ",
+ (ff & NOTE_WL_THREAD_REQUEST) ? 't' :
+ (ff & NOTE_WL_SYNC_WAIT) ? 'w' :
+ (ff & NOTE_WL_SYNC_IPC) ? 'i' : '-',
+ (ff & NOTE_WL_SYNC_WAKE) ? 'W' : '-',
+ (ff & NOTE_WL_UPDATE_QOS) ? 'q' : '-',
+ (ff & NOTE_WL_DISCOVER_OWNER) ? 'o' : '-',
+ (ff & NOTE_WL_IGNORE_ESTALE) ? 'e' : '-'
+ );
+ break;
+
+ default:
+ snprintf(str, len, "");
+ break;
+ };
+
+ return str;
+}
+
+
+static inline int
+filter_is_fd_type(int filter)
+{
+ switch (filter) {
+ case EVFILT_VNODE ... EVFILT_READ:
+ case EVFILT_SOCK:
+ case EVFILT_NW_CHANNEL:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static const char *
+thread_qos_name(uint8_t th_qos)
+{
+ switch (th_qos) {
+ case 0: return "--";
+ case 1: return "MT";
+ case 2: return "BG";
+ case 3: return "UT";
+ case 4: return "DF";
+ case 5: return "IN";
+ case 6: return "UI";
+ case 7: return "MG";
+ default: return "??";
+ }
+}
+
+/*
+ * find index of fd in a list of fdinfo of length nfds
+ */
+static inline int
+fd_list_getfd(struct proc_fdinfo *fds, int nfds, int fd)
+{
+ int i;
+ for (i = 0; i < nfds; i++) {
+ if (fds[i].proc_fd == fd) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+/*
+ * left truncate URL-form process names
+ */
+static const char *
+shorten_procname(const char *proc, int width)
+{
+ if (strcasestr(proc, "com.") == proc) {
+ long len = strlen(proc);
+ if (len > width) {
+ return &proc[len - width];
+ } else {
+ return proc;
+ }
+ } else {
+ return proc;
+ }
+}
+
+/*
+ * stringify knote ident where possible (signals, processes)
+ */
+static void
+print_ident(uint64_t ident, int16_t filter, int width)
+{
+ if (raw) {
+ printf("%#*llx ", width, ident);
+ return;
+ }
+
+ switch (filter) {
+
+ case EVFILT_SIGNAL:
+ case EVFILT_PROC: {
+ char str[128] = "";
+ char num[128];
+ char out[128];
+ int numlen = sprintf(num, "%llu", ident);
+ int strwidth = width - numlen - 1; // add room for a space
+
+ if (filter == EVFILT_SIGNAL) {
+ if (ident < ARRAYLEN(sig_strs)) {
+ snprintf(str, strwidth + 1, "%s", sig_strs[ident]);
+ }
+ } else {
+ /* FIXME: this should be cached */
+ struct proc_bsdinfo bsdinfo;
+ int ret = proc_pidinfo((int)ident, PROC_PIDTBSDINFO, 0, &bsdinfo, sizeof(bsdinfo));
+ if (ret == sizeof(bsdinfo)) {
+ char *procname = bsdinfo.pbi_name;
+ if (strlen(procname) == 0) {
+ procname = bsdinfo.pbi_comm;
+ }
+ snprintf(str, strwidth + 1, "%s", shorten_procname(procname, strwidth));
+ }
+ }
+
+ if (str[0] != '\0') {
+ snprintf(out, width + 1, "%-*s %s", strwidth, str, num);
+ } else {
+ snprintf(out, width + 1, "%s", num);
+ }
+
+ printf("%*s ", width, out);
+ break;
+ }
+
+ case EVFILT_MACHPORT:
+ case EVFILT_TIMER:
+ /* hex, to match lsmp */
+ printf("%#*llx ", width, ident);
+ break;
+
+ case EVFILT_WORKLOOP:
+ printf("%#*llx ", width, ident);
+ break;
+
+ default:
+ printf("%*llu ", width, ident);
+ break;
+ }
+
+}
+
+static void
+print_kqid(int state, uint64_t kqid)
+{
+ if (state & KQ_WORKQ) {
+ printf("%18s ", "wq");
+ } else if (state & KQ_WORKLOOP) {
+ printf("%#18" PRIx64 " ", kqid);
+ } else {
+ printf("fd %15" PRIi64 " ", kqid);
+ }
+}
+
+#define PROCNAME_WIDTH 20
+
+static void
+print_kq_info(int pid, const char *procname, uint64_t kqid, int state)
+{
+ if (raw) {
+ printf("%5u ", pid);
+ print_kqid(state, kqid);
+ printf("%#10x ", state);
+ } else {
+ char tmpstr[PROCNAME_WIDTH+1];
+ strlcpy(tmpstr, shorten_procname(procname, PROCNAME_WIDTH), PROCNAME_WIDTH+1);
+ printf("%-*s ", PROCNAME_WIDTH, tmpstr);
+ printf("%5u ", pid);
+ print_kqid(state, kqid);
+ printf(" %c%c%c ",
+ (state & KQ_SLEEP) ? 'k' : '-',
+ (state & KQ_SEL) ? 's' : '-',
+ (state & KQ_WORKQ) ? 'q' :
+ (state & KQ_WORKLOOP) ? 'l' : '-'
+ );
+ }
+}
+
+enum kqtype {
+ KQTYPE_FD,
+ KQTYPE_DYNAMIC
+};
+
+#define POLICY_TIMESHARE 1
+#define POLICY_RR 2
+#define POLICY_FIFO 4
+
+static int
+process_kqueue(int pid, const char *procname, enum kqtype type, uint64_t kqid,
+ struct proc_fdinfo *fdlist, int nfds)
+{
+ int ret, i, nknotes;
+ char tmpstr[256];
+ int maxknotes = 256; /* arbitrary starting point */
+ int kq_state;
+ bool is_kev_64, is_kev_qos;
+ int err = 0;
+ bool overflow = false;
+ int fd;
+ bool dynkq_printed = false;
+
+ /*
+ * get the basic kqueue info
+ */
+ struct kqueue_fdinfo kqfdinfo = {};
+ struct kqueue_dyninfo kqinfo = {};
+ switch (type) {
+ case KQTYPE_FD:
+ ret = proc_pidfdinfo(pid, (int)kqid, PROC_PIDFDKQUEUEINFO, &kqfdinfo, sizeof(kqfdinfo));
+ fd = (int)kqid;
+ break;
+ case KQTYPE_DYNAMIC:
+ ret = proc_piddynkqueueinfo(pid, PROC_PIDDYNKQUEUE_INFO, kqid, &kqinfo, sizeof(kqinfo));
+ break;
+ default:
+ os_crash("invalid kqueue type");
+ }
+
+ if (type == KQTYPE_FD && (int)kqid != -1) {
+ if (ret != sizeof(kqfdinfo)) {
+ /* every proc has an implicit workq kqueue, dont warn if its unused */
+ fprintf(stderr, "WARN: FD table changed (pid %i, kq %i)\n", pid,
+ fd);
+ }
+ } else if (type == KQTYPE_DYNAMIC) {
+ if (ret < sizeof(struct kqueue_info)) {
+ fprintf(stderr, "WARN: kqueue missing (pid %i, kq %#" PRIx64 ")\n",
+ pid, kqid);
+ } else {
+ kqfdinfo.kqueueinfo = kqinfo.kqdi_info;
+ }
+ if (verbose && ret >= sizeof(struct kqueue_dyninfo)) {
+ print_kq_info(pid, procname, kqid, kqinfo.kqdi_info.kq_state);
+
+ if (kqinfo.kqdi_owner) {
+ printf("%#18llx ", kqinfo.kqdi_owner); // ident
+ printf("%-9s ", "WL owned"); // filter
+ } else if (kqinfo.kqdi_servicer) {
+ printf("%#18llx ", kqinfo.kqdi_servicer); // ident
+ printf("%-9s ", "WL"); // filter
+ } else {
+ printf("%18s ", "-"); // ident
+ printf("%-9s ", "WL"); // filter
+ }
+ dynkq_printed = true;
+
+ if (raw) {
+ printf("%-10s ", " "); // fflags
+ printf("%-10s ", " "); // flags
+ printf("%-10s ", " "); // evst
+ } else {
+ const char *reqstate = "???";
+
+ switch (kqinfo.kqdi_request_state) {
+ case WORKQ_TR_STATE_IDLE:
+ reqstate = "";
+ break;
+ case WORKQ_TR_STATE_NEW:
+ reqstate = "new";
+ break;
+ case WORKQ_TR_STATE_QUEUED:
+ reqstate = "queued";
+ break;
+ case WORKQ_TR_STATE_CANCELED:
+ reqstate = "canceled";
+ break;
+ case WORKQ_TR_STATE_BINDING:
+ reqstate = "binding";
+ break;
+ case WORKQ_TR_STATE_BOUND:
+ reqstate = "bound";
+ break;
+ }
+
+ printf("%-8s ", reqstate); // fdtype
+ char policy_type;
+ switch (kqinfo.kqdi_pol) {
+ case POLICY_RR:
+ policy_type = 'R';
+ break;
+ case POLICY_FIFO:
+ policy_type = 'F';
+ case POLICY_TIMESHARE:
+ case 0:
+ default:
+ policy_type = '-';
+ break;
+ }
+ snprintf(tmpstr, 4, "%c%c%c", (kqinfo.kqdi_pri == 0)?'-':'P', policy_type, (kqinfo.kqdi_cpupercent == 0)?'-':'%');
+ printf("%-7s ", tmpstr); // fflags
+ printf("%-15s ", " "); // flags
+ printf("%-15s ", " "); // evst
+ }
+
+ if (!raw && kqinfo.kqdi_pri != 0) {
+ printf("%3d ", kqinfo.kqdi_pri); //qos
+ } else {
+ int qos = MAX(MAX(kqinfo.kqdi_events_qos, kqinfo.kqdi_async_qos),
+ kqinfo.kqdi_sync_waiter_qos);
+ printf("%3s ", thread_qos_name(qos)); //qos
+ }
+ printf("\n");
+ }
+ }
+
+ /*
+ * get extended kqueue info
+ */
+ struct kevent_extinfo *kqextinfo = NULL;
+ again:
+ if (!kqextinfo) {
+ kqextinfo = malloc(sizeof(struct kevent_extinfo) * maxknotes);
+ }
+ if (!kqextinfo) {
+ err = errno;
+ perror("failed allocating memory");
+ goto out;
+ }
+
+ errno = 0;
+ switch (type) {
+ case KQTYPE_FD:
+ nknotes = proc_pidfdinfo(pid, fd, PROC_PIDFDKQUEUE_EXTINFO,
+ kqextinfo, sizeof(struct kevent_extinfo) * maxknotes);
+ break;
+ case KQTYPE_DYNAMIC:
+ nknotes = proc_piddynkqueueinfo(pid, PROC_PIDDYNKQUEUE_EXTINFO, kqid,
+ kqextinfo, sizeof(struct kevent_extinfo) * maxknotes);
+ break;
+ default:
+ os_crash("invalid kqueue type");
+ }
+
+ if (nknotes <= 0) {
+ if (errno == 0) {
+ /* proc_*() can't distinguish between error and empty list */
+ } else if (errno == EAGAIN) {
+ goto again;
+ } else if (errno == EBADF) {
+ fprintf(stderr, "WARN: FD table changed (pid %i, kq %#" PRIx64 ")\n", pid, kqid);
+ goto out;
+ } else {
+ err = errno;
+ perror("failed to get extended kqueue info");
+ goto out;
+ }
+ }
+
+ if (nknotes > maxknotes) {
+ maxknotes = nknotes + 16; /* arbitrary safety margin */
+ free(kqextinfo);
+ kqextinfo = NULL;
+ goto again;
+ }
+
+ if (nknotes >= PROC_PIDFDKQUEUE_KNOTES_MAX) {
+ overflow = true;
+ }
+
+ kq_state = kqfdinfo.kqueueinfo.kq_state;
+ is_kev_64 = (kq_state & PROC_KQUEUE_64);
+ is_kev_qos = (kq_state & PROC_KQUEUE_QOS);
+
+ if (nknotes == 0) {
+ if (!ignore_empty && !dynkq_printed) {
+ /* for empty kqueues, print a single empty entry */
+ print_kq_info(pid, procname, kqid, kq_state);
+ printf("%18s \n", "-");
+ }
+ goto out;
+ }
+
+ for (i = 0; i < nknotes; i++) {
+ struct kevent_extinfo *info = &kqextinfo[i];
+
+ print_kq_info(pid, procname, kqid, kqfdinfo.kqueueinfo.kq_state);
+ print_ident(info->kqext_kev.ident, info->kqext_kev.filter, 18);
+ printf("%-9s ", filt_name(info->kqext_kev.filter));
+
+ if (raw) {
+ printf("%#10x ", info->kqext_sfflags);
+ printf("%#10x ", info->kqext_kev.flags);
+ printf("%#10x ", info->kqext_status);
+ } else {
+ /* for kevents attached to file descriptors, print the type of FD (file, socket, etc) */
+ const char *fdstr = "";
+ if (filter_is_fd_type(info->kqext_kev.filter)) {
+ fdstr = "<unkn>";
+ int knfd = fd_list_getfd(fdlist, nfds, (int)info->kqext_kev.ident);
+ if (knfd >= 0) {
+ fdstr = fdtype_str(fdlist[knfd].proc_fdtype);
+ }
+ }
+ printf("%-8s ", fdstr);
+
+ /* print filter flags */
+ printf("%7s ", fflags_build(info, tmpstr, sizeof(tmpstr)));
+
+ /* print generic flags */
+ unsigned flg = info->kqext_kev.flags;
+ printf("%c%c%c%c %c%c%c%c %c%c%c%c%c ",
+ (flg & EV_ADD) ? 'a' : '-',
+ (flg & EV_ENABLE) ? 'n' : '-',
+ (flg & EV_DISABLE) ? 'd' : '-',
+ (flg & EV_DELETE) ? 'x' : '-',
+
+ (flg & EV_RECEIPT) ? 'r' : '-',
+ (flg & EV_ONESHOT) ? '1' : '-',
+ (flg & EV_CLEAR) ? 'c' : '-',
+ (flg & EV_DISPATCH) ? 's' : '-',
+
+ (flg & EV_UDATA_SPECIFIC) ? 'u' : '-',
+ (flg & EV_FLAG0) ? 'p' : '-',
+ (flg & EV_FLAG1) ? 'b' : '-',
+ (flg & EV_EOF) ? 'o' : '-',
+ (flg & EV_ERROR) ? 'e' : '-'
+ );
+
+ unsigned st = info->kqext_status;
+ printf("%c%c%c%c%c %c%c%c%c %c%c%c ",
+ (st & KN_ACTIVE) ? 'a' : '-',
+ (st & KN_QUEUED) ? 'q' : '-',
+ (st & KN_DISABLED) ? 'd' : '-',
+ (st & KN_SUPPRESSED) ? 'p' : '-',
+ (st & KN_STAYACTIVE) ? 's' : '-',
+
+ (st & KN_DROPPING) ? 'd' : '-',
+ (st & KN_LOCKED) ? 'l' : '-',
+ (st & KN_POSTING) ? 'P' : '-',
+ (st & KN_MERGE_QOS) ? 'm' : '-',
+
+ (st & KN_DEFERDELETE) ? 'D' : '-',
+ (st & KN_REQVANISH) ? 'v' : '-',
+ (st & KN_VANISHED) ? 'n' : '-'
+ );
+ }
+
+ printf("%3s ", thread_qos_name(info->kqext_kev.qos));
+
+ printf("%#18llx ", (unsigned long long)info->kqext_kev.data);
+
+ if (verbose) {
+ printf("%#18llx ", (unsigned long long)info->kqext_kev.udata);
+ if (is_kev_qos || is_kev_64) {
+ printf("%#18llx ", (unsigned long long)info->kqext_kev.ext[0]);
+ printf("%#18llx ", (unsigned long long)info->kqext_kev.ext[1]);
+
+ if (is_kev_qos) {
+ printf("%#18llx ", (unsigned long long)info->kqext_kev.ext[2]);
+ printf("%#18llx ", (unsigned long long)info->kqext_kev.ext[3]);
+ printf("%#10lx ", (unsigned long)info->kqext_kev.xflags);
+ }
+ }
+ }
+
+ printf("\n");
+ }
+
+ if (overflow) {
+ printf(" ***** output truncated (>=%i knotes on kq %" PRIu64 ", proc %i) *****\n",
+ nknotes, kqid, pid);
+ }
+
+ out:
+ if (kqextinfo) {
+ free(kqextinfo);
+ kqextinfo = NULL;
+ }
+
+ return err;
+}
+
+static int
+pid_kqids(pid_t pid, kqueue_id_t **kqids_out)
+{
+ static int kqids_len = 256;
+ static kqueue_id_t *kqids = NULL;
+ static uint32_t kqids_size;
+
+ int nkqids;
+
+retry:
+ if (os_mul_overflow(sizeof(kqueue_id_t), kqids_len, &kqids_size)) {
+ assert(kqids_len > PROC_PIDDYNKQUEUES_MAX);
+ kqids_len = PROC_PIDDYNKQUEUES_MAX;
+ goto retry;
+ }
+ if (!kqids) {
+ kqids = malloc(kqids_size);
+ os_assert(kqids != NULL);
+ }
+
+ nkqids = proc_list_dynkqueueids(pid, kqids, kqids_size);
+ if (nkqids > kqids_len && kqids_len < PROC_PIDDYNKQUEUES_MAX) {
+ kqids_len *= 2;
+ if (kqids_len > PROC_PIDDYNKQUEUES_MAX) {
+ kqids_len = PROC_PIDDYNKQUEUES_MAX;
+ }
+ free(kqids);
+ kqids = NULL;
+ goto retry;
+ }
+
+ *kqids_out = kqids;
+ return MIN(nkqids, kqids_len);
+}
+
+static int
+process_pid(pid_t pid)
+{
+ int i, nfds, nkqids;
+ kqueue_id_t *kqids;
+ int ret = 0;
+ int maxfds = 256; /* arbitrary starting point */
+ struct proc_fdinfo *fdlist = NULL;
+
+ /* enumerate file descriptors */
+ again:
+ if (!fdlist) {
+ fdlist = malloc(sizeof(struct proc_fdinfo) * maxfds);
+ }
+ if (!fdlist) {
+ ret = errno;
+ perror("failed to allocate");
+ goto out;
+ }
+
+ nfds = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, fdlist,
+ sizeof(struct proc_fdinfo) * maxfds);
+ if (nfds <= 0) {
+ ret = errno;
+ fprintf(stderr, "%s: failed enumerating file descriptors of process %i: %s",
+ self, pid, strerror(ret));
+ if (ret == EPERM && geteuid() != 0) {
+ fprintf(stderr, " (are you root?)");
+ }
+ fprintf(stderr, "\n");
+ goto out;
+ }
+
+ nfds /= sizeof(struct proc_fdinfo);
+ if (nfds >= maxfds) {
+ maxfds = nfds + 16;
+ free(fdlist);
+ fdlist = NULL;
+ goto again;
+ }
+
+ /* get bsdinfo for the process name */
+ struct proc_bsdinfo bsdinfo;
+ ret = proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &bsdinfo, sizeof(bsdinfo));
+ if (ret != sizeof(bsdinfo)) {
+ perror("failed retrieving process info");
+ ret = -1;
+ goto out;
+ }
+
+ char *procname = bsdinfo.pbi_name;
+ if (strlen(procname) == 0) {
+ procname = bsdinfo.pbi_comm;
+ }
+
+ /* handle the special workq kq */
+ ret = process_kqueue(pid, procname, KQTYPE_FD, -1, fdlist, nfds);
+ if (ret) {
+ goto out;
+ }
+
+ for (i = 0; i < nfds; i++) {
+ if (fdlist[i].proc_fdtype == PROX_FDTYPE_KQUEUE) {
+ ret = process_kqueue(pid, procname, KQTYPE_FD,
+ (uint64_t)fdlist[i].proc_fd, fdlist, nfds);
+ if (ret) {
+ goto out;
+ }
+ }
+ }
+
+ nkqids = pid_kqids(pid, &kqids);
+
+ for (i = 0; i < nkqids; i++) {
+ ret = process_kqueue(pid, procname, KQTYPE_DYNAMIC, kqids[i], fdlist, nfds);
+ if (ret) {
+ goto out;
+ }
+ }
+
+ if (nkqids >= PROC_PIDDYNKQUEUES_MAX) {
+ printf(" ***** output truncated (>=%i dynamic kqueues in proc %i) *****\n",
+ nkqids, pid);
+ }
+
+ out:
+ if (fdlist) {
+ free(fdlist);
+ fdlist = NULL;
+ }
+
+ return ret;
+}
+
+static int
+process_all_pids(void)
+{
+ int i, npids;
+ int ret = 0;
+ int maxpids = 2048;
+ int *pids = NULL;
+
+ again:
+ if (!pids) {
+ pids = malloc(sizeof(int) * maxpids);
+ }
+ if (!pids) {
+ perror("failed allocating pids[]");
+ goto out;
+ }
+
+ errno = 0;
+ npids = proc_listpids(PROC_ALL_PIDS, 0, pids, sizeof(int) * maxpids);
+ if (npids <= 0) {
+ if (errno == 0) {
+ /* empty pid list */
+ } else if (errno == EAGAIN) {
+ goto again;
+ } else {
+ ret = errno;
+ perror("failed enumerating pids");
+ goto out;
+ }
+ }
+
+ npids /= sizeof(int);
+ if (npids >= maxpids) {
+ maxpids = npids + 16;
+ free(pids);
+ pids = NULL;
+ goto again;
+ }
+
+ for (i = 0; i < npids; i++) {
+ /* listpids gives us pid 0 for some reason */
+ if (pids[i]) {
+ ret = process_pid(pids[i]);
+ /* ignore races with processes exiting */
+ if (ret && ret != ESRCH) {
+ goto out;
+ }
+ }
+ }
+
+out:
+ if (pids) {
+ free(pids);
+ pids = NULL;
+ }
+
+ return ret;
+}
+
+static void
+cheatsheet(void)
+{
+ const char *bold = "\033[1m";
+ const char *reset = "\033[0m";
+ if (!isatty(STDERR_FILENO)) {
+ bold = reset = "";
+ }
+
+ fprintf(stderr, "\nFilter-independent flags:\n\n\
+%s\
+command pid kq kqst knid filter fdtype fflags flags evst qos%s\n%s\
+-------------------- ----- ------------------ ---- ------------------ --------- -------- ------- --------------- -------------- ---%s\n\
+ ┌ EV_UDATA_SPECIFIC\n\
+ EV_DISPATCH ┐ │┌ EV_FLAG0 (EV_POLL)\n\
+ EV_CLEAR ┐│ ││┌ EV_FLAG1 (EV_OOBAND)\n\
+ EV_ONESHOT ┐││ │││┌ EV_EOF\n\
+ EV_RECEIPT ┐│││ ││││┌ EV_ERROR\n\
+ ││││ │││││\n%s\
+launchd 1 4 ks- netbiosd 250 PROC ------- andx r1cs upboe aqdps dlPm Dvn IN%s\n\
+ │ │││ ││││ │││││ ││││ │││\n\
+ kqueue file descriptor/dynamic ID ┘ │││ EV_ADD ┘│││ KN_ACTIVE ┘││││ ││││ ││└ KN_VANISHED\n\
+ KQ_SLEEP ┘││ EV_ENABLE ┘││ KN_QUEUED ┘│││ ││││ │└ KN_REQVANISH\n\
+ KQ_SEL ┘│ EV_DISABLE ┘│ KN_DISABLED ┘││ ││││ └ KN_DEFERDELETE\n\
+ KQ_WORKQ (q) ┤ EV_DELETE ┘ KN_SUPPRESSED ┘│ ││││\n\
+ KQ_WORKLOOP (l) ┘ KN_STAYACTIVE ┘ ││││\n\
+ ││││\n\
+ KN_DROPPING ┘││└ KN_MERGE_QOS\n\
+ KN_LOCKED ┘└ KN_POSTING\n\
+ \n", bold, reset, bold, reset, bold, reset);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-vher] [-a | -p <pid>]\n", self);
+}
+
+static void
+print_header(void)
+{
+ if (raw) {
+ printf(" pid kq kqst knid filter fflags flags evst qos data");
+ } else {
+ printf("command pid kq kqst knid filter fdtype fflags flags evst qos data");
+ }
+
+ if (verbose) {
+ printf(" udata ext0 ext1 ext2 ext3 xflags");
+ }
+
+ printf("\n");
+
+ if (raw) {
+ printf("----- ------------------ ---------- ------------------ --------- ---------- ---------- ---------- --- ------------------");
+ } else {
+ printf("-------------------- ----- ------------------ ---- ------------------ --------- -------- ------- --------------- -------------- --- ------------------");
+ }
+
+ if (verbose) {
+ printf(" ------------------ ------------------ ------------------ ------------------ ------------------ ----------");
+ }
+ printf("\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ pid_t pid = 0;
+ int opt;
+
+ setlinebuf(stdout);
+
+ if (argc > 0) {
+ self = argv[0];
+ }
+
+ while ((opt = getopt(argc, argv, "eahvrp:")) != -1) {
+ switch (opt) {
+ case 'a':
+ all_pids = 1;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'p':
+ pid = atoi(optarg);
+ break;
+ case 'e':
+ ignore_empty = 1;
+ break;
+ case 'h':
+ usage();
+ cheatsheet();
+ return 0;
+ case 'r':
+ raw = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ return 1;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 1) {
+ /* also allow lskq <pid> */
+ if (pid || all_pids) {
+ usage();
+ return 1;
+ }
+
+ pid = atoi(argv[0]);
+ } else if (argc > 1) {
+ usage();
+ return 1;
+ }
+
+ /* exactly one of -p or -a is required */
+ if (!pid && !all_pids) {
+ usage();
+ return 1;
+ } else if (pid && all_pids) {
+ usage();
+ return 1;
+ }
+
+ print_header();
+
+ if (all_pids) {
+ return process_all_pids();
+ } else {
+ return process_pid(pid);
+ }
+
+ return 0;
+}