/***********************************************************************
*
* psinfo.c
*
* psinfo: process info
*
* Copyright (C) 2008-2010 Ward van Wanrooij <ward@ward.nu>
*
* This software may be distributed according to the terms of the GNU
* General Public License, version 2 or (at your option) any later
* version.
*
***********************************************************************/

#include <stdio.h>
#include <sys/utsname.h>
#include <sys/sysinfo.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <getopt.h>
#include <errno.h>
#include <dirent.h>
#include <limits.h>

#include "defines.h"
#include "psinfo.h"

#define CHECK_ERROR(x) { if (x) fprintf(stderr, "psinfo:%s:%d: error at %s\n", __FILE__, __LINE__, #x); }

int parse_proc_io(FILE * f, struct process_info *p)
{
	char key[1024], value[1024];

	while (fscanf(f, "%1023[^:]:%1023[^\n]\n", key, value) == 2) {
		if (!strcasecmp(key, "rchar")) {
			p->has_io = 1;
			p->rchar = atoll(value);
		} else if (!strcasecmp(key, "wchar")) {
			p->wchar = atoll(value);
		} else if (!strcasecmp(key, "syscr")) {
			p->syscr = atoll(value);
		} else if (!strcasecmp(key, "syscw")) {
			p->syscw = atoll(value);
		} else if (!strcasecmp(key, "read_bytes")) {
			p->read_bytes = atoll(value);
		} else if (!strcasecmp(key, "write_bytes")) {
			p->write_bytes = atoll(value);
		} else if (!strcasecmp(key, "cancelled_write_bytes")) {
			p->cancelled_write_bytes = atoll(value);
		}
	}
	return 0;
}

int parse_proc_oomscore(FILE * f, struct process_info *p)
{
	p->has_oom_score = (fscanf(f, "%d", &p->oom_score) == 1);
	return !p->has_oom_score;
}

int parse_proc_oomadj(FILE * f, struct process_info *p)
{
	p->has_oom_adj = (fscanf(f, "%d", &p->oom_adj) == 1);
	return !p->has_oom_adj;
}


int parse_proc_schedstat(FILE * f, struct process_info *p)
{
	p->has_schedstat = (fscanf(f, "%llu %llu %llu", &p->run_ticks, &p->wait_ticks, &p->nran) == 3);
	return !p->has_schedstat;
}

int parse_proc_stat(FILE * f, struct process_info *p)
{
	int m;

	if ((m = fscanf(f, "%d (%1023[^)]) %c %d %d %d %d %d %u %lu %lu %lu %lu %lu %lu %ld %ld %ld %ld %d %*d %llu %lu %ld %lu %lu %lu %lu %lu %lu %*u %*u %*u %*u %lu %*u %*u %d %d %u %u %llu %lu %ld", &p->pid, p->tcomm, &p->state, &p->ppid, &p->pgrp, &p->sid, &p->tty_nr, &p->tty_pgrp, &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->num_threads, &p->start_time, &p->vsize, &p->rss, &p->rlim, &p->start_code, &p->end_code, &p->start_stack, &p->esp, &p->eip, &p->wchan, &p->exit_signal, &p->task_cpu, &p->rt_priority, &p->policy, &p->blkio_ticks, &p->gtime, &p->cgtime)) >= 32) {
		p->has_blkio_ticks = (m >= 33);
		p->has_gtime = (m >= 35);
		p->utime -= p->gtime;
		p->cutime -= p->cgtime;
		return 0;
	} else {
		return 1;
	}
}

int parse_proc_statm(FILE * f, struct process_info *p)
{
	return (fscanf(f, "%d %d %d", &p->vmsizep, &p->vmresidentp, &p->vmsharedp) != 3);
}

int parse_proc_capbnd(FILE * f, struct process_info *p)
{
	p->has_capbnd = (fscanf(f, "%ld", &p->capbnd) == 1);
	return !p->has_capbnd;
}

