blob: f48505f2250db7b0bf08912bb75563731752bdf4 [file] [log] [blame]
/*
* Copyright 1998 by Albert Cahalan; all rights reserved.
* This file may be used subject to the terms and conditions of the
* GNU Library General Public License Version 2, or any later version
* at your option, as published by the Free Software Foundation.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*/
/* This is a minimal /bin/ps, designed to be smaller than the old ps
* while still supporting some of the more important features of the
* new ps. (for total size, note that this ps does not need libproc)
* It is suitable for Linux-on-a-floppy systems only.
*
* Maintainers: do not compile or install for normal systems.
* Anyone needing this will want to tweak their compiler anyway.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <asm/param.h> /* HZ */
static int P_euid;
static int P_pid;
static char P_cmd[16];
static char P_state;
static int P_ppid, P_pgrp, P_session, P_tty, P_tpgid;
static unsigned long P_flags, P_min_flt, P_cmin_flt, P_maj_flt, P_cmaj_flt,
P_utime, P_stime;
static long P_cutime, P_cstime, P_priority, P_nice, P_timeout, P_it_real_value;
static unsigned long P_start_time, P_vsize;
static long P_rss;
static unsigned long P_rss_rlim, P_start_code, P_end_code, P_start_stack,
P_kstk_esp, P_kstk_eip;
static unsigned P_signal, P_blocked, P_sigignore, P_sigcatch;
static unsigned long P_wchan, P_nswap, P_cnswap;
#if 0
static int screen_cols = 80;
static int w_count;
#endif
static int want_one_pid;
static const char *want_one_command;
static int select_notty;
static int select_all;
static int ps_format;
static int old_h_option;
/* we only pretend to support this */
static int show_args; /* implicit with -f and all BSD options */
static int bsd_c_option; /* this option overrides the above */
static int ps_argc; /* global argc */
static char **ps_argv; /* global argv */
static int thisarg; /* index into ps_argv */
static char *flagptr; /* current location in ps_argv[thisarg] */
#ifndef HZ
#warning HZ not defined, assuming it is 100
#define HZ 100
#endif
int page_shift; /* Page size as shift count */
static void usage(void)
{
fprintf(stderr,
"-C select by command name (minimal ps only accepts one)\n"
"-p select by process ID (minimal ps only accepts one)\n"
"-e all processes (same as ax)\n"
"a all processes w/ tty, including other users\n"
"x processes w/o controlling ttys\n"
"-f full format\n"
"-j,j job control format\n"
"v virtual memory format\n"
"-l,l long format\n"
"u user-oriented format\n"
"-o user-defined format (limited support, only \"ps -o pid=\")\n"
"h no header\n"
/*
"-A all processes (same as ax)\n"
"c true command name\n"
"-w,w wide output\n"
*/
);
exit(1);
}
/*
* Return the next argument, or call the usage function.
* This handles both: -oFOO -o FOO
*/
static const char *get_opt_arg(void)
{
const char *ret;
ret = flagptr + 1; /* assume argument is part of ps_argv[thisarg] */
if (*ret)
return ret;
if (++thisarg >= ps_argc)
usage(); /* there is nothing left */
/* argument is the new ps_argv[thisarg] */
ret = ps_argv[thisarg];
if (!ret || !*ret)
usage();
return ret;
}
/* return the PID, or 0 if nothing good */
static void parse_pid(const char *str)
{
char *endp;
int num;
if (!str)
goto bad;
num = strtol(str, &endp, 0);
if (*endp != '\0')
goto bad;
if (num < 1)
goto bad;
if (want_one_pid)
goto bad;
want_one_pid = num;
return;
bad:
usage();
}
/***************** parse SysV options, including Unix98 *****************/
static void parse_sysv_option(void)
{
do {
switch (*flagptr) {
/**** selection ****/
case 'C': /* end */
if (want_one_command)
usage();
want_one_command = get_opt_arg();
return; /* can't have any more options */
case 'p': /* end */
parse_pid(get_opt_arg());
return; /* can't have any more options */
case 'A':
case 'e':
select_all++;
select_notty++;
case 'w': /* here for now, since the real one is not used */
break;
/**** output format ****/
case 'f':
show_args = 1;
/* FALL THROUGH */
case 'j':
case 'l':
if (ps_format)
usage();
ps_format = *flagptr;
break;
case 'o': /* end */
/* We only support a limited form: "ps -o pid=" (yes, just "pid=") */
if (strcmp(get_opt_arg(), "pid="))
usage();
if (ps_format)
usage();
ps_format = 'o';
old_h_option++;
return; /* can't have any more options */
/**** other stuff ****/
#if 0
case 'w':
w_count++;
break;
#endif
default:
usage();
} /* switch */
} while (*++flagptr);
}
/************************* parse BSD options **********************/
static void parse_bsd_option(void)
{
do {
switch (*flagptr) {
/**** selection ****/
case 'a':
select_all++;
break;
case 'x':
select_notty++;
break;
case 'p': /* end */
parse_pid(get_opt_arg());
return; /* can't have any more options */
/**** output format ****/
case 'j':
case 'l':
case 'u':
case 'v':
if (ps_format)
usage();
ps_format = 0x80 | *flagptr; /* use 0x80 to tell BSD from SysV */
break;
/**** other stuff ****/
case 'c':
bsd_c_option++;
#if 0
break;
#endif
case 'w':
#if 0
w_count++;
#endif
break;
case 'h':
old_h_option++;
break;
default:
usage();
} /* switch */
} while (*++flagptr);
}
#if 0
/* not used yet */
static void choose_dimensions(void)
{
struct winsize ws;
char *columns;
/* screen_cols is 80 by default */
if (ioctl(1, TIOCGWINSZ, &ws) != -1 && ws.ws_col > 30)
screen_cols = ws.ws_col;
columns = getenv("COLUMNS");
if (columns && *columns) {
long t;
char *endptr;
t = strtol(columns, &endptr, 0);
if (!*endptr && (t > 30) && (t < (long)999999999))
screen_cols = (int)t;
}
if (w_count && (screen_cols < 132))
screen_cols = 132;
if (w_count > 1)
screen_cols = 999999999;
}
#endif
static void arg_parse(int argc, char *argv[])
{
int sel = 0; /* to verify option sanity */
ps_argc = argc;
ps_argv = argv;
thisarg = 0;
/**** iterate over the args ****/
while (++thisarg < ps_argc) {
flagptr = ps_argv[thisarg];
switch (*flagptr) {
case '0'...'9':
show_args = 1;
parse_pid(flagptr);
break;
case '-':
flagptr++;
parse_sysv_option();
break;
default:
show_args = 1;
parse_bsd_option();
break;
}
}
/**** sanity check and clean-up ****/
if (want_one_pid)
sel++;
if (want_one_command)
sel++;
if (select_notty || select_all)
sel++;
if (sel > 1 || select_notty > 1 || select_all > 1 || bsd_c_option > 1
|| old_h_option > 1)
usage();
if (bsd_c_option)
show_args = 0;
}
/* return 1 if it works, or 0 for failure */
static int stat2proc(int pid)
{
char buf[800]; /* about 40 fields, 64-bit decimal is about 20 chars */
int num;
int fd;
char *tmp;
struct stat sb; /* stat() used to get EUID */
snprintf(buf, 32, "/proc/%d/stat", pid);
fd = open(buf, O_RDONLY, 0);
if (fd == -1)
return 0;
num = read(fd, buf, sizeof buf - 1);
fstat(fd, &sb);
P_euid = sb.st_uid;
close(fd);
if (num < 80)
return 0;
buf[num] = '\0';
tmp = strrchr(buf, ')'); /* split into "PID (cmd" and "<rest>" */
*tmp = '\0'; /* replace trailing ')' with NUL */
/* parse these two strings separately, skipping the leading "(". */
memset(P_cmd, 0, sizeof P_cmd); /* clear */
sscanf(buf, "%d (%15c", &P_pid, P_cmd); /* comm[16] in kernel */
num = sscanf(tmp + 2, /* skip space after ')' too */
"%c " "%d %d %d %d %d " "%lu %lu %lu %lu %lu %lu %lu " "%ld %ld %ld %ld %ld %ld " "%lu %lu " "%ld " "%lu %lu %lu %lu %lu %lu " "%u %u %u %u " /* no use for RT signals */
"%lu %lu %lu",
&P_state,
&P_ppid, &P_pgrp, &P_session, &P_tty, &P_tpgid,
&P_flags, &P_min_flt, &P_cmin_flt, &P_maj_flt, &P_cmaj_flt,
&P_utime, &P_stime, &P_cutime, &P_cstime, &P_priority,
&P_nice, &P_timeout, &P_it_real_value, &P_start_time,
&P_vsize, &P_rss, &P_rss_rlim, &P_start_code, &P_end_code,
&P_start_stack, &P_kstk_esp, &P_kstk_eip, &P_signal,
&P_blocked, &P_sigignore, &P_sigcatch, &P_wchan, &P_nswap,
&P_cnswap);
/* fprintf(stderr, "stat2proc converted %d fields.\n",num); */
P_vsize /= 1024;
P_rss <<= page_shift - 10;
if (num < 30)
return 0;
if (P_pid != pid)
return 0;
return 1;
}
static const char *do_time(unsigned long t)
{
int hh, mm, ss;
static char buf[32];
int cnt = 0;
t /= HZ;
ss = t % 60;
t /= 60;
mm = t % 60;
t /= 60;
hh = t % 24;
t /= 24;
if (t)
cnt = snprintf(buf, sizeof buf, "%d-", (int)t);
snprintf(cnt + buf, sizeof(buf) - cnt, "%02d:%02d:%02d", hh, mm, ss);
return buf;
}
static void print_proc(void)
{
char tty[16];
snprintf(tty, sizeof tty, "%3d,%-3d", (P_tty >> 8) & 0xff,
P_tty & 0xff);
switch (ps_format) {
case 0:
printf("%5d %s %s", P_pid, tty, do_time(P_utime + P_stime));
break;
case 'o':
printf("%d\n", P_pid);
return; /* don't want the command */
case 'l':
printf("%03x %c %5d %5d %5d - %3d %3d - "
"%5ld %06x %s %s",
(unsigned)P_flags & 0x777, P_state, P_euid, P_pid,
P_ppid, (int)P_priority, (int)P_nice,
P_vsize >> (page_shift - 10),
(unsigned)(P_wchan & 0xffffff), tty,
do_time(P_utime + P_stime)
);
break;
case 'f':
printf("%5d %5d %5d - - %s %s",
P_euid, P_pid, P_ppid, tty, do_time(P_utime + P_stime)
);
break;
case 'j':
printf("%5d %5d %5d %s %s",
P_pid, P_pgrp, P_session, tty, do_time(P_utime + P_stime)
);
break;
case 'u' | 0x80:
printf("%5d %5d - - %5ld %5ld %s %c - %s",
P_euid, P_pid, P_vsize, P_rss, tty, P_state,
do_time(P_utime + P_stime)
);
break;
case 'v' | 0x80:
printf("%5d %s %c %s %6d - - %5d -",
P_pid, tty, P_state, do_time(P_utime + P_stime),
(int)P_maj_flt, (int)P_rss);
break;
case 'j' | 0x80:
printf("%5d %5d %5d %5d %s %5d %c %5d %s",
P_ppid, P_pid, P_pgrp, P_session, tty, P_tpgid, P_state,
P_euid, do_time(P_utime + P_stime)
);
break;
case 'l' | 0x80:
printf("%03x %5d %5d %5d %3d %3d "
"%5ld %4ld %06x %c %s %s",
(unsigned)P_flags & 0x777, P_euid, P_pid, P_ppid,
(int)P_priority, (int)P_nice, P_vsize, P_rss,
(unsigned)(P_wchan & 0xffffff), P_state, tty,
do_time(P_utime + P_stime)
);
break;
default:
break;
}
if (show_args)
printf(" [%s]\n", P_cmd);
else
printf(" %s\n", P_cmd);
}
int main(int argc, char *argv[])
{
arg_parse(argc, argv);
page_shift = __getpageshift();
#if 0
choose_dimensions();
#endif
if (!old_h_option) {
const char *head;
switch (ps_format) {
default: /* can't happen */
case 0:
head = " PID TTY TIME CMD";
break;
case 'l':
head =
" F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD";
break;
case 'f':
head =
" UID PID PPID C STIME TTY TIME CMD";
break;
case 'j':
head = " PID PGID SID TTY TIME CMD";
break;
case 'u' | 0x80:
head =
" UID PID %CPU %MEM VSZ RSS TTY S START TIME COMMAND";
break;
case 'v' | 0x80:
head =
" PID TTY S TIME MAJFL TRS DRS RSS %MEM COMMAND";
break;
case 'j' | 0x80:
head =
" PPID PID PGID SID TTY TPGID S UID TIME COMMAND";
break;
case 'l' | 0x80:
head =
" F UID PID PPID PRI NI VSZ RSS WCHAN S TTY TIME COMMAND";
break;
}
printf("%s\n", head);
}
if (want_one_pid) {
if (stat2proc(want_one_pid))
print_proc();
else
exit(1);
} else {
struct dirent *ent; /* dirent handle */
DIR *dir;
int ouruid;
int found_a_proc;
found_a_proc = 0;
ouruid = getuid();
dir = opendir("/proc");
if (!dir)
exit(1);
while ((ent = readdir(dir))) {
if (*ent->d_name < '0' || *ent->d_name > '9')
continue;
if (!stat2proc(atoi(ent->d_name)))
continue;
if (want_one_command) {
if (strcmp(want_one_command, P_cmd))
continue;
} else {
if (!select_notty && P_tty == -1)
continue;
if (!select_all && P_euid != ouruid)
continue;
}
found_a_proc++;
print_proc();
}
closedir(dir);
exit(!found_a_proc);
}
return 0;
}