blob: fddf2f3fe75fe3dcedfd5b7c0436de02df3157fe [file] [log] [blame]
/* ps.c - Show running process statistics.
*
* Copyright 2013 Sandeep Sharma <sandeep.jack2756@gmail.com>
* Copyright 2013 Kyungwan Han <asura321@gmail.com>
* See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ps.html
USE_PS(NEWTOY(ps, ">0o*T", TOYFLAG_BIN))
config PS
bool "ps"
default n
help
usage: ps [-o COL1,COL2=HEADER] [-T]
Show list of processes
-o COL1,COL2=HEADER Select columns for display
-T Show threads
*/
#define FOR_ps
#include "toys.h"
GLOBALS(
struct arg_list *llist_o;
unsigned screen_width;
)
#define BUFF_SIZE 1024
struct header_list {
char *name;
char *header;
char *format;
int width;
int position;
struct header_list *next;
};
struct header_list def_header[] = {
{"user", "USER", "%-*s ", 8, 0, NULL},
{"group", "GROUP", "%-*s ", 8, 1, NULL},
{"comm", "COMMAND", "%-*s ",16, 2, NULL},
{"args", "COMMAND", "%-*s ",30, 3, NULL},
{"pid", "PID", "%*s ", 5, 4, NULL},
{"ppid","PPID", "%*s ", 5, 5, NULL},
{"pgid", "PGID", "%*s ", 5, 6, NULL},
{"etime","ELAPSED", "%*s ", 7, 7, NULL},
{"nice", "NI", "%*s ", 5, 8, NULL},
{"rgroup","RGROUP", "%-*s ", 8, 9, NULL},
{"ruser","RUSER", "%-*s ", 8, 10, NULL},
{"time", "TIME", "%*s ", 6, 11, NULL},
{"tty", "TT", "%-*s ", 6, 12, NULL},
{"vsz","VSZ", "%*s ", 7, 13, NULL},
{"stat", "STAT", "%-*s ", 4, 14, NULL},
{"rss", "RSS", "%*s ", 4, 15, NULL},
{NULL, NULL, NULL, 0, 0, NULL},
};
struct header_list *o_list = NULL; //List of Header attributes.
/*
* create list of header attributes taking care of -o (-o ooid=MOM..)
* and width of attributes.
*/
static void list_add(struct header_list **list, struct header_list *data, char *c_data)
{
struct header_list *temp = *list, *new = xzalloc(sizeof(struct header_list));
new->name = data->name;
if (c_data) new->header = c_data;
else new->header = xstrdup(data->header);
if (c_data && (strlen(c_data) > data->width)) new->width = strlen(c_data);
else new->width = data->width;
new->format = data->format;
new->position = data->position;
if (temp) {
while (temp->next != NULL) temp = temp->next;
temp->next = new;
} else (*list) = new;
}
//print the default header OR header with -o args
static void print_header(void)
{
int i = 0;
char *ptr = NULL, *str, *temp;
struct arg_list *node = TT.llist_o;
if (!(toys.optflags & FLAG_o)) {
xprintf(" PID"" ""USER"" "" TIME"" ""COMMAND");
list_add(&o_list, &def_header[4], NULL); //pid
list_add(&o_list, &def_header[0], NULL); //user
list_add(&o_list, &def_header[11], NULL); //time
list_add(&o_list, &def_header[3], NULL); //comm
xputc('\n');
return ;
}
while (node) {
i = 0;
str = xstrdup(node->arg);
while (str) {
if ((ptr = strsep(&str, ","))) { //seprate list
if ((temp = strchr(ptr, '='))) { // Handle ppid = MOM
*temp = 0;
temp++;
while (def_header[i].name) {
if (!(strcmp(def_header[i].name, ptr))) { // search from default header
if (str) ptr = xmprintf("%s,%s", temp, str); //handle condition like ppid = M,OM
else ptr = xmprintf("%s", temp);
list_add(&o_list, &def_header[i], ptr);
break;
}
i++;
}
if (!def_header[i].name) perror_exit("Invalid arg for -o option");
break;
} else {
while (def_header[i].name) {
if (!(strcmp(def_header[i].name, ptr))) {
list_add(&o_list, &def_header[i], NULL);
break;
}
i++;
}
if (!def_header[i].name) perror_exit("Invalid arg for -o option");
i = 0;
}
}
}
node = node->next;
}
struct header_list *p = o_list;
while (p) { //print Header
printf(p->format , p->width, p->header);
p = p->next;
}
xputc('\n');
}
//get uid/gid for processes.
static void get_uid_gid(char *p, char *id_str, unsigned *id)
{
FILE *f;
if(!p) return;
f = xfopen(p, "r");
while (fgets(toybuf, BUFF_SIZE, f)) {
if (!strncmp(toybuf, id_str, strlen(id_str))) {
sscanf(toybuf, "%*s %u", id);
break;
}
}
fclose(f);
}
//get etime for processes.
void get_etime(unsigned long s_time)
{
unsigned long min;
unsigned sec;
struct sysinfo info;
char *temp;
sysinfo(&info);
min = s_time/sysconf(_SC_CLK_TCK);
min = info.uptime - min;
sec = min % 60;
min = min / 60;
temp = xmprintf("%3lu:%02u", min,sec);
xprintf("%*.*s",7,7,temp);
free(temp);
}
//get time attributes for processes.
void get_time(unsigned long s_time, unsigned long u_time)
{
unsigned long min;
unsigned sec;
char *temp;
min = (s_time + u_time)/sysconf(_SC_CLK_TCK);
sec = min % 60;
min = min / 60;
temp = xmprintf("%3lu:%02u", min,sec);
xprintf("%*.*s",6,6,temp);
free(temp);
}
/*
* read command line taking care of in between NUL's
* in command line
*/
static void read_cmdline(int fd, char *cmd_ptr)
{
int size = read(fd, cmd_ptr, 1024); //sizeof(cmd_buf)
cmd_ptr[size] = '\0';
while (--size > 0 && cmd_ptr[size] == '\0'); //reach to last char
while (size >= 0) {
if ((unsigned char)cmd_ptr[size] < ' ') cmd_ptr[size] = ' ';
size--;
}
}
/*
* get the processes stats and print the stats
* corresponding to header attributes.
*/
static void do_ps_line(int pid, int tid)
{
char *stat_buff = toybuf + 1024, *cmd_buff = toybuf + 2048;
char state[4] = {0,};
int tty, tty_major, tty_minor, fd, n, nice, width_counter = 0;
struct stat stats;
struct passwd *pw;
struct group *gr;
char *name, *user, *group, *ruser, *rgroup, *ptr;
long rss;
unsigned long stime, utime, start_time, vsz;
unsigned ppid, ruid, rgid, pgid;
struct header_list *p = o_list;
sprintf(stat_buff, "/proc/%d", pid);
if(stat(stat_buff, &stats)) return;
if (tid) {
if (snprintf(stat_buff, BUFF_SIZE, "/proc/%d/task/%d/stat", pid, tid) >= BUFF_SIZE) return;
if (snprintf(cmd_buff, BUFF_SIZE, "/proc/%d/task/%d/cmdline", pid, tid) >= BUFF_SIZE) return;
} else {
if (snprintf(stat_buff, BUFF_SIZE, "/proc/%d/stat", pid) >= BUFF_SIZE) return;
if (snprintf(cmd_buff, BUFF_SIZE, "/proc/%d/cmdline", pid) >= BUFF_SIZE) return;
}
fd = xopen(stat_buff, O_RDONLY);
n = readall(fd, stat_buff, BUFF_SIZE);
xclose(fd);
if (n < 0) return;
stat_buff[n] = 0; //Null terminate the buffer.
ptr = strchr(stat_buff, '(');
ptr++;
name = ptr;
ptr = strrchr(stat_buff, ')');
*ptr = '\0'; //unecessary if?
name = xmprintf("[%s]", name);
ptr += 2; // goto STATE
n = sscanf(ptr, "%c %u %u %*u %d %*s %*s %*s %*s %*s %*s "
"%lu %lu %*s %*s %*s %d %*s %*s %lu %lu %ld",
&state[0],&ppid, &pgid, &tty, &utime, &stime,
&nice,&start_time, &vsz,&rss);
if (tid) pid = tid;
vsz >>= 10; //Convert into KB
rss = rss * 4; //Express in pages
tty_major = (tty >> 8) & 0xfff;
tty_minor = (tty & 0xff) | ((tty >> 12) & 0xfff00);
if (vsz == 0 && state[0] != 'Z') state[1] = 'W';
else state[1] = ' ';
if (nice < 0 ) state[2] = '<';
else if (nice) state[2] = 'N';
else state[2] = ' ';
if (tid) {
if (snprintf(stat_buff, BUFF_SIZE, "/proc/%d/task/%d/status", pid, tid) >= BUFF_SIZE)
goto clean;
} else {
if (snprintf(stat_buff, BUFF_SIZE, "/proc/%d/status", pid) >= BUFF_SIZE)
goto clean;
}
fd = -1;
while (p) {
int width;
width = p->width;
width_counter += (width + 1); //how much screen we hv filled, +1, extra space b/w headers
switch (p->position) {
case 0:
pw = getpwuid(stats.st_uid);
if (!pw) user = xmprintf("%d",(int)stats.st_uid);
else user = xmprintf("%s", pw->pw_name);
printf("%-*.*s", width, width, user);
free(user);
break;
case 1:
gr = getgrgid(stats.st_gid);
if (!gr) group = xmprintf("%d",(int)stats.st_gid);
else group = xmprintf("%s", gr->gr_name);
printf("%-*.*s", width, width, group);
free(group);
break;
case 2:
name[strlen(name) - 1] = '\0';
printf("%-*.*s", width,width, name + 1);
name[strlen(name)] = ']'; //Refill it for further process.
break;
case 3:
{
int j = 0;
width_counter -= width;
if(p->next) j = width; //is args is in middle. ( -o pid,args,ppid)
else j = (TT.screen_width - width_counter % TT.screen_width); //how much screen left.
if (fd == -1) fd = open(cmd_buff, O_RDONLY); //don't want to die
else xlseek(fd, 0, SEEK_SET);
if (fd < 0) cmd_buff[0] = 0;
else read_cmdline(fd, cmd_buff);
if (cmd_buff[0]) printf("%-*.*s", j, j, cmd_buff);
else printf("%-*.*s", j, j, name);
width_counter += width;
break;
}
case 4:
printf("%*d", width, pid);
break;
case 5:
printf("%*d", width, ppid);
break;
case 6:
printf("%*d", width, pgid);
break;
case 7:
get_etime(start_time);
break;
case 8:
printf("%*d", width, nice);
break;
case 9:
get_uid_gid(stat_buff, "Gid:", &rgid);
gr = getgrgid(rgid);
if (!gr) rgroup = xmprintf("%d",(int)stats.st_gid);
else rgroup = xmprintf("%s", gr->gr_name);
printf("%-*.*s", width,width, rgroup);
free(rgroup);
break;
case 10:
get_uid_gid(stat_buff, "Uid:", &ruid);
pw = getpwuid(ruid);
if (!pw) ruser = xmprintf("%d",(int)stats.st_uid);
else ruser = xmprintf("%s", pw->pw_name);
printf("%-*.*s", width, width, ruser);
free(ruser);
break;
case 11:
get_time(utime, stime);
break;
case 12:
if (tty_major) {
char *temp = xmprintf("%d,%d", tty_major,tty_minor);
printf("%-*s", width, temp);
free(temp);
} else printf("%-*s", width, "?");
break;
case 13:
printf("%*lu", width, vsz);
break;
case 14:
printf("%-*s", width, state);
break;
case 15:
printf("%*lu", width, rss);
break;
}
p = p->next;
xputc(' '); //space char
}
if (fd >= 0) xclose(fd);
xputc('\n');
clean:
free(name);
}
//Do stats for threads (for -T option)
void do_ps_threads(int pid)
{
DIR *d;
int tid;
struct dirent *de;
char *tmp = xmprintf("/proc/%d/task",pid);
if (!(d = opendir(tmp))) {
free(tmp);
return;
}
while ((de = readdir(d))) {
if (isdigit(de->d_name[0])) {
tid = atoi(de->d_name);
if (tid == pid) continue;
do_ps_line(pid, tid);
}
}
closedir(d);
free(tmp);
}
void ps_main(void)
{
DIR *dp;
struct dirent *entry;
int pid;
TT.screen_width = 80; //default width
terminal_size(&TT.screen_width, NULL);
print_header();
if (!(dp = opendir("/proc"))) perror_exit("opendir");
while ((entry = readdir(dp))) {
if (isdigit(*entry->d_name)) {
pid = atoi(entry->d_name);
do_ps_line(pid, 0);
if (toys.optflags & FLAG_T)
do_ps_threads(pid);
}
}
closedir(dp);
if (CFG_TOYBOX_FREE) {
struct header_list *temp = o_list;
while(temp) {
o_list = o_list->next;
free(temp->header);
free(temp);
temp = o_list;
}
}
}