int parse_proc_status(FILE * f, struct process_info *p)
{
	char key[1024], value[1024];

	while (fscanf(f, "%1023[^:]:%1023[^\n]\n", key, value) == 2) {
		if (!strcasecmp(key, "sleepavg")) {
			p->sleepavg = atoi(value);
		} else if (!strcasecmp(key, "tracerpid")) {
			p->tracerpid = atoi(value);
		} else if (!strcasecmp(key, "uid")) {
			CHECK_ERROR(sscanf(value, "%d %d %d %d", &p->uid, &p->euid, &p->suid, &p->fsuid) != 4);
		} else if (!strcasecmp(key, "gid")) {
			CHECK_ERROR(sscanf(value, "%d %d %d %d", &p->gid, &p->egid, &p->sgid, &p->fsgid) != 4);
		} else if (!strcasecmp(key, "fdsize")) {
			p->fdsize = atoi(value);
		} else if (!strcasecmp(key, "vmpeak")) {
			p->has_vmpeak = 1;
			p->vmpeak = atol(value);
		} else if (!strcasecmp(key, "vmsize")) {
			p->vmsize = atol(value);
		} else if (!strcasecmp(key, "vmlck")) {
			p->vmlck = atol(value);
		} else if (!strcasecmp(key, "vmhwm")) {
			p->has_vmhwm = 1;
			p->vmhwm = atol(value);
		} else if (!strcasecmp(key, "vmrss")) {
			p->vmrss = atol(value);
		} else if (!strcasecmp(key, "vmdata")) {
			p->vmdata = atol(value);
		} else if (!strcasecmp(key, "vmstk")) {
			p->vmstk = atol(value);
		} else if (!strcasecmp(key, "vmexe")) {
			p->vmexe = atol(value);
		} else if (!strcasecmp(key, "vmlib")) {
			p->vmlib = atol(value);
		} else if (!strcasecmp(key, "vmpte")) {
			p->has_vmpte = 1;
			p->vmpte = atol(value);
		} else if (!strcasecmp(key, "sigq")) {
			p->has_sigq = 1;
			CHECK_ERROR(sscanf(value, "%lu/%lu", &p->sigqsize, &p->sigqlim) != 2);
		} else if (!strcasecmp(key, "sigpnd")) {
			CHECK_ERROR(sscanf(value, "%lx", &p->sigpending) != 1);
		} else if (!strcasecmp(key, "shapnd")) {
			CHECK_ERROR(sscanf(value, "%lx", &p->sigshpending) != 1);
		} else if (!strcasecmp(key, "sigblk")) {
			CHECK_ERROR(sscanf(value, "%lx", &p->sigblocked) != 1);
		} else if (!strcasecmp(key, "sigign")) {
			CHECK_ERROR(sscanf(value, "%lx", &p->sigignored) != 1);
		} else if (!strcasecmp(key, "sigcgt")) {
			CHECK_ERROR(sscanf(value, "%lx", &p->sigcaught) != 1);
		} else if (!strcasecmp(key, "capinh")) {
			CHECK_ERROR(sscanf(value, "%lx", &p->capinh) != 1);
		} else if (!strcasecmp(key, "capprm")) {
			CHECK_ERROR(sscanf(value, "%lx", &p->capprm) != 1);
		} else if (!strcasecmp(key, "capeff")) {
			CHECK_ERROR(sscanf(value, "%lx", &p->capeff) != 1);
		} else if (!strcasecmp(key, "capbnd")) {
			p->has_capbnd = 1;
			CHECK_ERROR(sscanf(value, "%lx", &p->capbnd) != 1);
		}
	}
	return 0;
}

int parse_proc_wchan(FILE * f, struct process_info *p)
{
	return (fscanf(f, "%1023s", p->wchan_decoded) != 1);
}

