/* Hacked into bit-o-snap by ZMAN, no original work, just editing         */
/* This was done so I could get a standalone process snapshot module to   */
/* link into other code.						  */
/*
 * Loosely derived from a simple hack posted to c.o.l
 * -Michael K. Johnson, johnsonm@stolaf.edu
 *
 * Some pieces from Branko Lankester's kmem ps, copyright 1992 Branko Lankester
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/dir.h>
#include <regex.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <pwd.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include "psdata.h"
#include "ps.h"


int mycpy(char *directory, char *ret, char *what, int cap, int nulls)
{
  static char filename[80];
  int fd;
  int nr_read, i;

  sprintf(filename, "/proc/%s/%s", directory, what);
  fd = open(filename, O_RDONLY, 0);
  if (fd != -1) {
    nr_read = read(fd, ret, cap-1);
    ret[nr_read]=0;
    if (nulls)
      for (i=0; i < nr_read; i++)
	if (ret[i]==0) ret[i]=' ';
  } else return 0;
  close(fd);
  return 1;
}



struct ps_proc_head *take_snapshot(char a, char u, char x, char m, char r,
				   uid_t uid, int ctty)
{
  DIR *proc;
  static struct direct *ent;
  static char filename[80];
  static char stat_str[4096];
  struct ps_proc_head *ph = NULL;
  struct ps_proc *this = NULL, *that = NULL;
  struct stat sb;

  proc = opendir("/proc");
  re_comp("^[0-9]*$");

  ph = (struct ps_proc_head *) xcalloc(ph, sizeof(struct ps_proc_head));
  /* initializes ph->head and ph->count to zero ;-) */
  ph->head = (struct ps_proc *) xcalloc(ph->head, sizeof(struct ps_proc));
  this = ph->head;

  while((ent = readdir(proc))) { /* Extra parens to make gcc -Wall happy... */
    if(!re_exec(ent->d_name)) continue;
    sprintf(filename, "/proc/%s", ent->d_name);
    stat(filename, &sb);
    if(!a && (sb.st_uid != uid)) continue;
    this->uid = sb.st_uid;
    mycpy(ent->d_name, this->cmdline, "cmdline", sizeof(this->cmdline), 1);
    if(!mycpy(ent->d_name, stat_str, "stat", sizeof(stat_str), 0)) continue;

    sscanf(stat_str, "%d %s %c %d %d %d %d %d %u %u \
%u %u %u %d %d %d %d %d %d %u %u %d %u %u %u %u %u %u %u %u %d \
%d %d %d %u",
	   &this->pid, this->cmd, &this->state, &this->ppid,
	   &this->pgrp, &this->session, &this->tty, &this->tpgid,
	   &this->flags, &this->min_flt, &this->cmin_flt,
	   &this->maj_flt, &this->cmaj_flt,
	   &this->utime, &this->stime, &this->cutime, &this->cstime,
	   &this->counter, &this->priority, &this->timeout,
	   &this->it_real_value, &this->start_time,
	   &this->vsize, &this->rss, &this->rss_rlim,
	   &this->start_code, &this->end_code, &this->start_stack,
	   &this->kstk_esp, &this->kstk_eip,
	   &this->signal, &this->blocked, &this->sigignore, &this->sigcatch,
	   &this->wchan);
    if ((ctty && (ctty != this->tty))
	|| (r && this->state != 'R' && this->state != 'D')
	|| (!x && (this->tty == -1))) 
 	       {
      		this->pid = 0;
      		continue;
    	       }
    /* 0 normally passed, which is never the value given as the tty from the
       proc filesystem, so this only happens if a specific tty was passed. */
    if(m) {
      if(!mycpy(ent->d_name, stat_str, "statm", sizeof(stat_str), 0)) continue;
      sscanf(stat_str, "%d %d %d %d %d %d %d",
	     &this->statm.size, &this->statm.resident,
	     &this->statm.share, &this->statm.trs,
	     &this->statm.lrs, &this->statm.drs,
	     &this->statm.dt);
    }
    if (this->state == 'Z') strcat(this->cmd," <zombie>");
    dev_to_tty(this->ttyc, this->tty);
    if(u) strncpy(this->user, user_from_uid(this->uid), 9);

    /* update the linked list and increase the count */
    if(this->pid) {
      that = this;
      this->next = (struct ps_proc *) xcalloc(this->next,
					      sizeof(struct ps_proc));
      this = this->next;
      ph->count++;
    }
  } /* end of the while loop */
  closedir(proc);
  if(!this->pid) { /* I beleive this will always be true, because it will try
		      one more readdir, and there will be a hanging entry... 
		      But I make it conditional to be safe */
    that->next = (struct ps_proc *) NULL;
    free (this);
  } else this->next = (struct ps_proc *) NULL;
  return ph;
}



