/*
   ``Sort of SPS for Solaris2.4'' -- a `ps' that shows process descendancy info

   By Matti Aarnio <mea@nic.funet.fi>  1996

*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <values.h> /* MAXINT et.al. */
#include <sys/time.h>
#include <sys/procfs.h>
#include <sys/sysmacros.h>
#include <fcntl.h>
#include <dirent.h>
#include <alloca.h>
#include <malloc.h>
#include <errno.h>
#include <pwd.h>

#define PGSIZEinKB 8

extern void idcache_init();
extern void idcache_read();
extern char *id_ttyname();
extern char *id_uidname();


int exiting = 0, zombies = 0;

/* Reuse couple variables.. */
#define pr_CHILD    pr_addr
#define pr_SIBLING  pr_wchan

int option_c = 0; /* show only the saved command name, not argv!   */
int option_l = 0; /* lots of meaningless process oriented info     */
int option_u = 1; /* show times as user-time only, not user+childs */
char *option_uname = NULL;
int option_uuid = -1;
int option_pid  = -1;
int option_i = 0; /* initialize /etc/passwd, and /dev/ -tables */
int option_n = 0; /* numerical only */

void print_argv(pp,pid)
struct prpsinfo *pp;
int pid;
{
	char buf[8192];
	int buflen = 8192;
	int rc;

	memset(buf,0,buflen);
	memcpy(buf,pp->pr_psargs,PRARGSZ);
	for (rc = 0; rc < buflen; ++rc) {
	  if (buf[rc] == 0) {
	    if (buf[rc+1]==0)
	      break;
	    buf[rc] = ' ';
	  }
	}
	buf[buflen-1] = 0;
	for (rc = strlen(buf)-1; rc >= 0; --rc)
	  if (buf[rc] == ' ') buf[rc] = 0;
	  else break;
	printf("%s",buf);
}

void errtrap(rc,str)
int rc;
char *str;
{
	if (rc == -1) {
	  fprintf(stderr,"sps: %s lookup failed with error %s\n",str,
		  strerror(errno));
	  exit(1);
	}
}

char *pr_ttyname(ttynum)
int ttynum;
{
	static char buf[20];

	if (ttynum == -1) return "-";
	sprintf(buf,"d:%d,%d",major(ttynum),minor(ttynum));
	return buf;
}

static int pr_compare(p2v, p1v)
const void *p2v, *p1v;
{
	const struct prpsinfo *p2 = p2v, *p1 = p1v;
	int rc;

	rc = p2->pr_ppid - p1->pr_ppid;
	if (rc == 0)
	  rc = p2->pr_pid - p1->pr_pid;

/* printf("cmp: (%5d,%5d) - (%5d,%5d) -> rc = %d\n",
   p2->pr_ppid,p2->pr_pid,
   p1->pr_ppid,p1->pr_pid, rc); */

	return rc;
}

char *pr_timestr(ts,urc)
timestruc_t ts;
int urc;
{
	static char buf[30];
	char *fmt;

	if (urc != 1) {
	  strcpy(buf," **:**.***");
	  return buf;
	}
	if (ts.tv_sec > (60*99))
	  fmt = "%d:%02d.%03d"; /* over 99 minutes.. */
	else
	  fmt = " %02d:%02d.%03d";
	sprintf(buf,fmt,
		ts.tv_sec / 60, ts.tv_sec % 60,
		(ts.tv_nsec+500000)/1000000 /* millisecs */);
	return buf;
}