int parse_proc_arrayfile(char *name, char ***s)
{
	FILE *f;
	int res = 1;

	if ((f = fopen(name, "r")) != NULL) {
		char *buf;
		int len = 0, bufsize = 1024, count = 0;

		buf = malloc(bufsize);
		while ((buf[len++] = fgetc(f)) != EOF) {
			if (buf[len - 1] == 0) {
				*s = realloc(*s, (count + 2) * sizeof(char *));
				(*s)[count] = malloc(len);
				strncpy((*s)[count], buf, len);
				count++;
				len = 0;
			}
			if (len == bufsize) {
				bufsize <<= 2;
				buf = realloc(buf, bufsize);
			}
		}
		if (*s != NULL)
			(*s)[count] = NULL;
		res = 0;
		fclose(f);
	}
	return res;
}

int parse_proc_file(char *name, struct process_info *p, int (*process) (FILE * f, struct process_info * p))
{
	FILE *f;

	if ((f = fopen(name, "r")) != NULL) {
		int res;

		res = process(f, p);
		fclose(f);
		return res;
	} else {
		return 1;
	}
}

int parse_proc_symlink(char *name, char **s)
{
	int count, res = 1;
	char buf[1024];

	if ((count = readlink(name, buf, sizeof(buf) - 1)) != -1) {
		*s = realloc(*s, count + 1);
		memcpy(*s, buf, count);
		(*s)[count] = 0;
		res = 0;
	}
	return res;
}

int parse_proc_task(char *name, int **s) {
	struct dirent **files;
	int cnt;

	if ((cnt = scandir(name, &files, NULL, NULL)) > 0) {
		int sz;

		sz = 0;
		*s = realloc(*s, sizeof(int) * cnt);
		memset(*s, 0, sizeof(int) * cnt);
		while (cnt--) {
			if (files[cnt]->d_name[0] != '.') {
				int tpid;

				tpid = atoi(files[cnt]->d_name);
				if (tpid != 0)
					(*s)[sz++] = tpid;
			}
			free(files[cnt]);
		}
		free(files);
	}
	return 0;
}

int tty_dev_to_name(unsigned int major, unsigned int minor, char *buf, int bufsize)
{
	FILE *f;

	if ((f = fopen("/proc/tty/drivers", "r")) != NULL) {
		int res;

		res = 1;
		while (!feof(f)) {
			unsigned int matches, lmajor, lminorstart, lminorend;
			char lname[32];

			lminorend = 0;
			if ((matches = fscanf(f, "%*s %31s %u %u-%u", lname, &lmajor, &lminorstart, &lminorend)) >= 3)
				if ((lmajor == major) && (((matches == 3) && (lminorstart == minor)) || ((matches == 4) && (lminorstart <= minor) && (lminorend >= minor)))) {
					snprintf(buf, bufsize, "%s%u", lname, minor - lminorstart);
					res = 0;
					break;
				}
			fscanf(f, "%*[^\n]\n");
		}
		fclose(f);
		return res;
	} else {
		return 1;
	}
}

