/*
 * New Interface to Process Table -- PROCTAB Stream (a la directory streams)
 * Charles L. Blake.
 */
#include "proc/version.h"
#include "proc/readproc.h"
#include "proc/ps.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <asm/string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/dir.h>
#include <sys/types.h>
#include <sys/stat.h>

extern int linux_version;

void *xcalloc(void *pointer, int size);

/* some file local utility functions */

PROCTAB* openproc(uid_t uid, dev_t tty, int flags, ... /* pid_t plist[], int len */) {
    PROCTAB* ret;
    ret = malloc(sizeof(PROCTAB));
    if (!(ret->procfs = opendir("/proc")))
    	return (PROCTAB*) 0;
    ret->uid = uid;
    ret->tty = tty;
    ret->flags = flags;
    if (flags & PROC_READPIDLIST) {
	va_list ap;
	va_start(ap, flags);			/*  Initialize the argument list.  */
    	ret->PidList = va_arg(ap, pid_t*);
	va_end  (ap);				/*  Clean up.  */
    } else
    	ret->PidList = 0;
    return ret;
}

/* terminate a process table query */
void closeproc(PROCTAB* PT) {
    if (PT->procfs) closedir(PT->procfs);
    if (PT)         free(PT);
}

/* readproc:
 * return a pointer to a proc_t filled with requested info about the next
 * process available matching the restriction set.  If no more such processes
 * are available, return a null pointer (boolean false).  Use the passed buffer
 * instead of allocating space if it is non-NULL.
 */
proc_t* readproc(PROCTAB* PT, proc_t* rbuf) {
    static struct direct *ent;		/* dirent handle */
    static struct stat sb;		/* stat buffer */
    static char path[80], sbuf[4096];	/* string buffers */
    int allocated = 0, matched = 0;	/* flags */
    pid_t* tmp;
    proc_t *ret = 0;
#   define Do(x) (PT->flags & PROC_ ## x)

    /* loop until a proc matching restrictions is found or no more processes */
    while ((ent = readdir(PT->procfs))) {
    	if (*ent->d_name < '0' || *ent->d_name > '9')	/* find a process subdir dirent */
	    continue;
	sprintf(path, "/proc/%s", ent->d_name); /* stat() subdir to set effective user ID */
	stat(path, &sb);
	if (Do(READPIDLIST)) {			/* check for inclusion in the PidList */
	    static pid_t p;
	    matched = 0;
	    p = atoi(ent->d_name);
	    for (tmp = PT->PidList; *tmp != p && *tmp; tmp++) /* */ ;
	    if (*tmp != p)
		continue;	/* skip: matching pids + no match */
	    matched = 1;
	}
	if (!matched && !(Do(READALL) || PT->tty) && sb.st_uid != PT->uid)
    	    continue;		/* skip: not matching all users/a ctty and uid mismatch */
	if (!allocated)				/* set up space for return buffer */
	    if (rbuf) ret = rbuf;		/* use passed return buf for storage */
	    else {				/* allocate mem for return value */
		ret = (proc_t*) xcalloc(ret, sizeof(proc_t));
		allocated = 1;			/* either way remember we have room now */
	    }					/* for future passes through this loop */
	ret->uid = sb.st_uid;			/* should have a way to get reuid too */
	if ((file2str(ent->d_name, "stat", sbuf, sizeof sbuf)) == -1)
    	    continue;		/* error: reading /proc/#/stat */
	stat2proc(sbuf, ret);			/* parse /proc/#/stat, check restrictions */
	if (!matched && PT->tty && PT->tty != ret->tty)
	    continue;		/* skip: matching ttys + wrong tty */
	if (PT->tty && PT->tty == ret-> tty)	/* matching ttys+match => forced accept */
	    matched = 1;
	if (!matched && !Do(READNOTTY) && ret->tty == -1)
	    continue;		/* skip: no ctty + not reading all */
	if (!matched && Do(READRUN) && !(ret->state == 'R' || ret->state != 'D'))
	    continue;		/* skip: matching runable + not runable */
	if (Do(FILLMEM)) {			/* read, parse /proc/#/statm */
	    if ((file2str(ent->d_name, "statm", sbuf, sizeof sbuf)) == -1 )
	    	continue;	/* error: reading /proc/#/statm */
	    statm2proc(sbuf, ret);
	}
	dev_to_tty(ret->ttyc, ret->tty);	/* perform some useful conversions */	
	strncpy(ret->user, user_from_uid(ret->uid), 9);
	if (Do(FILLCMDLINE))			/* read, parse /proc/#/cmdline */
	    ret->cmdline = file2strvec(ent->d_name, "cmdline");
	if (Do(FILLENV))			/* read, parse /proc/#/environ */
	    ret->environ = file2strvec(ent->d_name, "environ");
	if (ret->state == 'Z')			/* post-process cmd name for zombies */
    	    strcat(ret->cmd," <zombie>");
	return ret;		/* Note that we return from inside the while loop */
    }
    return (proc_t*) 0;
}

