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