void print_general(struct process_info *p)
{
	int i;
	time_t start_time;
	struct sysinfo info;
	char tty_name[32];

	start_time = time(NULL);
	if (!sysinfo(&info))
		start_time -= info.uptime;
	start_time += p->start_time / sysconf(_SC_CLK_TCK);

	if (p->tty_nr == 0) {
		snprintf(tty_name, sizeof(tty_name), "(none)");
	} else {
		unsigned int majordev, minordev;

		majordev = (p->tty_nr & 0xfff00) >> 8;
		minordev = (p->tty_nr & 0xff) | ((p->tty_nr >> 12) & 0xfff00);
		if (tty_dev_to_name(majordev, minordev, tty_name, sizeof(tty_name)))
			snprintf(tty_name, sizeof(tty_name), "%u:%u", majordev, minordev);
	}

	printf("General\n");
	printf("\tProcess id: %d\n", p->pid);
	printf("\tParent process id: %d\n", p->ppid);
	printf("\tProcess group: %d\n", p->pgrp);
	printf("\tSession id: %d\n", p->sid);
	printf("\tThreads: %d (", p->num_threads);
	for (i = 0; i < p->num_threads; i++)
		printf("%d%c", p->threads[i], (i == (p->num_threads - 1)) ? ')' : ' ');
	printf("\n");
	printf("\tState: ");
	switch (p->state) {
	case 'R':
		printf("R running at cpu %d\n", p->task_cpu);
		break;
	case 'S':
		printf("S interruptable sleeping %s\n", ((p->wchan == 0) || (p->wchan == (unsigned long)-1)) ? "" : p->wchan_decoded);
		break;
	case 'D':
		printf("D uninterruptable sleeping %s\n", ((p->wchan == 0) || (p->wchan == (unsigned long)-1)) ? "" : p->wchan_decoded);
		break;
	case 'T':
		printf("T tracing by pid %d\n", p->tracerpid);
		break;
	case 'Z':
		printf("Z zombie\n");
		break;
	case 'X':
		printf("X dead\n");
		break;
	default:
		printf("%c n/a\n", p->state);
		break;
	}
	printf("\tSleeping average: %d %%\n", p->sleepavg);
	printf("\tTTY: %s\n", tty_name);
	printf("\tStart time: %s", ctime(&start_time));
	printf("\tProcess name: %s\n", p->tcomm == NULL ? "n/a" : p->tcomm);
	printf("\tExecutable: %s\n", p->exe == NULL ? "n/a" : p->exe);
	printf("\tRoot directory: %s\n", p->root == NULL ? "n/a" : p->root);
	printf("\tWorking directory: %s\n", p->cwd == NULL ? "n/a" : p->cwd);
	printf("\tArguments: ");
	if (p->argv == NULL)
		printf("n/a\n");
	else
		for (i = 0; p->argv[i] != NULL; i++)
			printf("%s%d - %s\n", i == 0 ? "" : "\t           ", i, p->argv[i]);
	printf("\tEnvironment: ");
	if (p->env == NULL)
		printf("n/a\n");
	else
		for (i = 0; p->env[i] != NULL; i++)
			printf("%s%d - %s\n", i == 0 ? "" : "\t             ", i, p->env[i]);
	print_processflags("Flags:", p->flags);
}

void print_processflags(char *desc, long unsigned flags)
{
#define TEST_FLAG(x) if (flags & x) printf("\t       %016lx - %s\n", (long unsigned)x, #x);
	printf("\t%s %016lx\n", desc, flags);
	TEST_FLAG(PF_ALIGNWARN);
	TEST_FLAG(PF_STARTING);
	TEST_FLAG(PF_EXITING);
	TEST_FLAG(PF_EXITPIDONE);
	TEST_FLAG(PF_VCPU);
	TEST_FLAG(PF_FORKNOEXEC);
	TEST_FLAG(PF_SUPERPRIV);
	TEST_FLAG(PF_DUMPCORE);
	TEST_FLAG(PF_SIGNALED);
	TEST_FLAG(PF_MEMALLOC);
	TEST_FLAG(PF_FLUSHER);
	TEST_FLAG(PF_USED_MATH);
	TEST_FLAG(PF_NOFREEZE);
	TEST_FLAG(PF_FROZEN);
	TEST_FLAG(PF_FSTRANS);
	TEST_FLAG(PF_KSWAPD);
	TEST_FLAG(PF_SWAPOFF);
	TEST_FLAG(PF_LESS_THROTTLE);
	TEST_FLAG(PF_BORROWED_MM);
	TEST_FLAG(PF_RANDOMIZE);
	TEST_FLAG(PF_SWAPWRITE);
	TEST_FLAG(PF_SPREAD_PAGE);
	TEST_FLAG(PF_SPREAD_SLAB);
	TEST_FLAG(PF_MEMPOLICY);
	TEST_FLAG(PF_MUTEX_TESTER);
	TEST_FLAG(PF_FREEZER_SKIP);
}

