/*
 * psdata.c	- get info from psdatabase
 *
 * Copyright (c) 1992 Branko Lankester
 *
 */
#define __KERNEL__
#include <sys/types.h>
#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pwd.h>
#include <errno.h>
#include <ctype.h>
#include <time.h>
#include <sys/ioctl.h>
#include <linux/sched.h>
#include <linux/ptrace.h>
#include <fcntl.h>
#include <sys/utsname.h>
#include "ps.h"
#include "psdata.h"

/*
 * define NO_UPDATE if you want to use only the psdatabase
 * for symbol info, code in update_db.c is then not needed.
 * Open psdatabase with open_psdb().
 *
 * Without NO_UPDATE defined the program can specify a different 
 * file for namelist, and it can update/create the psdatabase.
 * Open the systemfile with open_sys(sysfile, update), if sysfile
 * is NULL the psdatabase will still be used, if it exists.
 */

static read_tbl(struct dbtbl_s *dbtbl, struct tbl_s *tbl);

struct tbl_s vars, fncs, ttys;

int psdb = -1;
struct psdb_hdr db_hdr;

char *swappath[8] = {
    "/dev/swap", "/dev/swap1", "/dev/swap2", "/dev/swap3",
    "/dev/swap4", "/dev/swap5", "/dev/swap6", "/dev/swap7",
};

char *kmem_path = "/dev/kmem";
int kmemfd = -1;

#ifdef DEBUG
int Debug = 1;
#endif

int
open_psdb(void)
{
#ifdef VERSION_CHECK
    struct utsname uts;
#endif

    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, PS_MAGIC, sizeof(db_hdr.magic))) {
	fprintf(stderr, "invalid psdatabase\n");
	return -1;
    }
    swappath[0] = db_hdr.swap_path;
    
    if (Debug > 1) {
	fprintf(stderr, "psdatabase is from %s  %8.8s %-8.8s\n",
		db_hdr.sys_path, db_hdr.uts_release, db_hdr.uts_version);
	fprintf(stderr, "swap file: %s\n", db_hdr.swap_path);
	fprintf(stderr, "%d bss/data symbols\n%d text symbols\n%d ttys\n",
		db_hdr.vars.nsym, db_hdr.fncs.nsym, db_hdr.ttys.nsym);
    }
#ifdef VERSION_CHECK
    kmemread(&uts, k_addr("_system_utsname"), sizeof uts);
    if (strncmp(uts.release, db_hdr.uts_release, sizeof db_hdr.uts_release) ||
	strncmp(uts.version, db_hdr.uts_version, sizeof db_hdr.uts_version)) {
	fprintf(stderr, "psdatabase is out of date, run 'ps -U'\n\n\
NOTE: You may have to recompile \"ps\" for this version of Linux\n\n");
	return -1;
    }
#endif

    if (kmemfd == -1 && (kmemfd = open(kmem_path, O_RDONLY)) == -1) {
	perror(kmem_path);
	exit(1);
    }
    return(0);
}

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

/*
 * get address of data symbol
 */
unsigned long
k_addr(char *sym)
{
    struct sym_s *p, key;

    if (vars.tbl == NULL)
#ifndef NO_UPDATE
	if (psdb == -1) make_vartbl(); else
#endif
	    read_tbl(&db_hdr.vars, &vars);

    key.name = sym - vars.strings;
    p = (struct sym_s *) bsearch(&key, vars.tbl, vars.nsym,
				 sizeof(struct sym_s), varcmp);
    if (Debug && p == NULL)
	fprintf(stderr, "symbol '%s' not found\n", sym);
    return(p ? p->addr : -1);
}

/*
 * Find the name of a function on the value of a saved instruction ptr.
 * Don't use bsearch because "fuzzy" matches are OK.
 */