/* deallocate the space allocated by readproc if the passed rbuf was NULL */
void freeproc(proc_t* p) {
    if (!p)	/* in case p is NULL */
	return;
    /* ptrs are after strings to avoid copying memory when building them. */
    /* so free is called on the address of the address of strvec[0]. */
    if (p->cmdline)
	free((void*)*p->cmdline);
    if (p->environ)
	free((void*)*p->environ);
    free(p);
}

/* stat2proc() makes sure it can handle arbitrary executable file basenames
   for `cmd', i.e. those with embedded whitespace or embedded ')'s.  Such names
   confuse %s (see scanf(3)), so the string is split and %39c is used instead.
*/
void stat2proc(char* S, proc_t* P) {
    char* tmp = strrchr(S, ')');	/* split S into "PID (cmd" and "<rest>" */
    *tmp = '\0';			/* replace trailing ')' with NUL */
    /* parse these two strings separately, skipping the leading "(". */
    memset(P->cmd, 0, sizeof P->cmd);	/* need clear even though *P xcalloc'd ?! */
    sscanf(S, "%d (%39c", &P->pid, P->cmd);
    sscanf(tmp + 2,			/* skip space after ')' too */
	   "%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",
	   &P->state, &P->ppid, &P->pgrp, &P->session, &P->tty, &P->tpgid, &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->timeout,
	   &P->it_real_value, &P->start_time, &P->vsize, &P->rss, &P->rss_rlim,
	   &P->start_code, &P->end_code, &P->start_stack, &P->kstk_esp, &P->kstk_eip,
	   &P->signal, &P->blocked, &P->sigignore, &P->sigcatch, &P->wchan);
    if (P->tty == 0)
	P->tty = -1;  /* this was the old no tty value other places need updates */
    if (linux_version_code < LINUX_VERSION(1,3,39)) {
	P->priority = 2*15 - P->priority;	/* map old meanings into new ones */
	P->nice = 15 - P->nice;
    }
    if (linux_version_code < LINUX_VERSION(1,1,30) && P->tty != -1)
	P->tty = 4*0x100 + P->tty;		/* when tty was not full devno */
}

void statm2proc(char* s, proc_t* P) {
    sscanf(s, "%d %d %d %d %d %d %d",
	   &P->size, &P->resident, &P->share,
	   &P->trs, &P->lrs, &P->drs, &P->dt);
}

void nulls2sep(char* str, int len, char sep) {
    int i;
    for (i = 0; i < len; i++)
    	if (str[i] == 0)
    	    str[i] = sep;
}

int file2str(char *directory, char *what, char *ret, int cap) {
    static char filename[80];
    int fd, num_read;
    
    sprintf(filename, "/proc/%s/%s", directory, what);
    if ( (fd       = open(filename, O_RDONLY, 0)) == -1 ) return -1;
    if ( (num_read = read(fd, ret, cap - 1))      == -1 ) return -1;
    ret[num_read] = 0;
    close(fd);
    return num_read;
}

#define N 2048	/* size of chunks to read at a time */

char** file2strvec(char* directory, char* what) {
    char buf[N], *p, *rbuf = 0, *endbuf, **q, **ret;
    int fd, tot = 0, n, c, end_of_file = 0;
    
    sprintf(buf, "/proc/%s/%s", directory, what);
    if ( (fd = open(buf, O_RDONLY, 0) ) == -1 ) return (char**)0;

    /* read whole file into a memory buffer, allocating as we go */
    
    while ((n = read(fd, buf, N - 1)) > 0) {
	if (n < N - 1)
	    end_of_file = 1;
	if (n < 0) {
	    if (rbuf)
		free(rbuf);
	    return (char**)0;	/* read error */
	}
	if (end_of_file && buf[n-1])		/* last read char not null */
	    buf[n++] = '\0';			/* so append null-terminator */
	rbuf = realloc(rbuf, tot + n);		/* allocate more memory */
	memcpy(rbuf + tot, buf, n);		/* copy buffer into it */
	tot += n;				/* increment total byte ctr */
	if (end_of_file)
	    break;
    }
    close(fd);
    if (n <= 0 && !end_of_file) {
	if (rbuf) free(rbuf);
	return (char**)0;	/* read error */
    }
    endbuf = rbuf + tot;			/* count space for pointers */
    for (c = 0, p = rbuf; p < endbuf; p++)
    	if (!*p)
	    c += sizeof(char*);
    c += sizeof(char*);				/* one extra for null terminator */
    
    rbuf = realloc(rbuf, tot + c);		/* make room for ptrs AT THE END */
    endbuf = rbuf + tot;			/* address just past str buffers  */
    q = ret = (char**) endbuf;			/* ==> free(*ret) to deallocate */
    *q++ = p = rbuf;				/* point ptrs to the strings */
    endbuf--;					/* do not traverse final NUL */
    while (++p < endbuf) 
    	if (!*p)				/* NUL char implies that */
	    *q++ = p+1;				/* next string -> next char */

    *q = 0;					/* null ptr list terminator */
    return ret;
}
#undef N