void print_cpu(struct process_info *p)
{
	printf("CPU usage\n");
	printf("\tNice: %ld\n", p->nice);
	printf("\tPriority level: %ld\n", p->priority);
	printf("\tRealtime priority: %u\n", p->rt_priority);
	printf("\tScheduling policy: ");
	switch (p->policy) {
	case SCHED_OTHER:
		printf("default Linux time-sharing\n");
		break;
	case SCHED_FIFO:
		printf("first in-first out\n");
		break;
	case SCHED_RR:
		printf("round robin\n");
		break;
	case SCHED_BATCH:
		printf("batch\n");
		break;
	default:
		printf("n/a\n");
	}
	printf("\tRuns:");
	if (p->has_schedstat)
		printf(" %llu\n", p->nran);
	else
		printf(" n/a\n");
	printf("\tSeconds:\n");
	printf("\t              user    system     guest     total\n");
	printf("\tprocess   %8.2f  %8.2f", (double) p->utime / KERNEL_HZ, (double) p->stime / KERNEL_HZ);
	if (p->has_gtime)
		printf("  %8.2f", (double) p->gtime / KERNEL_HZ);
	else
		printf("       n/a");
	printf("  %8.2f\n", (double) (p->utime + p->stime + p->gtime) / KERNEL_HZ);
	printf("\tchildren  %8.2f  %8.2f", (double) p->cutime / KERNEL_HZ, (double) p->cstime / KERNEL_HZ);
	if (p->has_gtime)
		printf("  %8.2f", (double) p->cgtime / KERNEL_HZ);
	else
		printf("       n/a");
	printf("  %8.2f\n", (double) (p->cutime + p->cstime + p->cgtime) / KERNEL_HZ);
	printf("\ttotal     %8.2f  %8.2f", (double) (p->utime + p->cutime) / KERNEL_HZ, (double) (p->stime + p->cstime) / KERNEL_HZ);
	if (p->has_gtime)
		printf("  %8.2f", (double) (p->gtime + p->cgtime) / KERNEL_HZ);
	else
		printf("       n/a");
	printf("  %8.2f\n", (double) (p->utime + p->stime + p->gtime + p->cutime + p->cstime + p->cgtime) / KERNEL_HZ);
}

void print_signal(struct process_info *p)
{
	printf("Signals\n");
	printf("\tUser signal queue:");
	if (p->has_sigq)
		printf(" %lu/%lu\n", p->sigqsize, p->sigqlim);
	else
		printf(" n/a\n");
	print_sigset("Pending private:", p->sigpending);
	print_sigset("Pending shared: ", p->sigshpending);
	print_sigset("Blocked:        ", p->sigblocked);
	print_sigset("Ignored:        ", p->sigignored);
	print_sigset("Caught:         ", p->sigcaught);
}

void print_sigset(char *desc, long unsigned sig)
{
#define TEST_SIGNAL(x) if (sig & (1 << (x - 1))) printf("\t                 %d - %s\n", x, #x);
	printf("\t%s %016lx\n", desc, sig);
	TEST_SIGNAL(SIGHUP);
	TEST_SIGNAL(SIGINT);
	TEST_SIGNAL(SIGQUIT);
	TEST_SIGNAL(SIGILL);
	TEST_SIGNAL(SIGTRAP);
	TEST_SIGNAL(SIGABRT);
	TEST_SIGNAL(SIGIOT);
	TEST_SIGNAL(SIGBUS);
	TEST_SIGNAL(SIGFPE);
	TEST_SIGNAL(SIGKILL);
	TEST_SIGNAL(SIGUSR1);
	TEST_SIGNAL(SIGSEGV);
	TEST_SIGNAL(SIGUSR2);
	TEST_SIGNAL(SIGPIPE);
	TEST_SIGNAL(SIGALRM);
	TEST_SIGNAL(SIGTERM);
	TEST_SIGNAL(SIGSTKFLT);
	TEST_SIGNAL(SIGCHLD);
	TEST_SIGNAL(SIGCONT);
	TEST_SIGNAL(SIGSTOP);
	TEST_SIGNAL(SIGTSTP);
	TEST_SIGNAL(SIGTTIN);
	TEST_SIGNAL(SIGTTOU);
	TEST_SIGNAL(SIGURG);
	TEST_SIGNAL(SIGXCPU);
	TEST_SIGNAL(SIGXFSZ);
	TEST_SIGNAL(SIGVTALRM);
	TEST_SIGNAL(SIGPROF);
	TEST_SIGNAL(SIGWINCH);
	TEST_SIGNAL(SIGIO);
	TEST_SIGNAL(SIGPOLL);
	TEST_SIGNAL(SIGPWR);
	TEST_SIGNAL(SIGSYS);
	TEST_SIGNAL(SIGUNUSED);
}

