1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 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 * $FreeBSD$ 31 */ 32 33 #include <sys/param.h> 34 #include <sys/sysctl.h> 35 #include <sys/user.h> 36 37 #include <err.h> 38 #include <libprocstat.h> 39 #include <stdio.h> 40 #include <stdlib.h> 41 #include <string.h> 42 #include <sysexits.h> 43 #include <unistd.h> 44 45 #include "procstat.h" 46 47 enum { 48 PS_CMP_NORMAL = 0x00, 49 PS_CMP_PLURAL = 0x01, 50 PS_CMP_SUBSTR = 0x02 51 }; 52 53 struct procstat_cmd { 54 const char *command; 55 const char *xocontainer; 56 const char *usage; 57 void (*cmd)(struct procstat *, struct kinfo_proc *); 58 void (*opt)(int, char * const *); 59 int cmp; 60 }; 61 62 int procstat_opts = 0; 63 64 static void cmdopt_none(int argc, char * const argv[]); 65 static void cmdopt_verbose(int argc, char * const argv[]); 66 static void cmdopt_signals(int argc, char * const argv[]); 67 static void cmdopt_rusage(int argc, char * const argv[]); 68 static void cmdopt_files(int argc, char * const argv[]); 69 static void cmdopt_cpuset(int argc, char * const argv[]); 70 71 static const struct procstat_cmd cmd_table[] = { 72 { "argument", "arguments", NULL, &procstat_args, &cmdopt_none, 73 PS_CMP_PLURAL | PS_CMP_SUBSTR }, 74 { "auxv", "auxv", NULL, &procstat_auxv, &cmdopt_none, PS_CMP_NORMAL }, 75 { "basic", "basic", NULL, &procstat_basic, &cmdopt_none, 76 PS_CMP_NORMAL }, 77 { "binary", "binary", NULL, &procstat_bin, &cmdopt_none, 78 PS_CMP_SUBSTR }, 79 { "cpuset", "cs", NULL, &procstat_cs, &cmdopt_cpuset, PS_CMP_NORMAL }, 80 { "cs", "cs", NULL, &procstat_cs, &cmdopt_cpuset, PS_CMP_NORMAL }, 81 { "credential", "credentials", NULL, &procstat_cred, &cmdopt_none, 82 PS_CMP_PLURAL | PS_CMP_SUBSTR }, 83 { "environment", "environment", NULL, &procstat_env, &cmdopt_none, 84 PS_CMP_SUBSTR }, 85 { "fd", "files", "[-C]", &procstat_files, &cmdopt_files, 86 PS_CMP_PLURAL }, 87 { "file", "files", "[-C]", &procstat_files, &cmdopt_files, 88 PS_CMP_PLURAL }, 89 { "kstack", "kstack", "[-v]", &procstat_kstack, &cmdopt_verbose, 90 PS_CMP_NORMAL }, 91 { "ptlwpinfo", "ptlwpinfo", NULL, &procstat_ptlwpinfo, &cmdopt_none, 92 PS_CMP_NORMAL }, 93 { "rlimit", "rlimit", NULL, &procstat_rlimit, &cmdopt_none, 94 PS_CMP_NORMAL }, 95 { "rusage", "rusage", "[-Ht]", &procstat_rusage, &cmdopt_rusage, 96 PS_CMP_NORMAL }, 97 { "signal", "signals", "[-n]", &procstat_sigs, &cmdopt_signals, 98 PS_CMP_PLURAL | PS_CMP_SUBSTR }, 99 { "thread", "threads", NULL, &procstat_threads, &cmdopt_none, 100 PS_CMP_PLURAL }, 101 { "tsignal", "thread_signals", "[-n]", &procstat_threads_sigs, 102 &cmdopt_signals, PS_CMP_PLURAL | PS_CMP_SUBSTR }, 103 { "vm", "vm", NULL, &procstat_vm, &cmdopt_none, PS_CMP_NORMAL } 104 }; 105 106 static void 107 usage(void) 108 { 109 size_t i, l; 110 int multi; 111 112 xo_error("usage: procstat [--libxo] [-h] [-M core] [-N system]" 113 " [-w interval] command\n" 114 " [pid ... | core ...]\n" 115 " procstat [--libxo] -a [-h] [-M core] [-N system] " 116 " [-w interval] command\n" 117 " procstat [--libxo] [-h] [-M core] [-N system]" 118 " [-w interval]\n" 119 " [-S | -b | -c | -e | -f [-C] | -i [-n] | " 120 "-j [-n] | -k [-k] |\n" 121 " -l | -r [-H] | -s | -t | -v | -x] " 122 "[pid ... | core ...]\n" 123 " procstat [--libxo] -a [-h] [-M core] [-N system]" 124 " [-w interval]\n" 125 " [-S | -b | -c | -e | -f [-C] | -i [-n] | " 126 "-j [-n] | -k [-k] |\n" 127 " -l | -r [-H] | -s | -t | -v | -x]\n" 128 " procstat [--libxo] -L [-h] [-M core] [-N system] core ...\n" 129 "Available commands:\n"); 130 for (i = 0, l = nitems(cmd_table); i < l; i++) { 131 multi = i + 1 < l && cmd_table[i].cmd == cmd_table[i + 1].cmd; 132 xo_error(" %s%s%s", multi ? "[" : "", 133 cmd_table[i].command, (cmd_table[i].cmp & PS_CMP_PLURAL) ? 134 "(s)" : ""); 135 for (; i + 1 < l && cmd_table[i].cmd == cmd_table[i + 1].cmd; 136 i++) 137 xo_error(" | %s%s", cmd_table[i + 1].command, 138 (cmd_table[i].cmp & PS_CMP_PLURAL) ? "(s)" : ""); 139 if (multi) 140 xo_error("]"); 141 if (cmd_table[i].usage != NULL) 142 xo_error(" %s", cmd_table[i].usage); 143 xo_error("\n"); 144 } 145 xo_finish(); 146 exit(EX_USAGE); 147 } 148 149 static void 150 procstat(const struct procstat_cmd *cmd, struct procstat *prstat, 151 struct kinfo_proc *kipp) 152 { 153 char *pidstr = NULL; 154 155 asprintf(&pidstr, "%d", kipp->ki_pid); 156 if (pidstr == NULL) 157 xo_errc(1, ENOMEM, "Failed to allocate memory in procstat()"); 158 xo_open_container(pidstr); 159 cmd->cmd(prstat, kipp); 160 xo_close_container(pidstr); 161 free(pidstr); 162 } 163 164 /* 165 * Sort processes first by pid and then tid. 166 */ 167 static int 168 kinfo_proc_compare(const void *a, const void *b) 169 { 170 int i; 171 172 i = ((const struct kinfo_proc *)a)->ki_pid - 173 ((const struct kinfo_proc *)b)->ki_pid; 174 if (i != 0) 175 return (i); 176 i = ((const struct kinfo_proc *)a)->ki_tid - 177 ((const struct kinfo_proc *)b)->ki_tid; 178 return (i); 179 } 180 181 void 182 kinfo_proc_sort(struct kinfo_proc *kipp, int count) 183 { 184 185 qsort(kipp, count, sizeof(*kipp), kinfo_proc_compare); 186 } 187 188 const char * 189 kinfo_proc_thread_name(const struct kinfo_proc *kipp) 190 { 191 static char name[MAXCOMLEN+1]; 192 193 strlcpy(name, kipp->ki_tdname, sizeof(name)); 194 strlcat(name, kipp->ki_moretdname, sizeof(name)); 195 if (name[0] == '\0' || strcmp(kipp->ki_comm, name) == 0) { 196 name[0] = '-'; 197 name[1] = '\0'; 198 } 199 200 return (name); 201 } 202 203 static const struct procstat_cmd * 204 getcmd(const char *str) 205 { 206 const struct procstat_cmd *cmd; 207 size_t i, l; 208 int cmp, s; 209 210 if (str == NULL) 211 return (NULL); 212 cmd = NULL; 213 if ((l = strlen(str)) == 0) 214 return (getcmd("basic")); 215 s = l > 1 && strcasecmp(str + l - 1, "s") == 0; 216 for (i = 0; i < nitems(cmd_table); i++) { 217 /* 218 * After the first match substring matches are disabled, 219 * allowing subsequent full matches to take precedence. 220 */ 221 if (cmd == NULL && (cmd_table[i].cmp & PS_CMP_SUBSTR)) 222 cmp = strncasecmp(str, cmd_table[i].command, l - 223 ((cmd_table[i].cmp & PS_CMP_PLURAL) && s ? 1 : 0)); 224 else if ((cmd_table[i].cmp & PS_CMP_PLURAL) && s && 225 l == strlen(cmd_table[i].command) + 1) 226 cmp = strncasecmp(str, cmd_table[i].command, l - 1); 227 else 228 cmp = strcasecmp(str, cmd_table[i].command); 229 if (cmp == 0) 230 cmd = &cmd_table[i]; 231 } 232 return (cmd); 233 } 234 235 int 236 main(int argc, char *argv[]) 237 { 238 int ch, interval; 239 int i; 240 struct kinfo_proc *p; 241 const struct procstat_cmd *cmd; 242 struct procstat *prstat, *cprstat; 243 long l; 244 pid_t pid; 245 char *dummy; 246 char *nlistf, *memf; 247 int aflag; 248 int cnt; 249 250 interval = 0; 251 cmd = NULL; 252 memf = nlistf = NULL; 253 aflag = 0; 254 argc = xo_parse_args(argc, argv); 255 256 while ((ch = getopt(argc, argv, "abCcefHhijkLlM:N:nrSstvw:x")) != -1) { 257 switch (ch) { 258 case 'a': 259 aflag++; 260 break; 261 case 'b': 262 if (cmd != NULL) 263 usage(); 264 cmd = getcmd("binary"); 265 break; 266 case 'C': 267 procstat_opts |= PS_OPT_CAPABILITIES; 268 break; 269 case 'c': 270 if (cmd != NULL) 271 usage(); 272 cmd = getcmd("arguments"); 273 break; 274 case 'e': 275 if (cmd != NULL) 276 usage(); 277 cmd = getcmd("environment"); 278 break; 279 case 'f': 280 if (cmd != NULL) 281 usage(); 282 cmd = getcmd("files"); 283 break; 284 case 'H': 285 procstat_opts |= PS_OPT_PERTHREAD; 286 break; 287 case 'h': 288 procstat_opts |= PS_OPT_NOHEADER; 289 break; 290 case 'i': 291 if (cmd != NULL) 292 usage(); 293 cmd = getcmd("signals"); 294 break; 295 case 'j': 296 if (cmd != NULL) 297 usage(); 298 cmd = getcmd("tsignals"); 299 break; 300 case 'k': 301 if (cmd != NULL && cmd->cmd == procstat_kstack) { 302 if ((procstat_opts & PS_OPT_VERBOSE) != 0) 303 usage(); 304 procstat_opts |= PS_OPT_VERBOSE; 305 } else { 306 if (cmd != NULL) 307 usage(); 308 cmd = getcmd("kstack"); 309 } 310 break; 311 case 'L': 312 if (cmd != NULL) 313 usage(); 314 cmd = getcmd("ptlwpinfo"); 315 break; 316 case 'l': 317 if (cmd != NULL) 318 usage(); 319 cmd = getcmd("rlimit"); 320 break; 321 case 'M': 322 memf = optarg; 323 break; 324 case 'N': 325 nlistf = optarg; 326 break; 327 case 'n': 328 procstat_opts |= PS_OPT_SIGNUM; 329 break; 330 case 'r': 331 if (cmd != NULL) 332 usage(); 333 cmd = getcmd("rusage"); 334 break; 335 case 'S': 336 if (cmd != NULL) 337 usage(); 338 cmd = getcmd("cpuset"); 339 break; 340 case 's': 341 if (cmd != NULL) 342 usage(); 343 cmd = getcmd("credentials"); 344 break; 345 case 't': 346 if (cmd != NULL) 347 usage(); 348 cmd = getcmd("threads"); 349 break; 350 case 'v': 351 if (cmd != NULL) 352 usage(); 353 cmd = getcmd("vm"); 354 break; 355 case 'w': 356 l = strtol(optarg, &dummy, 10); 357 if (*dummy != '\0') 358 usage(); 359 if (l < 1 || l > INT_MAX) 360 usage(); 361 interval = l; 362 break; 363 case 'x': 364 if (cmd != NULL) 365 usage(); 366 cmd = getcmd("auxv"); 367 break; 368 case '?': 369 default: 370 usage(); 371 } 372 373 } 374 argc -= optind; 375 argv += optind; 376 377 if (cmd == NULL && argv[0] != NULL && (cmd = getcmd(argv[0])) != NULL) { 378 if ((procstat_opts & PS_SUBCOMMAND_OPTS) != 0) 379 usage(); 380 if (cmd->opt != NULL) { 381 optreset = 1; 382 optind = 1; 383 cmd->opt(argc, argv); 384 argc -= optind; 385 argv += optind; 386 } else { 387 argc -= 1; 388 argv += 1; 389 } 390 } else { 391 if (cmd == NULL) 392 cmd = getcmd("basic"); 393 if (cmd->cmd != procstat_files && 394 (procstat_opts & PS_OPT_CAPABILITIES) != 0) 395 usage(); 396 } 397 398 /* Must specify either the -a flag or a list of pids. */ 399 if (!(aflag == 1 && argc == 0) && !(aflag == 0 && argc > 0)) 400 usage(); 401 402 if (memf != NULL) 403 prstat = procstat_open_kvm(nlistf, memf); 404 else 405 prstat = procstat_open_sysctl(); 406 if (prstat == NULL) 407 xo_errx(1, "procstat_open()"); 408 do { 409 xo_set_version(PROCSTAT_XO_VERSION); 410 xo_open_container("procstat"); 411 xo_open_container(cmd->xocontainer); 412 413 if (aflag) { 414 p = procstat_getprocs(prstat, KERN_PROC_PROC, 0, &cnt); 415 if (p == NULL) 416 xo_errx(1, "procstat_getprocs()"); 417 kinfo_proc_sort(p, cnt); 418 for (i = 0; i < cnt; i++) { 419 procstat(cmd, prstat, &p[i]); 420 421 /* Suppress header after first process. */ 422 procstat_opts |= PS_OPT_NOHEADER; 423 xo_flush(); 424 } 425 procstat_freeprocs(prstat, p); 426 } 427 for (i = 0; i < argc; i++) { 428 l = strtol(argv[i], &dummy, 10); 429 if (*dummy == '\0') { 430 if (l < 0) 431 usage(); 432 pid = l; 433 434 p = procstat_getprocs(prstat, KERN_PROC_PID, 435 pid, &cnt); 436 if (p == NULL) 437 xo_errx(1, "procstat_getprocs()"); 438 if (cnt != 0) 439 procstat(cmd, prstat, p); 440 procstat_freeprocs(prstat, p); 441 } else { 442 cprstat = procstat_open_core(argv[i]); 443 if (cprstat == NULL) { 444 warnx("procstat_open()"); 445 continue; 446 } 447 p = procstat_getprocs(cprstat, KERN_PROC_PID, 448 -1, &cnt); 449 if (p == NULL) 450 xo_errx(1, "procstat_getprocs()"); 451 if (cnt != 0) 452 procstat(cmd, cprstat, p); 453 procstat_freeprocs(cprstat, p); 454 procstat_close(cprstat); 455 } 456 /* Suppress header after first process. */ 457 procstat_opts |= PS_OPT_NOHEADER; 458 } 459 460 xo_close_container(cmd->xocontainer); 461 xo_close_container("procstat"); 462 xo_finish(); 463 if (interval) 464 sleep(interval); 465 } while (interval); 466 467 procstat_close(prstat); 468 469 exit(0); 470 } 471 472 void 473 cmdopt_none(int argc, char * const argv[]) 474 { 475 int ch; 476 477 while ((ch = getopt(argc, argv, "")) != -1) { 478 switch (ch) { 479 case '?': 480 default: 481 usage(); 482 } 483 } 484 } 485 486 void 487 cmdopt_verbose(int argc, char * const argv[]) 488 { 489 int ch; 490 491 while ((ch = getopt(argc, argv, "v")) != -1) { 492 switch (ch) { 493 case 'v': 494 procstat_opts |= PS_OPT_VERBOSE; 495 break; 496 case '?': 497 default: 498 usage(); 499 } 500 } 501 } 502 503 void 504 cmdopt_signals(int argc, char * const argv[]) 505 { 506 int ch; 507 508 while ((ch = getopt(argc, argv, "n")) != -1) { 509 switch (ch) { 510 case 'n': 511 procstat_opts |= PS_OPT_SIGNUM; 512 break; 513 case '?': 514 default: 515 usage(); 516 } 517 } 518 } 519 520 void 521 cmdopt_rusage(int argc, char * const argv[]) 522 { 523 int ch; 524 525 while ((ch = getopt(argc, argv, "Ht")) != -1) { 526 switch (ch) { 527 case 'H': 528 /* FALLTHROUGH */ 529 case 't': 530 procstat_opts |= PS_OPT_PERTHREAD; 531 break; 532 case '?': 533 default: 534 usage(); 535 } 536 } 537 } 538 539 void 540 cmdopt_files(int argc, char * const argv[]) 541 { 542 int ch; 543 544 while ((ch = getopt(argc, argv, "C")) != -1) { 545 switch (ch) { 546 case 'C': 547 procstat_opts |= PS_OPT_CAPABILITIES; 548 break; 549 case '?': 550 default: 551 usage(); 552 } 553 } 554 } 555 556 void 557 cmdopt_cpuset(int argc, char * const argv[]) 558 { 559 560 procstat_opts |= PS_OPT_PERTHREAD; 561 cmdopt_none(argc, argv); 562 } 563