2 * Copyright 1998,2004 by Albert Cahalan; all rights reserved.
3 * This file may be used subject to the terms and conditions of the
4 * GNU Library General Public License Version 2, or any later version
5 * at your option, as published by the Free Software Foundation.
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 * GNU Library General Public License for more details.
12 /* This is a minimal /bin/ps, designed to be smaller than the old ps
13 * while still supporting some of the more important features of the
14 * new ps. (for total size, note that this ps does not need libproc)
15 * It is suitable for Linux-on-a-floppy systems only.
17 * Maintainers: do not compile or install for normal systems.
18 * Anyone needing this will want to tweak their compiler anyway.
25 #include <sys/ioctl.h>
26 #include <sys/types.h>
33 #define DEV_ENCODE(M,m) ( \
34 ( (M&0xfff) << 8) | ( (m&0xfff00) << 12) | (m&0xff) \
37 ///////////////////////////////////////////////////////
39 #include <sys/mkdev.h>
40 #define _STRUCTURED_PROC 1
41 #include <sys/procfs.h>
42 #define NO_TTY_VALUE DEV_ENCODE(-1,-1)
43 #define HZ 1 // only bother with seconds
46 ///////////////////////////////////////////////////////
48 #include <sys/param.h>
49 #include <sys/sysctl.h>
53 #define NO_TTY_VALUE DEV_ENCODE(-1,-1)
54 #define HZ 1 // only bother with seconds
57 ///////////////////////////////////////////////////////
59 #include <asm/param.h> /* HZ */
60 #include <asm/page.h> /* PAGE_SIZE */
61 #define NO_TTY_VALUE DEV_ENCODE(0,0)
63 #warning HZ not defined, assuming it is 100
68 ///////////////////////////////////////////////////////////
71 #warning PAGE_SIZE not defined, assuming it is 4096
72 #define PAGE_SIZE 4096
77 static char P_tty_text[16];
78 static char P_cmd[16];
82 static int P_ppid, P_pgrp, P_session, P_tty_num, P_tpgid;
83 static unsigned long P_flags, P_min_flt, P_cmin_flt, P_maj_flt, P_cmaj_flt, P_utime, P_stime;
84 static long P_cutime, P_cstime, P_priority, P_nice, P_timeout, P_alarm;
85 static unsigned long P_start_time, P_vsize;
87 static unsigned long P_rss_rlim, P_start_code, P_end_code, P_start_stack, P_kstk_esp, P_kstk_eip;
88 static unsigned P_signal, P_blocked, P_sigignore, P_sigcatch;
89 static unsigned long P_wchan, P_nswap, P_cnswap;
94 static int screen_cols = 80;
98 static int want_one_pid;
99 static const char *want_one_command;
100 static int select_notty;
101 static int select_all;
103 static int ps_format;
104 static int old_h_option;
106 /* we only pretend to support this */
107 static int show_args; /* implicit with -f and all BSD options */
108 static int bsd_c_option; /* this option overrides the above */
110 static int ps_argc; /* global argc */
111 static char **ps_argv; /* global argv */
112 static int thisarg; /* index into ps_argv */
113 static char *flagptr; /* current location in ps_argv[thisarg] */
118 static void usage(void){
120 "-C select by command name (minimal ps only accepts one)\n"
121 "-p select by process ID (minimal ps only accepts one)\n"
122 "-e all processes (same as ax)\n"
123 "a all processes w/ tty, including other users\n"
124 "x processes w/o controlling ttys\n"
126 "-j,j job control format\n"
127 "v virtual memory format\n"
129 "u user-oriented format\n"
130 "-o user-defined format (limited support, only \"ps -o pid=\")\n"
133 "-A all processes (same as ax)\n"
134 "c true command name\n"
142 * Return the next argument, or call the usage function.
143 * This handles both: -oFOO -o FOO
145 static const char *get_opt_arg(void){
147 ret = flagptr+1; /* assume argument is part of ps_argv[thisarg] */
149 if(++thisarg >= ps_argc) usage(); /* there is nothing left */
150 /* argument is the new ps_argv[thisarg] */
151 ret = ps_argv[thisarg];
152 if(!ret || !*ret) usage();
157 /* return the PID, or 0 if nothing good */
158 static void parse_pid(const char *str){
162 num = strtol(str, &endp, 0);
163 if(*endp != '\0') goto bad;
165 if(want_one_pid) goto bad;
172 /***************** parse SysV options, including Unix98 *****************/
173 static void parse_sysv_option(void){
176 /**** selection ****/
178 if(want_one_command) usage();
179 want_one_command = get_opt_arg();
180 return; /* can't have any more options */
182 parse_pid(get_opt_arg());
183 return; /* can't have any more options */
188 case 'w': /* here for now, since the real one is not used */
190 /**** output format ****/
196 if(ps_format) usage();
197 ps_format = *flagptr;
200 /* We only support a limited form: "ps -o pid=" (yes, just "pid=") */
201 if(strcmp(get_opt_arg(),"pid=")) usage();
202 if(ps_format) usage();
205 return; /* can't have any more options */
206 /**** other stuff ****/
218 /************************* parse BSD options **********************/
219 static void parse_bsd_option(void){
222 /**** selection ****/
230 parse_pid(get_opt_arg());
231 return; /* can't have any more options */
232 /**** output format ****/
237 if(ps_format) usage();
238 ps_format = 0x80 | *flagptr; /* use 0x80 to tell BSD from SysV */
240 /**** other stuff ****/
263 static void choose_dimensions(void){
266 /* screen_cols is 80 by default */
267 if(ioctl(1, TIOCGWINSZ, &ws) != -1 && ws.ws_col>30) screen_cols = ws.ws_col;
268 columns = getenv("COLUMNS");
269 if(columns && *columns){
272 t = strtol(columns, &endptr, 0);
273 if(!*endptr && (t>30) && (t<(long)999999999)) screen_cols = (int)t;
275 if(w_count && (screen_cols<132)) screen_cols=132;
276 if(w_count>1) screen_cols=999999999;
280 static void arg_parse(int argc, char *argv[]){
281 int sel = 0; /* to verify option sanity */
285 /**** iterate over the args ****/
286 while(++thisarg < ps_argc){
287 flagptr = ps_argv[thisarg];
303 /**** sanity check and clean-up ****/
304 if(want_one_pid) sel++;
305 if(want_one_command) sel++;
306 if(select_notty || select_all) sel++;
307 if(sel>1 || select_notty>1 || select_all>1 || bsd_c_option>1 || old_h_option>1) usage();
308 if(bsd_c_option) show_args = 0;
312 /* return 1 if it works, or 0 for failure */
313 static int stat2proc(int pid) {
314 struct psinfo p; // /proc/*/psinfo, struct psinfo, psinfo_t
318 int tty_maj, tty_min;
319 snprintf(buf, sizeof buf, "/proc/%d/psinfo", pid);
320 if ( (fd = open(buf, O_RDONLY, 0) ) == -1 ) return 0;
321 num = read(fd, &p, sizeof p);
323 if(num != sizeof p) return 0;
326 if (num >= sizeof P_cmd) num = sizeof P_cmd - 1;
327 memcpy(P_cmd, p.pr_fname, num); // p.pr_fname or p.pr_lwp.pr_name
333 P_session = p.pr_sid;
337 P_start_time = p.pr_start.tv_sec;
338 P_wchan = p.pr_lwp.pr_wchan;
339 P_state = p.pr_lwp.pr_sname;
340 P_nice = p.pr_lwp.pr_nice;
341 P_priority = p.pr_lwp.pr_pri; // or pr_oldpri
342 // P_ruid = p.pr_uid;
343 // P_rgid = p.pr_gid;
344 // P_egid = p.pr_egid;
347 // don't support these
349 P_min_flt, P_cmin_flt, P_maj_flt, P_cmaj_flt, P_utime, P_stime;
350 P_cutime, P_cstime, P_timeout, P_alarm;
351 P_rss_rlim, P_start_code, P_end_code, P_start_stack, P_kstk_esp, P_kstk_eip;
352 P_signal, P_blocked, P_sigignore, P_sigcatch;
356 // we like it Linux-encoded :-)
357 tty_maj = major(p.pr_ttydev);
358 tty_min = minor(p.pr_ttydev);
359 P_tty_num = DEV_ENCODE(tty_maj,tty_min);
361 snprintf(P_tty_text, sizeof P_tty_text, "%3d,%-3d", tty_maj, tty_min);
363 if (tty_maj == 24) snprintf(P_tty_text, sizeof P_tty_text, "pts/%-3u", tty_min);
364 if (P_tty_num == NO_TTY_VALUE) memcpy(P_tty_text, " ? ", 8);
365 if (P_tty_num == DEV_ENCODE(0,0)) memcpy(P_tty_text, "console", 8);
368 if(P_pid != pid) return 0;
374 /* return 1 if it works, or 0 for failure */
375 static int stat2proc(int pid) {
380 int tty_maj, tty_min;
381 snprintf(buf, 32, "/proc/%d/status", pid);
382 if ( (fd = open(buf, O_RDONLY, 0) ) == -1 ) return 0;
383 num = read(fd, buf, sizeof buf - 1);
390 // FreeBSD /proc/*/status is seriously fucked. Unlike the Linux
391 // files, we can't use strrchr to find the end of a command name.
392 // Spaces in command names do not get escaped. To avoid spoofing,
393 // one may skip 20 characters and then look _forward_ only to
394 // find a pattern of entries that are {with,with,without} a comma.
395 // The entry without a comma is wchan. Then count backwards!
397 // Don't bother for now. FreeBSD isn't worth the trouble.
399 tmp = strchr(buf,' ');
401 if (num >= sizeof P_cmd) num = sizeof P_cmd - 1;
402 memcpy(P_cmd,buf,num);
414 &P_pid, &P_ppid, &P_pgrp, &P_session,
416 /* SKIP funny flags thing */
417 &P_start_time, /* SKIP microseconds */
418 &P_utime, /* SKIP microseconds */
419 &P_stime, /* SKIP microseconds */
420 /* SKIP &P_wchan, for now -- it is a string */
421 &P_euid, &P_euid // don't know which is which
423 /* fprintf(stderr, "stat2proc converted %d fields.\n",num); */
425 snprintf(P_tty_text, sizeof P_tty_text, "%3d,%-3d", tty_maj, tty_min);
426 P_tty_num = DEV_ENCODE(tty_maj,tty_min);
427 // tty decode is 224 to 256 bytes on i386
430 if (tty_maj == 5) tmp = " ttyp%c ";
431 if (tty_maj == 12) tmp = " ttyv%c ";
432 if (tty_maj == 28) tmp = " ttyd%c ";
433 if (P_tty_num == NO_TTY_VALUE) tmp = " ? ";
434 if (P_tty_num == DEV_ENCODE(0,0)) tmp = "console";
435 if (P_tty_num == DEV_ENCODE(12,255)) tmp = "consolectl";
441 "0123456789abcdefghijklmnopqrstuvwxyz"[tty_min&31]
446 if(num < 9) return 0;
447 if(P_pid != pid) return 0;
453 /* return 1 if it works, or 0 for failure */
454 static int stat2proc(int pid) {
455 char buf[800]; /* about 40 fields, 64-bit decimal is about 20 chars */
459 struct stat sb; /* stat() used to get EUID */
460 snprintf(buf, 32, "/proc/%d/stat", pid);
461 if ( (fd = open(buf, O_RDONLY, 0) ) == -1 ) return 0;
462 num = read(fd, buf, sizeof buf - 1);
468 tmp = strrchr(buf, ')'); /* split into "PID (cmd" and "<rest>" */
469 *tmp = '\0'; /* replace trailing ')' with NUL */
470 /* parse these two strings separately, skipping the leading "(". */
471 memset(P_cmd, 0, sizeof P_cmd); /* clear */
472 sscanf(buf, "%d (%15c", &P_pid, P_cmd); /* comm[16] in kernel */
473 num = sscanf(tmp + 2, /* skip space after ')' too */
476 "%lu %lu %lu %lu %lu %lu %lu "
477 "%ld %ld %ld %ld %ld %ld "
480 "%lu %lu %lu %lu %lu %lu "
481 "%u %u %u %u " /* no use for RT signals */
484 &P_ppid, &P_pgrp, &P_session, &P_tty_num, &P_tpgid,
485 &P_flags, &P_min_flt, &P_cmin_flt, &P_maj_flt, &P_cmaj_flt, &P_utime, &P_stime,
486 &P_cutime, &P_cstime, &P_priority, &P_nice, &P_timeout, &P_alarm,
487 &P_start_time, &P_vsize,
489 &P_rss_rlim, &P_start_code, &P_end_code, &P_start_stack, &P_kstk_esp, &P_kstk_eip,
490 &P_signal, &P_blocked, &P_sigignore, &P_sigcatch,
491 &P_wchan, &P_nswap, &P_cnswap
493 /* fprintf(stderr, "stat2proc converted %d fields.\n",num); */
495 P_rss *= (PAGE_SIZE/1024);
497 memcpy(P_tty_text, " ? ", 8);
498 if (P_tty_num != NO_TTY_VALUE) {
499 int tty_maj = (P_tty_num>>8)&0xfff;
500 int tty_min = (P_tty_num&0xff) | ((P_tty_num>>12)&0xfff00);
501 snprintf(P_tty_text, sizeof P_tty_text, "%3d,%-3d", tty_maj, tty_min);
504 if(num < 30) return 0;
505 if(P_pid != pid) return 0;
510 static const char *do_time(unsigned long t){
521 if(t) cnt = snprintf(buf, sizeof buf, "%d-", (int)t);
522 snprintf(cnt + buf, sizeof(buf)-cnt, "%02d:%02d:%02d", hh, mm, ss);
526 static const char *do_user(void){
528 static struct passwd *p;
529 static int lastuid = -1;
530 if(P_euid != lastuid){
531 p = getpwuid(P_euid);
532 if(p) snprintf(buf, sizeof buf, "%-8.8s", p->pw_name);
533 else snprintf(buf, sizeof buf, "%5d ", P_euid);
538 static const char *do_cpu(int longform){
541 if(!longform) buf[2] = '\0';
545 static const char *do_mem(int longform){
548 if(!longform) buf[2] = '\0';
552 static const char *do_stime(void){
558 static void print_proc(void){
561 printf("%5d %s %s", P_pid, P_tty_text, do_time(P_utime+P_stime));
564 printf("%d\n", P_pid);
565 return; /* don't want the command */
568 "0 %c %5d %5d %5d %s %3d %3d - "
570 P_state, P_euid, P_pid, P_ppid, do_cpu(0),
571 (int)P_priority, (int)P_nice, P_vsize/(PAGE_SIZE/1024),
572 (unsigned)(P_wchan&0xffffff), P_tty_text, do_time(P_utime+P_stime)
577 "%8s %5d %5d %s %s %s %s",
578 do_user(), P_pid, P_ppid, do_cpu(0), do_stime(), P_tty_text, do_time(P_utime+P_stime)
584 P_pid, P_pgrp, P_session, P_tty_text, do_time(P_utime+P_stime)
589 "%8s %5d %s %s %5ld %4ld %s %c %s %s",
590 do_user(), P_pid, do_cpu(1), do_mem(1), P_vsize, P_rss, P_tty_text, P_state,
591 do_stime(), do_time(P_utime+P_stime)
596 "%5d %s %c %s %6d - - %5d %s",
597 P_pid, P_tty_text, P_state, do_time(P_utime+P_stime), (int)P_maj_flt,
598 (int)P_rss, do_mem(1)
603 "%5d %5d %5d %5d %s %5d %c %5d %s",
604 P_ppid, P_pid, P_pgrp, P_session, P_tty_text, P_tpgid, P_state, P_euid, do_time(P_utime+P_stime)
609 "0 %5d %5d %5d %3d %3d "
610 "%5ld %4ld %06x %c %s %s",
611 P_euid, P_pid, P_ppid, (int)P_priority, (int)P_nice,
612 P_vsize, P_rss, (unsigned)(P_wchan&0xffffff), P_state, P_tty_text, do_time(P_utime+P_stime)
618 if(show_args) printf(" [%s]\n", P_cmd);
619 else printf(" %s\n", P_cmd);
623 int main(int argc, char *argv[]){
624 arg_parse(argc, argv);
631 default: /* can't happen */
632 case 0: head = " PID TTY TIME CMD"; break;
633 case 'l': head = "F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD"; break;
634 case 'f': head = "USER PID PPID C STIME TTY TIME CMD"; break;
635 case 'j': head = " PID PGID SID TTY TIME CMD"; break;
636 case 'u'|0x80: head = "USER PID %CPU %MEM VSZ RSS TTY S START TIME COMMAND"; break;
637 case 'v'|0x80: head = " PID TTY S TIME MAJFL TRS DRS RSS %MEM COMMAND"; break;
638 case 'j'|0x80: head = " PPID PID PGID SID TTY TPGID S UID TIME COMMAND"; break;
639 case 'l'|0x80: head = "F UID PID PPID PRI NI VSZ RSS WCHAN S TTY TIME COMMAND"; break;
644 if(stat2proc(want_one_pid)) print_proc();
647 struct dirent *ent; /* dirent handle */
653 dir = opendir("/proc");
654 while(( ent = readdir(dir) )){
655 if(*ent->d_name<'0' || *ent->d_name>'9') continue;
656 if(!stat2proc(atoi(ent->d_name))) continue;
657 if(want_one_command){
658 if(strcmp(want_one_command,P_cmd)) continue;
660 if(!select_notty && P_tty_num==NO_TTY_VALUE) continue;
661 if(!select_all && P_euid!=ouruid) continue;