void print_memory(struct process_info *p)
{
	printf("Memory\n");
	printf("\tOut-of-memory killer score:");
	if (p->has_oom_score)
		printf(" %d\n", p->oom_score);
	else
		printf(" n/a\n");
	printf("\tOut-of-memory killer adjustment:");
	if (p->has_oom_adj)
		printf(" %d\n", p->oom_adj);
	else
		printf(" n/a\n");
	printf("\tVM size: %lu kB", p->vmsize);
	if (p->has_vmpeak)
		printf(" (peak %lu kB)\n", p->vmpeak);
	else
		printf(" (peak n/a)\n");
	printf("\tVM locked size: %lu kB\n", p->vmlck);
	printf("\tVM resident set size: %lu kB", p->vmrss);
	if (p->has_vmhwm)
		printf(" (peak %lu kB)\n", p->vmhwm);
	else
		printf(" (peak n/a)\n");
	printf("\tVM data segment size: %lu kB\n", p->vmdata);
	printf("\tVM stack segment size: %lu kB\n", p->vmstk);
	printf("\tVM text segment size: %lu kB\n", p->vmexe);
	printf("\tVM shared library size: %lu kB\n", p->vmlib);
	printf("\tVM page table entries size:");
	if (p->has_vmpte)
		printf(" %lu kB\n", p->vmpte);
	else
		printf(" n/a\n");
	printf("\tPagefaults:\n");
	printf("\t             minor     major\n");
	printf("\tprocess   %8lu  %8lu\n", p->min_flt, p->maj_flt);
	printf("\tchildren  %8lu  %8lu\n", p->cmin_flt, p->cmaj_flt);
	printf("\ttotal     %8lu  %8lu\n", p->min_flt + p->cmin_flt, p->maj_flt + p->cmaj_flt);
}

void print_privilege(struct process_info *p)
{
	printf("Privilege\n");
	printf("\tIdentity:\n");
	printf("\t              real effective     saved        fs\n");
	printf("\tuser      %8d  %8d  %8d  %8d\n", p->uid, p->euid, p->suid, p->fsuid);
	printf("\tgroup     %8d  %8d  %8d  %8d\n", p->gid, p->egid, p->sgid, p->fsgid);
	print_capability("Cap inheritable: ", p->capinh);
	print_capability("Cap permitted:   ", p->capprm);
	print_capability("Cap effective:   ", p->capeff);
	if (p->has_capbnd)
		print_capability("Cap bounding set:", p->capbnd);
	else
		printf("\tCap bounding set: n/a\n");
}

