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