]>
Commit | Line | Data |
---|---|---|
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 */ | |
50 | static int verbose; | |
51 | static int all_pids; | |
52 | static int ignore_empty; | |
53 | static int raw; | |
54 | ||
55 | static char *self = "lskq"; | |
56 | ||
57 | static inline const char * | |
58 | filt_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 | ||
70 | static inline const char * | |
71 | fdtype_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 | ||
82 | static char * | |
83 | fflags_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 | ||
178 | static inline int | |
179 | filter_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 | ||
191 | static const char * | |
192 | thread_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 | */ | |
210 | static inline int | |
211 | fd_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 | */ | |
226 | static const char * | |
227 | shorten_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 | */ | |
244 | static void | |
245 | print_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 | ||
306 | static void | |
307 | print_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 | ||
320 | static void | |
321 | print_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 | ||
342 | enum 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 | ||
351 | static int | |
352 | process_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 | ||
636 | static int | |
637 | pid_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 | ||
645 | retry: | |
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 | ||
671 | static int | |
672 | process_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 | ||
765 | static int | |
766 | process_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 | ||
815 | out: | |
816 | if (pids) { | |
817 | free(pids); | |
818 | pids = NULL; | |
819 | } | |
820 | ||
821 | return ret; | |
822 | } | |
823 | ||
824 | static void | |
825 | cheatsheet(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\ | |
835 | command 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\ | |
843 | launchd 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 | ||
856 | static void | |
857 | usage(void) | |
858 | { | |
859 | fprintf(stderr, "usage: %s [-vher] [-a | -p <pid>]\n", self); | |
860 | } | |
861 | ||
862 | static void | |
863 | print_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 | ||
889 | int | |
890 | main(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 | } |