char *
find_func(unsigned long eip)
{
    int n;
    struct sym_s *p;
    char *s;

    if (fncs.tbl == NULL)
#ifndef NO_UPDATE
	if (psdb == -1) make_fnctbl(); else
#endif
	    read_tbl(&db_hdr.fncs, &fncs);

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

static int cur;

char *
next_func(unsigned long *eip)
{
    char *s;

    if (fncs.tbl == NULL)
#ifndef NO_UPDATE
	if (psdb == -1) make_fnctbl(); else
#endif
	    read_tbl(&db_hdr.fncs, &fncs);

    if (cur == fncs.nsym) {
	if (eip)
	    *eip = 0;
	return NULL;
    }
    if (eip)
	*eip = fncs.tbl[cur].addr;
    s = fncs.tbl[cur].name + fncs.strings;
    ++cur;
    return s;
}

char *
first_func(unsigned long *eip)
{
    cur = 0;
    return next_func(eip);
}
    
/*
 * Find the tty name for a character device.
 */
char *
find_tty(dev_t rdev)
{
    struct sym_s *p, key;

    if (ttys.tbl == NULL)
#ifndef NO_UPDATE
	if (psdb == -1) make_ttytbl(); else
#endif
	    read_tbl(&db_hdr.ttys, &ttys);

    key.addr = rdev;
    p = (struct sym_s *) bsearch(&key, ttys.tbl, ttys.nsym,
				 sizeof(struct sym_s), addrcmp);
    if (Debug && p == NULL)
	fprintf(stderr, "device '%#x' not found\n", rdev);
    return p ? ttys.strings + p->name : NULL;
}

static 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);
    if (Debug == 3) {
	fprintf(stderr, "read table from psdatabase\n");
	dump_tbl(tbl);
    }
    return(0);
}

/*
 * vars are sorted on name
 */
int
varcmp(const void *p1, const void *p2)
{
    struct sym_s *s1 = (struct sym_s *) p1;
    struct sym_s *s2 = (struct sym_s *) p2;

    return strcmp(vars.strings + s1->name, vars.strings + s2->name);
}

/*
 * funcs and ttys are sorted on address/rdev
 */
int
addrcmp(const void *p1, const void *p2)
{
    struct sym_s *s1 = (struct sym_s *) p1;
    struct sym_s *s2 = (struct sym_s *) p2;

    return s1->addr < s2->addr ? -1 : s1->addr > s2->addr ? 1 : 0;
}

void
dump_tbl(struct tbl_s *tbl)
{
    int i;
    if (tbl == &vars)
	fprintf(stderr, "vars table:\n");
    else if (tbl == &fncs)
	fprintf(stderr, "fncs table:\n");
    else if (tbl == &ttys)
	fprintf(stderr, "ttys table:\n");
    fprintf(stderr, "nsym = %d   (tbl @0x%lx, strings @0x%lx)\n",
	    tbl->nsym, (unsigned long) tbl->tbl,
	    (unsigned long) tbl->strings);
    for (i = 0; i<tbl->nsym; ++i)
	fprintf(stderr, "%8lx   %s\n", tbl->tbl[i].addr,
		    tbl->strings + tbl->tbl[i].name);
}

/*
 * misc stuff needed
 */

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

    if (size == 0) ++size;
    if ((p = (char *) malloc(size)) == NULL) {
	perror("xmalloc");
	exit(1);
    }
    return(p);
}

int
kmemread(void *buf, unsigned long addr, int size)
{
    int n;

    if (kmemfd == -1 && (kmemfd = open(kmem_path, O_RDONLY)) == -1) {
	perror(kmem_path);
	exit(1);
    }

    if (addr == (unsigned) -1) {	/* symbol not found */
	fprintf(stderr, "trying to read invalid address\n");
	exit(1);
    }
    if (lseek(kmemfd, addr, SEEK_SET) == -1) {
	perror("lseek kmem");
	return -1;
    }
    if ((n = read(kmemfd, buf, size)) != size) {
	fprintf(stderr, "error reading kmem (offset = 0x%lx)\n", addr);
	if (n == -1)
	    perror("");
	return -1;
    }
    return n;
}

#ifdef USE_MMAP
unsigned long
get_kword(unsigned long addr)
{
    return KWORD(addr);
}
#else
unsigned long
get_kword(unsigned long addr)
{
    unsigned long w;

    kmemread(&w, addr, sizeof w);
    return(w);
}
#endif
