1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2007, 2011 Robert N. M. Watson 5 * Copyright (c) 2015 Allan Jude <allanjude@freebsd.org> 6 * Copyright (c) 2017 Dell EMC 7 * All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 */ 30 31 #include <sys/param.h> 32 #include <sys/sysctl.h> 33 #include <sys/user.h> 34 35 #include <err.h> 36 #include <libprocstat.h> 37 #include <stdio.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #include <sysexits.h> 41 #include <unistd.h> 42 43 #include "procstat.h" 44 45 enum { 46 PS_CMP_NORMAL = 0x00, 47 PS_CMP_PLURAL = 0x01, 48 PS_CMP_SUBSTR = 0x02 49 }; 50 51 struct procstat_cmd { 52 const char *command; 53 const char *xocontainer; 54 const char *usage; 55 void (*cmd)(struct procstat *, struct kinfo_proc *); 56 void (*opt)(int, char * const *); 57 int cmp; 58 }; 59 60 int procstat_opts = 0; 61 62 static void cmdopt_none(int argc, char * const argv[]); 63 static void cmdopt_verbose(int argc, char * const argv[]); 64 static void cmdopt_signals(int argc, char * const argv[]); 65 static void cmdopt_rusage(int argc, char * const argv[]); 66 static void cmdopt_files(int argc, char * const argv[]); 67 static void cmdopt_cpuset(int argc, char * const argv[]); 68 static void cmdopt_kqueue(int argc, char * const argv[]); 69 70 static const char *progname; 71 72 /* aliased program parameters and arguments 73 * - usage field is abused to hold the pointer to the function 74 * displaying program usage 75 */ 76 static const struct procstat_cmd pacmd_table[] = { 77 /* arguments are the same as for pwdx: pid or core file */ 78 { "pargs", "args", NULL, &procstat_pargs, &cmdopt_none, 79 PS_CMP_NORMAL | PS_MODE_COMPAT }, 80 { "penv", "env", NULL, &procstat_penv, &cmdopt_none, 81 PS_CMP_NORMAL | PS_MODE_COMPAT }, 82 { "pwdx", "pwd", NULL, &procstat_pwdx, &cmdopt_none, 83 PS_CMP_NORMAL | PS_MODE_COMPAT } 84 }; 85 86 /* procstat parameters and arguments */ 87 static const struct procstat_cmd cmd_table[] = { 88 { "advlock", "advisory_locks", NULL, &procstat_advlocks, &cmdopt_none, 89 PS_CMP_PLURAL | PS_CMP_SUBSTR | PS_MODE_NO_KINFO_PROC }, 90 { "argument", "arguments", NULL, &procstat_args, &cmdopt_none, 91 PS_CMP_PLURAL | PS_CMP_SUBSTR }, 92 { "auxv", "auxv", NULL, &procstat_auxv, &cmdopt_none, PS_CMP_NORMAL }, 93 { "basic", "basic", NULL, &procstat_basic, &cmdopt_none, 94 PS_CMP_NORMAL }, 95 { "binary", "binary", NULL, &procstat_bin, &cmdopt_none, 96 PS_CMP_SUBSTR }, 97 { "cpuset", "cs", NULL, &procstat_cs, &cmdopt_cpuset, PS_CMP_NORMAL }, 98 { "cs", "cs", NULL, &procstat_cs, &cmdopt_cpuset, PS_CMP_NORMAL }, 99 { "credential", "credentials", NULL, &procstat_cred, &cmdopt_none, 100 PS_CMP_PLURAL | PS_CMP_SUBSTR }, 101 { "environment", "environment", NULL, &procstat_env, &cmdopt_none, 102 PS_CMP_SUBSTR }, 103 { "fd", "files", "[-C]", &procstat_files, &cmdopt_files, 104 PS_CMP_PLURAL }, 105 { "file", "files", "[-C]", &procstat_files, &cmdopt_files, 106 PS_CMP_PLURAL }, 107 { "kqueue", "kqueues", NULL, &procstat_kqueues, &cmdopt_kqueue, 108 PS_CMP_PLURAL }, 109 { "kstack", "kstack", "[-v]", &procstat_kstack, &cmdopt_verbose, 110 PS_CMP_NORMAL }, 111 { "pargs", "args", NULL, &procstat_pargs, &cmdopt_none, 112 PS_CMP_NORMAL }, 113 { "penv", "env", NULL, &procstat_penv, &cmdopt_none, 114 PS_CMP_NORMAL }, 115 { "ptlwpinfo", "ptlwpinfo", NULL, &procstat_ptlwpinfo, &cmdopt_none, 116 PS_CMP_NORMAL }, 117 { "pwdx", "pwd", NULL, &procstat_pwdx, &cmdopt_none, 118 PS_CMP_NORMAL }, 119 { "rlimit", "rlimit", NULL, &procstat_rlimit, &cmdopt_none, 120 PS_CMP_NORMAL }, 121 { "rlimitusage", "rlimitusage", NULL, &procstat_rlimitusage, 122 &cmdopt_none, PS_CMP_NORMAL }, 123 { "rusage", "rusage", "[-Ht]", &procstat_rusage, &cmdopt_rusage, 124 PS_CMP_NORMAL }, 125 { "sigfastblock", "sigfastblock", NULL, &procstat_sigfastblock, 126 &cmdopt_none, PS_CMP_NORMAL }, 127 { "signal", "signals", "[-n]", &procstat_sigs, &cmdopt_signals, 128 PS_CMP_PLURAL | PS_CMP_SUBSTR }, 129 { "thread", "threads", NULL, &procstat_threads, &cmdopt_none, 130 PS_CMP_PLURAL }, 131 { "tsignal", "thread_signals", "[-n]", &procstat_threads_sigs, 132 &cmdopt_signals, PS_CMP_PLURAL | PS_CMP_SUBSTR }, 133 { "vm", "vm", NULL, &procstat_vm, &cmdopt_none, PS_CMP_NORMAL } 134 }; 135 136 static void 137 usage(const struct procstat_cmd *cmd) 138 { 139 size_t i, l; 140 int multi; 141 142 if (cmd == NULL || (cmd->cmp & PS_MODE_COMPAT) == 0) { 143 xo_error("usage: procstat [--libxo] [-h] [-M core] [-N system]" 144 " [-w interval] command\n" 145 " [pid ... | core ...]\n" 146 " procstat [--libxo] -a [-h] [-M core] [-N system] " 147 " [-w interval] command\n" 148 " procstat [--libxo] [-h] [-M core] [-N system]" 149 " [-w interval]\n" 150 " [-S | -b | -c | -e | -f [-C] | -i [-n] | " 151 "-j [-n] | -k [-k] |\n" 152 " -l | -r [-H] | -s | -t | -v | -x] " 153 "[pid ... | core ...]\n" 154 " procstat [--libxo] -a [-h] [-M core] [-N system]" 155 " [-w interval]\n" 156 " [-S | -b | -c | -e | -f [-C] | -i [-n] | " 157 "-j [-n] | -k [-k] |\n" 158 " -l | -r [-H] | -s | -t | -v | -x]\n" 159 " procstat [--libxo] -L [-h] [-M core] [-N system] core ...\n" 160 "Available commands:\n"); 161 for (i = 0, l = nitems(cmd_table); i < l; i++) { 162 multi = i + 1 < l && cmd_table[i].cmd == 163 cmd_table[i + 1].cmd; 164 xo_error(" %s%s%s", multi ? "[" : "", 165 cmd_table[i].command, (cmd_table[i].cmp & 166 PS_CMP_PLURAL) ? "(s)" : ""); 167 for (; i + 1 < l && cmd_table[i].cmd == 168 cmd_table[i + 1].cmd; i++) 169 xo_error(" | %s%s", cmd_table[i + 1].command, 170 (cmd_table[i].cmp & PS_CMP_PLURAL) ? 171 "(s)" : ""); 172 if (multi) 173 xo_error("]"); 174 if (cmd_table[i].usage != NULL) 175 xo_error(" %s", cmd_table[i].usage); 176 xo_error("\n"); 177 } 178 } else { 179 xo_error("usage: %s [--libxo] pid ...\n", progname); 180 } 181 xo_finish(); 182 exit(EX_USAGE); 183 } 184 185 static void 186 procstat(const struct procstat_cmd *cmd, struct procstat *prstat, 187 struct kinfo_proc *kipp) 188 { 189 char *pidstr = NULL; 190 191 asprintf(&pidstr, "%d", kipp->ki_pid); 192 if (pidstr == NULL) 193 xo_errc(1, ENOMEM, "Failed to allocate memory in procstat()"); 194 xo_open_container(pidstr); 195 cmd->cmd(prstat, kipp); 196 xo_close_container(pidstr); 197 free(pidstr); 198 } 199 200 /* 201 * Sort processes first by pid and then tid. 202 */ 203 static int 204 kinfo_proc_compare(const void *a, const void *b) 205 { 206 int i; 207 208 i = ((const struct kinfo_proc *)a)->ki_pid - 209 ((const struct kinfo_proc *)b)->ki_pid; 210 if (i != 0) 211 return (i); 212 i = ((const struct kinfo_proc *)a)->ki_tid - 213 ((const struct kinfo_proc *)b)->ki_tid; 214 return (i); 215 } 216 217 void 218 kinfo_proc_sort(struct kinfo_proc *kipp, int count) 219 { 220 221 qsort(kipp, count, sizeof(*kipp), kinfo_proc_compare); 222 } 223 224 const char * 225 kinfo_proc_thread_name(const struct kinfo_proc *kipp) 226 { 227 static char name[MAXCOMLEN+1]; 228 229 strlcpy(name, kipp->ki_tdname, sizeof(name)); 230 strlcat(name, kipp->ki_moretdname, sizeof(name)); 231 if (name[0] == '\0' || strcmp(kipp->ki_comm, name) == 0) { 232 name[0] = '-'; 233 name[1] = '\0'; 234 } 235 236 return (name); 237 } 238 239 static const struct procstat_cmd * 240 getcmdbyprogname(const char *pprogname) 241 { 242 const char *ca; 243 size_t i; 244 245 if (pprogname == NULL) 246 return (NULL); 247 248 for (i = 0; i < nitems(pacmd_table); i++) { 249 ca = pacmd_table[i].command; 250 if (ca != NULL && strcmp(ca, pprogname) == 0) 251 return (&pacmd_table[i]); 252 } 253 254 return (NULL); 255 } 256 257 static const struct procstat_cmd * 258 getcmd(const char *str) 259 { 260 const struct procstat_cmd *cmd; 261 size_t i, l; 262 int cmp, s; 263 264 if (str == NULL) 265 return (NULL); 266 cmd = NULL; 267 if ((l = strlen(str)) == 0) 268 return (getcmd("basic")); 269 s = l > 1 && strcasecmp(str + l - 1, "s") == 0; 270 for (i = 0; i < nitems(cmd_table); i++) { 271 /* 272 * After the first match substring matches are disabled, 273 * allowing subsequent full matches to take precedence. 274 */ 275 if (cmd == NULL && (cmd_table[i].cmp & PS_CMP_SUBSTR)) 276 cmp = strncasecmp(str, cmd_table[i].command, l - 277 ((cmd_table[i].cmp & PS_CMP_PLURAL) && s ? 1 : 0)); 278 else if ((cmd_table[i].cmp & PS_CMP_PLURAL) && s && 279 l == strlen(cmd_table[i].command) + 1) 280 cmp = strncasecmp(str, cmd_table[i].command, l - 1); 281 else 282 cmp = strcasecmp(str, cmd_table[i].command); 283 if (cmp == 0) 284 cmd = &cmd_table[i]; 285 } 286 return (cmd); 287 } 288 289 int 290 main(int argc, char *argv[]) 291 { 292 struct kinfo_proc *p; 293 const struct procstat_cmd *cmd; 294 struct procstat *prstat, *cprstat; 295 char *dummy, *nlistf, *memf; 296 const char *xocontainer; 297 long l; 298 pid_t pid; 299 int aflag, ch, cnt, i, interval; 300 301 interval = 0; 302 cmd = NULL; 303 memf = nlistf = NULL; 304 aflag = 0; 305 argc = xo_parse_args(argc, argv); 306 307 progname = getprogname(); 308 cmd = getcmdbyprogname(progname); 309 310 while ((ch = getopt(argc, argv, "abCcefHhijkLlM:N:nrSstvw:x")) != -1) { 311 switch (ch) { 312 case 'a': 313 aflag++; 314 break; 315 case 'b': 316 if (cmd != NULL) 317 usage(cmd); 318 cmd = getcmd("binary"); 319 break; 320 case 'C': 321 procstat_opts |= PS_OPT_CAPABILITIES; 322 break; 323 case 'c': 324 if (cmd != NULL) 325 usage(cmd); 326 cmd = getcmd("arguments"); 327 break; 328 case 'e': 329 if (cmd != NULL) 330 usage(cmd); 331 cmd = getcmd("environment"); 332 break; 333 case 'f': 334 if (cmd != NULL) 335 usage(cmd); 336 cmd = getcmd("files"); 337 break; 338 case 'H': 339 procstat_opts |= PS_OPT_PERTHREAD; 340 break; 341 case 'h': 342 procstat_opts |= PS_OPT_NOHEADER; 343 break; 344 case 'i': 345 if (cmd != NULL) 346 usage(cmd); 347 cmd = getcmd("signals"); 348 break; 349 case 'j': 350 if (cmd != NULL) 351 usage(cmd); 352 cmd = getcmd("tsignals"); 353 break; 354 case 'k': 355 if (cmd != NULL && cmd->cmd == procstat_kstack) { 356 if ((procstat_opts & PS_OPT_VERBOSE) != 0) 357 usage(cmd); 358 procstat_opts |= PS_OPT_VERBOSE; 359 } else { 360 if (cmd != NULL) 361 usage(cmd); 362 cmd = getcmd("kstack"); 363 } 364 break; 365 case 'L': 366 if (cmd != NULL) 367 usage(cmd); 368 cmd = getcmd("ptlwpinfo"); 369 break; 370 case 'l': 371 if (cmd != NULL) 372 usage(cmd); 373 cmd = getcmd("rlimit"); 374 break; 375 case 'M': 376 memf = optarg; 377 break; 378 case 'N': 379 nlistf = optarg; 380 break; 381 case 'n': 382 procstat_opts |= PS_OPT_SIGNUM; 383 break; 384 case 'r': 385 if (cmd != NULL) 386 usage(cmd); 387 cmd = getcmd("rusage"); 388 break; 389 case 'S': 390 if (cmd != NULL) 391 usage(cmd); 392 cmd = getcmd("cpuset"); 393 break; 394 case 's': 395 if (cmd != NULL) 396 usage(cmd); 397 cmd = getcmd("credentials"); 398 break; 399 case 't': 400 if (cmd != NULL) 401 usage(cmd); 402 cmd = getcmd("threads"); 403 break; 404 case 'v': 405 if (cmd != NULL) 406 usage(cmd); 407 cmd = getcmd("vm"); 408 break; 409 case 'w': 410 l = strtol(optarg, &dummy, 10); 411 if (*dummy != '\0') 412 usage(cmd); 413 if (l < 1 || l > INT_MAX) 414 usage(cmd); 415 interval = l; 416 break; 417 case 'x': 418 if (cmd != NULL) 419 usage(cmd); 420 cmd = getcmd("auxv"); 421 break; 422 case '?': 423 default: 424 usage(cmd); 425 } 426 427 } 428 argc -= optind; 429 argv += optind; 430 431 if (cmd == NULL && argv[0] != NULL && (cmd = getcmd(argv[0])) != NULL) { 432 if ((procstat_opts & PS_SUBCOMMAND_OPTS) != 0) 433 usage(cmd); 434 if (cmd->opt != NULL) { 435 optreset = 1; 436 optind = 1; 437 cmd->opt(argc, argv); 438 if ((cmd->cmp & PS_MODE_COMPAT) == 0) { 439 argc -= optind; 440 argv += optind; 441 } 442 } else { 443 argc -= 1; 444 argv += 1; 445 } 446 } else { 447 if (cmd == NULL) 448 cmd = getcmd("basic"); 449 if (cmd->cmd != procstat_files && 450 (procstat_opts & PS_OPT_CAPABILITIES) != 0 && 451 (cmd->cmp & PS_MODE_COMPAT) == 0) 452 usage(cmd); 453 } 454 455 /* Must specify either the -a flag or a list of pids. */ 456 if (!(aflag == 1 && argc == 0) && !(aflag == 0 && argc > 0) && 457 (cmd->cmp & PS_MODE_NO_KINFO_PROC) == 0) 458 usage(cmd); 459 460 if (memf != NULL) 461 prstat = procstat_open_kvm(nlistf, memf); 462 else 463 prstat = procstat_open_sysctl(); 464 if (prstat == NULL) 465 xo_errx(1, "procstat_open()"); 466 do { 467 xocontainer = cmd->xocontainer != NULL ? cmd->xocontainer : 468 cmd->command; 469 xo_set_version(PROCSTAT_XO_VERSION); 470 xo_open_container(progname); 471 xo_open_container(xocontainer); 472 473 if ((cmd->cmp & PS_MODE_NO_KINFO_PROC) != 0) { 474 cmd->cmd(prstat, NULL); 475 goto iter; 476 } 477 478 if (aflag) { 479 p = procstat_getprocs(prstat, KERN_PROC_PROC, 0, &cnt); 480 if (p == NULL) 481 xo_errx(1, "procstat_getprocs()"); 482 kinfo_proc_sort(p, cnt); 483 for (i = 0; i < cnt; i++) { 484 procstat(cmd, prstat, &p[i]); 485 486 /* Suppress header after first process. */ 487 procstat_opts |= PS_OPT_NOHEADER; 488 xo_flush(); 489 } 490 procstat_freeprocs(prstat, p); 491 } 492 for (i = 0; i < argc; i++) { 493 l = strtol(argv[i], &dummy, 10); 494 if (*dummy == '\0') { 495 if (l < 0) 496 usage(cmd); 497 pid = l; 498 499 p = procstat_getprocs(prstat, KERN_PROC_PID, 500 pid, &cnt); 501 if (p == NULL) 502 xo_errx(1, "procstat_getprocs()"); 503 if (cnt != 0) 504 procstat(cmd, prstat, p); 505 procstat_freeprocs(prstat, p); 506 } else { 507 if ((cmd->cmp & PS_MODE_COMPAT) == 0) { 508 cprstat = procstat_open_core(argv[i]); 509 if (cprstat == NULL) { 510 warnx("procstat_open()"); 511 continue; 512 } 513 p = procstat_getprocs(cprstat, 514 KERN_PROC_PID, -1, &cnt); 515 if (p == NULL) { 516 xo_errx(1, 517 "procstat_getprocs()"); 518 } 519 if (cnt != 0) 520 procstat(cmd, cprstat, p); 521 procstat_freeprocs(cprstat, p); 522 procstat_close(cprstat); 523 } else { 524 usage(cmd); 525 } 526 } 527 if ((cmd->cmp & PS_MODE_COMPAT) == 0) { 528 /* Suppress header after first process. */ 529 procstat_opts |= PS_OPT_NOHEADER; 530 } 531 } 532 533 iter: 534 xo_close_container(xocontainer); 535 xo_close_container(progname); 536 xo_finish(); 537 if (interval) 538 sleep(interval); 539 } while (interval); 540 541 procstat_close(prstat); 542 543 exit(0); 544 } 545 546 void 547 cmdopt_none(int argc, char * const argv[]) 548 { 549 int ch; 550 551 while ((ch = getopt(argc, argv, "")) != -1) { 552 switch (ch) { 553 case '?': 554 default: 555 usage(NULL); 556 } 557 } 558 } 559 560 void 561 cmdopt_verbose(int argc, char * const argv[]) 562 { 563 int ch; 564 565 while ((ch = getopt(argc, argv, "v")) != -1) { 566 switch (ch) { 567 case 'v': 568 procstat_opts |= PS_OPT_VERBOSE; 569 break; 570 case '?': 571 default: 572 usage(NULL); 573 } 574 } 575 } 576 577 void 578 cmdopt_signals(int argc, char * const argv[]) 579 { 580 int ch; 581 582 while ((ch = getopt(argc, argv, "n")) != -1) { 583 switch (ch) { 584 case 'n': 585 procstat_opts |= PS_OPT_SIGNUM; 586 break; 587 case '?': 588 default: 589 usage(NULL); 590 } 591 } 592 } 593 594 void 595 cmdopt_rusage(int argc, char * const argv[]) 596 { 597 int ch; 598 599 while ((ch = getopt(argc, argv, "Ht")) != -1) { 600 switch (ch) { 601 case 'H': 602 /* FALLTHROUGH */ 603 case 't': 604 procstat_opts |= PS_OPT_PERTHREAD; 605 break; 606 case '?': 607 default: 608 usage(NULL); 609 } 610 } 611 } 612 613 void 614 cmdopt_files(int argc, char * const argv[]) 615 { 616 int ch; 617 618 while ((ch = getopt(argc, argv, "C")) != -1) { 619 switch (ch) { 620 case 'C': 621 procstat_opts |= PS_OPT_CAPABILITIES; 622 break; 623 case '?': 624 default: 625 usage(NULL); 626 } 627 } 628 } 629 630 void 631 cmdopt_cpuset(int argc, char * const argv[]) 632 { 633 634 procstat_opts |= PS_OPT_PERTHREAD; 635 cmdopt_none(argc, argv); 636 } 637 638 void 639 cmdopt_kqueue(int argc, char * const argv[]) 640 { 641 int ch; 642 643 while ((ch = getopt(argc, argv, "v")) != -1) { 644 switch (ch) { 645 case 'v': 646 procstat_opts |= PS_OPT_VERBOSE; 647 break; 648 case '?': 649 default: 650 usage(NULL); 651 } 652 } 653 } 654