1 /* ps.c - show process list 2 * 3 * Copyright 2015 Rob Landley <rob (at) landley.net> 4 * 5 * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ps.html 6 * And http://kernel.org/doc/Documentation/filesystems/proc.txt Table 1-4 7 * And linux kernel source fs/proc/array.c function do_task_stat() 8 * 9 * Deviations from posix: no -n because /proc/self/wchan exists; we use -n to 10 * mean "show numeric users and groups" instead. 11 * Posix says default output should have field named "TTY" but if you "-o tty" 12 * the same field should be called "TT" which is _INSANE_ and I'm not doing it. 13 * Similarly -f outputs USER but calls it UID (we call it USER). 14 * It also says that -o "args" and "comm" should behave differently but use 15 * the same title, which is not the same title as the default output. (No.) 16 * Select by session id is -s not -g. 17 * 18 * Posix defines -o ADDR as "The address of the process" but the process 19 * start address is a constant on any elf system with mmu. The procps ADDR 20 * field always prints "-" with an alignment of 1, which is why it has 11 21 * characters left for "cmd" in in 80 column "ps -l" mode. On x86-64 you 22 * need 12 chars, leaving nothing for cmd: I.E. posix 2008 ps -l mode can't 23 * be sanely implemented on 64 bit Linux systems. In procps there's ps -y 24 * which changes -l by removing the "F" column and swapping RSS for ADDR, 25 * leaving 9 chars for cmd, so we're using that as our -l output. 26 * 27 * Added a bunch of new -o fields posix doesn't mention, and we don't 28 * label "ps -o command,args,comm" as "COMMAND COMMAND COMMAND". We don't 29 * output argv[0] unmodified for -o comm or -o args (but procps violates 30 * posix for -o comm anyway, it's stat[2] not argv[0]). 31 * 32 * Note: iotop is STAYROOT so it can read other process's /proc/$PID/io 33 * files (why they're not globally readable when the rest of proc 34 * data is...?) and get a global I/O picture. Normal top is NOT, 35 * even though you can -o AIO there, to give sysadmins the option 36 * to reduce security exposure.) 37 * 38 * TODO: ps aux (att & bsd style "ps -ax" vs "ps ax" behavior difference) 39 * TODO: switch -fl to -y 40 * TODO: thread support /proc/$d/task/%d/stat (and -o stat has "l") 41 * TODO: iotop: Window size change: respond immediately. Why not padding 42 * at right edge? (Not adjusting to screen size at all? Header wraps?) 43 * TODO: top: thread support and SMP 44 * TODO: pgrep -f only searches the amount of cmdline that fits in toybuf. 45 46 USE_PS(NEWTOY(ps, "k(sort)*P(ppid)*aAdeflMno*O*p(pid)*s*t*u*U*g*G*wZ[!ol][+Ae]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE)) 47 // stayroot because iotop needs root to read other process' proc/$$/io 48 USE_TOP(NEWTOY(top, ">0m" "k*o*p*u*s#<1=9d#=3<1n#<1bq", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE)) 49 USE_IOTOP(NEWTOY(iotop, ">0AaKO" "k*o*p*u*s#<1=7d#=3<1n#<1bq", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT|TOYFLAG_LOCALE)) 50 USE_PGREP(NEWTOY(pgrep, "?cld:u*U*t*s*P*g*G*fnovxL:[-no]", TOYFLAG_USR|TOYFLAG_BIN)) 51 USE_PKILL(NEWTOY(pkill, "Vu*U*t*s*P*g*G*fnovxl:[-no]", TOYFLAG_USR|TOYFLAG_BIN)) 52 53 config PS 54 bool "ps" 55 default y 56 help 57 usage: ps [-AadeflnwZ] [-gG GROUP,] [-k FIELD,] [-o FIELD,] [-p PID,] [-t TTY,] [-uU USER,] 58 59 List processes. 60 61 Which processes to show (selections may be comma separated lists): 62 63 -A All processes 64 -a Processes with terminals that aren't session leaders 65 -d All processes that aren't session leaders 66 -e Same as -A 67 -g Belonging to GROUPs 68 -G Belonging to real GROUPs (before sgid) 69 -p PIDs (--pid) 70 -P Parent PIDs (--ppid) 71 -s In session IDs 72 -t Attached to selected TTYs 73 -u Owned by USERs 74 -U Owned by real USERs (before suid) 75 76 Output modifiers: 77 78 -k Sort FIELDs in +increasing or -decreasting order (--sort) 79 -M Measure field widths (expanding as necessary) 80 -n Show numeric USER and GROUP 81 -w Wide output (don't truncate at terminal width) 82 83 Which FIELDs to show. (Default = -o PID,TTY,TIME,CMD) 84 85 -f Full listing (-o USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD) 86 -l Long listing (-o F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD) 87 -o Output FIELDs instead of defaults, each with optional :size and =title 88 -O Add FIELDS to defaults 89 -Z Include LABEL 90 91 Available -o FIELDs: 92 93 ADDR Instruction pointer ARGS Command line (argv[] -path) 94 CMD COMM without -f, ARGS with -f CMDLINE Command line (argv[]) 95 COMM Original command name COMMAND Original command path 96 CPU Which processor running on ETIME Elapsed time since PID start 97 F Flags (1=FORKNOEXEC 4=SUPERPRIV) GID Group id 98 GROUP Group name LABEL Security label 99 MAJFL Major page faults MINFL Minor page faults 100 NAME Command name (argv[0]) NI Niceness (lower is faster) 101 PCPU Percentage of CPU time used PGID Process Group ID 102 PID Process ID PPID Parent Process ID 103 PRI Priority (higher is faster) PSR Processor last executed on 104 RGID Real (before sgid) group ID RGROUP Real (before sgid) group name 105 RSS Resident Set Size (pages in use) RTPRIO Realtime priority 106 RUID Real (before suid) user ID RUSER Real (before suid) user name 107 S Process state: 108 R (running) S (sleeping) D (device I/O) T (stopped) t (traced) 109 Z (zombie) X (deader) x (dead) K (wakekill) W (waking) 110 SCHED Scheduling policy (0=other, 1=fifo, 2=rr, 3=batch, 4=iso, 5=idle) 111 STAT Process state (S) plus: 112 < high priority N low priority L locked memory 113 s session leader + foreground l multithreaded 114 STIME Start time of process in hh:mm (size :19 shows yyyy-mm-dd hh:mm:ss) 115 SZ Memory Size (4k pages needed to completely swap out process) 116 TIME CPU time consumed TTY Controlling terminal 117 UID User id USER User name 118 VSZ Virtual memory size (1k units) %VSZ VSZ as % of physical memory 119 WCHAN Waiting in kernel for 120 121 config TOP 122 bool "top" 123 default y 124 help 125 usage: top [-m] [ -d seconds ] [ -n iterations ] 126 127 Show process activity in real time. 128 129 -k Fallback sort FIELDS (default -S,-%CPU,-ETIME,-PID) 130 -o Show FIELDS (def PID,USER,PR,NI,VIRT,RES,SHR,S,%CPU,%MEM,TIME+,CMDLINE) 131 -s Sort by field number (1-X, default 9) 132 133 # Requires CONFIG_IRQ_TIME_ACCOUNTING in the kernel for /proc/$$/io 134 config IOTOP 135 bool "iotop" 136 default y 137 help 138 usage: iotop [-AaKO] 139 140 Rank processes by I/O. 141 142 -A All I/O, not just disk 143 -a Accumulated I/O (not percentage) 144 -K Kilobytes 145 -k Fallback sort FIELDS (default -[D]IO,-ETIME,-PID) 146 -O Only show processes doing I/O 147 -o Show FIELDS (default PID,PR,USER,[D]READ,[D]WRITE,SWAP,[D]IO,COMM) 148 -s Sort by field number (0-X, default 6) 149 150 config TOP_COMMON 151 bool 152 default y 153 help 154 usage: COMMON [-bq] [-n NUMBER] [-d SECONDS] [-p PID,] [-u USER,] [-s SORT] 155 156 -b Batch mode (no tty) 157 -d Delay SECONDS between each cycle (default 3) 158 -n Exit after NUMBER iterations 159 -p Show these PIDs 160 -u Show these USERs 161 -q Quiet (no header lines) 162 163 Cursor LEFT/RIGHT to change sort, UP/DOWN move list, space to force 164 update, R to reverse sort, Q to exit. 165 166 config PGREP 167 bool "pgrep" 168 default y 169 depends on PGKILL_COMMON 170 help 171 usage: pgrep [-cL] [-d DELIM] [-L SIGNAL] [PATTERN] 172 173 Search for process(es). PATTERN is an extended regular expression checked 174 against command names. 175 176 -c Show only count of matches 177 -d Use DELIM instead of newline 178 -L Send SIGNAL instead of printing name 179 -l Show command name 180 181 config PGKILL_COMMON 182 bool 183 default y 184 help 185 usage: pgrep [-fnovx] [-G GID,] [-g PGRP,] [-P PPID,] [-s SID,] [-t TERM,] [-U UID,] [-u EUID,] 186 187 -f Check full command line for PATTERN 188 -G Match real Group ID(s) 189 -g Match Process Group(s) (0 is current user) 190 -n Newest match only 191 -o Oldest match only 192 -P Match Parent Process ID(s) 193 -s Match Session ID(s) (0 for current) 194 -t Match Terminal(s) 195 -U Match real User ID(s) 196 -u Match effective User ID(s) 197 -v Negate the match 198 -x Match whole command (not substring) 199 200 config PKILL 201 bool "pkill" 202 default y 203 help 204 usage: pkill [-l SIGNAL] [PATTERN] 205 206 -l SIGNAL to send 207 -V verbose 208 */ 209 210 #define FOR_ps 211 #include "toys.h" 212 213 GLOBALS( 214 union { 215 struct { 216 struct arg_list *G; 217 struct arg_list *g; 218 struct arg_list *U; 219 struct arg_list *u; 220 struct arg_list *t; 221 struct arg_list *s; 222 struct arg_list *p; 223 struct arg_list *O; 224 struct arg_list *o; 225 struct arg_list *P; 226 struct arg_list *k; 227 } ps; 228 struct { 229 long n; 230 long d; 231 long s; 232 struct arg_list *u; 233 struct arg_list *p; 234 struct arg_list *o; 235 struct arg_list *k; 236 } top; 237 struct{ 238 char *L; 239 struct arg_list *G; 240 struct arg_list *g; 241 struct arg_list *P; 242 struct arg_list *s; 243 struct arg_list *t; 244 struct arg_list *U; 245 struct arg_list *u; 246 char *d; 247 248 void *regexes, *snapshot; 249 int signal; 250 pid_t self, match; 251 } pgrep; 252 }; 253 254 struct sysinfo si; 255 struct ptr_len gg, GG, pp, PP, ss, tt, uu, UU; 256 unsigned width, height; 257 dev_t tty; 258 void *fields, *kfields; 259 long long ticks, bits, time; 260 int kcount, forcek, sortpos; 261 int (*match_process)(long long *slot); 262 void (*show_process)(void *tb); 263 ) 264 265 struct strawberry { 266 struct strawberry *next, *prev; 267 short which, len, reverse; 268 char *title; 269 char forever[]; 270 }; 271 272 /* The slot[] array is mostly populated from /proc/$PID/stat (kernel proc.txt 273 * table 1-4) but we shift and repurpose fields, with the result being: */ 274 275 enum { 276 SLOT_pid, /*process id*/ SLOT_ppid, // parent process id 277 SLOT_pgrp, /*process group*/ SLOT_sid, // session id 278 SLOT_ttynr, /*tty the process uses*/ SLOT_ttypgrp, // pgrp of the tty 279 SLOT_flags, /*task flags*/ SLOT_minflt, // minor faults 280 SLOT_cminflt, /*minor faults+child*/ SLOT_majflt, // major faults 281 SLOT_cmajflt, /*major faults+child*/ SLOT_utime, // user+kernel jiffies 282 SLOT_stime, /*kernel mode jiffies*/ SLOT_cutime, // utime+child 283 SLOT_cstime, /*stime+child*/ SLOT_priority, // priority level 284 SLOT_nice, /*nice level*/ SLOT_numthreads,// thread count 285 SLOT_vmlck, /*locked memory*/ SLOT_starttime, // jiffies after boot 286 SLOT_vsize, /*virtual memory size*/ SLOT_rss, // resident set size 287 SLOT_rsslim, /*limit in bytes on rss*/ SLOT_startcode, // code segment addr 288 SLOT_endcode, /*code segment address*/ SLOT_startstack,// stack address 289 SLOT_esp, /*task stack pointer*/ SLOT_eip, // instruction pointer 290 SLOT_iobytes, /*All I/O bytes*/ SLOT_diobytes, // disk I/O bytes 291 SLOT_utime2, /*relative utime (top)*/ SLOT_uid, // user id 292 SLOT_ruid, /*real user id*/ SLOT_gid, // group id 293 SLOT_rgid, /*real group id*/ SLOT_exitsig, // sent to parent 294 SLOT_taskcpu, /*CPU running on*/ SLOT_rtprio, // realtime priority 295 SLOT_policy, /*man sched_setscheduler*/SLOT_blkioticks,// IO wait time 296 SLOT_gtime, /*guest jiffies of task*/ SLOT_cgtime, // gtime+child 297 SLOT_startbss, /*data/bss address*/ SLOT_endbss, // end addr data+bss 298 SLOT_upticks, /*46-19 (divisor for %)*/ SLOT_argv0len, // argv[0] length 299 SLOT_uptime, /*si.uptime @read time*/ SLOT_vsz, // Virtual mem Size 300 SLOT_rss2, /*Resident Set Size*/ SLOT_shr, // Shared memory 301 SLOT_rchar, /*All bytes read*/ SLOT_wchar, // All bytes written 302 SLOT_rbytes, /*Disk bytes read*/ SLOT_wbytes, // Disk bytes written 303 SLOT_swap, /*Swap pages used*/ 304 }; 305 306 // Data layout in toybuf 307 struct carveup { 308 long long slot[55]; // data from /proc 309 unsigned short offset[5]; // offset of fields in str[] (skip name, always 0) 310 char state; 311 char str[]; // name, tty, command, wchan, attr, cmdline 312 }; 313 314 // TODO: Android uses -30 for LABEL, but ideally it would auto-size. 315 // 64|slot means compare as string when sorting 316 struct typography { 317 char *name; 318 signed char width, slot; 319 } static const typos[] = TAGGED_ARRAY(PS, 320 // Numbers 321 {"PID", 5, SLOT_pid}, {"PPID", 5, SLOT_ppid}, {"PRI", 3, SLOT_priority}, 322 {"NI", 3, SLOT_nice}, {"ADDR", 4+sizeof(long), SLOT_eip}, 323 {"SZ", 5, SLOT_vsize}, {"RSS", 5, SLOT_rss}, {"PGID", 5, SLOT_pgrp}, 324 {"VSZ", 6, SLOT_vsize}, {"MAJFL", 6, SLOT_majflt}, {"MINFL", 6, SLOT_minflt}, 325 {"PR", 2, SLOT_priority}, {"PSR", 3, SLOT_taskcpu}, 326 {"RTPRIO", 6, SLOT_rtprio}, {"SCH", 3, SLOT_policy}, {"CPU", 3, SLOT_taskcpu}, 327 328 // String fields 329 {"COMM", -15, -1}, {"TTY", -8, -2}, {"WCHAN", -6, -3}, {"LABEL", -30, -4}, 330 {"COMMAND", -27, -5}, {"CMDLINE", -27, -6}, {"ARGS", -27, -6}, 331 {"NAME", -15, -6}, {"CMD", -27, -1}, 332 333 // user/group 334 {"UID", 5, SLOT_uid}, {"USER", -8, 64|SLOT_uid}, {"RUID", 4, SLOT_ruid}, 335 {"RUSER", -8, 64|SLOT_ruid}, {"GID", 8, SLOT_gid}, {"GROUP", -8, 64|SLOT_gid}, 336 {"RGID", 4, SLOT_rgid}, {"RGROUP", -8, 64|SLOT_rgid}, 337 338 // clock displays 339 {"TIME", 8, SLOT_utime}, {"ELAPSED", 11, SLOT_starttime}, 340 {"TIME+", 9, SLOT_utime}, 341 342 // Percentage displays 343 {"C", 1, SLOT_utime2}, {"%VSZ", 5, SLOT_vsize}, {"%MEM", 5, SLOT_rss}, 344 {"%CPU", 4, SLOT_utime2}, 345 346 // human_readable 347 {"VIRT", 4, SLOT_vsz}, {"RES", 4, SLOT_rss2}, 348 {"SHR", 4, SLOT_shr}, {"READ", 6, SLOT_rchar}, {"WRITE", 6, SLOT_wchar}, 349 {"IO", 6, SLOT_iobytes}, {"DREAD", 6, SLOT_rbytes}, 350 {"DWRITE", 6, SLOT_wbytes}, {"SWAP", 6, SLOT_swap}, {"DIO", 6, SLOT_diobytes}, 351 352 // Misc 353 {"STIME", 5, SLOT_starttime}, {"F", 1, 64|SLOT_flags}, {"S", -1, 64}, 354 {"STAT", -5, 64}, 355 ); 356 357 // Return 0 to discard, nonzero to keep 358 static int shared_match_process(long long *slot) 359 { 360 struct ptr_len match[] = { 361 {&TT.gg, SLOT_gid}, {&TT.GG, SLOT_rgid}, {&TT.pp, SLOT_pid}, 362 {&TT.PP, SLOT_ppid}, {&TT.ss, SLOT_sid}, {&TT.tt, SLOT_ttynr}, 363 {&TT.uu, SLOT_uid}, {&TT.UU, SLOT_ruid} 364 }; 365 int i, j; 366 long *ll = 0; 367 368 // Do we have -g -G -p -P -s -t -u -U options selecting processes? 369 for (i = 0; i < ARRAY_LEN(match); i++) { 370 struct ptr_len *mm = match[i].ptr; 371 if (mm->len) { 372 ll = mm->ptr; 373 for (j = 0; j<mm->len; j++) if (ll[j] == slot[match[i].len]) return 1; 374 } 375 } 376 377 return ll ? 0 : -1; 378 } 379 380 381 // Return 0 to discard, nonzero to keep 382 static int ps_match_process(long long *slot) 383 { 384 int i = shared_match_process(slot); 385 386 if (i>0) return 1; 387 // If we had selections and didn't match them, don't display 388 if (!i) return 0; 389 390 // Filter implicit categories for other display types 391 if ((toys.optflags&(FLAG_a|FLAG_d)) && slot[SLOT_sid]==*slot) return 0; 392 if ((toys.optflags&FLAG_a) && !slot[SLOT_ttynr]) return 0; 393 if (!(toys.optflags&(FLAG_a|FLAG_d|FLAG_A|FLAG_e)) 394 && TT.tty!=slot[SLOT_ttynr]) return 0; 395 396 return 1; 397 } 398 399 // Convert field to string representation 400 static char *string_field(struct carveup *tb, struct strawberry *field) 401 { 402 char *buf = toybuf+sizeof(toybuf)-260, *out = buf, *s; 403 int which = field->which, sl = typos[which].slot; 404 long long *slot = tb->slot, ll = (sl >= 0) ? slot[sl&63] : 0; 405 406 // numbers, mostly from /proc/$PID/stat 407 if (which <= PS_CPU) { 408 char *fmt = "%lld"; 409 410 if (which==PS_PRI) ll = 39-ll; 411 if (which==PS_ADDR) fmt = "%llx"; 412 else if (which==PS_SZ) ll >>= 12; 413 else if (which==PS_RSS) ll <<= 2; 414 else if (which==PS_VSZ) ll >>= 10; 415 else if (which==PS_PR && ll<-9) fmt="RT"; 416 else if (which==PS_RTPRIO && ll == 0) fmt="-"; 417 sprintf(out, fmt, ll); 418 419 // String fields 420 } else if (sl < 0) { 421 if (slot[SLOT_argv0len]) 422 tb->str[tb->offset[4]+slot[SLOT_argv0len]] = (which==PS_NAME) ? 0 : ' '; 423 out = tb->str; 424 sl *= -1; 425 if (--sl) out += tb->offset[--sl]; 426 if (which==PS_ARGS) 427 for (s = out; *s && *s != ' '; s++) if (*s == '/') out = s+1; 428 if (which>=PS_COMMAND && !*out) sprintf(out = buf, "[%s]", tb->str); 429 430 // user/group 431 } else if (which <= PS_RGROUP) { 432 sprintf(out, "%lld", ll); 433 if (sl&64) { 434 if (which > PS_RUSER) { 435 struct group *gr = getgrgid(ll); 436 437 if (gr) out = gr->gr_name; 438 } else { 439 struct passwd *pw = getpwuid(ll); 440 441 if (pw) out = pw->pw_name; 442 } 443 } 444 445 // Clock displays 446 } else if (which <= PS_TIME_) { 447 int unit = 60, pad = 2, j = TT.ticks; 448 time_t seconds; 449 450 if (which!=PS_TIME_) unit *= 60*24; 451 else pad = 0; 452 // top adjusts slot[SLOT_upticks], we want original meaning. 453 if (which==PS_ELAPSED) ll = (slot[SLOT_uptime]*j)-slot[SLOT_starttime]; 454 seconds = ll/j; 455 456 // Output days-hours:mins:secs, skipping non-required fields with zero 457 // TIME has 3 required fields, ETIME has 2. (Posix!) TIME+ is from top 458 for (s = 0, j = 2*(which==PS_TIME_); j<4; j++) { 459 if (!s && (seconds>unit || j == 1+(which!=PS_TIME))) s = out; 460 if (s) { 461 s += sprintf(s, j ? "%0*ld": "%*ld", pad, (long)(seconds/unit)); 462 pad = 2; 463 if ((*s = "-::"[j])) s++; 464 } 465 seconds %= unit; 466 unit /= j ? 60 : 24; 467 } 468 if (which==PS_TIME_ && s-out<8) 469 sprintf(s, ".%02lld", (100*(ll%TT.ticks))/TT.ticks); 470 471 // Percentage displays 472 } else if (which <= PS__CPU) { 473 ll = slot[sl&63]*1000; 474 if (which==PS__VSZ || which==PS__MEM) 475 ll /= TT.si.totalram/((which==PS__VSZ) ? 1024 : 4096); 476 else if (slot[SLOT_upticks]) ll /= slot[SLOT_upticks]; 477 sl = ll; 478 if (which==PS_C) sl += 5; 479 sprintf(out, "%d", sl/10); 480 if (which!=PS_C && sl<1000) sprintf(out+strlen(out), ".%d", sl%10); 481 482 // Human readable 483 } else if (which <= PS_DIO) { 484 ll = slot[typos[which].slot]; 485 if (which <= PS_SHR) ll *= sysconf(_SC_PAGESIZE); 486 if (TT.forcek) sprintf(out, "%lldk", ll/1024); 487 else human_readable(out, ll, 0); 488 489 // Posix doesn't specify what flags should say. Man page says 490 // 1 for PF_FORKNOEXEC and 4 for PF_SUPERPRIV from linux/sched.h 491 } else if (which==PS_F) sprintf(out, "%llo", (slot[SLOT_flags]>>6)&5); 492 else if (which==PS_S || which==PS_STAT) { 493 s = out; 494 *s++ = tb->state; 495 if (which==PS_STAT) { 496 // TODO l = multithreaded 497 if (slot[SLOT_nice]<0) *s++ = '<'; 498 else if (slot[SLOT_nice]>0) *s++ = 'N'; 499 if (slot[SLOT_sid]==*slot) *s++ = 's'; 500 if (slot[SLOT_vmlck]) *s++ = 'L'; 501 if (slot[SLOT_ttypgrp]==*slot) *s++ = '+'; 502 } 503 *s = 0; 504 } else if (which==PS_STIME) { 505 time_t t = time(0)-slot[SLOT_uptime]+slot[SLOT_starttime]/TT.ticks; 506 507 // Padding behavior's a bit odd: default field size is just hh:mm. 508 // Increasing stime:size reveals more data at left until full, 509 // so move start address so yyyy-mm-dd hh:mm revealed on left at :16, 510 // then add :ss on right for :19. 511 strftime(out, 260, "%F %T", localtime(&t)); 512 out = out+strlen(out)-3-abs(field->len); 513 if (out<buf) out = buf; 514 515 } else if (CFG_TOYBOX_DEBUG) error_exit("bad which %d", which); 516 517 return out; 518 } 519 520 // Display process data that get_ps() read from /proc, formatting with TT.fields 521 static void show_ps(struct carveup *tb) 522 { 523 struct strawberry *field; 524 int pad, len, width = TT.width; 525 526 // Loop through fields to display 527 for (field = TT.fields; field; field = field->next) { 528 char *out = string_field(tb, field); 529 530 // Output the field, appropriately padded 531 if (field != TT.fields) { 532 putchar(' '); 533 width--; 534 } 535 len = width; 536 pad = 0; 537 if (field->next || field->len>0) 538 len = abs(pad = width<abs(field->len) ? width : field->len); 539 540 if (TT.tty) width -= draw_trim(out, pad, len); 541 else width -= printf("%*.*s", pad, len, out); 542 if (!width) break; 543 } 544 xputc(TT.time ? '\r' : '\n'); 545 } 546 547 // dirtree callback: read data about process to display, store, or discard it. 548 // Fills toybuf with struct carveup and either DIRTREE_SAVEs a copy to ->extra 549 // (in -k mode) or calls show_ps on toybuf (no malloc/copy/free there). 550 static int get_ps(struct dirtree *new) 551 { 552 struct { 553 char *name; 554 long long bits; 555 } fetch[] = { 556 {"fd/", _PS_TTY}, {"wchan", _PS_WCHAN}, {"attr/current", _PS_LABEL}, 557 {"exe", _PS_COMMAND}, {"cmdline", _PS_CMDLINE|_PS_ARGS|_PS_NAME} 558 }; 559 struct carveup *tb = (void *)toybuf; 560 long long *slot = tb->slot; 561 char *name, *s, *buf = tb->str, *end = 0; 562 int i, j, fd; 563 off_t len; 564 565 // Recurse one level into /proc children, skip non-numeric entries 566 if (!new->parent) 567 return DIRTREE_RECURSE|DIRTREE_SHUTUP|(DIRTREE_SAVE*!TT.show_process); 568 569 memset(slot, 0, sizeof(tb->slot)); 570 if (!(*slot = atol(new->name))) return 0; 571 fd = dirtree_parentfd(new); 572 573 len = 2048; 574 sprintf(buf, "%lld/stat", *slot); 575 if (!readfileat(fd, buf, buf, &len)) return 0; 576 577 // parse oddball fields (name and state). Name can have embedded ')' so match 578 // _last_ ')' in stat (although VFS limits filenames to 255 bytes max). 579 // All remaining fields should be numeric. 580 if (!(name = strchr(buf, '('))) return 0; 581 for (s = ++name; *s; s++) if (*s == ')') end = s; 582 if (!end || end-name>255) return 0; 583 584 // Parse numeric fields (starting at 4th field in slot[SLOT_ppid]) 585 if (1>sscanf(s = end, ") %c%n", &tb->state, &i)) return 0; 586 for (j = 1; j<50; j++) if (1>sscanf(s += i, " %lld%n", slot+j, &i)) break; 587 588 // Now we've read the data, move status and name right after slot[] array, 589 // and convert low chars to ? for non-tty display while we're at it. 590 for (i = 0; i<end-name; i++) 591 if ((tb->str[i] = name[i]) < ' ') 592 if (!TT.tty) tb->str[i] = '?'; 593 buf = tb->str+i; 594 *buf++ = 0; 595 len = sizeof(toybuf)-(buf-toybuf); 596 597 // save uid, ruid, gid, gid, and rgid int slots 31-34 (we don't use sigcatch 598 // or numeric wchan, and the remaining two are always zero), and vmlck into 599 // 18 (which is "obsolete, always 0" from stat) 600 slot[SLOT_uid] = new->st.st_uid; 601 slot[SLOT_gid] = new->st.st_gid; 602 603 // TIME and TIME+ use combined value, ksort needs 'em added. 604 slot[SLOT_utime] += slot[SLOT_stime]; 605 slot[SLOT_utime2] = slot[SLOT_utime]; 606 607 // If RGROUP RUSER STAT RUID RGID SWAP happening, or -G or -U, parse "status" 608 // and save ruid, rgid, and vmlck. 609 if ((TT.bits&(_PS_RGROUP|_PS_RUSER|_PS_STAT|_PS_RUID|_PS_RGID|_PS_SWAP 610 |_PS_IO|_PS_DIO)) || TT.GG.len || TT.UU.len) 611 { 612 off_t temp = len; 613 614 sprintf(buf, "%lld/status", *slot); 615 if (!readfileat(fd, buf, buf, &temp)) *buf = 0; 616 s = strafter(buf, "\nUid:"); 617 slot[SLOT_ruid] = s ? atol(s) : new->st.st_uid; 618 s = strafter(buf, "\nGid:"); 619 slot[SLOT_rgid] = s ? atol(s) : new->st.st_gid; 620 if ((s = strafter(buf, "\nVmLck:"))) slot[SLOT_vmlck] = atoll(s); 621 if ((s = strafter(buf, "\nVmSwap:"))) slot[SLOT_swap] = atoll(s); 622 } 623 624 // Do we need to read "io"? 625 if (TT.bits&(_PS_READ|_PS_WRITE|_PS_DREAD|_PS_DWRITE|_PS_IO|_PS_DIO)) { 626 off_t temp = len; 627 628 sprintf(buf, "%lld/io", *slot); 629 if (!readfileat(fd, buf, buf, &temp)) *buf = 0; 630 if ((s = strafter(buf, "rchar:"))) slot[SLOT_rchar] = atoll(s); 631 if ((s = strafter(buf, "wchar:"))) slot[SLOT_wchar] = atoll(s); 632 if ((s = strafter(buf, "read_bytes:"))) slot[SLOT_rbytes] = atoll(s); 633 if ((s = strafter(buf, "write_bytes:"))) slot[SLOT_wbytes] = atoll(s); 634 slot[SLOT_iobytes] = slot[SLOT_rchar]+slot[SLOT_wchar]+slot[SLOT_swap]; 635 slot[SLOT_diobytes] = slot[SLOT_rbytes]+slot[SLOT_wbytes]+slot[SLOT_swap]; 636 } 637 638 // We now know enough to skip processes we don't care about. 639 if (TT.match_process && !TT.match_process(slot)) return 0; 640 641 // /proc data is generated as it's read, so for maximum accuracy on slow 642 // systems (or ps | more) we re-fetch uptime as we fetch each /proc line. 643 sysinfo(&TT.si); 644 slot[SLOT_uptime] = TT.si.uptime; 645 slot[SLOT_upticks] = slot[SLOT_uptime]*TT.ticks - slot[SLOT_starttime]; 646 647 // Do we need to read "statm"? 648 if (TT.bits&(_PS_VIRT|_PS_RES|_PS_SHR)) { 649 off_t temp = len; 650 651 sprintf(buf, "%lld/statm", *slot); 652 if (!readfileat(fd, buf, buf, &temp)) *buf = 0; 653 654 for (s = buf, i=0; i<3; i++) 655 if (!sscanf(s, " %lld%n", slot+SLOT_vsz+i, &j)) slot[SLOT_vsz+i] = 0; 656 else s += j; 657 } 658 659 // Fetch string data while parentfd still available, appending to buf. 660 // (There's well over 3k of toybuf left. We could dynamically malloc, but 661 // it'd almost never get used, querying length of a proc file is awkward, 662 // fixed buffer is nommu friendly... Wait for somebody to complain. :) 663 slot[SLOT_argv0len] = 0; 664 for (j = 0; j<ARRAY_LEN(fetch); j++) { 665 tb->offset[j] = buf-(tb->str); 666 if (!(TT.bits&fetch[j].bits)) { 667 *buf++ = 0; 668 continue; 669 } 670 671 // Determine remaining space, reserving minimum of 256 bytes/field and 672 // 260 bytes scratch space at the end (for output conversion later). 673 len = sizeof(toybuf)-(buf-toybuf)-260-256*(ARRAY_LEN(fetch)-j); 674 sprintf(buf, "%lld/%s", *slot, fetch[j].name); 675 676 // For cmdline we readlink instead of read contents 677 if (j==3) { 678 if ((len = readlinkat(fd, buf, buf, len))>0) buf[len] = 0; 679 else *buf = 0; 680 681 // If it's not the TTY field, data we want is in a file. 682 // Last length saved in slot[] is command line (which has embedded NULs) 683 } else if (!j) { 684 int rdev = slot[SLOT_ttynr]; 685 struct stat st; 686 687 // Call no tty "?" rather than "0:0". 688 strcpy(buf, "?"); 689 if (rdev) { 690 // Can we readlink() our way to a name? 691 for (i = 0; i<3; i++) { 692 sprintf(buf, "%lld/fd/%i", *slot, i); 693 if (!fstatat(fd, buf, &st, 0) && S_ISCHR(st.st_mode) 694 && st.st_rdev == rdev && 0<(len = readlinkat(fd, buf, buf, len))) 695 { 696 buf[len] = 0; 697 break; 698 } 699 } 700 701 // Couldn't find it, try all the tty drivers. 702 if (i == 3) { 703 FILE *fp = fopen("/proc/tty/drivers", "r"); 704 int tty_major = 0, maj = major(rdev), min = minor(rdev); 705 706 if (fp) { 707 while (fscanf(fp, "%*s %256s %d %*s %*s", buf, &tty_major) == 2) { 708 // TODO: we could parse the minor range too. 709 if (tty_major == maj) { 710 sprintf(buf+strlen(buf), "%d", min); 711 if (!stat(buf, &st) && S_ISCHR(st.st_mode) && st.st_rdev==rdev) 712 break; 713 } 714 tty_major = 0; 715 } 716 fclose(fp); 717 } 718 719 // Really couldn't find it, so just show major:minor. 720 if (!tty_major) sprintf(buf, "%d:%d", maj, min); 721 } 722 723 s = buf; 724 if (strstart(&s, "/dev/")) memmove(buf, s, strlen(s)+1); 725 } 726 727 // Data we want is in a file. 728 // Last length saved in slot[] is command line (which has embedded NULs) 729 } else { 730 731 // When command has no arguments, don't space over the NUL 732 if (readfileat(fd, buf, buf, &len) && len>0) { 733 int temp = 0; 734 735 if (buf[len-1]=='\n') buf[--len] = 0; 736 737 // Turn NUL to space, other low ascii to ? (in non-tty mode) 738 for (i=0; i<len; i++) { 739 char c = buf[i]; 740 741 if (!c) { 742 if (!temp) temp = i; 743 c = ' '; 744 } else if (!TT.tty && c<' ') c = '?'; 745 buf[i] = c; 746 } 747 len = temp; // position of _first_ NUL 748 } else *buf = len = 0; 749 // Store end of argv[0] so NAME and CMDLINE can differ. 750 slot[SLOT_argv0len] = len; 751 } 752 753 buf += strlen(buf)+1; 754 } 755 756 TT.kcount++; 757 if (TT.show_process) { 758 TT.show_process(tb); 759 760 return 0; 761 } 762 763 // If we need to sort the output, add it to the list and return. 764 s = xmalloc(buf-toybuf); 765 new->extra = (long)s; 766 memcpy(s, toybuf, buf-toybuf); 767 768 return DIRTREE_SAVE; 769 } 770 771 static char *parse_ko(void *data, char *type, int length) 772 { 773 struct strawberry *field; 774 char *width, *title, *end, *s; 775 int i, j, k; 776 777 // Get title, length of title, type, end of type, and display width 778 779 // Chip off =name to display 780 if ((end = strchr(type, '=')) && length>(end-type)) { 781 title = end+1; 782 length -= (end-type)+1; 783 } else { 784 end = type+length; 785 title = 0; 786 } 787 788 // Chip off :width to display 789 if ((width = strchr(type, ':')) && width<end) { 790 if (!title) length = width-type; 791 } else width = 0; 792 793 // Allocate structure, copy title 794 field = xzalloc(sizeof(struct strawberry)+(length+1)*!!title); 795 if (title) { 796 memcpy(field->title = field->forever, title, length); 797 field->title[field->len = length] = 0; 798 } 799 800 if (width) { 801 field->len = strtol(++width, &title, 10); 802 if (!isdigit(*width) || title != end) return title; 803 end = --width; 804 } 805 806 // Find type 807 field->reverse = 1; 808 if (*type == '-') field->reverse = -1; 809 else if (*type != '+') type--; 810 type++; 811 for (i = 0; i<ARRAY_LEN(typos); i++) { 812 field->which = i; 813 for (j = 0; j<2; j++) { 814 if (!j) s = typos[i].name; 815 // posix requires alternate names for some fields 816 else if (-1==(k = stridx((char []){PS_NI, PS_SCH, PS_ELAPSED, PS__CPU, 817 PS_VSZ, PS_USER, 0}, i))) continue; 818 else 819 s = ((char *[]){"NICE", "SCHED", "ETIME", "PCPU", "VSIZE", "UNAME"})[k]; 820 821 if (!strncasecmp(type, s, end-type) && strlen(s)==end-type) break; 822 } 823 if (j!=2) break; 824 } 825 if (i==ARRAY_LEN(typos)) return type; 826 if (!field->title) field->title = typos[field->which].name; 827 if (!field->len) field->len = typos[field->which].width; 828 else if (typos[field->which].width<0) field->len *= -1; 829 dlist_add_nomalloc(data, (void *)field); 830 831 return 0; 832 } 833 834 long long get_headers(struct strawberry *fields, char *buf, int blen) 835 { 836 long long bits = 0; 837 int len = 0; 838 839 for (; fields; fields = fields->next) { 840 len += snprintf(buf+len, blen-len, " %*s"+!bits, fields->len, 841 fields->title); 842 bits |= 1LL<<fields->which; 843 } 844 845 return bits; 846 } 847 848 // Parse -p -s -t -u -U -g -G 849 static char *parse_rest(void *data, char *str, int len) 850 { 851 struct ptr_len *pl = (struct ptr_len *)data; 852 long *ll = pl->ptr; 853 char *end; 854 int num = 0; 855 856 // Allocate next chunk of data 857 if (!(15&pl->len)) 858 ll = pl->ptr = xrealloc(pl->ptr, sizeof(long)*(pl->len+16)); 859 860 // Parse numerical input 861 if (isdigit(*str)) { 862 ll[pl->len] = xstrtol(str, &end, 10); 863 if (end==(len+str)) num++; 864 } 865 866 if (pl==&TT.pp || pl==&TT.ss) { 867 if (num && ll[pl->len]>0) { 868 pl->len++; 869 870 return 0; 871 } 872 } else if (pl==&TT.tt) { 873 // -t pts = 12,pts/12 tty = /dev/tty2,tty2,S0 874 if (!num) { 875 if (strstart(&str, strcpy(toybuf, "/dev/"))) len -= 5; 876 if (strstart(&str, "pts/")) { 877 len -= 4; 878 num++; 879 } else if (strstart(&str, "tty")) len -= 3; 880 } 881 if (len<256 && (!(end = strchr(str, '/')) || end-str>len)) { 882 struct stat st; 883 884 end = toybuf + sprintf(toybuf, "/dev/%s", num ? "pts/" : "tty"); 885 memcpy(end, str, len); 886 end[len] = 0; 887 xstat(toybuf, &st); 888 ll[pl->len++] = st.st_rdev; 889 890 return 0; 891 } 892 } else if (len<255) { 893 char name[256]; 894 895 if (num) { 896 pl->len++; 897 898 return 0; 899 } 900 901 memcpy(name, str, len); 902 name[len] = 0; 903 if (pl==&TT.gg || pl==&TT.GG) { 904 struct group *gr = getgrnam(name); 905 if (gr) { 906 ll[pl->len++] = gr->gr_gid; 907 908 return 0; 909 } 910 } else if (pl==&TT.uu || pl==&TT.UU) { 911 struct passwd *pw = getpwnam(name); 912 if (pw) { 913 ll[pl->len++] = pw->pw_uid; 914 915 return 0; 916 } 917 } 918 } 919 920 // Return error 921 return str; 922 } 923 924 // sort for -k 925 static int ksort(void *aa, void *bb) 926 { 927 struct strawberry *field; 928 struct carveup *ta = *(struct carveup **)aa, *tb = *(struct carveup **)bb; 929 int ret = 0, slot; 930 931 for (field = TT.kfields; field && !ret; field = field->next) { 932 slot = typos[field->which].slot; 933 934 // Can we do numeric sort? 935 if (!(slot&64)) { 936 if (ta->slot[slot]<tb->slot[slot]) ret = -1; 937 if (ta->slot[slot]>tb->slot[slot]) ret = 1; 938 } 939 940 // fallback to string sort 941 if (!ret) { 942 memccpy(toybuf, string_field(ta, field), 0, 2048); 943 toybuf[2048] = 0; 944 ret = strcmp(toybuf, string_field(tb, field)); 945 } 946 ret *= field->reverse; 947 } 948 949 return ret; 950 } 951 952 static struct carveup **collate(int count, struct dirtree *dt, 953 int (*sort)(void *a, void *b)) 954 { 955 struct dirtree *temp; 956 struct carveup **tbsort = xmalloc(count*sizeof(struct carveup *)); 957 int i; 958 959 // descend into child list 960 *tbsort = (void *)dt; 961 dt = dt->child; 962 free(*tbsort); 963 964 // populate array 965 for (i = 0; i < count; i++) { 966 temp = dt->next; 967 tbsort[i] = (void *)dt->extra; 968 free(dt); 969 dt = temp; 970 } 971 972 return tbsort; 973 } 974 975 static void default_ko(char *s, void *fields, char *err, struct arg_list *arg) 976 { 977 struct arg_list def; 978 979 memset(&def, 0, sizeof(struct arg_list)); 980 def.arg = s; 981 comma_args(arg ? arg : &def, fields, err, parse_ko); 982 } 983 984 static void shared_main(void) 985 { 986 int i; 987 988 TT.ticks = sysconf(_SC_CLK_TCK); 989 if (!TT.width) { 990 TT.width = (toys.which->name[1] == 's') ? 99999 : 80; 991 TT.height = 25; 992 terminal_size(&TT.width, &TT.height); 993 } 994 995 // find controlling tty, falling back to /dev/tty if none 996 for (i = 0; !TT.tty && i<4; i++) { 997 struct stat st; 998 int fd = i; 999 1000 if (i==3 && -1==(fd = open("/dev/tty", O_RDONLY))) break; 1001 1002 if (isatty(fd) && !fstat(fd, &st)) TT.tty = st.st_rdev; 1003 if (i==3) close(fd); 1004 } 1005 } 1006 1007 void ps_main(void) 1008 { 1009 struct dirtree *dt; 1010 char *s; 1011 int i; 1012 1013 if (toys.optflags&FLAG_w) TT.width = 99999; 1014 shared_main(); 1015 1016 // parse command line options other than -o 1017 comma_args(TT.ps.P, &TT.PP, "bad -P", parse_rest); 1018 comma_args(TT.ps.p, &TT.pp, "bad -p", parse_rest); 1019 comma_args(TT.ps.t, &TT.tt, "bad -t", parse_rest); 1020 comma_args(TT.ps.s, &TT.ss, "bad -s", parse_rest); 1021 comma_args(TT.ps.u, &TT.uu, "bad -u", parse_rest); 1022 comma_args(TT.ps.U, &TT.UU, "bad -U", parse_rest); 1023 comma_args(TT.ps.g, &TT.gg, "bad -g", parse_rest); 1024 comma_args(TT.ps.G, &TT.GG, "bad -G", parse_rest); 1025 comma_args(TT.ps.k, &TT.kfields, "bad -k", parse_ko); 1026 dlist_terminate(TT.kfields); 1027 1028 // Parse manual field selection, or default/-f/-l, plus -Z and -O 1029 if (toys.optflags&FLAG_Z) default_ko("LABEL", &TT.fields, 0, 0); 1030 if (toys.optflags&FLAG_f) s = "USER:8=UID,PID,PPID,C,STIME,TTY,TIME,CMD"; 1031 else if (toys.optflags&FLAG_l) 1032 s = "F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD"; 1033 else if (CFG_TOYBOX_ON_ANDROID) 1034 s = "USER,PID,PPID,VSIZE,RSS,WCHAN:10,ADDR:10=PC,S,NAME"; 1035 else s = "PID,TTY,TIME,CMD"; 1036 default_ko(s, &TT.fields, "bad -o", TT.ps.o); 1037 if (TT.ps.O) { 1038 if (TT.fields) TT.fields = ((struct strawberry *)TT.fields)->prev; 1039 comma_args(TT.ps.O, &TT.fields, "bad -O", parse_ko); 1040 if (TT.fields) TT.fields = ((struct strawberry *)TT.fields)->next; 1041 } 1042 dlist_terminate(TT.fields); 1043 1044 // -f and -n change the meaning of some fields 1045 if (toys.optflags&(FLAG_f|FLAG_n)) { 1046 struct strawberry *ever; 1047 1048 for (ever = TT.fields; ever; ever = ever->next) { 1049 if ((toys.optflags&FLAG_f) && ever->which==PS_CMD) ever->which = PS_ARGS; 1050 if ((toys.optflags&FLAG_n) && ever->which>=PS_UID 1051 && ever->which<=PS_RGROUP && (typos[ever->which].slot&64)) 1052 ever->which--; 1053 } 1054 } 1055 1056 // Calculate seen fields bit array, and if we aren't deferring printing 1057 // print headers now (for low memory/nommu systems). 1058 TT.bits = get_headers(TT.fields, toybuf, sizeof(toybuf)); 1059 if (!(toys.optflags&FLAG_M)) printf("%.*s\n", TT.width, toybuf); 1060 if (!(toys.optflags&(FLAG_k|FLAG_M))) TT.show_process = (void *)show_ps; 1061 TT.match_process = ps_match_process; 1062 dt = dirtree_read("/proc", get_ps); 1063 1064 if (toys.optflags&(FLAG_k|FLAG_M)) { 1065 struct carveup **tbsort = collate(TT.kcount, dt, ksort); 1066 1067 if (toys.optflags&FLAG_M) { 1068 for (i = 0; i<TT.kcount; i++) { 1069 struct strawberry *field; 1070 1071 for (field = TT.fields; field; field = field->next) { 1072 int len = strlen(string_field(tbsort[i], field)); 1073 1074 if (abs(field->len)<len) field->len = (field->len<0) ? -len : len; 1075 } 1076 } 1077 1078 // Now that we've recalculated field widths, re-pad headers again 1079 get_headers(TT.fields, toybuf, sizeof(toybuf)); 1080 printf("%.*s\n", TT.width, toybuf); 1081 } 1082 1083 if (toys.optflags&FLAG_k) 1084 qsort(tbsort, TT.kcount, sizeof(struct carveup *), (void *)ksort); 1085 for (i = 0; i<TT.kcount; i++) { 1086 show_ps(tbsort[i]); 1087 free(tbsort[i]); 1088 } 1089 if (CFG_TOYBOX_FREE) free(tbsort); 1090 } 1091 1092 if (CFG_TOYBOX_FREE) { 1093 free(TT.gg.ptr); 1094 free(TT.GG.ptr); 1095 free(TT.pp.ptr); 1096 free(TT.PP.ptr); 1097 free(TT.ss.ptr); 1098 free(TT.tt.ptr); 1099 free(TT.uu.ptr); 1100 free(TT.UU.ptr); 1101 llist_traverse(TT.fields, free); 1102 } 1103 } 1104 1105 #define CLEANUP_ps 1106 #define FOR_top 1107 #include "generated/flags.h" 1108 1109 // select which of the -o fields to sort by 1110 static void setsort(int pos) 1111 { 1112 struct strawberry *field, *going2; 1113 int i = 0; 1114 1115 if (pos<0) pos = 0; 1116 1117 for (field = TT.fields; field; field = field->next) { 1118 if ((TT.sortpos = i++)<pos && field->next) continue; 1119 going2 = TT.kfields; 1120 going2->which = field->which; 1121 going2->len = field->len; 1122 break; 1123 } 1124 } 1125 1126 // If we have both, adjust slot[deltas[]] to be relative to previous 1127 // measurement rather than process start. Stomping old.data is fine 1128 // because we free it after displaying. 1129 static int merge_deltas(long long *oslot, long long *nslot, int milis) 1130 { 1131 char deltas[] = {SLOT_utime2, SLOT_iobytes, SLOT_diobytes, SLOT_rchar, 1132 SLOT_wchar, SLOT_rbytes, SLOT_wbytes, SLOT_swap}; 1133 int i; 1134 1135 for (i = 0; i<ARRAY_LEN(deltas); i++) 1136 oslot[deltas[i]] = nslot[deltas[i]] - oslot[deltas[i]]; 1137 oslot[SLOT_upticks] = (milis*TT.ticks)/1000; 1138 1139 return 1; 1140 } 1141 1142 static int header_line(int line, int rev) 1143 { 1144 if (!line) return 0; 1145 1146 printf("%s%*.*s%s\r\n", rev ? "\033[7m" : "", 1147 (toys.optflags&FLAG_b) ? 0 : -TT.width, TT.width, toybuf, 1148 rev ? "\033[0m" : ""); 1149 1150 return line-1; 1151 } 1152 1153 // Get current time in miliseconds 1154 static long long militime(void) 1155 { 1156 struct timespec ts; 1157 1158 clock_gettime(CLOCK_MONOTONIC, &ts); 1159 1160 return ts.tv_sec*1000+ts.tv_nsec/1000000; 1161 } 1162 1163 static void top_common( 1164 int (*filter)(long long *oslot, long long *nslot, int milis)) 1165 { 1166 long long timeout = 0, now, stats[16]; 1167 struct proclist { 1168 struct carveup **tb; 1169 int count; 1170 long long whence; 1171 } plist[2], *plold, *plnew, old, new, mix; 1172 char scratch[16], *pos, *cpufields[] = {"user", "nice", "sys", "idle", 1173 "iow", "irq", "sirq", "host"}; 1174 1175 unsigned tock = 0; 1176 int i, lines, topoff = 0, done = 0; 1177 1178 toys.signal = SIGWINCH; 1179 TT.bits = get_headers(TT.fields, toybuf, sizeof(toybuf)); 1180 *scratch = 0; 1181 memset(plist, 0, sizeof(plist)); 1182 memset(stats, 0, sizeof(stats)); 1183 do { 1184 struct dirtree *dt; 1185 int recalc = 1; 1186 1187 plold = plist+(tock++&1); 1188 plnew = plist+(tock&1); 1189 plnew->whence = militime(); 1190 dt= dirtree_read("/proc", get_ps); 1191 plnew->tb = collate(plnew->count = TT.kcount, dt, ksort); 1192 TT.kcount = 0; 1193 1194 if (readfile("/proc/stat", pos = toybuf, sizeof(toybuf))) { 1195 long long *st = stats+8*(tock&1); 1196 1197 // user nice system idle iowait irq softirq host 1198 sscanf(pos, "cpu %lld %lld %lld %lld %lld %lld %lld %lld", 1199 st, st+1, st+2, st+3, st+4, st+5, st+6, st+7); 1200 } 1201 1202 // First time, wait a quarter of a second to collect a little delta data. 1203 if (!plold->tb) { 1204 msleep(250); 1205 continue; 1206 } 1207 1208 // Collate old and new into "mix", depends on /proc read in pid sort order 1209 old = *plold; 1210 new = *plnew; 1211 mix.tb = xmalloc((old.count+new.count)*sizeof(struct carveup)); 1212 mix.count = 0; 1213 1214 while (old.count || new.count) { 1215 struct carveup *otb = *old.tb, *ntb = *new.tb; 1216 1217 // If we just have old, discard it. 1218 if (old.count && (!new.count || *otb->slot < *ntb->slot)) { 1219 old.tb++; 1220 old.count--; 1221 1222 continue; 1223 } 1224 1225 // If we just have new, use it verbatim 1226 if (!old.count || *otb->slot > *ntb->slot) mix.tb[mix.count] = ntb; 1227 else { 1228 // Keep or discard 1229 if (filter(otb->slot, ntb->slot, new.whence-old.whence)) { 1230 mix.tb[mix.count] = otb; 1231 mix.count++; 1232 } 1233 old.tb++; 1234 old.count--; 1235 } 1236 new.tb++; 1237 new.count--; 1238 } 1239 1240 // We will re-fetch no data before its time. - Mork calling Orson Welles 1241 for (;;) { 1242 char was, is; 1243 1244 if (recalc) { 1245 qsort(mix.tb, mix.count, sizeof(struct carveup *), (void *)ksort); 1246 if (!(toys.optflags&FLAG_b)) { 1247 printf("\033[H\033[J"); 1248 if (toys.signal) { 1249 toys.signal = 0; 1250 terminal_probesize(&TT.width, &TT.height); 1251 } 1252 } 1253 lines = TT.height; 1254 } 1255 if (recalc && !(toys.optflags&FLAG_q)) { 1256 if (*toys.which->name == 't') { 1257 struct strawberry alluc; 1258 long long ll, up = 0; 1259 long run[6]; 1260 int j; 1261 1262 alluc.which = PS_S; 1263 memset(run, 0, sizeof(run)); 1264 for (i = 0; i<mix.count; i++) 1265 run[1+stridx("RSTZ", *string_field(mix.tb[i], &alluc))]++; 1266 1267 sprintf(toybuf, 1268 "Tasks: %d total,%4ld running,%4ld sleeping,%4ld stopped," 1269 "%4ld zombie", mix.count, run[1], run[2], run[3], run[4]); 1270 lines = header_line(lines, 0); 1271 1272 if (readfile("/proc/meminfo", toybuf, sizeof(toybuf))) { 1273 for (i=0; i<6; i++) { 1274 pos = strafter(toybuf, (char *[]){"MemTotal:","\nMemFree:", 1275 "\nBuffers:","\nCached:","\nSwapTotal:","\nSwapFree:"}[i]); 1276 run[i] = pos ? atol(pos) : 0; 1277 } 1278 sprintf(toybuf, 1279 "Mem:%10ldk total,%9ldk used,%9ldk free,%9ldk buffers", 1280 run[0], run[0]-run[1], run[1], run[2]); 1281 lines = header_line(lines, 0); 1282 sprintf(toybuf, 1283 "Swap:%9ldk total,%9ldk used,%9ldk free,%9ldk cached", 1284 run[4], run[4]-run[5], run[5], run[3]); 1285 lines = header_line(lines, 0); 1286 } 1287 1288 pos = toybuf; 1289 i = sysconf(_SC_NPROCESSORS_CONF); 1290 pos += sprintf(pos, "%d%%cpu", i*100); 1291 j = 4+(i>10); 1292 1293 // If a processor goes idle it's powered down and its idle ticks don't 1294 // advance, so calculate idle time as potential time - used. 1295 if (mix.count) up = mix.tb[0]->slot[SLOT_upticks]; 1296 if (!up) up = 1; 1297 now = up*i; 1298 ll = stats[3] = stats[11] = 0; 1299 for (i = 0; i<8; i++) ll += stats[i]-stats[i+8]; 1300 stats[3] = now - llabs(ll); 1301 1302 for (i = 0; i<8; i++) { 1303 ll = (llabs(stats[i]-stats[i+8])*1000)/up; 1304 pos += sprintf(pos, "% *lld%%%s", j, (ll+5)/10, cpufields[i]); 1305 } 1306 lines = header_line(lines, 0); 1307 } else { 1308 struct strawberry *fields; 1309 struct carveup tb; 1310 1311 memset(&tb, 0, sizeof(struct carveup)); 1312 pos = stpcpy(toybuf, "Totals:"); 1313 for (fields = TT.fields; fields; fields = fields->next) { 1314 long long ll, bits = 0; 1315 int slot = typos[fields->which].slot&63; 1316 1317 if (fields->which<PS_C || fields->which>PS_DIO) continue; 1318 ll = 1LL<<fields->which; 1319 if (bits&ll) continue; 1320 bits |= ll; 1321 for (i=0; i<mix.count; i++) 1322 tb.slot[slot] += mix.tb[i]->slot[slot]; 1323 pos += snprintf(pos, sizeof(toybuf)/2-(pos-toybuf), 1324 " %s: %*s,", typos[fields->which].name, 1325 fields->len, string_field(&tb, fields)); 1326 } 1327 *--pos = 0; 1328 lines = header_line(lines, 0); 1329 } 1330 1331 get_headers(TT.fields, pos = toybuf, sizeof(toybuf)); 1332 for (i = 0, is = *pos; *pos; pos++) { 1333 was = is; 1334 is = *pos; 1335 if (isspace(was) && !isspace(is) && i++==TT.sortpos) pos[-1] = '['; 1336 if (!isspace(was) && isspace(is) && i==TT.sortpos+1) *pos = ']'; 1337 } 1338 *pos = 0; 1339 lines = header_line(lines, 1); 1340 } 1341 if (!recalc) printf("\033[%dH\033[J", 1+TT.height-lines); 1342 recalc = 1; 1343 1344 for (i = 0; i<lines && i+topoff<mix.count; i++) { 1345 if (i) xputc('\n'); 1346 show_ps(mix.tb[i+topoff]); 1347 } 1348 1349 if (TT.top.n && !--TT.top.n) { 1350 done++; 1351 break; 1352 } 1353 1354 // Get current time in miliseconds 1355 now = militime(); 1356 if (timeout<=now) timeout = new.whence+TT.top.d; 1357 if (timeout<=now || timeout>now+TT.top.d) timeout = now+TT.top.d; 1358 1359 i = scan_key_getsize(scratch, timeout-now, &TT.width, &TT.height); 1360 if (i==-1 || i==3 || toupper(i)=='Q') { 1361 done++; 1362 break; 1363 } 1364 if (i==-2) break; 1365 1366 // Flush unknown escape sequences. 1367 if (i==27) while (0<scan_key_getsize(scratch, 0, &TT.width, &TT.height)); 1368 else if (i==' ') { 1369 timeout = 0; 1370 break; 1371 } else if (toupper(i)=='R') 1372 ((struct strawberry *)TT.kfields)->reverse *= -1; 1373 else { 1374 i -= 256; 1375 if (i == KEY_LEFT) setsort(TT.sortpos-1); 1376 else if (i == KEY_RIGHT) setsort(TT.sortpos+1); 1377 // KEY_UP is 0, so at end of strchr 1378 else if (strchr((char []){KEY_DOWN,KEY_PGUP,KEY_PGDN,KEY_UP}, i)) { 1379 recalc = 0; 1380 1381 if (i == KEY_UP) topoff--; 1382 else if (i == KEY_DOWN) topoff++; 1383 else if (i == KEY_PGDN) topoff += lines; 1384 else if (i == KEY_PGUP) topoff -= lines; 1385 if (topoff<0) topoff = 0; 1386 if (topoff>mix.count) topoff = mix.count; 1387 } 1388 } 1389 continue; 1390 } 1391 1392 free(mix.tb); 1393 for (i=0; i<plold->count; i++) free(plold->tb[i]); 1394 free(plold->tb); 1395 } while (!done); 1396 1397 if (!(toys.optflags&FLAG_b)) tty_reset(); 1398 } 1399 1400 static void top_setup(char *defo, char *defk) 1401 { 1402 int len; 1403 1404 TT.time = militime(); 1405 TT.top.d *= 1000; 1406 if (toys.optflags&FLAG_b) TT.width = TT.height = 99999; 1407 else { 1408 xset_terminal(0, 1, 0); 1409 sigatexit(tty_sigreset); 1410 xsignal(SIGWINCH, generic_signal); 1411 printf("\033[?25l\033[0m"); 1412 } 1413 shared_main(); 1414 1415 comma_args(TT.top.u, &TT.uu, "bad -u", parse_rest); 1416 comma_args(TT.top.p, &TT.pp, "bad -p", parse_rest); 1417 TT.match_process = shared_match_process; 1418 1419 default_ko(defo, &TT.fields, "bad -o", TT.top.o); 1420 dlist_terminate(TT.fields); 1421 len = strlen(toybuf); 1422 if (toybuf[len-1]!=' ' && len<sizeof(toybuf)-1) strcpy(toybuf+len, " "); 1423 1424 // First (dummy) sort field is overwritten by setsort() 1425 default_ko("-S", &TT.kfields, 0, 0); 1426 default_ko(defk, &TT.kfields, "bad -k", TT.top.k); 1427 dlist_terminate(TT.kfields); 1428 setsort(TT.top.s-1); 1429 } 1430 1431 void top_main(void) 1432 { 1433 // usage: [-h HEADER] -o OUTPUT -k SORT 1434 1435 top_setup( 1436 "PID,USER,PR,NI,VIRT,RES,SHR,S,%CPU,%MEM,TIME+,ARGS", 1437 "-%CPU,-ETIME,-PID"); 1438 top_common(merge_deltas); 1439 } 1440 1441 #define CLEANUP_top 1442 #define FOR_iotop 1443 #include "generated/flags.h" 1444 1445 static int iotop_filter(long long *oslot, long long *nslot, int milis) 1446 { 1447 if (!(toys.optflags&FLAG_a)) merge_deltas(oslot, nslot, milis); 1448 else oslot[SLOT_upticks] = ((militime()-TT.time)*TT.ticks)/1000; 1449 1450 return !(toys.optflags&FLAG_o)||oslot[SLOT_iobytes+!(toys.optflags&FLAG_A)]; 1451 } 1452 1453 void iotop_main(void) 1454 { 1455 char *s1 = 0, *s2 = 0, *d = "D"+!!(toys.optflags&FLAG_A); 1456 1457 if (toys.optflags&FLAG_K) TT.forcek++; 1458 1459 top_setup(s1 = xmprintf("PID,PR,USER,%sREAD,%sWRITE,SWAP,%sIO,COMM",d,d,d), 1460 s2 = xmprintf("-%sIO,-ETIME,-PID",d)); 1461 free(s1); 1462 free(s2); 1463 top_common(iotop_filter); 1464 } 1465 1466 // pkill's plumbing wraps pgrep's and thus mostly takes place in pgrep's flag 1467 // context, so force pgrep's flags on even when building pkill standalone. 1468 // (All the pgrep/pkill functions drop out when building ps standalone.) 1469 #define FORCE_FLAGS 1470 #define CLEANUP_iotop 1471 #define FOR_pgrep 1472 #include "generated/flags.h" 1473 1474 struct regex_list { 1475 struct regex_list *next; 1476 regex_t reg; 1477 }; 1478 1479 static void do_pgk(struct carveup *tb) 1480 { 1481 if (TT.pgrep.signal) { 1482 if (kill(*tb->slot, TT.pgrep.signal)) { 1483 char *s = num_to_sig(TT.pgrep.signal); 1484 1485 if (!s) sprintf(s = toybuf, "%d", TT.pgrep.signal); 1486 perror_msg("%s->%lld", s, *tb->slot); 1487 } 1488 } 1489 if (!(toys.optflags&FLAG_c) && (!TT.pgrep.signal || TT.tty)) { 1490 printf("%lld", *tb->slot); 1491 if (toys.optflags&FLAG_l) 1492 printf(" %s", tb->str+tb->offset[4]*!!(toys.optflags&FLAG_f)); 1493 1494 printf("%s", TT.pgrep.d ? TT.pgrep.d : "\n"); 1495 } 1496 } 1497 1498 static void match_pgrep(struct carveup *tb) 1499 { 1500 regmatch_t match; 1501 struct regex_list *reg; 1502 char *name = tb->str+tb->offset[4]*!!(toys.optflags&FLAG_f);; 1503 1504 // Never match ourselves. 1505 if (TT.pgrep.self == *tb->slot) return; 1506 1507 if (TT.pgrep.regexes) { 1508 for (reg = TT.pgrep.regexes; reg; reg = reg->next) { 1509 if (regexec(®->reg, name, 1, &match, 0)) continue; 1510 if (toys.optflags&FLAG_x) 1511 if (match.rm_so || match.rm_eo!=strlen(name)) continue; 1512 break; 1513 } 1514 if ((toys.optflags&FLAG_v) ? !!reg : !reg) return; 1515 } 1516 1517 // Repurpose a field for -c count 1518 TT.sortpos++; 1519 if (toys.optflags&(FLAG_n|FLAG_o)) { 1520 long long ll = tb->slot[SLOT_starttime]; 1521 1522 if (toys.optflags&FLAG_o) ll *= -1; 1523 if (TT.time && TT.time>ll) return; 1524 TT.time = ll; 1525 free(TT.pgrep.snapshot); 1526 TT.pgrep.snapshot = xmemdup(toybuf, (name+strlen(name)+1)-toybuf); 1527 } else do_pgk(tb); 1528 } 1529 1530 static int pgrep_match_process(long long *slot) 1531 { 1532 int match = shared_match_process(slot); 1533 1534 return (toys.optflags&FLAG_v) ? !match : match; 1535 } 1536 1537 void pgrep_main(void) 1538 { 1539 char **arg; 1540 struct regex_list *reg; 1541 1542 TT.pgrep.self = getpid(); 1543 1544 // No signal names start with "L", so no need for "L: " parsing. 1545 if (TT.pgrep.L && 1>(TT.pgrep.signal = sig_to_num(TT.pgrep.L))) 1546 error_exit("bad -L '%s'", TT.pgrep.L); 1547 1548 comma_args(TT.pgrep.G, &TT.GG, "bad -G", parse_rest); 1549 comma_args(TT.pgrep.g, &TT.gg, "bad -g", parse_rest); 1550 comma_args(TT.pgrep.P, &TT.PP, "bad -P", parse_rest); 1551 comma_args(TT.pgrep.s, &TT.ss, "bad -s", parse_rest); 1552 comma_args(TT.pgrep.t, &TT.tt, "bad -t", parse_rest); 1553 comma_args(TT.pgrep.U, &TT.UU, "bad -U", parse_rest); 1554 comma_args(TT.pgrep.u, &TT.uu, "bad -u", parse_rest); 1555 1556 if ((toys.optflags&(FLAG_x|FLAG_f)) || 1557 !(toys.optflags&(FLAG_G|FLAG_g|FLAG_P|FLAG_s|FLAG_t|FLAG_U|FLAG_u))) 1558 if (!toys.optc) help_exit("No PATTERN"); 1559 1560 if (toys.optflags&FLAG_f) TT.bits |= _PS_CMDLINE; 1561 for (arg = toys.optargs; *arg; arg++) { 1562 reg = xmalloc(sizeof(struct regex_list)); 1563 xregcomp(®->reg, *arg, REG_EXTENDED); 1564 reg->next = TT.pgrep.regexes; 1565 TT.pgrep.regexes = reg; 1566 } 1567 TT.match_process = pgrep_match_process; 1568 TT.show_process = (void *)match_pgrep; 1569 1570 dirtree_read("/proc", get_ps); 1571 if (toys.optflags&FLAG_c) printf("%d\n", TT.sortpos); 1572 if (TT.pgrep.snapshot) { 1573 do_pgk(TT.pgrep.snapshot); 1574 if (CFG_TOYBOX_FREE) free(TT.pgrep.snapshot); 1575 } 1576 if (TT.pgrep.d) xputc('\n'); 1577 } 1578 1579 #define CLEANUP_pgrep 1580 #define FOR_pkill 1581 #include "generated/flags.h" 1582 1583 void pkill_main(void) 1584 { 1585 if (!TT.pgrep.L) TT.pgrep.signal = SIGTERM; 1586 if (toys.optflags & FLAG_V) TT.tty = 1; 1587 pgrep_main(); 1588 } 1589