/* Loosely derived from a simple hack posted to c.o.l
 * -Michael K. Johnson, johnsonm@sunsite.unc.edu
 *
 * Some pieces from Branko Lankester's kmem ps, copyright 1992 Branko Lankester
 *
 * Modified 1994/05/25 Michael Shields <mjshield@nyx.cs.du.edu>
 * xrealloc() added and xmalloc() cleaned up.
 * Made get_process() take a pid_t.
 * Added get_processes().  Split off do_get_process().
 */

#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 "proc/psdata.h"
#include "proc/ps.h"
#include "proc/version.h"

/* The next few routines are direct copies of those in readproc.c.  Another copy
   is needed since typeid(struct ps_proc*) != typeid(proc_t*) :-)  Somehow that
   seemed less offensive than a macro since these routines are temporary.
*/
void parse_stat(char* S, struct ps_proc* 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 parse_stat_m(char* s, struct ps_proc* P) {
    sscanf(s, "%d %d %d %d %d %d %d",
           &P->statm.size, &P->statm.resident, &P->statm.share, &P->statm.trs,
	   &P->statm.lrs, &P->statm.drs, &P->statm.dt);
}

/* note: mycpy eqivalent to nulls2spc(ret, file2str(..., ret, ...)) */
static void nulls2spc(char* str, int len) {
    int i;
    for (i=0; i < len; i++)
    	if (str[i]==0)
    	    str[i]=' ';
}

static int file2str(char *directory, char *ret, char *what, 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;
}

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;
    int cmd_len;
    
    if ((proc = opendir("/proc")) == NULL) {
	perror("opendir /proc");
	exit(1);
    }
    
    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))) {
	if(*ent->d_name < '0' || *ent->d_name > '9') continue;
	sprintf(filename, "/proc/%s", ent->d_name);
	stat(filename, &sb);
	if(!a && (sb.st_uid != uid)) continue;
	this->uid = sb.st_uid;
	cmd_len = file2str(ent->d_name, this->cmdline, "cmdline", sizeof(this->cmdline));
	if ( (file2str(ent->d_name, stat_str, "stat", sizeof(stat_str))) == -1 )
	    continue;
	parse_stat(stat_str,this);
	nulls2spc(this->cmdline, cmd_len);
	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((file2str(ent->d_name, stat_str, "statm", sizeof(stat_str))) == -1)
	    	continue;
	    parse_stat_m(stat_str,this);
	}
	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 (ph->count != 0)
    	if(!this->pid) { /* I believe 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;
}

/*
 * Fill a struct with information about the given PID.  M means to fill
 * the statm field.  The structure is freshly allocated.  If this fails,
 * it will return NULL.
 */
static struct ps_proc *do_get_process(pid_t pid, int m) {
    static char stat_str[256];
    static char filename[80];
    char *fn = filename;
    struct ps_proc *ret = NULL;
    struct stat sb;
    int cmd_len;
    
    ret = (struct ps_proc *) xcalloc(ret, sizeof(struct ps_proc));
    
    sprintf(fn, "/proc/%u", pid);
    stat(fn, &sb);
    ret->uid = sb.st_uid;
    fn += 6; /* cut "/proc/" out of fn cheaply */
    cmd_len = file2str(fn, ret->cmdline, "cmdline", sizeof(ret->cmdline));
    if((file2str(fn, stat_str, "stat", sizeof(stat_str))) == -1) {
	free(ret);
	return NULL;
    }
    parse_stat(stat_str,ret);
    nulls2spc(ret->cmdline, cmd_len);
    if (m) {
	if ((file2str(fn, stat_str, "statm", sizeof(stat_str))) == -1) {
	    free(ret);
	    return NULL;
	}
	parse_stat_m(stat_str, ret);
    }
    if (ret->state == 'Z') strcat(ret->cmd," <zombie>");
    dev_to_tty(ret->ttyc, ret->tty);
    strncpy(ret->user, user_from_uid(ret->uid), 9);
    
    return ret;
}

struct ps_proc_head *get_process(pid_t pid, int m) {
    struct ps_proc_head *ph = NULL;
    ph = xmalloc(sizeof(struct ps_proc_head));
    ph->head = do_get_process(pid, m);
    ph->count = ph->head ? 1 : 0;
    return ph;
}

/*
* Return the status of the given processes; PIDS is a zero-terminated
* list of pids.  M is same as for get_process().
*/
struct ps_proc_head *get_processes(pid_t *pids, int m) {
    struct ps_proc_head *ph;
    struct ps_proc *this_process;
    struct ps_proc **next_of_last;
    
    if (!pids)
    	/* Shouldn't happen. */
    return(NULL);
    
    ph = xmalloc(sizeof(struct ps_proc_head));
    ph->count = 0;
    next_of_last = &ph->head;
    
    while (*pids) {
        this_process = do_get_process(*pids, m);
        if (this_process) {
            *next_of_last = this_process;
            next_of_last = &this_process->next;
            ph->count++;
        }
        pids++;
    }
    
    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;
    int cmd_len;
    
    if ((proc = opendir("/proc")) == NULL) {
	perror("opendir /proc");
	exit(1);
    }    
    ph->count = 0;
    this = ph->head;
    
    while((ent = readdir(proc))) { /* Extra parens to make gcc -Wall happy... */
	/*  if(!re_exec(ent->d_name)) continue; */
	if(*ent->d_name < '0' || *ent->d_name > '9') continue;
	sprintf(filename, "/proc/%s", ent->d_name);
	stat(filename, &sb);
	if(!a && (sb.st_uid != uid)) continue;
	this->uid = sb.st_uid;
	cmd_len = file2str(ent->d_name, this->cmdline, "cmdline", sizeof(this->cmdline));
	if((file2str(ent->d_name, stat_str, "stat", sizeof(stat_str))) == -1)
	    continue;
	parse_stat(stat_str,this);
	nulls2spc(this->cmdline, cmd_len);
	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((file2str(ent->d_name, stat_str, "statm", sizeof(stat_str))) == -1)
	    	continue;
	    parse_stat_m(stat_str,this);
	}
	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 * a) {
    struct ps_proc *b;
    for(; a != NULL; a = b) {
	b = a->next;
	free(a);
    }
}

/* The next few functions are modified versions of functions from
 * various files in the kmem ps.  They are not as complete at error
 * checking, but that's life.  Thanks, Branko.  I had to change them
 * not to look at /dev/kmem, and to make my life simpler for a while,
 * error checking came out.  It's a stupid move, and I'll regret it,
 * but quite a bit of it depends on kmem reading, so I just chopped
 * it out...
*/

struct tbl_s vars, fncs;
struct psdb_hdr db_hdr;
int psdb = -1;

void *xmalloc(unsigned int size) {
    void *p;

    if (size == 0)
        ++size;
    p = malloc(size);
    if (!p) {
	fprintf(stderr, "xmalloc: malloc(%d) failed", size);
	perror(NULL);
	exit(1);
    }
    return(p);
}

void *xrealloc(void *oldp, unsigned int size) {
    void *p;

    if (size == 0)
        ++size;
    p = realloc(oldp, size);
    if (!p) {
	fprintf(stderr, "xrealloc: realloc(%d) failed", size);
	perror(NULL);
	exit(1);
    }
    return(p);
}

int open_psdb(void) {    
    if ((psdb = open(PSDATABASE, O_RDONLY)) == -1)
    	return -1;
    if (read(psdb, (char *) &db_hdr, sizeof(db_hdr)) != sizeof(db_hdr))
    	return -1;   
    if (strncmp(db_hdr.magic, procps_version, sizeof(db_hdr.magic))) {
	fprintf(stderr, "invalid psdatabase\n");
	return -1;
    }    
    return(0);
}

void close_psdb(void) {
    if (psdb != -1)
    	close(psdb);
    psdb = -1;
}

int read_tbl(struct dbtbl_s *dbtbl, struct tbl_s *tbl) {
    lseek(psdb, dbtbl->off, SEEK_SET);
    tbl->tbl = (struct sym_s *) xmalloc(dbtbl->size);
    if (read(psdb, (char *) tbl->tbl, dbtbl->size) != dbtbl->size) {
	perror(PSDATABASE);
	exit(1);
    }
    tbl->nsym = dbtbl->nsym;
    tbl->strings = (char *) (tbl->tbl + tbl->nsym);
    return(0);
}

/*
 * misc stuff needed
 */

char * find_func(unsigned long address)	{
    int n;
    struct sym_s *p;
    char *s;
    
    if (fncs.tbl == NULL)
    	read_tbl(&db_hdr.fncs, &fncs);

    p = fncs.tbl;
    n = fncs.nsym;
    while (n) {
	int i = n / 2;
	if (p[i].addr < address) {
	    p = &p[i+1];
	    if (p->addr > address) {
		--p;
		break;
	    }
	    --n;
	}
	n /= 2;
    }
    s = p->name + fncs.strings;
    return(*s == '_' ? s+1 : s);
}

char * wchan(unsigned int address) {
    static char zero = 0;
    char *p;
    
    if (address) {
	p = find_func(address);
	if (strncmp(p, "sys_", 4) == 0)
	    p += 4;
	while (*p == '_' && *p)
	    ++p;
    } else { /* 0 address means not in kernel space */
	p = &zero;
    }
    return(p);
}