void print_capability(char *desc, long unsigned cap)
{
#define TEST_CAPABILITY(x) if (cap & (1 << x)) printf("\t                  %d - %s\n", x, #x);
	printf("\t%s %016lx\n", desc, cap);
	TEST_CAPABILITY(CAP_CHOWN);
	TEST_CAPABILITY(CAP_DAC_OVERRIDE);
	TEST_CAPABILITY(CAP_DAC_READ_SEARCH);
	TEST_CAPABILITY(CAP_FOWNER);
	TEST_CAPABILITY(CAP_FSETID);
	TEST_CAPABILITY(CAP_KILL);
	TEST_CAPABILITY(CAP_SETGID);
	TEST_CAPABILITY(CAP_SETUID);
	TEST_CAPABILITY(CAP_SETPCAP);
	TEST_CAPABILITY(CAP_LINUX_IMMUTABLE);
	TEST_CAPABILITY(CAP_NET_BIND_SERVICE);
	TEST_CAPABILITY(CAP_NET_BROADCAST);
	TEST_CAPABILITY(CAP_NET_ADMIN);
	TEST_CAPABILITY(CAP_NET_RAW);
	TEST_CAPABILITY(CAP_IPC_LOCK);
	TEST_CAPABILITY(CAP_IPC_OWNER);
	TEST_CAPABILITY(CAP_SYS_MODULE);
	TEST_CAPABILITY(CAP_SYS_RAWIO);
	TEST_CAPABILITY(CAP_SYS_CHROOT);
	TEST_CAPABILITY(CAP_SYS_PTRACE);
	TEST_CAPABILITY(CAP_SYS_PACCT);
	TEST_CAPABILITY(CAP_SYS_ADMIN);
	TEST_CAPABILITY(CAP_SYS_BOOT);
	TEST_CAPABILITY(CAP_SYS_NICE);
	TEST_CAPABILITY(CAP_SYS_RESOURCE);
	TEST_CAPABILITY(CAP_SYS_TIME);
	TEST_CAPABILITY(CAP_SYS_TTY_CONFIG);
	TEST_CAPABILITY(CAP_MKNOD);
	TEST_CAPABILITY(CAP_LEASE);
	TEST_CAPABILITY(CAP_AUDIT_WRITE);
	TEST_CAPABILITY(CAP_AUDIT_CONTROL);
	TEST_CAPABILITY(CAP_FS_MASK);
}

void print_io(struct process_info *p)
{
	printf("I/O\n");
	printf("\tFile descriptor table size: %d\n", p->fdsize);
	printf("\tTime spent waiting for blocking I/O:");
	if (p->has_blkio_ticks)
		printf(" %.2f s\n", (double) p->blkio_ticks / KERNEL_HZ);
	else
		printf(" n/a\n");
	printf("\tStatistics:");
	if (p->has_io) {
		printf("\n");
		printf("\t          syscalls   tput kB   disk kB\n");
		printf("\tread      %8llu  %8llu  %8llu\n", p->syscr, p->rchar / 1024, p->read_bytes / 1024);
		printf("\twritten   %8llu  %8llu  %8llu\n", p->syscw, p->wchar / 1024, p->write_bytes / 1024);
		printf("\tcancelled        -         -  %8llu\n", p->cancelled_write_bytes / 1024);
	} else {
		printf(" n/a\n");
	}
}

int parse_proc(char *base, int pid, struct process_info *p)
{
	char filename[FILENAME_MAX];

	snprintf(filename, sizeof(filename), "%s/%d/stat", base, pid);
	if (parse_proc_file(filename, p, &parse_proc_stat)) {
		fprintf(stderr, "psinfo: process %d does not exist\n", pid);
		return 1;
	}
	snprintf(filename, sizeof(filename), "%s/%d/statm", base, pid);
	CHECK_ERROR(parse_proc_file(filename, p, &parse_proc_statm));
	parse_proc_file("/proc/sys/kernel/cap-bound", p, &parse_proc_capbnd);
	snprintf(filename, sizeof(filename), "%s/%d/status", base, pid);
	CHECK_ERROR(parse_proc_file(filename, p, &parse_proc_status));
	snprintf(filename, sizeof(filename), "%s/%d/cmdline", base, pid);
	CHECK_ERROR(parse_proc_arrayfile(filename, &p->argv));
	snprintf(filename, sizeof(filename), "%s/%d/task", base, pid);
	CHECK_ERROR(parse_proc_task(filename, &p->threads));
	snprintf(filename, sizeof(filename), "%s/%d/cwd", base, pid);
	parse_proc_symlink(filename, &p->cwd);
	snprintf(filename, sizeof(filename), "%s/%d/environ", base, pid);
	parse_proc_arrayfile(filename, &p->env);
	snprintf(filename, sizeof(filename), "%s/%d/root", base, pid);
	parse_proc_symlink(filename, &p->root);
	snprintf(filename, sizeof(filename), "%s/%d/exe", base, pid);
	parse_proc_symlink(filename, &p->exe);
	snprintf(filename, sizeof(filename), "%s/%d/io", base, pid);
	parse_proc_file(filename, p, &parse_proc_io);
	snprintf(filename, sizeof(filename), "%s/%d/oom_adj", base, pid);
	parse_proc_file(filename, p, &parse_proc_oomadj);
	snprintf(filename, sizeof(filename), "%s/%d/oom_score", base, pid);
	parse_proc_file(filename, p, &parse_proc_oomscore);
	snprintf(filename, sizeof(filename), "%s/%d/schedstat", base, pid);
	parse_proc_file(filename, p, &parse_proc_schedstat);
	snprintf(filename, sizeof(filename), "%s/%d/wchan", base, pid);
	parse_proc_file(filename, p, &parse_proc_wchan);
	return 0;
}

