/* Copyright (C) 2008 Emmanuel Varoquaux
This file is part of XOS.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see . */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PROC_ROOT "/proc"
struct procinfo_struct {
pid_t pid;
pid_t ppid;
pid_t pgid;
unsigned long vsz;
int state;
unsigned foreground : 1;
char *command;
struct procinfo_struct *next;
};
struct fieldwidth_struct {
int pid;
int ppid;
int pgid;
int vsz;
int state;
};
typedef int (*comparator_t)(const struct procinfo_struct *, const struct procinfo_struct *);
struct proc_list_struct {
comparator_t comparator;
struct procinfo_struct *first;
struct fieldwidth_struct fieldwidth;
};
static const struct {
const char *pid;
const char *ppid;
const char *pgid;
const char *vsz;
const char *state;
const char *command;
} header = {
.pid = "PID",
.ppid = "PPID",
.pgid = "PGID",
.vsz = "VSZ",
.state = "STATE",
.command = "COMMAND"
};
static const char *proc_root = PROC_ROOT;
static int screenwidth;
static void get_stat_from_file(const char *filename, int *pid, int *ppid, int *pgid, unsigned long *vsz, int *state)
{
FILE *fp;
unsigned long vsize;
unsigned char c;
if (!(fp = fopen(filename, "r")))
goto error;
if (fscanf(fp, "%d %c %d %d %lu", pid, &c, ppid, pgid, &vsize) != 5)
goto error_fclose;
*vsz = vsize / 1024;
*state = c;
fclose(fp);
return;
error_fclose:
fclose(fp);
error:
*pid = -1;
*state = -1;
*ppid = -1;
*pgid = -1;
*vsz = -1;
}
static void get_cmdline_from_file(const char *filename, char **s)
{
struct stat statbuf;
size_t size;
FILE *fp;
char *s1;
size_t len;
if (stat(filename, &statbuf) == -1)
goto error;
if ((size = statbuf.st_size)) {
if (!(*s = malloc(size)))
error(EXIT_FAILURE, errno, "malloc");
if (!(fp = fopen(filename, "r")))
goto error_free;
if (fread(*s, 1, size, fp) < size)
goto error_fclose;
fclose(fp);
s1 = *s;
while (size) {
if ((len = strnlen(s1, size)) == size)
goto error_free;
s1 += len;
*s1 = ' ';
s1++;
if (size < len + 1)
goto error_free;
size -= len + 1;
}
}
else
if (!(*s = strdup("(defunct)")))
error(EXIT_FAILURE, errno, "strdup");
return;
error_fclose:
fclose(fp);
error_free:
free(*s);
error:
*s = NULL;
}
static void build_procinfo(struct procinfo_struct *procinfo, pid_t pid, const char *path)
{
char *buf;
pid_t fpgid;
if (!(buf = malloc(strlen(path) + 1 + NAME_MAX + 1)))
error(EXIT_FAILURE, errno, "malloc");
sprintf(buf, "%s/stat", path);
get_stat_from_file(buf, &procinfo->pid, &procinfo->ppid, &procinfo->pgid, &procinfo->vsz, &procinfo->state);
if (procinfo->state != -1)
procinfo->foreground = procinfo->pgid != -1 && (fpgid = tcgetpgrp(fileno(stderr))) != -1 && procinfo->pgid == fpgid ? 1 : 0;
sprintf(buf, "%s/cmdline", path);
get_cmdline_from_file(buf, &procinfo->command);
free(buf);
}
static void destroy_procinfo(struct procinfo_struct *procinfo)
{
if (procinfo->command)
free(procinfo->command);
}
static void set_header_fieldwidth(struct fieldwidth_struct *fieldwidth)
{
fieldwidth->pid = strlen(header.pid);
fieldwidth->ppid = strlen(header.ppid);
fieldwidth->pgid = strlen(header.pgid);
fieldwidth->vsz = strlen(header.vsz);
fieldwidth->state = strlen(header.state);
}
static int num_fieldwidth(int n)
{
return snprintf(NULL, 0, "%d", n);
}
static void update_fieldwidth(struct fieldwidth_struct *fieldwidth, const struct procinfo_struct *procinfo)
{
int w;
if ((w = num_fieldwidth(procinfo->pid)) > fieldwidth->pid)
fieldwidth->pid = w;
if ((w = procinfo->ppid != -1 ? num_fieldwidth(procinfo->ppid) : 1) > fieldwidth->ppid)
fieldwidth->ppid = w;
if ((w = procinfo->pgid != -1 ? num_fieldwidth(procinfo->pgid) : 1) > fieldwidth->pgid)
fieldwidth->pgid = w;
if ((w = procinfo->vsz != (unsigned long)-1 ? num_fieldwidth(procinfo->vsz) : 1) > fieldwidth->vsz)
fieldwidth->vsz = w;
if (fieldwidth->state < 2)
fieldwidth->state = 2;
}
static void init_proc_list(struct proc_list_struct *proc_list, comparator_t comparator)
{
proc_list->comparator = comparator;
proc_list->first = NULL;
set_header_fieldwidth(&proc_list->fieldwidth);
}
static void destroy_proc_list(struct proc_list_struct *proc_list)
{
struct procinfo_struct *procinfo, *next;
procinfo = proc_list->first;
while (procinfo) {
next = procinfo->next;
destroy_procinfo(procinfo);
free(procinfo);
procinfo = next;
}
}
static int pid_comparator(const struct procinfo_struct *procinfo1, const struct procinfo_struct *procinfo2)
{
return procinfo1->pid < procinfo2->pid;
}
static int compare(const struct procinfo_struct *procinfo1, const struct procinfo_struct *procinfo2, struct proc_list_struct *proc_list)
{
return proc_list->comparator(procinfo1, procinfo2);
}
static void insert_into_proc_list(struct proc_list_struct *proc_list, struct procinfo_struct *procinfo)
{
struct procinfo_struct *procinfo1, *procinfo2;
procinfo1 = NULL;
procinfo2 = proc_list->first;
while (procinfo2 && !compare(procinfo, procinfo2, proc_list)) {
procinfo1 = procinfo2;
procinfo2 = procinfo2->next;
}
procinfo->next = procinfo2;
if (!procinfo1)
proc_list->first = procinfo;
else
procinfo1->next = procinfo;
update_fieldwidth(&proc_list->fieldwidth, procinfo);
}
static int populate_proc_list(struct proc_list_struct *proc_list)
{
int err;
DIR *dir;
struct dirent *dent;
int num;
char *buf;
struct procinfo_struct *procinfo;
err = 0;
if (!(dir = opendir(proc_root))) {
error(0, errno, "cannot open %s", proc_root);
err = 1;
goto end;
}
while (1) {
errno = 0;
if (!(dent = readdir(dir))) {
if (errno) {
error(0, errno, "reading directory %s", proc_root);
err = 1;
goto closedir;
}
break;
}
if (sscanf(dent->d_name, "%d", &num) != 1)
continue;
if (!(buf = malloc(strlen(proc_root) + 1 + strlen(dent->d_name) + 1)))
error(EXIT_FAILURE, errno, "malloc");
if (!(procinfo = malloc(sizeof (struct procinfo_struct))))
error(EXIT_FAILURE, errno, "malloc");
sprintf(buf, "%s/%s", proc_root, dent->d_name);
build_procinfo(procinfo, num, buf);
free(buf);
insert_into_proc_list(proc_list, procinfo);
}
closedir:
if (closedir(dir) == -1) {
error(0, errno, "closing directory %s", proc_root);
err = 1;
}
end:
return !err;
}
static void print_header(const struct fieldwidth_struct *fieldwidth)
{
printf("%-*s", fieldwidth->pid, header.pid);
putchar(' ');
printf("%-*s", fieldwidth->ppid, header.ppid);
putchar(' ');
printf("%-*s", fieldwidth->pgid, header.pgid);
putchar(' ');
printf("%-*s", fieldwidth->vsz, header.vsz);
putchar(' ');
printf("%-*s", fieldwidth->state, header.state);
putchar(' ');
printf("%s", header.command);
putchar('\n');
}
static char *make_state_str(const struct procinfo_struct *procinfo, char *buf)
{
char *p;
p = buf;
*p++ = procinfo->state;
if (procinfo->foreground)
*p++ = '+';
*p = '\0';
return buf;
}
static void print_procinfo(const struct procinfo_struct *procinfo, const struct fieldwidth_struct *fieldwidth)
{
int rem_width;
char state_buf[3];
rem_width = screenwidth;
if (procinfo->pid != -1)
printf("%*d", fieldwidth->pid, procinfo->pid);
else
printf("%*s", fieldwidth->pid, "?");
putchar(' ');
rem_width -= fieldwidth->pid + 1;
if (procinfo->ppid != -1)
printf("%*d", fieldwidth->ppid, procinfo->ppid);
else
printf("%*s", fieldwidth->ppid, "?");
putchar(' ');
rem_width -= fieldwidth->ppid + 1;
if (procinfo->pgid != -1)
printf("%*d", fieldwidth->pgid, procinfo->pgid);
else
printf("%*s", fieldwidth->pgid, "?");
putchar(' ');
rem_width -= fieldwidth->pgid + 1;
if (procinfo->vsz != (unsigned long)-1)
printf("%*lu", fieldwidth->vsz, procinfo->vsz);
else
printf("%*s", fieldwidth->vsz, "?");
putchar(' ');
rem_width -= fieldwidth->vsz + 1;
printf("%-*s", fieldwidth->state, procinfo->state != -1 ? make_state_str(procinfo, state_buf) : "?");
putchar(' ');
rem_width -= fieldwidth->state + 1;
if (screenwidth >= 0)
printf("%.*s", rem_width, procinfo->command ? : "?");
else
printf("%s", procinfo->command ? : "?");
putchar('\n');
}
static void print_proc_list(const struct proc_list_struct *proc_list)
{
const struct procinfo_struct *procinfo;
print_header(&proc_list->fieldwidth);
for (procinfo = proc_list->first; procinfo; procinfo = procinfo->next)
print_procinfo(procinfo, &proc_list->fieldwidth);
}
static int list_processes(comparator_t comparator)
{
int err;
struct proc_list_struct proc_list;
err = 0;
init_proc_list(&proc_list, comparator);
if (!populate_proc_list(&proc_list))
err = 1;
print_proc_list(&proc_list);
destroy_proc_list(&proc_list);
return !err;
}
static void __attribute__ ((noreturn)) die_bad_usage()
{
fprintf(stderr, "Try `%s --help' for more information.\n", program_invocation_name);
exit(EXIT_FAILURE);
}
static void print_help()
{
printf("Usage: %s [OPTION]...\n", program_invocation_name);
puts("Print a snapshot of the current processes.");
putchar('\n');
puts(" -h, --help display this help and exit");
}
int main(int argc, char **argv)
{
static const struct option longopts[] = {
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
int c;
struct winsize winsize;
while ((c = getopt_long(argc, argv, "h", longopts, NULL)) != -1)
switch (c) {
case 'h':
print_help();
return EXIT_SUCCESS;
case '?':
die_bad_usage();
}
screenwidth = ioctl(fileno(stdout), TIOCGWINSZ, &winsize) != -1 ? winsize.ws_col : -1;
return list_processes(pid_comparator) ? EXIT_SUCCESS : EXIT_FAILURE;
}