struct ps_proc_head *refresh_snapshot(struct ps_proc_head *ph,
				      char a, char u, char x, char m, char r,
				      uid_t uid, int ctty)
{
  DIR *proc;
  static struct direct *ent;
  static char filename[80];
  static char stat_str[4096];
  struct ps_proc *this = NULL, *that = NULL;
  struct stat sb;

  proc = opendir("/proc");
  re_comp("^[0-9]*$");

  ph->count = 0;
  this = ph->head;

  while((ent = readdir(proc))) { /* Extra parens to make gcc -Wall happy... */
    if(!re_exec(ent->d_name)) continue;
    sprintf(filename, "/proc/%s", ent->d_name);
    stat(filename, &sb);
    if(!a && (sb.st_uid != uid)) continue;
    this->uid = sb.st_uid;
    mycpy(ent->d_name, this->cmdline, "cmdline", sizeof(this->cmdline), 1);
    if(!mycpy(ent->d_name, stat_str, "stat", sizeof(stat_str), 0)) continue;

    sscanf(stat_str, "%d %s %c %d %d %d %d %d %u %u \
%u %u %u %d %d %d %d %d %d %u %u %d %u %u %u %u %u %u %u %u %d \
%d %d %d %u",
	   &this->pid, this->cmd, &this->state, &this->ppid,
	   &this->pgrp, &this->session, &this->tty, &this->tpgid,
	   &this->flags, &this->min_flt, &this->cmin_flt,
	   &this->maj_flt, &this->cmaj_flt,
	   &this->utime, &this->stime, &this->cutime, &this->cstime,
	   &this->counter, &this->priority, &this->timeout,
	   &this->it_real_value, &this->start_time,
	   &this->vsize, &this->rss, &this->rss_rlim,
	   &this->start_code, &this->end_code, &this->start_stack,
	   &this->kstk_esp, &this->kstk_eip,
	   &this->signal, &this->blocked, &this->sigignore, &this->sigcatch,
	   &this->wchan);
    if ((ctty && (ctty != this->tty))
	|| (r && this->state != 'R' && this->state != 'D')
	|| (!x && (this->tty == -1))) {
      this->pid = 0;
      continue;
    }
    /* 0 normally passed, which is never the value given as the tty from the
       proc filesystem, so this only happens if a specific tty was passed. */
    if(m) {
      if(!mycpy(ent->d_name, stat_str, "statm", sizeof(stat_str), 0)) continue;
      sscanf(stat_str, "%d %d %d %d %d %d %d",
	     &this->statm.size, &this->statm.resident,
	     &this->statm.share, &this->statm.trs,
	     &this->statm.lrs, &this->statm.drs,
	     &this->statm.dt);
    }
    if (this->state == 'Z') strcat(this->cmd," <zombie>");
    dev_to_tty(this->ttyc, this->tty);
    if(u) strncpy(this->user, user_from_uid(this->uid), 9);

    /* update the linked list and increase the count */
    if(this->pid) {
      that = this;
      if(!this->next)
	this->next = (struct ps_proc *) xcalloc(this->next,
						sizeof(struct ps_proc));
      this = this->next;
      this->pid = 0;
      ph->count++;
    }
  } /* end of the while loop */
  closedir(proc);
  if(!this->pid) { /* if the last slot was not used */
    if(that->next)
      free_psproc(that->next);
    that->next = (struct ps_proc *) NULL;
  } else {
    if (this->next)
      free_psproc(this->next);
    this->next = (struct ps_proc *) NULL;
  }
  return ph;
}




void free_psproc(struct ps_proc * this) 
{
  	struct ps_proc *that;

  	for(; this != NULL; this = that) 
  	       {
    		that = this->next;
    		free(this);
  	       }
}



/***********************************************************************\
*	Copyright (C) 1992 by Michael K. Johnson, johnsonm@stolaf.edu	*
*									*
*	This function is placed under the conditions of the GNU public	*
*	license, version 2, or any later version.  See file COPYING	*
*	for information on distribution conditions.			*
\***********************************************************************/
void *xcalloc(void *pointer, int size) 
{
  	void * ret;

  	if (pointer) 
  		free(pointer);
  	if (!(ret = calloc(1, size))) 
  	       {
    		fprintf(stderr, "xcalloc: allocation error, size = %d\n", size);
    		exit(1);
  	       } 
  	else 
  	       {
    		return ret;
  	       }
}


/* modified by Michael K. Johnson, johnsonm@stolaf.edu for YAPPS
 * 
 * I am changing this significantly to provide a more reasonable
 * output.  This will make this w incompatible with utmp's ut_id
 * Tough.  utmp is a mess anyway.  Until utmp gets saner, w may
 * grow its own devname stuff, if I have time.
 *
 * ttynames:
 * 	vc00:  v01 v02...	virtual consoles
 *	tty00: s00 s01...	serial lines
 *	ttyp0: p00 p01...	pty's
 */

static char *ttgrp = "    StuvPQRWpqrs";
static char *ttsub = "0123456789abcdef";

void dev_to_tty(char *tty, int dev)
{
  	if (dev == -1)
    		strcpy(tty," ? ");
  	else if (dev == 0)
    		strcpy(tty,"con");
  	else 
  		if (dev < 64) 
  	       	       {
    			sprintf(tty, "v%02d", dev);
  	               } 
  		else 
  		       {
    			if (dev < 128) 
    			       {
      				sprintf(tty, "s%02d", (dev - 64));
    			       } 
    			else 
    			       {
      				tty[0] = 'p';
      				tty[1] = ttgrp[(dev >> 4) & 017];
      				tty[2] = ttsub[dev & 017];
    			       } 
  		       }
  	tty[3] = 0;
}



#define	HASHSIZE	16			/* power of 2 */
#define	HASH(x)		((x) & (HASHSIZE - 1))


static struct pwbuf {
    			int uid;
    			char name[12];
    			struct pwbuf *next;
					} *pwhash[HASHSIZE];

char *user_from_uid(int uid)
{
    	struct pwbuf **p;
    	struct passwd *pw;

    	p = &pwhash[HASH(uid)];
    	while (*p) 
    	       {
		if ((*p)->uid == uid)
	    		return((*p)->name);
		p = &(*p)->next;
   	       }
    	*p = (struct pwbuf *) malloc(sizeof(struct pwbuf));
    	(*p)->uid = uid;
    	if ((pw = getpwuid(uid)) == NULL)
		sprintf((*p)->name, "#%d", uid);
    	else
		sprintf((*p)->name, "%-.8s", pw->pw_name);
    	(*p)->next = NULL;
    	return((*p)->name);
}
