/*
 *  ``Sort of PS'' -- shows process hierarchy dependencies
 */

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <pwd.h>
#ifdef __SVR4 /* SunOS 5.x */
#include <sys/sysmacros.h> /* major(), minor() */
#endif

#ifndef SPS_CACHE
#define SPS_CACHE "/tmp/.spsdata"
#endif

struct spscache {
	int	uidcnt;
	int	devcnt;
} cache_head;

struct uidcache {
	int	uid;
	char	uname[12];
};

struct devcache {
	dev_t	dev;
	char	dname[12];
};

/* For scan-in we build an open hash table in addition to
   the linear table.. */
#define HEADCNT 256
struct heads {
	int headcnt;
	int headspc;
	void **tbl;
};
static struct heads HEADS[HEADCNT] = { { 0, 0, NULL } };

struct uidcache *ucache = NULL;
int uidcnt = 0;
int uidspc = 0;

struct devcache *dcache = NULL;
int devcnt = 0;
int devspc = 0;

int  cache_ok = 0;

static int ucache_compare(p1,p2)
const void *p1, *p2;
{
	const struct uidcache *u1 = p1;
	const struct uidcache *u2 = p2;

	return (u1->uid - u2->uid);
}

static void heads_insert(hash,dp)
int hash;
void *dp;
{
	int i = hash % HEADCNT;

	if (HEADS[i].tbl == NULL) {
	  HEADS[i].headspc = 8;
	  HEADS[i].headcnt = 0;
	  HEADS[i].tbl = malloc(sizeof(void*) * 8);
	} else if (HEADS[i].headcnt >= HEADS[i].headspc) {
	  HEADS[i].headspc <<= 1; /* Double it */
	  HEADS[i].tbl = realloc(HEADS[i].tbl,
				 sizeof(void*) * HEADS[i].headspc);
	}
	/* We have room, insert it.. */
	HEADS[i].tbl[HEADS[i].headcnt] = dp;
	HEADS[i].headcnt += 1;
}

static void heads_clear()
{
	int i;
	for (i = 0; i < HEADCNT; ++i)
	  HEADS[i].headcnt = 0;
}

static int _uid_duplicate(uid)
int uid;
{
	int i, j;
	j = uid % HEADCNT;
	for (i = 0; i < HEADS[j].headcnt; ++i)
	  if (((struct uidcache*)HEADS[j].tbl[i])->uid == uid)
	    return 1;
	return 0;
}

int scan_passwd()
{
	struct passwd *pw;
	int i;

	heads_clear();

	setpwent();

	while ((pw = getpwent()) != NULL) {
	  if (_uid_duplicate(pw->pw_uid))
	    continue;
	  if (uidcnt >= uidspc) {
	    /* Enlarge the space */
	    if (uidcnt == 0) {
	      uidspc = 256;
	      ucache = malloc(sizeof(struct uidcache) * uidspc);
	    } else {
	      uidspc <<= 1;
	      ucache = realloc(ucache,sizeof(struct uidcache) * uidspc);
	      if (!ucache) abort();
	      heads_clear();
	      for (i = 0; i < uidcnt; ++i)
		heads_insert((int)ucache[i].uid, (void*)&ucache[i]);
	    }
	  }
	  heads_insert((int)pw->pw_uid,(void*)&ucache[uidcnt]);
	  ucache[uidcnt].uid = pw->pw_uid;
	  strncpy(ucache[uidcnt].uname,pw->pw_name,12);
	  ucache[uidcnt].uname[11] = 0;
	  ++uidcnt;
	}
	/* All collected, sort them.. */
	qsort(ucache,uidcnt,sizeof(*ucache),ucache_compare);
	cache_head.uidcnt = uidcnt;

	endpwent();

	return uidcnt;
}

int _insert_dev(dev,path)
int dev;
char *path;
{
	int buflen, i;

	path = strchr(path+1,'/');
	if (!path) abort(); /* ???? Not "/dev/..." ???? */
	++path;
	buflen = strlen(path);
	
	if (buflen > 11) /* we skip the head, if too long.. */
	  buflen -= 11;
	else
	  buflen = 0;
	if (devcnt >= devspc) {
	  /* Enlarge the space */
	  if (devcnt == 0) {
	    devspc = 256;
	    dcache = malloc(sizeof(struct devcache) * devspc);
	  } else {
	    devspc <<= 1;
	    dcache = realloc(dcache,sizeof(struct devcache) * devspc);
	    heads_clear();
	    for (i = 0; i < devcnt; ++i)
	      heads_insert((int)dcache[i].dev, (void*)&dcache[i]);
	  }
	}
	heads_insert(dev,(void*)&dcache[i]);
	dcache[devcnt].dev = dev;
	strncpy(dcache[devcnt].dname,path+buflen,12);
	dcache[devcnt].dname[11] = 0;
	++devcnt;
	return 1;
}

