]> git.saurik.com Git - apple/system_cmds.git/blob - lskq.tproj/lskq.c
6a3c74da2b387155523f28406e169f51b201cb68
[apple/system_cmds.git] / lskq.tproj / lskq.c
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 <stdio.h>
25 #include <stdlib.h>
26 #include <unistd.h>
27 #include <strings.h>
28 #include <assert.h>
29 #include <errno.h>
30
31 #include <sys/types.h>
32 #include <sys/event.h>
33 #include <sys/time.h>
34 #include <sys/proc_info.h>
35 #include <mach/message.h>
36 #include <libproc.h>
37
38 #include "common.h"
39
40 #define ARRAYLEN(x) (sizeof((x))/sizeof((x[0])))
41
42 /* command line options */
43 static int verbose;
44 static int all_pids;
45 static int ignore_empty;
46 static int raw;
47
48 static char *self = "lskq";
49
50 static inline const char *
51 filt_name(int16_t filt)
52 {
53 static char unkn_filt[32];
54 int idx = -filt;
55 if (idx >= 0 && idx < ARRAYLEN(filt_strs)) {
56 return filt_strs[idx];
57 } else {
58 snprintf(unkn_filt, sizeof(unkn_filt), "%i (?)", idx);
59 return unkn_filt;
60 }
61 }
62
63 static inline const char *
64 fdtype_str(uint32_t type)
65 {
66 static char unkn_fdtype[32];
67 if (type < ARRAYLEN(fdtype_strs)) {
68 return fdtype_strs[type];
69 } else {
70 snprintf(unkn_fdtype, sizeof(unkn_fdtype), "%i (?)", type);
71 return unkn_fdtype;
72 }
73 }
74
75 static char *
76 fflags_build(struct kevent_extinfo *info, char *str, int len)
77 {
78 unsigned ff = info->kqext_sfflags;
79
80 switch (info->kqext_kev.filter) {
81
82 case EVFILT_READ: {
83 snprintf(str, len, "%c ",
84 (ff & NOTE_LOWAT) ? 'l' : '-'
85 );
86 break;
87 }
88
89 case EVFILT_MACHPORT: {
90 snprintf(str, len, "%c ",
91 (ff & MACH_RCV_MSG) ? 'r' : '-'
92 );
93 break;
94 }
95
96 case EVFILT_VNODE: {
97 snprintf(str, len, "%c%c%c%c%c%c%c",
98 (ff & NOTE_DELETE) ? 'd' : '-',
99 (ff & NOTE_WRITE) ? 'w' : '-',
100 (ff & NOTE_EXTEND) ? 'e' : '-',
101 (ff & NOTE_ATTRIB) ? 'a' : '-',
102 (ff & NOTE_LINK) ? 'l' : '-',
103 (ff & NOTE_RENAME) ? 'r' : '-',
104 (ff & NOTE_REVOKE) ? 'v' : '-'
105 );
106 break;
107 }
108
109 case EVFILT_PROC: {
110 /* NOTE_REAP is deprecated, but we still want to show if it's used */
111 #pragma clang diagnostic push
112 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
113 snprintf(str, len, "%c%c%c%c%c%c%c",
114 (ff & NOTE_EXIT) ? 'x' : '-',
115 (ff & NOTE_EXITSTATUS) ? 't' : '-',
116 (ff & NOTE_EXIT_DETAIL)? 'd' : '-',
117 (ff & NOTE_FORK) ? 'f' : '-',
118 (ff & NOTE_EXEC) ? 'e' : '-',
119 (ff & NOTE_SIGNAL) ? 's' : '-',
120 (ff & NOTE_REAP) ? 'r' : '-'
121 );
122 break;
123 #pragma clang diagnostic pop
124 }
125
126 case EVFILT_TIMER: {
127 snprintf(str, len, "%c%c%c%c%c ",
128 (ff & NOTE_SECONDS) ? 's' :
129 (ff & NOTE_USECONDS) ? 'u' :
130 (ff & NOTE_NSECONDS) ? 'n' : '?',
131 (ff & NOTE_ABSOLUTE) ? 'a' : '-',
132 (ff & NOTE_CRITICAL) ? 'c' : '-',
133 (ff & NOTE_BACKGROUND) ? 'b' : '-',
134 (ff & NOTE_LEEWAY) ? 'l' : '-'
135 );
136 break;
137 }
138
139 case EVFILT_USER: {
140 snprintf(str, len, "%c%c%c ",
141 (ff & NOTE_TRIGGER) ? 't' : '-',
142 (ff & NOTE_FFAND) ? 'a' : '-',
143 (ff & NOTE_FFOR) ? 'o' : '-'
144 );
145 break;
146 }
147
148 default:
149 snprintf(str, len, "");
150 break;
151 };
152
153 return str;
154 }
155
156
157 static inline int
158 filter_is_fd_type(int filter)
159 {
160 if (filter <= EVFILT_READ && filter >= EVFILT_VNODE) {
161 return 1;
162 } else {
163 return 0;
164 }
165 }
166
167 /*
168 * find index of fd in a list of fdinfo of length nfds
169 */
170 static inline int
171 fd_list_getfd(struct proc_fdinfo *fds, int nfds, int fd)
172 {
173 int i;
174 for (i = 0; i < nfds; i++) {
175 if (fds[i].proc_fd == fd) {
176 return i;
177 }
178 }
179
180 return -1;
181 }
182
183 /*
184 * left truncate URL-form process names
185 */
186 static const char *
187 shorten_procname(const char *proc, int width)
188 {
189 if (strcasestr(proc, "com.") == proc) {
190 long len = strlen(proc);
191 if (len > width) {
192 return &proc[len - width];
193 } else {
194 return proc;
195 }
196 } else {
197 return proc;
198 }
199 }
200
201 /*
202 * stringify knote ident where possible (signals, processes)
203 */
204 static void
205 print_ident(uint64_t ident, int16_t filter, int width)
206 {
207 if (raw) {
208 printf("%#*llx ", width, ident);
209 return;
210 }
211
212 switch (filter) {
213
214 case EVFILT_SIGNAL:
215 case EVFILT_PROC: {
216 char str[128] = "";
217 char num[128];
218 char out[128];
219 int numlen = sprintf(num, "%llu", ident);
220 int strwidth = width - numlen - 1; // add room for a space
221
222 if (filter == EVFILT_SIGNAL) {
223 if (ident < ARRAYLEN(sig_strs)) {
224 snprintf(str, strwidth + 1, "%s", sig_strs[ident]);
225 }
226 } else {
227 /* FIXME: this should be cached */
228 struct proc_bsdinfo bsdinfo;
229 int ret = proc_pidinfo((int)ident, PROC_PIDTBSDINFO, 0, &bsdinfo, sizeof(bsdinfo));
230 if (ret == sizeof(bsdinfo)) {
231 char *procname = bsdinfo.pbi_name;
232 if (strlen(procname) == 0) {
233 procname = bsdinfo.pbi_comm;
234 }
235 snprintf(str, strwidth + 1, "%s", shorten_procname(procname, strwidth));
236 }
237 }
238
239 if (str[0] != '\0') {
240 snprintf(out, width + 1, "%-*s %s", strwidth, str, num);
241 } else {
242 snprintf(out, width + 1, "%s", num);
243 }
244
245 printf("%*s ", width, out);
246 break;
247 }
248
249 case EVFILT_MACHPORT:
250 case EVFILT_TIMER:
251 /* hex, to match lsmp */
252 printf("%#*llx ", width, ident);
253 break;
254
255 default:
256 printf("%*llu ", width, ident);
257 break;
258 }
259
260 }
261
262 static void
263 print_kqfd(int kqfd, int width)
264 {
265 if (kqfd == -1) {
266 printf("%*s ", width, "wq");
267 } else {
268 printf("%*u ", width, kqfd);
269 }
270 }
271
272 #define PROCNAME_WIDTH 20
273
274 static void
275 print_kq_info(int pid, const char *procname, int kqfd, int state)
276 {
277 if (raw) {
278 printf("%5u ", pid);
279 print_kqfd(kqfd, 5);
280 printf("%#10x ", state);
281 } else {
282 char tmpstr[PROCNAME_WIDTH+1];
283 strlcpy(tmpstr, shorten_procname(procname, PROCNAME_WIDTH), PROCNAME_WIDTH+1);
284 printf("%-*s ", PROCNAME_WIDTH, tmpstr);
285 printf("%5u ", pid);
286 print_kqfd(kqfd, 5);
287 printf(" %c%c%c ",
288 (state & KQ_SLEEP) ? 'k' : '-',
289 (state & KQ_SEL) ? 's' : '-',
290 (state & KQ_KEV32) ? '3' :
291 (state & KQ_KEV64) ? '6' :
292 (state & KQ_KEV_QOS) ? 'q' : '-'
293 );
294 }
295 }
296
297 static int
298 process_kqueue_on_fd(int pid, const char *procname, int kqfd, struct proc_fdinfo *fdlist, int nfds)
299 {
300 int ret, i, nknotes;
301 char tmpstr[256];
302 int maxknotes = 256; /* arbitrary starting point */
303 int err = 0;
304 bool overflow = false;
305
306 /*
307 * get the basic kqueue info
308 */
309 struct kqueue_fdinfo kqfdinfo = {};
310 ret = proc_pidfdinfo(pid, kqfd, PROC_PIDFDKQUEUEINFO, &kqfdinfo, sizeof(kqfdinfo));
311 if (ret != sizeof(kqfdinfo) && kqfd != -1) {
312 /* every proc has an implicit workq kqueue, dont warn if its unused */
313 fprintf(stderr, "WARN: FD table changed (pid %i, kq %i)\n", pid, kqfd);
314 }
315
316 /*
317 * get extended kqueue info
318 */
319 struct kevent_extinfo *kqextinfo = NULL;
320 again:
321 if (!kqextinfo) {
322 kqextinfo = malloc(sizeof(struct kevent_extinfo) * maxknotes);
323 }
324 if (!kqextinfo) {
325 perror("failed allocating memory");
326 err = errno;
327 goto out;
328 }
329
330 errno = 0;
331 nknotes = proc_pidfdinfo(pid, kqfd, PROC_PIDFDKQUEUE_EXTINFO, kqextinfo,
332 sizeof(struct kevent_extinfo) * maxknotes);
333 if (nknotes <= 0) {
334 if (errno == 0) {
335 /* proc_*() can't distinguish between error and empty list */
336 } else if (errno == EAGAIN) {
337 goto again;
338 } else if (errno == EBADF) {
339 fprintf(stderr, "WARN: FD table changed (pid %i, kq %i)\n", pid, kqfd);
340 goto out;
341 } else {
342 perror("failed to get extended kqueue info");
343 err = errno;
344 goto out;
345 }
346 }
347
348 if (nknotes > maxknotes) {
349 maxknotes = nknotes + 16; /* arbitrary safety margin */
350 free(kqextinfo);
351 kqextinfo = NULL;
352 goto again;
353 }
354
355 if (nknotes >= PROC_PIDFDKQUEUE_KNOTES_MAX) {
356 overflow = true;
357 }
358
359 if (nknotes == 0) {
360 if (!ignore_empty) {
361 /* for empty kqueues, print a single empty entry */
362 print_kq_info(pid, procname, kqfd, kqfdinfo.kqueueinfo.kq_state);
363 printf("%18s \n", "-");
364 }
365 goto out;
366 }
367
368 for (i = 0; i < nknotes; i++) {
369 struct kevent_extinfo *info = &kqextinfo[i];
370
371 print_kq_info(pid, procname, kqfd, kqfdinfo.kqueueinfo.kq_state);
372 print_ident(info->kqext_kev.ident, info->kqext_kev.filter, 18);
373 printf("%-9s ", filt_name(info->kqext_kev.filter));
374
375 if (raw) {
376 printf("%#10x ", info->kqext_sfflags);
377 printf("%#10x ", info->kqext_kev.flags);
378 printf("%#10x ", info->kqext_status);
379 } else {
380
381 /* for kevents attached to file descriptors, print the type of FD (file, socket, etc) */
382 const char *fdstr = "";
383 if (filter_is_fd_type(info->kqext_kev.filter)) {
384 fdstr = "<unkn>";
385 int knfd = fd_list_getfd(fdlist, nfds, (int)info->kqext_kev.ident);
386 if (knfd >= 0) {
387 fdstr = fdtype_str(fdlist[knfd].proc_fdtype);
388 }
389 }
390 printf("%-8s ", fdstr);
391
392 /* print filter flags */
393 printf("%7s ", fflags_build(info, tmpstr, sizeof(tmpstr)));
394
395 /* print generic flags */
396 unsigned flg = info->kqext_kev.flags;
397 printf("%c%c%c%c %c%c%c%c %c%c%c%c%c ",
398 (flg & EV_ADD) ? 'a' : '-',
399 (flg & EV_ENABLE) ? 'n' : '-',
400 (flg & EV_DISABLE) ? 'd' : '-',
401 (flg & EV_DELETE) ? 'x' : '-',
402
403 (flg & EV_RECEIPT) ? 'r' : '-',
404 (flg & EV_ONESHOT) ? '1' : '-',
405 (flg & EV_CLEAR) ? 'c' : '-',
406 (flg & EV_DISPATCH) ? 's' : '-',
407
408 (flg & EV_UDATA_SPECIFIC) ? 'u' : '-',
409 (flg & EV_FLAG0) ? 'p' : '-',
410 (flg & EV_FLAG1) ? 'b' : '-',
411 (flg & EV_EOF) ? 'o' : '-',
412 (flg & EV_ERROR) ? 'e' : '-'
413 );
414
415 unsigned st = info->kqext_status;
416 printf("%c%c%c%c %c%c%c%c%c",
417 (st & KN_ACTIVE) ? 'a' : '-',
418 (st & KN_QUEUED) ? 'q' : '-',
419 (st & KN_DISABLED) ? 'd' : '-',
420 (st & KN_STAYQUEUED) ? 's' : '-',
421
422 (st & KN_DROPPING) ? 'o' : '-',
423 (st & KN_USEWAIT) ? 'u' : '-',
424 (st & KN_ATTACHING) ? 'c' : '-',
425 (st & KN_DEFERDROP) ? 'f' : '-',
426 (st & KN_TOUCH) ? 't' : '-'
427 );
428 }
429
430 printf("%#18llx ", (unsigned long long)info->kqext_kev.data);
431
432 if (verbose) {
433 printf("%#18llx ", (unsigned long long)info->kqext_kev.udata);
434 if (kqfdinfo.kqueueinfo.kq_state & (KQ_KEV64|KQ_KEV_QOS)) {
435 printf("%#18llx ", (unsigned long long)info->kqext_kev.ext[0]);
436 printf("%#18llx ", (unsigned long long)info->kqext_kev.ext[1]);
437 }
438 if (kqfdinfo.kqueueinfo.kq_state & KQ_KEV_QOS) {
439 printf("%#18llx ", (unsigned long long)info->kqext_kev.ext[2]);
440 printf("%#18llx ", (unsigned long long)info->kqext_kev.ext[3]);
441 printf("%#10lx ", (unsigned long)info->kqext_kev.xflags);
442 }
443 }
444
445 printf("\n");
446 }
447
448 if (overflow) {
449 printf(" ***** output truncated (>=%i knotes on kq %i, proc %i) *****\n",
450 nknotes, kqfd, pid);
451 }
452
453 out:
454 if (kqextinfo) {
455 free(kqextinfo);
456 kqextinfo = NULL;
457 }
458
459 return err;
460 }
461
462 static int
463 process_pid(pid_t pid)
464 {
465 int i, nfds;
466 int ret = 0;
467 int maxfds = 256; /* arbitrary starting point */
468 struct proc_fdinfo *fdlist = NULL;
469
470 /* enumerate file descriptors */
471 again:
472 if (!fdlist) {
473 fdlist = malloc(sizeof(struct proc_fdinfo) * maxfds);
474 }
475 if (!fdlist) {
476 perror("failed to allocate");
477 ret = errno;
478 goto out;
479 }
480
481 nfds = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, fdlist,
482 sizeof(struct proc_fdinfo) * maxfds);
483 if (nfds <= 0) {
484 fprintf(stderr, "%s: failed enumerating file descriptors of process %i: %s",
485 self, pid, strerror(errno));
486 if (errno == EPERM && geteuid() != 0) {
487 fprintf(stderr, " (are you root?)");
488 }
489 fprintf(stderr, "\n");
490 ret = errno;
491 goto out;
492 }
493
494 nfds /= sizeof(struct proc_fdinfo);
495 if (nfds >= maxfds) {
496 maxfds = nfds + 16;
497 free(fdlist);
498 fdlist = NULL;
499 goto again;
500 }
501
502 /* get bsdinfo for the process name */
503 struct proc_bsdinfo bsdinfo;
504 ret = proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &bsdinfo, sizeof(bsdinfo));
505 if (ret != sizeof(bsdinfo)) {
506 perror("failed retrieving process info");
507 ret = -1;
508 goto out;
509 }
510
511 char *procname = bsdinfo.pbi_name;
512 if (strlen(procname) == 0) {
513 procname = bsdinfo.pbi_comm;
514 }
515
516 /* handle the special workq kq */
517 ret = process_kqueue_on_fd(pid, procname, -1, fdlist, nfds);
518 if (ret) {
519 goto out;
520 }
521
522 for (i = 0; i < nfds; i++) {
523 if (fdlist[i].proc_fdtype == PROX_FDTYPE_KQUEUE) {
524 ret = process_kqueue_on_fd(pid, procname, fdlist[i].proc_fd, fdlist, nfds);
525 if (ret) {
526 goto out;
527 }
528 }
529 }
530
531 out:
532 if (fdlist) {
533 free(fdlist);
534 fdlist = NULL;
535 }
536
537 return ret;
538 }
539
540 static int
541 process_all_pids(void)
542 {
543 int i, npids;
544 int ret = 0;
545 int maxpids = 2048;
546 int *pids = NULL;
547
548 again:
549 if (!pids) {
550 pids = malloc(sizeof(int) * maxpids);
551 }
552 if (!pids) {
553 perror("failed allocating pids[]");
554 goto out;
555 }
556
557 errno = 0;
558 npids = proc_listpids(PROC_ALL_PIDS, 0, pids, sizeof(int) * maxpids);
559 if (npids <= 0) {
560 if (errno == 0) {
561 /* empty pid list */
562 } else if (errno == EAGAIN) {
563 goto again;
564 } else {
565 perror("failed enumerating pids");
566 ret = errno;
567 goto out;
568 }
569 }
570
571 npids /= sizeof(int);
572 if (npids >= maxpids) {
573 maxpids = npids + 16;
574 free(pids);
575 pids = NULL;
576 goto again;
577 }
578
579 for (i = 0; i < npids; i++) {
580 /* listpids gives us pid 0 for some reason */
581 if (pids[i]) {
582 ret = process_pid(pids[i]);
583 if (ret) {
584 goto out;
585 }
586 }
587 }
588
589 out:
590 if (pids) {
591 free(pids);
592 pids = NULL;
593 }
594
595 return ret;
596 }
597
598 static void
599 cheatsheet(void)
600 {
601 fprintf(stderr, "\nFilter-independent flags:\n\n\
602 \033[1mcommand pid kq kqst ident filter fdtype fflags flags evst\033[0m\n\
603 \033[1m-------------------- ----- ----- ---- ------------------ --------- -------- ------- --------------- ----------\033[0m\n\
604 ┌ EV_UDATA_SPECIFIC\n\
605 EV_DISPATCH ┐ │┌ EV_FLAG0 (EV_POLL)\n\
606 EV_CLEAR ┐│ ││┌ EV_FLAG1 (EV_OOBAND)\n\
607 EV_ONESHOT ┐││ │││┌ EV_EOF\n\
608 EV_RECEIPT ┐│││ ││││┌ EV_ERROR\n\
609 ││││ │││││\n\
610 \033[1mlaunchd 1 4 ks- netbiosd 250 PROC ------- andx r1cs upboe aqds oucft\033[0m \n\
611 │ │││ ││││ ││││ │││││\n\
612 kqueue file descriptor ┘ │││ EV_ADD ┘│││ KN_ACTIVE ┘│││ ││││└ KN_TOUCH\n\
613 KQ_SLEEP ┘││ EV_ENABLE ┘││ KN_QUEUED ┘││ │││└ KN_DEFERDROP\n\
614 KQ_SEL ┘│ EV_DISABLE ┘│ KN_DISABLED ┘│ ││└ KN_ATTACHING\n\
615 KEV32 (3) ┤ EV_DELETE ┘ KN_STAYQUEUED ┘ │└ KN_USEWAIT\n\
616 KEV64 (6) ┤ └ KN_DROPPING\n\
617 KEV_QOS (q) ┘\n\
618 \n");
619 }
620
621 static void
622 usage(void)
623 {
624 fprintf(stderr, "usage: %s [-vher] [-a | -p <pid>]\n", self);
625 }
626
627 static void
628 print_header(void)
629 {
630 if (raw) {
631 printf(" pid kq kqst ident filter fflags flags evst data");
632 if (verbose) {
633 printf(" udata ext0 ext1 ext2 ext3 xflags");
634 }
635 printf("\n");
636 printf("----- ----- ---------- ------------------ --------- ---------- ---------- ---------- ------------------");
637
638 } else {
639 printf("command pid kq kqst ident filter fdtype fflags flags evst data");
640 if (verbose) {
641 printf(" udata ext0 ext1 ext2 ext3 xflags");
642 }
643 printf("\n");
644 printf("-------------------- ----- ----- ---- ------------------ --------- -------- ------- --------------- ---------- -----------------");
645 }
646
647 if (verbose) {
648 printf(" ------------------ ------------------ ------------------ ------------------ ------------------ ----------");
649 }
650 printf("\n");
651 }
652
653 int
654 main(int argc, char *argv[])
655 {
656 pid_t pid = 0;
657 int opt;
658
659 setlinebuf(stdout);
660
661 if (argc > 0) {
662 self = argv[0];
663 }
664
665 while ((opt = getopt(argc, argv, "eahvrp:")) != -1) {
666 switch (opt) {
667 case 'a':
668 all_pids = 1;
669 break;
670 case 'v':
671 verbose++;
672 break;
673 case 'p':
674 pid = atoi(optarg);
675 break;
676 case 'e':
677 ignore_empty = 1;
678 break;
679 case 'h':
680 usage();
681 cheatsheet();
682 return 0;
683 case 'r':
684 raw = 1;
685 break;
686 case '?':
687 default:
688 usage();
689 return 1;
690 }
691 }
692
693 argc -= optind;
694 argv += optind;
695
696 if (argc == 1) {
697 /* also allow lskq <pid> */
698 if (pid || all_pids) {
699 usage();
700 return 1;
701 }
702
703 pid = atoi(argv[0]);
704 } else if (argc > 1) {
705 usage();
706 return 1;
707 }
708
709 /* exactly one of -p or -a is required */
710 if (!pid && !all_pids) {
711 usage();
712 return 1;
713 } else if (pid && all_pids) {
714 usage();
715 return 1;
716 }
717
718 print_header();
719
720 if (all_pids) {
721 return process_all_pids();
722 } else {
723 return process_pid(pid);
724 }
725
726 return 0;
727 }