]> git.saurik.com Git - apple/system_cmds.git/blame_incremental - lskq.tproj/lskq.c
system_cmds-880.100.5.tar.gz
[apple/system_cmds.git] / lskq.tproj / lskq.c
... / ...
CommitLineData
1/*
2 * Copyright (c) 2015-2016 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include <inttypes.h>
25#include <stdio.h>
26#include <stdlib.h>
27#include <unistd.h>
28#include <strings.h>
29#include <assert.h>
30#include <errno.h>
31
32#include <sys/types.h>
33#include <sys/event.h>
34#include <sys/time.h>
35#include <sys/proc_info.h>
36#include <sys/param.h>
37#include <pthread/pthread.h>
38#include <mach/message.h>
39#define PRIVATE
40#include <libproc.h>
41#undef PRIVATE
42#include <os/assumes.h>
43#include <os/overflow.h>
44
45#include "common.h"
46
47#define ARRAYLEN(x) (sizeof((x))/sizeof((x[0])))
48
49/* command line options */
50static int verbose;
51static int all_pids;
52static int ignore_empty;
53static int raw;
54
55static char *self = "lskq";
56
57static inline const char *
58filt_name(int16_t filt)
59{
60 static char unkn_filt[32];
61 int idx = -filt;
62 if (idx >= 0 && idx < ARRAYLEN(filt_strs)) {
63 return filt_strs[idx];
64 } else {
65 snprintf(unkn_filt, sizeof(unkn_filt), "%i (?)", idx);
66 return unkn_filt;
67 }
68}
69
70static inline const char *
71fdtype_str(uint32_t type)
72{
73 static char unkn_fdtype[32];
74 if (type < ARRAYLEN(fdtype_strs)) {
75 return fdtype_strs[type];
76 } else {
77 snprintf(unkn_fdtype, sizeof(unkn_fdtype), "%i (?)", type);
78 return unkn_fdtype;
79 }
80}
81
82static char *
83fflags_build(struct kevent_extinfo *info, char *str, int len)
84{
85 unsigned ff = info->kqext_sfflags;
86
87 switch (info->kqext_kev.filter) {
88
89 case EVFILT_READ: {
90 snprintf(str, len, "%c ",
91 (ff & NOTE_LOWAT) ? 'l' : '-'
92 );
93 break;
94 }
95
96 case EVFILT_MACHPORT: {
97 snprintf(str, len, "%c ",
98 (ff & MACH_RCV_MSG) ? 'r' : '-'
99 );
100 break;
101 }
102
103 case EVFILT_VNODE: {
104 snprintf(str, len, "%c%c%c%c%c%c%c%c",
105 (ff & NOTE_DELETE) ? 'd' : '-',
106 (ff & NOTE_WRITE) ? 'w' : '-',
107 (ff & NOTE_EXTEND) ? 'e' : '-',
108 (ff & NOTE_ATTRIB) ? 'a' : '-',
109 (ff & NOTE_LINK) ? 'l' : '-',
110 (ff & NOTE_RENAME) ? 'r' : '-',
111 (ff & NOTE_REVOKE) ? 'v' : '-',
112 (ff & NOTE_FUNLOCK) ? 'u' : '-'
113 );
114 break;
115 }
116
117 case EVFILT_PROC: {
118/* NOTE_REAP is deprecated, but we still want to show if it's used */
119#pragma clang diagnostic push
120#pragma clang diagnostic ignored "-Wdeprecated-declarations"
121 snprintf(str, len, "%c%c%c%c%c%c%c",
122 (ff & NOTE_EXIT) ? 'x' : '-',
123 (ff & NOTE_EXITSTATUS) ? 't' : '-',
124 (ff & NOTE_EXIT_DETAIL)? 'd' : '-',
125 (ff & NOTE_FORK) ? 'f' : '-',
126 (ff & NOTE_EXEC) ? 'e' : '-',
127 (ff & NOTE_SIGNAL) ? 's' : '-',
128 (ff & NOTE_REAP) ? 'r' : '-'
129 );
130 break;
131#pragma clang diagnostic pop
132 }
133
134 case EVFILT_TIMER: {
135 snprintf(str, len, "%c%c%c%c%c ",
136 (ff & NOTE_SECONDS) ? 's' :
137 (ff & NOTE_USECONDS) ? 'u' :
138 (ff & NOTE_NSECONDS) ? 'n' :
139 (ff & NOTE_MACHTIME) ? 'm' : '?',
140 (ff & NOTE_ABSOLUTE) ? 'a' :
141 (ff & NOTE_MACH_CONTINUOUS_TIME) ? 'A' : '-',
142 (ff & NOTE_CRITICAL) ? 'c' : '-',
143 (ff & NOTE_BACKGROUND) ? 'b' : '-',
144 (ff & NOTE_LEEWAY) ? 'l' : '-'
145 );
146 break;
147 }
148
149 case EVFILT_USER:
150 snprintf(str, len, "%c%c%c ",
151 (ff & NOTE_TRIGGER) ? 't' : '-',
152 (ff & NOTE_FFAND) ? 'a' : '-',
153 (ff & NOTE_FFOR) ? 'o' : '-'
154 );
155 break;
156
157 case EVFILT_WORKLOOP:
158 snprintf(str, len, "%c%c%c%c%c ",
159 (ff & NOTE_WL_THREAD_REQUEST) ? 't' :
160 (ff & NOTE_WL_SYNC_WAIT) ? 'w' :
161 (ff & NOTE_WL_SYNC_IPC) ? 'i' : '-',
162 (ff & NOTE_WL_SYNC_WAKE) ? 'W' : '-',
163 (ff & NOTE_WL_UPDATE_QOS) ? 'q' : '-',
164 (ff & NOTE_WL_DISCOVER_OWNER) ? 'o' : '-',
165 (ff & NOTE_WL_IGNORE_ESTALE) ? 'e' : '-'
166 );
167 break;
168
169 default:
170 snprintf(str, len, "");
171 break;
172 };
173
174 return str;
175}
176
177
178static inline int
179filter_is_fd_type(int filter)
180{
181 switch (filter) {
182 case EVFILT_VNODE ... EVFILT_READ:
183 case EVFILT_SOCK:
184 case EVFILT_NW_CHANNEL:
185 return 1;
186 default:
187 return 0;
188 }
189}
190
191static const char *
192thread_qos_name(uint8_t th_qos)
193{
194 switch (th_qos) {
195 case 0: return "--";
196 case 1: return "MT";
197 case 2: return "BG";
198 case 3: return "UT";
199 case 4: return "DF";
200 case 5: return "IN";
201 case 6: return "UI";
202 case 7: return "MG";
203 default: return "??";
204 }
205}
206
207/*
208 * find index of fd in a list of fdinfo of length nfds
209 */
210static inline int
211fd_list_getfd(struct proc_fdinfo *fds, int nfds, int fd)
212{
213 int i;
214 for (i = 0; i < nfds; i++) {
215 if (fds[i].proc_fd == fd) {
216 return i;
217 }
218 }
219
220 return -1;
221}
222
223/*
224 * left truncate URL-form process names
225 */
226static const char *
227shorten_procname(const char *proc, int width)
228{
229 if (strcasestr(proc, "com.") == proc) {
230 long len = strlen(proc);
231 if (len > width) {
232 return &proc[len - width];
233 } else {
234 return proc;
235 }
236 } else {
237 return proc;
238 }
239}
240
241/*
242 * stringify knote ident where possible (signals, processes)
243 */
244static void
245print_ident(uint64_t ident, int16_t filter, int width)
246{
247 if (raw) {
248 printf("%#*llx ", width, ident);
249 return;
250 }
251
252 switch (filter) {
253
254 case EVFILT_SIGNAL:
255 case EVFILT_PROC: {
256 char str[128] = "";
257 char num[128];
258 char out[128];
259 int numlen = sprintf(num, "%llu", ident);
260 int strwidth = width - numlen - 1; // add room for a space
261
262 if (filter == EVFILT_SIGNAL) {
263 if (ident < ARRAYLEN(sig_strs)) {
264 snprintf(str, strwidth + 1, "%s", sig_strs[ident]);
265 }
266 } else {
267 /* FIXME: this should be cached */
268 struct proc_bsdinfo bsdinfo;
269 int ret = proc_pidinfo((int)ident, PROC_PIDTBSDINFO, 0, &bsdinfo, sizeof(bsdinfo));
270 if (ret == sizeof(bsdinfo)) {
271 char *procname = bsdinfo.pbi_name;
272 if (strlen(procname) == 0) {
273 procname = bsdinfo.pbi_comm;
274 }
275 snprintf(str, strwidth + 1, "%s", shorten_procname(procname, strwidth));
276 }
277 }
278
279 if (str[0] != '\0') {
280 snprintf(out, width + 1, "%-*s %s", strwidth, str, num);
281 } else {
282 snprintf(out, width + 1, "%s", num);
283 }
284
285 printf("%*s ", width, out);
286 break;
287 }
288
289 case EVFILT_MACHPORT:
290 case EVFILT_TIMER:
291 /* hex, to match lsmp */
292 printf("%#*llx ", width, ident);
293 break;
294
295 case EVFILT_WORKLOOP:
296 printf("%#*llx ", width, ident);
297 break;
298
299 default:
300 printf("%*llu ", width, ident);
301 break;
302 }
303
304}
305
306static void
307print_kqid(int state, uint64_t kqid)
308{
309 if (state & KQ_WORKQ) {
310 printf("%18s ", "wq");
311 } else if (state & KQ_WORKLOOP) {
312 printf("%#18" PRIx64 " ", kqid);
313 } else {
314 printf("fd %15" PRIi64 " ", kqid);
315 }
316}
317
318#define PROCNAME_WIDTH 20
319
320static void
321print_kq_info(int pid, const char *procname, uint64_t kqid, int state)
322{
323 if (raw) {
324 printf("%5u ", pid);
325 print_kqid(state, kqid);
326 printf("%#10x ", state);
327 } else {
328 char tmpstr[PROCNAME_WIDTH+1];
329 strlcpy(tmpstr, shorten_procname(procname, PROCNAME_WIDTH), PROCNAME_WIDTH+1);
330 printf("%-*s ", PROCNAME_WIDTH, tmpstr);
331 printf("%5u ", pid);
332 print_kqid(state, kqid);
333 printf(" %c%c%c ",
334 (state & KQ_SLEEP) ? 'k' : '-',
335 (state & KQ_SEL) ? 's' : '-',
336 (state & KQ_WORKQ) ? 'q' :
337 (state & KQ_WORKLOOP) ? 'l' : '-'
338 );
339 }
340}
341
342enum kqtype {
343 KQTYPE_FD,
344 KQTYPE_DYNAMIC
345};
346
347#define POLICY_TIMESHARE 1
348#define POLICY_RR 2
349#define POLICY_FIFO 4
350
351static int
352process_kqueue(int pid, const char *procname, enum kqtype type, uint64_t kqid,
353 struct proc_fdinfo *fdlist, int nfds)
354{
355 int ret, i, nknotes;
356 char tmpstr[256];
357 int maxknotes = 256; /* arbitrary starting point */
358 int kq_state;
359 bool is_kev_64, is_kev_qos;
360 int err = 0;
361 bool overflow = false;
362 int fd;
363 bool dynkq_printed = false;
364
365 /*
366 * get the basic kqueue info
367 */
368 struct kqueue_fdinfo kqfdinfo = {};
369 struct kqueue_dyninfo kqinfo = {};
370 switch (type) {
371 case KQTYPE_FD:
372 ret = proc_pidfdinfo(pid, (int)kqid, PROC_PIDFDKQUEUEINFO, &kqfdinfo, sizeof(kqfdinfo));
373 fd = (int)kqid;
374 break;
375 case KQTYPE_DYNAMIC:
376 ret = proc_piddynkqueueinfo(pid, PROC_PIDDYNKQUEUE_INFO, kqid, &kqinfo, sizeof(kqinfo));
377 break;
378 default:
379 os_crash("invalid kqueue type");
380 }
381
382 if (type == KQTYPE_FD && (int)kqid != -1) {
383 if (ret != sizeof(kqfdinfo)) {
384 /* every proc has an implicit workq kqueue, dont warn if its unused */
385 fprintf(stderr, "WARN: FD table changed (pid %i, kq %i)\n", pid,
386 fd);
387 }
388 } else if (type == KQTYPE_DYNAMIC) {
389 if (ret < sizeof(struct kqueue_info)) {
390 fprintf(stderr, "WARN: kqueue missing (pid %i, kq %#" PRIx64 ")\n",
391 pid, kqid);
392 } else {
393 kqfdinfo.kqueueinfo = kqinfo.kqdi_info;
394 }
395 if (verbose && ret >= sizeof(struct kqueue_dyninfo)) {
396 print_kq_info(pid, procname, kqid, kqinfo.kqdi_info.kq_state);
397
398 if (kqinfo.kqdi_owner) {
399 printf("%#18llx ", kqinfo.kqdi_owner); // ident
400 printf("%-9s ", "WL owned"); // filter
401 } else if (kqinfo.kqdi_servicer) {
402 printf("%#18llx ", kqinfo.kqdi_servicer); // ident
403 printf("%-9s ", "WL"); // filter
404 } else {
405 printf("%18s ", "-"); // ident
406 printf("%-9s ", "WL"); // filter
407 }
408 dynkq_printed = true;
409
410 if (raw) {
411 printf("%-10s ", " "); // fflags
412 printf("%-10s ", " "); // flags
413 printf("%-10s ", " "); // evst
414 } else {
415 const char *reqstate = "???";
416
417 switch (kqinfo.kqdi_request_state) {
418 case WORKQ_TR_STATE_IDLE:
419 reqstate = "";
420 break;
421 case WORKQ_TR_STATE_NEW:
422 reqstate = "new";
423 break;
424 case WORKQ_TR_STATE_QUEUED:
425 reqstate = "queued";
426 break;
427 case WORKQ_TR_STATE_CANCELED:
428 reqstate = "canceled";
429 break;
430 case WORKQ_TR_STATE_BINDING:
431 reqstate = "binding";
432 break;
433 case WORKQ_TR_STATE_BOUND:
434 reqstate = "bound";
435 break;
436 }
437
438 printf("%-8s ", reqstate); // fdtype
439 char policy_type;
440 switch (kqinfo.kqdi_pol) {
441 case POLICY_RR:
442 policy_type = 'R';
443 break;
444 case POLICY_FIFO:
445 policy_type = 'F';
446 case POLICY_TIMESHARE:
447 case 0:
448 default:
449 policy_type = '-';
450 break;
451 }
452 snprintf(tmpstr, 4, "%c%c%c", (kqinfo.kqdi_pri == 0)?'-':'P', policy_type, (kqinfo.kqdi_cpupercent == 0)?'-':'%');
453 printf("%-7s ", tmpstr); // fflags
454 printf("%-15s ", " "); // flags
455 printf("%-15s ", " "); // evst
456 }
457
458 if (!raw && kqinfo.kqdi_pri != 0) {
459 printf("%3d ", kqinfo.kqdi_pri); //qos
460 } else {
461 int qos = MAX(MAX(kqinfo.kqdi_events_qos, kqinfo.kqdi_async_qos),
462 kqinfo.kqdi_sync_waiter_qos);
463 printf("%3s ", thread_qos_name(qos)); //qos
464 }
465 printf("\n");
466 }
467 }
468
469 /*
470 * get extended kqueue info
471 */
472 struct kevent_extinfo *kqextinfo = NULL;
473 again:
474 if (!kqextinfo) {
475 kqextinfo = malloc(sizeof(struct kevent_extinfo) * maxknotes);
476 }
477 if (!kqextinfo) {
478 err = errno;
479 perror("failed allocating memory");
480 goto out;
481 }
482
483 errno = 0;
484 switch (type) {
485 case KQTYPE_FD:
486 nknotes = proc_pidfdinfo(pid, fd, PROC_PIDFDKQUEUE_EXTINFO,
487 kqextinfo, sizeof(struct kevent_extinfo) * maxknotes);
488 break;
489 case KQTYPE_DYNAMIC:
490 nknotes = proc_piddynkqueueinfo(pid, PROC_PIDDYNKQUEUE_EXTINFO, kqid,
491 kqextinfo, sizeof(struct kevent_extinfo) * maxknotes);
492 break;
493 default:
494 os_crash("invalid kqueue type");
495 }
496
497 if (nknotes <= 0) {
498 if (errno == 0) {
499 /* proc_*() can't distinguish between error and empty list */
500 } else if (errno == EAGAIN) {
501 goto again;
502 } else if (errno == EBADF) {
503 fprintf(stderr, "WARN: FD table changed (pid %i, kq %#" PRIx64 ")\n", pid, kqid);
504 goto out;
505 } else {
506 err = errno;
507 perror("failed to get extended kqueue info");
508 goto out;
509 }
510 }
511
512 if (nknotes > maxknotes) {
513 maxknotes = nknotes + 16; /* arbitrary safety margin */
514 free(kqextinfo);
515 kqextinfo = NULL;
516 goto again;
517 }
518
519 if (nknotes >= PROC_PIDFDKQUEUE_KNOTES_MAX) {
520 overflow = true;
521 }
522
523 kq_state = kqfdinfo.kqueueinfo.kq_state;
524 is_kev_64 = (kq_state & PROC_KQUEUE_64);
525 is_kev_qos = (kq_state & PROC_KQUEUE_QOS);
526
527 if (nknotes == 0) {
528 if (!ignore_empty && !dynkq_printed) {
529 /* for empty kqueues, print a single empty entry */
530 print_kq_info(pid, procname, kqid, kq_state);
531 printf("%18s \n", "-");
532 }
533 goto out;
534 }
535
536 for (i = 0; i < nknotes; i++) {
537 struct kevent_extinfo *info = &kqextinfo[i];
538
539 print_kq_info(pid, procname, kqid, kqfdinfo.kqueueinfo.kq_state);
540 print_ident(info->kqext_kev.ident, info->kqext_kev.filter, 18);
541 printf("%-9s ", filt_name(info->kqext_kev.filter));
542
543 if (raw) {
544 printf("%#10x ", info->kqext_sfflags);
545 printf("%#10x ", info->kqext_kev.flags);
546 printf("%#10x ", info->kqext_status);
547 } else {
548 /* for kevents attached to file descriptors, print the type of FD (file, socket, etc) */
549 const char *fdstr = "";
550 if (filter_is_fd_type(info->kqext_kev.filter)) {
551 fdstr = "<unkn>";
552 int knfd = fd_list_getfd(fdlist, nfds, (int)info->kqext_kev.ident);
553 if (knfd >= 0) {
554 fdstr = fdtype_str(fdlist[knfd].proc_fdtype);
555 }
556 }
557 printf("%-8s ", fdstr);
558
559 /* print filter flags */
560 printf("%7s ", fflags_build(info, tmpstr, sizeof(tmpstr)));
561
562 /* print generic flags */
563 unsigned flg = info->kqext_kev.flags;
564 printf("%c%c%c%c %c%c%c%c %c%c%c%c%c ",
565 (flg & EV_ADD) ? 'a' : '-',
566 (flg & EV_ENABLE) ? 'n' : '-',
567 (flg & EV_DISABLE) ? 'd' : '-',
568 (flg & EV_DELETE) ? 'x' : '-',
569
570 (flg & EV_RECEIPT) ? 'r' : '-',
571 (flg & EV_ONESHOT) ? '1' : '-',
572 (flg & EV_CLEAR) ? 'c' : '-',
573 (flg & EV_DISPATCH) ? 's' : '-',
574
575 (flg & EV_UDATA_SPECIFIC) ? 'u' : '-',
576 (flg & EV_FLAG0) ? 'p' : '-',
577 (flg & EV_FLAG1) ? 'b' : '-',
578 (flg & EV_EOF) ? 'o' : '-',
579 (flg & EV_ERROR) ? 'e' : '-'
580 );
581
582 unsigned st = info->kqext_status;
583 printf("%c%c%c%c%c %c%c%c%c %c%c%c ",
584 (st & KN_ACTIVE) ? 'a' : '-',
585 (st & KN_QUEUED) ? 'q' : '-',
586 (st & KN_DISABLED) ? 'd' : '-',
587 (st & KN_SUPPRESSED) ? 'p' : '-',
588 (st & KN_STAYACTIVE) ? 's' : '-',
589
590 (st & KN_DROPPING) ? 'd' : '-',
591 (st & KN_LOCKED) ? 'l' : '-',
592 (st & KN_POSTING) ? 'P' : '-',
593 (st & KN_MERGE_QOS) ? 'm' : '-',
594
595 (st & KN_DEFERDELETE) ? 'D' : '-',
596 (st & KN_REQVANISH) ? 'v' : '-',
597 (st & KN_VANISHED) ? 'n' : '-'
598 );
599 }
600
601 printf("%3s ", thread_qos_name(info->kqext_kev.qos));
602
603 printf("%#18llx ", (unsigned long long)info->kqext_kev.data);
604
605 if (verbose) {
606 printf("%#18llx ", (unsigned long long)info->kqext_kev.udata);
607 if (is_kev_qos || is_kev_64) {
608 printf("%#18llx ", (unsigned long long)info->kqext_kev.ext[0]);
609 printf("%#18llx ", (unsigned long long)info->kqext_kev.ext[1]);
610
611 if (is_kev_qos) {
612 printf("%#18llx ", (unsigned long long)info->kqext_kev.ext[2]);
613 printf("%#18llx ", (unsigned long long)info->kqext_kev.ext[3]);
614 printf("%#10lx ", (unsigned long)info->kqext_kev.xflags);
615 }
616 }
617 }
618
619 printf("\n");
620 }
621
622 if (overflow) {
623 printf(" ***** output truncated (>=%i knotes on kq %" PRIu64 ", proc %i) *****\n",
624 nknotes, kqid, pid);
625 }
626
627 out:
628 if (kqextinfo) {
629 free(kqextinfo);
630 kqextinfo = NULL;
631 }
632
633 return err;
634}
635
636static int
637pid_kqids(pid_t pid, kqueue_id_t **kqids_out)
638{
639 static int kqids_len = 256;
640 static kqueue_id_t *kqids = NULL;
641 static uint32_t kqids_size;
642
643 int nkqids;
644
645retry:
646 if (os_mul_overflow(sizeof(kqueue_id_t), kqids_len, &kqids_size)) {
647 assert(kqids_len > PROC_PIDDYNKQUEUES_MAX);
648 kqids_len = PROC_PIDDYNKQUEUES_MAX;
649 goto retry;
650 }
651 if (!kqids) {
652 kqids = malloc(kqids_size);
653 os_assert(kqids != NULL);
654 }
655
656 nkqids = proc_list_dynkqueueids(pid, kqids, kqids_size);
657 if (nkqids > kqids_len && kqids_len < PROC_PIDDYNKQUEUES_MAX) {
658 kqids_len *= 2;
659 if (kqids_len > PROC_PIDDYNKQUEUES_MAX) {
660 kqids_len = PROC_PIDDYNKQUEUES_MAX;
661 }
662 free(kqids);
663 kqids = NULL;
664 goto retry;
665 }
666
667 *kqids_out = kqids;
668 return MIN(nkqids, kqids_len);
669}
670
671static int
672process_pid(pid_t pid)
673{
674 int i, nfds, nkqids;
675 kqueue_id_t *kqids;
676 int ret = 0;
677 int maxfds = 256; /* arbitrary starting point */
678 struct proc_fdinfo *fdlist = NULL;
679
680 /* enumerate file descriptors */
681 again:
682 if (!fdlist) {
683 fdlist = malloc(sizeof(struct proc_fdinfo) * maxfds);
684 }
685 if (!fdlist) {
686 ret = errno;
687 perror("failed to allocate");
688 goto out;
689 }
690
691 nfds = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, fdlist,
692 sizeof(struct proc_fdinfo) * maxfds);
693 if (nfds <= 0) {
694 ret = errno;
695 fprintf(stderr, "%s: failed enumerating file descriptors of process %i: %s",
696 self, pid, strerror(ret));
697 if (ret == EPERM && geteuid() != 0) {
698 fprintf(stderr, " (are you root?)");
699 }
700 fprintf(stderr, "\n");
701 goto out;
702 }
703
704 nfds /= sizeof(struct proc_fdinfo);
705 if (nfds >= maxfds) {
706 maxfds = nfds + 16;
707 free(fdlist);
708 fdlist = NULL;
709 goto again;
710 }
711
712 /* get bsdinfo for the process name */
713 struct proc_bsdinfo bsdinfo;
714 ret = proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &bsdinfo, sizeof(bsdinfo));
715 if (ret != sizeof(bsdinfo)) {
716 perror("failed retrieving process info");
717 ret = -1;
718 goto out;
719 }
720
721 char *procname = bsdinfo.pbi_name;
722 if (strlen(procname) == 0) {
723 procname = bsdinfo.pbi_comm;
724 }
725
726 /* handle the special workq kq */
727 ret = process_kqueue(pid, procname, KQTYPE_FD, -1, fdlist, nfds);
728 if (ret) {
729 goto out;
730 }
731
732 for (i = 0; i < nfds; i++) {
733 if (fdlist[i].proc_fdtype == PROX_FDTYPE_KQUEUE) {
734 ret = process_kqueue(pid, procname, KQTYPE_FD,
735 (uint64_t)fdlist[i].proc_fd, fdlist, nfds);
736 if (ret) {
737 goto out;
738 }
739 }
740 }
741
742 nkqids = pid_kqids(pid, &kqids);
743
744 for (i = 0; i < nkqids; i++) {
745 ret = process_kqueue(pid, procname, KQTYPE_DYNAMIC, kqids[i], fdlist, nfds);
746 if (ret) {
747 goto out;
748 }
749 }
750
751 if (nkqids >= PROC_PIDDYNKQUEUES_MAX) {
752 printf(" ***** output truncated (>=%i dynamic kqueues in proc %i) *****\n",
753 nkqids, pid);
754 }
755
756 out:
757 if (fdlist) {
758 free(fdlist);
759 fdlist = NULL;
760 }
761
762 return ret;
763}
764
765static int
766process_all_pids(void)
767{
768 int i, npids;
769 int ret = 0;
770 int maxpids = 2048;
771 int *pids = NULL;
772
773 again:
774 if (!pids) {
775 pids = malloc(sizeof(int) * maxpids);
776 }
777 if (!pids) {
778 perror("failed allocating pids[]");
779 goto out;
780 }
781
782 errno = 0;
783 npids = proc_listpids(PROC_ALL_PIDS, 0, pids, sizeof(int) * maxpids);
784 if (npids <= 0) {
785 if (errno == 0) {
786 /* empty pid list */
787 } else if (errno == EAGAIN) {
788 goto again;
789 } else {
790 ret = errno;
791 perror("failed enumerating pids");
792 goto out;
793 }
794 }
795
796 npids /= sizeof(int);
797 if (npids >= maxpids) {
798 maxpids = npids + 16;
799 free(pids);
800 pids = NULL;
801 goto again;
802 }
803
804 for (i = 0; i < npids; i++) {
805 /* listpids gives us pid 0 for some reason */
806 if (pids[i]) {
807 ret = process_pid(pids[i]);
808 /* ignore races with processes exiting */
809 if (ret && ret != ESRCH) {
810 goto out;
811 }
812 }
813 }
814
815out:
816 if (pids) {
817 free(pids);
818 pids = NULL;
819 }
820
821 return ret;
822}
823
824static void
825cheatsheet(void)
826{
827 const char *bold = "\033[1m";
828 const char *reset = "\033[0m";
829 if (!isatty(STDERR_FILENO)) {
830 bold = reset = "";
831 }
832
833 fprintf(stderr, "\nFilter-independent flags:\n\n\
834%s\
835command pid kq kqst knid filter fdtype fflags flags evst qos%s\n%s\
836-------------------- ----- ------------------ ---- ------------------ --------- -------- ------- --------------- -------------- ---%s\n\
837 ┌ EV_UDATA_SPECIFIC\n\
838 EV_DISPATCH ┐ │┌ EV_FLAG0 (EV_POLL)\n\
839 EV_CLEAR ┐│ ││┌ EV_FLAG1 (EV_OOBAND)\n\
840 EV_ONESHOT ┐││ │││┌ EV_EOF\n\
841 EV_RECEIPT ┐│││ ││││┌ EV_ERROR\n\
842 ││││ │││││\n%s\
843launchd 1 4 ks- netbiosd 250 PROC ------- andx r1cs upboe aqdps dlPm Dvn IN%s\n\
844 │ │││ ││││ │││││ ││││ │││\n\
845 kqueue file descriptor/dynamic ID ┘ │││ EV_ADD ┘│││ KN_ACTIVE ┘││││ ││││ ││└ KN_VANISHED\n\
846 KQ_SLEEP ┘││ EV_ENABLE ┘││ KN_QUEUED ┘│││ ││││ │└ KN_REQVANISH\n\
847 KQ_SEL ┘│ EV_DISABLE ┘│ KN_DISABLED ┘││ ││││ └ KN_DEFERDELETE\n\
848 KQ_WORKQ (q) ┤ EV_DELETE ┘ KN_SUPPRESSED ┘│ ││││\n\
849 KQ_WORKLOOP (l) ┘ KN_STAYACTIVE ┘ ││││\n\
850 ││││\n\
851 KN_DROPPING ┘││└ KN_MERGE_QOS\n\
852 KN_LOCKED ┘└ KN_POSTING\n\
853 \n", bold, reset, bold, reset, bold, reset);
854}
855
856static void
857usage(void)
858{
859 fprintf(stderr, "usage: %s [-vher] [-a | -p <pid>]\n", self);
860}
861
862static void
863print_header(void)
864{
865 if (raw) {
866 printf(" pid kq kqst knid filter fflags flags evst qos data");
867 } else {
868 printf("command pid kq kqst knid filter fdtype fflags flags evst qos data");
869 }
870
871 if (verbose) {
872 printf(" udata ext0 ext1 ext2 ext3 xflags");
873 }
874
875 printf("\n");
876
877 if (raw) {
878 printf("----- ------------------ ---------- ------------------ --------- ---------- ---------- ---------- --- ------------------");
879 } else {
880 printf("-------------------- ----- ------------------ ---- ------------------ --------- -------- ------- --------------- -------------- --- ------------------");
881 }
882
883 if (verbose) {
884 printf(" ------------------ ------------------ ------------------ ------------------ ------------------ ----------");
885 }
886 printf("\n");
887}
888
889int
890main(int argc, char *argv[])
891{
892 pid_t pid = 0;
893 int opt;
894
895 setlinebuf(stdout);
896
897 if (argc > 0) {
898 self = argv[0];
899 }
900
901 while ((opt = getopt(argc, argv, "eahvrp:")) != -1) {
902 switch (opt) {
903 case 'a':
904 all_pids = 1;
905 break;
906 case 'v':
907 verbose++;
908 break;
909 case 'p':
910 pid = atoi(optarg);
911 break;
912 case 'e':
913 ignore_empty = 1;
914 break;
915 case 'h':
916 usage();
917 cheatsheet();
918 return 0;
919 case 'r':
920 raw = 1;
921 break;
922 case '?':
923 default:
924 usage();
925 return 1;
926 }
927 }
928
929 argc -= optind;
930 argv += optind;
931
932 if (argc == 1) {
933 /* also allow lskq <pid> */
934 if (pid || all_pids) {
935 usage();
936 return 1;
937 }
938
939 pid = atoi(argv[0]);
940 } else if (argc > 1) {
941 usage();
942 return 1;
943 }
944
945 /* exactly one of -p or -a is required */
946 if (!pid && !all_pids) {
947 usage();
948 return 1;
949 } else if (pid && all_pids) {
950 usage();
951 return 1;
952 }
953
954 print_header();
955
956 if (all_pids) {
957 return process_all_pids();
958 } else {
959 return process_pid(pid);
960 }
961
962 return 0;
963}