void print_help()
{
	printf("psinfo 0.12\n");
	printf("Copyright (C) 2008-2010 Ward van Wanrooij <ward@ward.nu>\n");
	printf("This software may be distributed according to the terms of the GNU\n");
	printf("General Public License, version 2 or (at your option) any later version.\n");
	printf("\n");
	printf("Syntax: psinfo -agcimpsHV pid\n");
	printf("\t-a show all information (default)\n");
	printf("\t-g show general information\n");
	printf("\t-c show cpu information\n");
	printf("\t-i show i/o information\n");
	printf("\t-m show memory information\n");
	printf("\t-p show privilege information\n");
	printf("\t-s show signal information\n");
	printf("\t-H show help\n");
	printf("\t-V show version\n");
}

int main(int argc, char **argv)
{
	struct process_info *p;
	int pid, output_general = 0, output_cpu = 0, output_io = 0, output_memory = 0, output_privilege = 0, output_signal = 0, result = 0;
	char c;

	opterr = 0;
	if (argc <= 1) {
		print_help();
		return 0;
	}
	while ((c = getopt(argc, argv, "agcimpsHV")) != -1) {
		switch (c) {
		case 'H':
		case 'V':
			print_help();
			return 0;
		case 'a':
			break;
		case 'g':
			output_general = 1;
			break;
		case 'c':
			output_cpu = 1;
			break;
		case 'i':
			output_io = 1;
			break;
		case 'm':
			output_memory = 1;
			break;
		case 'p':
			output_privilege = 1;
			break;
		case 's':
			output_signal = 1;
			break;
		default:
			fprintf(stderr, "psinfo: invalid command line option '%c'\n", c);
			return 1;
		}
	}
	if (optind < argc) {
		char *end;

		errno = 0;
		pid = strtol(argv[optind], &end, 10);
		if (errno || (argv[optind] == end)) {
			fprintf(stderr, "psinfo: invalid process identifier '%s'\n", argv[optind]);
			return 1;
		}
	} else {
		fprintf(stderr, "psinfo: no process identifier specified\n");
		return 1;
	}
	if ((output_general | output_cpu | output_io | output_memory | output_privilege | output_signal) == 0) {
		output_general = 1;
		output_cpu = 1;
		output_io = 1;
		output_memory = 1;
		output_privilege = 1;
		output_signal = 1;
	}
	if ((p = malloc(sizeof(struct process_info))) != NULL) {
		tzset();
		memset(p, 0, sizeof(struct process_info));
		if (!(result = (parse_proc("/proc", pid, p)))) {
			if (output_general)
				print_general(p);
			if (output_cpu)
				print_cpu(p);
			if (output_io)
				print_io(p);
			if (output_memory)
				print_memory(p);
			if (output_privilege)
				print_privilege(p);
			if (output_signal)
				print_signal(p);
		}
		free(p->threads);
		free(p);
	}
	return result;
}
