]>
Commit | Line | Data |
---|---|---|
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 */ | |
43 | static int verbose; | |
44 | static int all_pids; | |
45 | static int ignore_empty; | |
46 | ||
47 | static char *self = "lskq"; | |
48 | ||
49 | static inline const char * | |
50 | filt_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 | ||
60 | static inline const char * | |
61 | fdtype_str(uint32_t type) | |
62 | { | |
63 | if (type < ARRAYLEN(fdtype_strs)) { | |
64 | return fdtype_strs[type]; | |
65 | } else { | |
66 | return "<unknown>"; | |
67 | } | |
68 | } | |
69 | ||
70 | static char * | |
71 | fflags_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 | ||
142 | static inline int | |
143 | filter_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 | */ | |
155 | static inline int | |
156 | fd_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 | */ | |
171 | static const char * | |
172 | shorten_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 | */ | |
189 | static void | |
190 | print_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 | ||
237 | static void | |
238 | print_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 | ||
247 | static void | |
248 | print_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 | ||
266 | static int | |
267 | process_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 | ||
383 | static int | |
384 | process_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 | ||
440 | static int | |
441 | process_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 | ||
466 | static void | |
467 | usage(void) | |
468 | { | |
469 | fprintf(stderr, "usage: %s [-vhe] [-a | -p <pid>]\n", self); | |
470 | } | |
471 | ||
472 | int 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 |