59d85e2bb0
Partly because there's no supplied target string ala sprintf, and partly because I can never remember what order the m and s go in.
420 lines
11 KiB
C
420 lines
11 KiB
C
/* 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;
|
|
}
|
|
}
|
|
}
|