void print_proc(pp, prefix)
struct prpsinfo *pp;
char *prefix;
{
	int i, urc;
	timestruc_t rutim;

	if (option_u)
	  rutim = pp->pr_time;
	else {
	  rutim = pp->pr_time;
	  rutim.tv_sec  += pp->pr_ctime.tv_sec;
	  rutim.tv_nsec += pp->pr_ctime.tv_nsec;
	  if (rutim.tv_nsec > 1000000000) {
	    rutim.tv_sec += 1;
	    rutim.tv_nsec -= 1000000000;
	  }
	}

	urc = 1;

	if (option_l) {

	  printf("%8s %5d %5d %7s %08x %c %s %5ld %5ld ",
		 id_uidname(pp->pr_uid, option_n),
		 pp->pr_pid,
		 pp->pr_ppid,
		 pr_ttyname(pp->pr_lttydev, option_n),
		 pp->pr_flag,
		 pp->pr_sname,
		 pr_timestr(rutim,urc),
		 PGSIZEinKB * (long)pp->pr_size,
		 (long)(pp->pr_byrssize >> 10));

	} else {
	  printf("%8s %5d %s ",
		 id_uidname(pp->pr_uid,option_n),
		 pp->pr_pid,pr_timestr(rutim,urc));
	}

	printf("%s",prefix);
	if (option_c)
	  printf("%s",pp->pr_fname);
	else
	  print_argv(pp,pp->pr_pid);

	printf("\n");
	fflush(stdout);
}

void print_proc_tree(ppa,idx,prefix)
struct prpsinfo *ppa;
int idx;
char *prefix;
{
	int len = strlen(prefix);
	int once = 1;

	while (idx >= 0) {
	  if (ppa[idx].pr_uid >= 0)
	    print_proc(&ppa[idx], prefix);
	  ppa[idx].pr_uid = -3;

	  if (once && len >= 3 && prefix[len-2]=='-') {
	    once = 0;
	    if (prefix[len-3] == '\\')
	      prefix[len-3] = ' ';
	    prefix[len-2] = ' ';
	    prefix[len-1] = ' ';
	  }

	  /* print child processes */
	  if ((int)ppa[idx].pr_CHILD != idx) {
	    char *newpathprefix = (char*)alloca(len+4);
	    if (newpathprefix != NULL)
	      if ((int)ppa[idx].pr_SIBLING >= 0)
		sprintf(newpathprefix,"%s%s",prefix,"|--");
	      else
		sprintf(newpathprefix,"%s%s",prefix,"\\--");
	    else
	      newpathprefix="*";
	    print_proc_tree(ppa,ppa[idx].pr_CHILD, newpathprefix);
	  }
	  /* print brother processes */
	  idx = (int)ppa[idx].pr_SIBLING;
	}
}

void search_proc_tree(ppa,idx) /* uses external criteria */
struct prpsinfo *ppa;
int idx;
{
	int i;

	while (idx >= 0) {

	  if (option_uuid >= 0 &&
	      option_uuid == ppa[idx].pr_uid) {
	    i = (int)ppa[idx].pr_SIBLING;
	    ppa[idx].pr_SIBLING = (void*) -1;
	    print_proc_tree(ppa, idx, "");
	    ppa[idx].pr_uid     = -3;
	    ppa[idx].pr_SIBLING = (void*) i;
	  } else {
	    /* something else .. */
	  }

	  /* search child processes */
	  if ((int)ppa[idx].pr_CHILD != idx) {
	    search_proc_tree(ppa,ppa[idx].pr_CHILD);
	  }
	  /* search brother processes */
	  idx = (int)ppa[idx].pr_SIBLING;
	}
}

int proc_find_pid(ppa,cnt,pid)
struct prpsinfo *ppa;
int cnt, pid;
{
	int i;
	for (i = 0; i < cnt; ++i)
	  if (ppa[i].pr_pid == pid)
	    return i;
	return -1;
}

void proc_sibling_link(ppa,cnt)
struct prpsinfo *ppa;
int cnt;
{
	int pppid = -1; /* previous ppid */
	int i, j;

	for (i = 0; i < cnt; ++i) {
	  if (ppa[i].pr_ppid != pppid) {
	    pppid = ppa[i].pr_ppid;
	    j = proc_find_pid(ppa,cnt,pppid);
	    if (j >= 0)
	      ppa[j].pr_CHILD = (void*)i;
	  } else {
	    ppa[i-1].pr_SIBLING = (void*)i;
	  }
	}
}

