]> git.saurik.com Git - apple/system_cmds.git/blame_incremental - lskq.tproj/lskq.c
system_cmds-671.10.3.tar.gz
[apple/system_cmds.git] / lskq.tproj / lskq.c
... / ...
CommitLineData
1/*
2 * Copyright (c) 2015 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 */
43static int verbose;
44static int all_pids;
45static int ignore_empty;
46
47static char *self = "lskq";
48
49static inline const char *
50filt_name(int16_t filt)
51{
52 int idx = -filt;
53 if (idx >= 0 && idx < ARRAYLEN(filt_strs)) {
54 return filt_strs[idx];
55 } else {
56 return "<inval>";
57 }
58}
59
60static inline const char *
61fdtype_str(uint32_t type)
62{
63 if (type < ARRAYLEN(fdtype_strs)) {
64 return fdtype_strs[type];
65 } else {
66 return "<unknown>";
67 }
68}
69
70static char *
71fflags_build(struct kevent_extinfo *info, char *str, int len)
72{
73 unsigned ff = info->kqext_sfflags;
74
75 switch (info->kqext_kev.filter) {
76
77 case EVFILT_READ: {
78 snprintf(str, len, "%c ",
79 (ff & NOTE_LOWAT) ? 'l' : '-'
80 );
81 break;
82 }
83
84 case EVFILT_MACHPORT: {
85 snprintf(str, len, "%c ",
86 (ff & MACH_RCV_MSG) ? 'r' : '-'
87 );
88 break;
89 }
90
91 case EVFILT_VNODE: {
92 snprintf(str, len, "%c%c%c%c%c%c%c",
93 (ff & NOTE_DELETE) ? 'd' : '-',
94 (ff & NOTE_WRITE) ? 'w' : '-',
95 (ff & NOTE_EXTEND) ? 'e' : '-',
96 (ff & NOTE_ATTRIB) ? 'a' : '-',
97 (ff & NOTE_LINK) ? 'l' : '-',
98 (ff & NOTE_RENAME) ? 'r' : '-',
99 (ff & NOTE_REVOKE) ? 'v' : '-'
100 );
101 break;
102 }
103
104 case EVFILT_PROC: {
105/* NOTE_REAP is deprecated, but we still want to show if it's used */
106#pragma clang diagnostic push
107#pragma clang diagnostic ignored "-Wdeprecated-declarations"
108 snprintf(str, len, "%c%c%c%c%c%c ",
109 (ff & NOTE_EXIT) ? 'x' : '-',
110 (ff & NOTE_EXITSTATUS) ? 't' : '-',
111 (ff & NOTE_FORK) ? 'f' : '-',
112 (ff & NOTE_EXEC) ? 'e' : '-',
113 (ff & NOTE_SIGNAL) ? 's' : '-',
114 (ff & NOTE_REAP) ? 'r' : '-'
115 );
116 break;
117#pragma clang diagnostic pop
118 }
119
120 case EVFILT_TIMER: {
121 snprintf(str, len, "%c%c%c%c%c ",
122 (ff & NOTE_SECONDS) ? 's' :
123 (ff & NOTE_USECONDS) ? 'u' :
124 (ff & NOTE_NSECONDS) ? 'n' : '?',
125 (ff & NOTE_ABSOLUTE) ? 'a' : '-',
126 (ff & NOTE_CRITICAL) ? 'c' : '-',
127 (ff & NOTE_BACKGROUND) ? 'b' : '-',
128 (ff & NOTE_LEEWAY) ? 'l' : '-'
129 );
130 break;
131 }
132
133 default:
134 snprintf(str, len, "");
135 break;
136 };
137
138 return str;
139}
140
141
142static inline int
143filter_is_fd_type(int filter)
144{
145 if (filter <= EVFILT_READ && filter >= EVFILT_VNODE) {
146 return 1;
147 } else {
148 return 0;
149 }
150}
151
152/*
153 * find index of fd in a list of fdinfo of length nfds
154 */
155static inline int
156fd_list_getfd(struct proc_fdinfo *fds, int nfds, int fd)
157{
158 int i;
159 for (i = 0; i < nfds; i++) {
160 if (fds[i].proc_fd == fd) {
161 return i;
162 }
163 }
164
165 return -1;
166}
167
168/*
169 * left truncate URL-form process names
170 */
171static const char *
172shorten_procname(const char *proc, int width)
173{
174 if (strcasestr(proc, "com.") == proc) {
175 long len = strlen(proc);
176 if (len > width) {
177 return &proc[len - width];
178 } else {
179 return proc;
180 }
181 } else {
182 return proc;
183 }
184}
185
186/*
187 * stringify knote ident where possible (signals, processes)
188 */
189static void
190print_ident(uint64_t ident, int16_t filter, int width)
191{
192 switch (filter) {
193
194 case EVFILT_SIGNAL:
195 case EVFILT_PROC: {
196 char str[128] = "";
197 char num[128];
198 char out[128];
199 int numlen = sprintf(num, "%llu", ident);
200 int strwidth = width - numlen - 3; // add room for brackets and space
201
202 if (filter == EVFILT_SIGNAL) {
203 if (ident < ARRAYLEN(sig_strs)) {
204 snprintf(str, strwidth + 1, "%s", sig_strs[ident]);
205 }
206 } else {
207 /* FIXME: this should be cached */
208 struct proc_bsdinfo bsdinfo;
209 int ret = proc_pidinfo((int)ident, PROC_PIDTBSDINFO, 0, &bsdinfo, sizeof(bsdinfo));
210 if (ret == sizeof(bsdinfo)) {
211 char *procname = bsdinfo.pbi_name;
212 if (strlen(procname) == 0) {
213 procname = bsdinfo.pbi_comm;
214 }
215 snprintf(str, strwidth + 1, "%s",
216 shorten_procname(procname, strwidth));
217 }
218 }
219
220 if (str[0] != '\0') {
221 snprintf(out, width + 1, "(%s) %s", str, num);
222 } else {
223 snprintf(out, width + 1, "%s", num);
224 }
225
226 printf("%*s ", width, out);
227 break;
228 }
229
230 default:
231 printf("%*llu ", width, ident);
232 break;
233 }
234
235}
236
237static void
238print_kqfd(int kqfd, int width)
239{
240 if (kqfd == -1) {
241 printf("%*s ", width, "wq");
242 } else {
243 printf("%*u ", width, kqfd);
244 }
245}
246
247static void
248print_kq_info(int pid, const char *procname, int kqfd, int state)
249{
250 char tmpstr[16];
251 strlcpy(tmpstr, shorten_procname(procname, 10), 11);
252 printf("%-10s ", tmpstr);
253 printf("%5u ", pid);
254 print_kqfd(kqfd, 5);
255 printf(" %c%c%c ",
256 (state & KQ_SLEEP) ? 'k' : '-',
257 (state & KQ_SEL) ? 's' : '-',
258 (state & KQ_KEV32) ? '3' :
259 (state & KQ_KEV64) ? '6' :
260 (state & KQ_KEV_QOS) ? 'q' : '-'
261 );
262}
263
264#define MAXENTRIES 2048
265
266static int
267process_kqueue_on_fd(int pid, const char *procname, int kqfd, struct proc_fdinfo *fdlist, int nfds)
268{
269 int ret, i, nknotes;
270 char tmpstr[256];
271
272 /*
273 * get the basic kqueue info
274 */
275 struct kqueue_fdinfo kqfdinfo = {};
276 ret = proc_pidfdinfo(pid, kqfd, PROC_PIDFDKQUEUEINFO, &kqfdinfo, sizeof(kqfdinfo));
277 if (ret != sizeof(kqfdinfo) && kqfd != -1) {
278 /* every proc has an implicit workq kqueue, dont warn if its unused */
279 fprintf(stderr, "WARN: FD table changed (pid %i, kq %i)\n", pid, kqfd);
280 }
281
282 /*
283 * get extended kqueue info
284 */
285 struct kevent_extinfo kqextinfo[MAXENTRIES];
286 again:
287 errno = 0;
288 nknotes = proc_pidfdinfo(pid, kqfd, PROC_PIDFDKQUEUE_EXTINFO, kqextinfo, sizeof(kqextinfo));
289 if (nknotes <= 0) {
290 if (errno == 0) {
291 /* proc_*() can't distinguish between error and empty list */
292 } else if (errno == EAGAIN) {
293 goto again;
294 } else if (errno == EBADF) {
295 fprintf(stderr, "WARN: FD table changed (pid %i, kq %i)\n", pid, kqfd);
296 return 0;
297 } else {
298 perror("failed to get extended kqueue info");
299 return errno;
300 }
301 }
302
303 if (nknotes > MAXENTRIES) {
304 fprintf(stderr, "WARN: truncated knote list (pid %i, kq %i)\n", pid, kqfd);
305 nknotes = MAXENTRIES;
306 }
307
308 if (nknotes == 0) {
309 if (!ignore_empty) {
310 /* for empty kqueues, print a single empty entry */
311 print_kq_info(pid, procname, kqfd, kqfdinfo.kqueueinfo.kq_state);
312 printf("%20s \n", "-");
313 }
314 return 0;
315 }
316
317 for (i = 0; i < nknotes; i++) {
318 struct kevent_extinfo *info = &kqextinfo[i];
319
320 print_kq_info(pid, procname, kqfd, kqfdinfo.kqueueinfo.kq_state);
321 print_ident(info->kqext_kev.ident, info->kqext_kev.filter, 20);
322 printf("%-9s ", filt_name(info->kqext_kev.filter));
323
324 /* for kevents attached to file descriptors, print the type of FD (file, socket, etc) */
325 const char *fdstr = "";
326 if (filter_is_fd_type(info->kqext_kev.filter)) {
327 fdstr = "<UNKN>";
328 int knfd = (info->kqext_kev.ident < nfds)
329 ? fd_list_getfd(fdlist, nfds, (int)info->kqext_kev.ident)
330 : -1;
331 if (knfd >= 0) {
332 fdstr = fdtype_str(fdlist[knfd].proc_fdtype);
333 }
334 }
335 printf("%-8s ", fdstr);
336
337 /* print filter flags */
338 printf("%7s ", fflags_build(info, tmpstr, sizeof(tmpstr)));
339
340 /* print generic flags */
341 unsigned flg = info->kqext_kev.flags;
342 printf("%c%c%c%c%c%c%c%c%c ",
343 (flg & EV_ADD) ? 'a' : '-',
344 (flg & EV_ENABLE) ? 'n' : '-',
345 (flg & EV_DISABLE) ? 'd' : '-',
346 (flg & EV_DELETE) ? 'x' : '-',
347 (flg & EV_RECEIPT) ? 'r' : '-',
348 (flg & EV_ONESHOT) ? '1' : '-',
349 (flg & EV_CLEAR) ? 'c' : '-',
350 (flg & EV_EOF) ? 'o' : '-',
351 (flg & EV_ERROR) ? 'e' : '-'
352 );
353
354 unsigned st = info->kqext_status;
355 printf("%c%c%c%c ",
356 (st & KN_ACTIVE) ? 'a' : '-',
357 (st & KN_QUEUED) ? 'q' : '-',
358 (st & KN_DISABLED) ? 'd' : '-',
359 (st & KN_STAYQUEUED) ? 's' : '-'
360 );
361
362 printf("%#18llx ", (unsigned long long)info->kqext_kev.data);
363
364 if (verbose) {
365 printf("%#18llx ", (unsigned long long)info->kqext_kev.udata);
366 if (kqfdinfo.kqueueinfo.kq_state & (KQ_KEV64|KQ_KEV_QOS)) {
367 printf("%#18llx ", (unsigned long long)info->kqext_kev.ext[0]);
368 printf("%#18llx ", (unsigned long long)info->kqext_kev.ext[1]);
369 }
370 if (kqfdinfo.kqueueinfo.kq_state & KQ_KEV_QOS) {
371 printf("%#18llx ", (unsigned long long)info->kqext_kev.ext[2]);
372 printf("%#18llx ", (unsigned long long)info->kqext_kev.ext[3]);
373 printf("%#10lx ", (unsigned long)info->kqext_kev.xflags);
374 }
375 }
376
377 printf("\n");
378 }
379
380 return 0;
381}
382
383static int
384process_pid(pid_t pid)
385{
386 int i, ret, nfds;
387
388 /* enumerate file descriptors */
389 struct proc_fdinfo fdlist[MAXENTRIES];
390 nfds = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, fdlist, sizeof(fdlist));
391 if (nfds <= 0) {
392 fprintf(stderr, "%s: failed enumerating file descriptors of process %i: %s",
393 self, pid, strerror(errno));
394 if (errno == EPERM && geteuid() != 0) {
395 fprintf(stderr, " (are you root?)");
396 }
397 fprintf(stderr, "\n");
398 return 1;
399 }
400
401 nfds /= sizeof(struct proc_fdinfo);
402 if (nfds > MAXENTRIES) {
403 fprintf(stderr, "WARN: truncated FD list (proc %i)\n", pid);
404 nfds = MAXENTRIES;
405 }
406
407 /* get bsdinfo for the process name */
408 struct proc_bsdinfo bsdinfo;
409 ret = proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &bsdinfo, sizeof(bsdinfo));
410 if (ret != sizeof(bsdinfo)) {
411 perror("failed retrieving process info");
412 return 1;
413 }
414
415 char *procname = bsdinfo.pbi_name;
416 if (strlen(procname) == 0) {
417 procname = bsdinfo.pbi_comm;
418 }
419
420 /* handle the special workq kq */
421 ret = process_kqueue_on_fd(pid, procname, -1, fdlist, nfds);
422 if (ret) {
423 return ret;
424 }
425
426 for (i = 0; i < nfds; i++) {
427 if (fdlist[i].proc_fdtype == PROX_FDTYPE_KQUEUE) {
428 ret = process_kqueue_on_fd(pid, procname, fdlist[i].proc_fd, fdlist, nfds);
429 if (ret) {
430 return ret;
431 }
432 }
433 }
434
435 return 0;
436}
437
438#define MAXPIDS 4096
439
440static int
441process_all_pids(void)
442{
443 int i, npids, ret;
444 int pids[MAXPIDS];
445
446 npids = proc_listpids(PROC_ALL_PIDS, 0, pids, sizeof(pids));
447 if (npids <= 0) {
448 perror("failed enumerating pids");
449 return 1;
450 }
451 npids /= sizeof(int);
452
453 for (i = 0; i < npids; i++) {
454 /* listpids gives us pid 0 for some reason */
455 if (pids[i]) {
456 ret = process_pid(pids[i]);
457 if (ret) {
458 return ret;
459 }
460 }
461 }
462
463 return 0;
464}
465
466static void
467usage(void)
468{
469 fprintf(stderr, "usage: %s [-vhe] [-a | -p <pid>]\n", self);
470}
471
472int main(int argc, char *argv[])
473{
474 pid_t pid = 0;
475 int opt;
476
477 if (argc > 0) {
478 self = argv[0];
479 }
480
481 while ((opt = getopt(argc, argv, "eahvp:")) != -1) {
482 switch (opt) {
483 case 'a':
484 all_pids = 1;
485 break;
486 case 'v':
487 verbose++;
488 break;
489 case 'p':
490 pid = atoi(optarg);
491 break;
492 case 'e':
493 ignore_empty = 1;
494 break;
495 case 'h':
496 usage();
497 return 0;
498 case '?':
499 default:
500 usage();
501 return 1;
502 }
503 }
504
505 argc -= optind;
506 argv += optind;
507
508 if (argc == 1) {
509 /* also allow lskq <pid> */
510 if (pid || all_pids) {
511 usage();
512 return 1;
513 }
514
515 pid = atoi(argv[0]);
516 } else if (argc > 1) {
517 usage();
518 return 1;
519 }
520
521 /* exactly one of -p or -a is required */
522 if (!pid && !all_pids) {
523 usage();
524 return 1;
525 } else if (pid && all_pids) {
526 usage();
527 return 1;
528 }
529
530 printf("command pid kq kqst ident filter fdtype fflags flags evst data");
531 if (verbose) {
532 printf(" udata ext0 ext1 ext2 ext3 xflags");
533 }
534 printf("\n");
535 printf("---------- ----- ----- ---- -------------------- --------- -------- ------- --------- ---- ------------------");
536 if (verbose) {
537 printf(" ------------------ ------------------ ------------------ ------------------ ------------------ ----------");
538 }
539 printf("\n");
540
541 if (all_pids) {
542 return process_all_pids();
543 } else {
544 return process_pid(pid);
545 }
546
547 return 0;
548}
549