diff options
author | 2021-05-09 14:20:58 -0400 | |
---|---|---|
committer | 2021-05-09 14:20:58 -0400 | |
commit | 5fd83771641d15c418f747bd343ba6738d3875f7 (patch) | |
tree | 5abf0f78f680d9837dbd93d4d4c3933bb7509599 /system_cmds/lskq.tproj | |
download | apple_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.h | 167 | ||||
-rw-r--r-- | system_cmds/lskq.tproj/lskq.1 | 236 | ||||
-rw-r--r-- | system_cmds/lskq.tproj/lskq.c | 963 |
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; +} |