int _scan_dev(dirpath)
char *dirpath;
{
	DIR *dirp;
	struct dirent *dp;
	struct stat st;
	int cnt = 0;

	dirp = opendir(dirpath);
	if (!dirp) return 0;
	while ((dp = readdir(dirp)) != NULL) {
	  char buf[80];
	  sprintf(buf,"%s/%s",dirpath,dp->d_name);
	  if (stat(buf,&st) != 0)
	    continue;
	  if (S_ISDIR(st.st_mode)) {
	    if (dp->d_name[0] == '.' &&
		(dp->d_name[1] == 0 || (dp->d_name[1] == '.' &&
					dp->d_name[2] == 0)))
	      continue; /* "." and ".." */
	    cnt += _scan_dev(buf);
	    continue;
	  }
	  if (!S_ISCHR(st.st_mode))
	    continue;
	  cnt += _insert_dev(st.st_rdev,buf);
	}
	closedir(dirp);
	return cnt;
}

static int dcache_compare(p1,p2)
const void *p1, *p2;
{
	const struct devcache *d1 = p1;
	const struct devcache *d2 = p2;

	return (d1->dev - d2->dev);
}

int scan_dev()
{
	heads_clear();
	cache_head.devcnt =  _scan_dev("/dev");
	if (cache_head.devcnt > 1) {
	  qsort(dcache,cache_head.devcnt,sizeof(*dcache),dcache_compare);
	}
}


int idcache_init() /* scan and write! */
{
	FILE *cf;

	umask(0333);
	unlink(SPS_CACHE);
	cf = fopen(SPS_CACHE,"w");
	scan_dev();
	scan_passwd();
	if (ucache != NULL && dcache != NULL)
	  cache_ok = 1;
	if (!cf) return -1;
	if (!cache_ok ||
	    fwrite(&cache_head, sizeof(cache_head), 1, cf) != 1 ||
	    fwrite((void*)ucache,sizeof(*ucache),uidcnt, cf) != uidcnt ||
	    fwrite((void*)dcache,sizeof(*dcache),devcnt, cf) != devcnt ||
	    fflush(cf) != 0 || ferror(cf)) {
	  unlink(SPS_CACHE);
	}
	fclose(cf);
}

int idcache_read() /* read from file! */
{
	FILE *cf = fopen(SPS_CACHE,"r");

	cache_ok = 0;
	if (!cf)
	  return -1;
	if (fread(&cache_head,sizeof(cache_head),1,cf) != 1)
	  return -2;
	ucache = malloc(sizeof(*ucache) * cache_head.uidcnt);
	if (!ucache) return -3;
	dcache = malloc(sizeof(*dcache) * cache_head.devcnt);
	if (!dcache) return -4;
	if (fread((void*)ucache,sizeof(*ucache),cache_head.uidcnt,cf) !=
	    cache_head.uidcnt)
	  return -5;
	if (fread((void*)dcache,sizeof(*dcache),cache_head.devcnt,cf) !=
	    cache_head.devcnt)
	  return -6;
	cache_ok = 1;
	return 0;
}

char *id_ttyname(ttynum,numonly)
int ttynum, numonly;
{
	int lo, hi, mid;
	static char buf[20];

	if (ttynum == -1)
	  strcpy(buf,"-");
	else
	  sprintf(buf,"%d/%d",major(ttynum),minor(ttynum));
	if (!cache_ok || numonly)
	  return buf;

	lo = 0;
	hi = cache_head.devcnt -1;
	while (lo <= hi) {
	  mid = (lo + hi) >> 1;
	  if (dcache[mid].dev == ttynum)
	    return dcache[mid].dname;
	  if (dcache[mid].dev > ttynum) {
	    hi = mid-1;
	  } else {
	    lo = mid+1;
	  }
	}
	return buf;
}

char *id_uidname(uid, numonly)
int uid, numonly;
{
	int lo, hi, mid;
	static char buf[20];

	sprintf(buf,"%d",uid);
	if (!cache_ok || numonly)
	  return buf;

	lo = 0;
	hi = cache_head.uidcnt -1;
	while (lo <= hi) {
	  mid = (lo + hi) >> 1;
	  if (ucache[mid].uid == uid)
	    return ucache[mid].uname;
	  if (ucache[mid].uid > uid) {
	    hi = mid-1;
	  } else {
	    lo = mid+1;
	  }
	}
	return buf;
}