void proc_report(procs, pused)
struct prpsinfo *procs;
int pused;
{
	int pid_idx = 0;
	if (option_pid >= 0) {
	  pid_idx = proc_find_pid(procs,pused,option_pid);
	  if (pid_idx < 0) {
	    printf("Sorry, pid=%d not around\n",option_pid);
	    return;
	  } else {
	    procs[pid_idx].pr_SIBLING = (void*)-1;
	  }
	}

	if (option_l)
	  printf("     UID   PID  PPID     TTY Flags Status  CPUtime SIZkB RSSkB CMD\n");
	else
	  printf("     UID   PID   CPU Time CMD\n");
	/* All processes are childs of the INIT,
	   and INIT is the first process.. */
	if (option_uuid >= 0)
	  search_proc_tree(procs,pid_idx);
	else
	  print_proc_tree(procs,pid_idx,"");
	printf("Sum: %d procs %d exiting %d zombies\n",
	       pused, exiting, zombies);
}



struct prpsinfo *procs = NULL;
int procs_alloc = 0;
int procs_count = 0;

int read_procinfo(dirnam)
const char *dirnam;
{
	int i, setbase, rc, cnt;
	struct prpsinfo *pps;
	DIR *dirfp = opendir(dirnam);
	struct dirent *de;

	if (!dirfp) return -1;

	chdir(dirnam);

	while ((de = readdir(dirfp)) != NULL) {
	  i = de->d_name[0];
	  if ('0' <= i && i <= '9') {
	    int fd;
	    struct prpsinfo prps;

	    if ((fd = open (de->d_name, O_RDONLY)) < 0)
	      continue;

	    if (ioctl (fd, PIOCPSINFO, &prps) < 0) {
	      (void) close (fd);
	      continue;
	    }
	    (void) close (fd);

	    if (procs_alloc == 0) {
	      procs_alloc = 8;
	      procs = malloc(sizeof(procs[0]) * procs_alloc);
	    } else if ((procs_count +1) >= procs_alloc) {
	      procs_alloc <<= 1;
	      procs = realloc(procs,sizeof(procs[0]) * procs_alloc);
	    }
	    memcpy(&procs[procs_count],&prps,sizeof(prps));
	    ++procs_count;
	  }
	}
	closedir(dirfp);


	/* Count them, and collect pointers */
	pps = procs;
	for (i = 0; i < procs_count; ++i, ++pps) {
	  if (pps->pr_zomb) ++zombies;
	  pps->pr_CHILD   = (void*)-1;
	  pps->pr_SIBLING = (void*)-1;
	}
	/* Sort them */
	qsort(procs,procs_count,sizeof(*procs),pr_compare);
	proc_sibling_link(procs,procs_count);

	return procs_count;
}

int get_uidbyuname(uname)
char *uname;
{
	struct passwd *pw = getpwnam(uname);
	if (!pw) return -1;
	return pw->pw_uid;
}

void usage()
{
	fprintf(stderr,"sps: [-clu][-p pid][-U uname]  -- ``Sort of SPS for Solaris''\n");
	fprintf(stderr,"  -c    -- show saved command name, not full argv\n");
	fprintf(stderr,"  -l    -- show lots of details about process\n");
	fprintf(stderr,"  -u    -- show accumulated user+childs time\n");
	fprintf(stderr,"  -p pid -- show all processes that are childs of this process\n");
	fprintf(stderr,"  -U uname -- all processes of this user\n");
	fprintf(stderr,"  -n    -- show devs and uids in numeric format\n");
	fprintf(stderr,"  -i    -- initialize /etc/passwd, and /dev/ -tables\n");
	exit(64);
}

int main(argc,argv)
int argc;
char *argv[];
{
	int rc;
	int c;

	while ((c = getopt(argc,argv,"?cilnup:U:")) != -1) {
	  switch (c) {
	    case 'c':
	        option_c = 1;
		break;
	    case 'i':
		option_i = 1;
		break;
	    case 'l':
		option_l = 1;
		break;
	    case 'n':
		option_n = 1;
		break;
	    case 'u':
		option_u = 0;
		break;
	    case 'p':
		option_pid = atoi(optarg);
		break;
	    case 'U':
		option_uname = optarg;
		option_uuid = get_uidbyuname(option_uname);
		break;
	    case '?':
	    default:
		usage();
		break;
	  }
	}


	if (option_i)
	  idcache_init();
	else
	  idcache_read();

	/* Read the processes */
	read_procinfo("/proc");

	printf("sps: stats: procs %d\n", procs_count);

	/* Print them */
	proc_report(procs, procs_count);

	return 0;
}
