/*
 * update_db.c	- create/update 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 <a.out.h>
#include <dirent.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include "ps.h"
#include "psdata.h"

#ifndef SYSTEM_FILE
#define	SYSTEM_FILE "/usr/src/linux/vmlinux"
#endif


static update_psdb(char *syspath);
static read_nlist(char *systemfile);
static write_tbl(int fd, struct dbtbl_s *dbtbl, struct tbl_s *tbl);

static struct nlist *namelist;
static int nsym;
static char *strings;
static int stringsize;
static int data_start;


/*
 * Open systemfile, if update is non zero the psdatabase will be
 * updated from sysfile. If sysfile is NULL the system file that
 * was used to create the existing database will be used for
 * updating.
 */
int
open_sys(char *sysfile, int update)
{
    if (sysfile == NULL || *sysfile == '\0') {
	if (open_psdb() == -1) {
	    sysfile = SYSTEM_FILE;
	    read_nlist(sysfile);
	} else
	    sysfile = db_hdr.sys_path;
    } else
	read_nlist(sysfile);

    if (update)
	update_psdb(sysfile);

    return(0);
}


static int
update_psdb(char *syspath)
{
    int fd, sysfd, uts_offset;
    struct utsname uts;

    if (namelist == NULL)
	read_nlist(syspath);
    
    close_psdb();

    if ((fd = open(PSDATABASE, O_RDWR|O_TRUNC|O_CREAT, 0666)) == -1) {
	perror(PSDATABASE);
	exit(1);
    }
    if (*syspath != '/') {
	memset(db_hdr.sys_path, 0, sizeof db_hdr.sys_path);
	if (getcwd(db_hdr.sys_path, sizeof db_hdr.sys_path - 2) != NULL)
	    strcat(db_hdr.sys_path, "/");
	strncat(db_hdr.sys_path, syspath, sizeof db_hdr.sys_path -
				strlen(db_hdr.sys_path) - 1);
    } else
	strncpy(db_hdr.sys_path, syspath, sizeof db_hdr.sys_path);
    strncpy(db_hdr.magic, PS_MAGIC, sizeof db_hdr.magic);

    strncpy(db_hdr.swap_path, swappath[0], sizeof db_hdr.swap_path);


    make_vartbl();
    make_fnctbl();
    make_ttytbl();

    write(fd, (char *) &db_hdr, sizeof db_hdr);
    write_tbl(fd, &db_hdr.vars, &vars);
    write_tbl(fd, &db_hdr.fncs, &fncs);
    write_tbl(fd, &db_hdr.ttys, &ttys);

    if ((sysfd = open(syspath, O_RDONLY)) == -1) {
	perror(syspath);
	exit(1);
    }

    uts_offset = k_addr("_system_utsname") - k_addr("_memory_start");
    uts_offset = data_start + uts_offset;
    lseek(sysfd, uts_offset, SEEK_SET);
    read(sysfd, (char *) &uts, sizeof(struct utsname));
    close(sysfd);
    strncpy(db_hdr.uts_release, uts.release, sizeof db_hdr.uts_release);
    strncpy(db_hdr.uts_version, uts.version, sizeof db_hdr.uts_version);

    lseek(fd, 0L, SEEK_SET);
    write(fd, (char *) &db_hdr, sizeof db_hdr);
    close(fd);

    free(namelist);
    namelist = NULL;
    return(0);
}

static int
read_nlist(char *systemfile)
{
    int fd;
    struct exec hdr;
    unsigned symsize, size;

    if ((fd = open(systemfile, O_RDONLY)) < 0) {
	perror(systemfile);
	exit(1);
    }
    if (read(fd, (char *) &hdr, sizeof(hdr)) != sizeof(hdr)) {
	perror(systemfile);
	exit(1);
    }
    if (N_BADMAG(hdr)) {
	fprintf(stderr, "%s: bad magic number\n", systemfile);
	exit(1);
    }
    if (N_STROFF(hdr) == 0) {
	fprintf(stderr, "%s has no symbols\n", systemfile);
	exit(1);
    }
    if ((data_start = N_DATOFF(hdr)) == 0) {
        fprintf(stderr, "%s has no data\n", systemfile);
	exit(1);
    }
    lseek(fd, N_STROFF(hdr), SEEK_SET);
    read(fd, (char *) &stringsize, sizeof(stringsize));
    symsize = N_STROFF(hdr) - N_SYMOFF(hdr);
    size = symsize + stringsize;
    namelist = (struct nlist *) xmalloc(size);
    lseek(fd, N_SYMOFF(hdr), SEEK_SET);
    if (read(fd, (char *) namelist, size) != size) {
	perror(systemfile);
	exit(1);
    }
    close(fd);

    strings = ((char *) namelist) + symsize;
    nsym = symsize / sizeof(struct nlist);
    if (Debug > 1)
	fprintf(stderr, "read %d symbols from %s\n", nsym, systemfile);
    return(0);
}

void
make_vartbl(void)
{
    int i;
    struct sym_s *vp;

    vp= vars.tbl= (struct sym_s *) xmalloc(nsym * sizeof(struct sym_s));
    vars.strings = strings;
    vars.stringsize = stringsize;
    for (i = 0; i < nsym; ++i) {
	int typ = namelist[i].n_type & ~N_EXT;

	if (typ == N_DATA || typ == N_BSS) {
	    vp->addr = namelist[i].n_value;
	    vp->name = namelist[i].n_un.n_strx;
	    ++vp;
	}
    }
    vars.nsym = vp - vars.tbl;

    if (Debug > 1)
	fprintf(stderr, "%d data/bss symbols\n", vars.nsym);

    qsort(vars.tbl, vars.nsym, sizeof(struct sym_s), varcmp);
    if (Debug == 3)
	dump_tbl(&vars);
}

/*
 * make list of all text symbols, sorted on address for easy
 * lookup of wait channel.
 */
void
make_fnctbl(void)
{
    int i;
    struct sym_s *fp;

    fp = fncs.tbl = (struct sym_s *) xmalloc(nsym * sizeof(struct sym_s));
    fncs.strings = strings;
    fncs.stringsize = stringsize;
    for (i = 0; i < nsym; ++i) {
	if ((namelist[i].n_type & ~N_EXT) == N_TEXT
		&& !strchr(strings + namelist[i].n_un.n_strx, '.')
		&& strcmp(strings + namelist[i].n_un.n_strx,
			 "___gnu_compiled_c") != 0) {
	    fp->addr = namelist[i].n_value;
	    fp->name = namelist[i].n_un.n_strx;
	    ++fp;
	}
    }
    fncs.nsym = fp - fncs.tbl;

    if (Debug > 1)
	fprintf(stderr, "%d text symbols\n", fncs.nsym);
    qsort(fncs.tbl, fncs.nsym, sizeof(struct sym_s), addrcmp);
    if (Debug > 1)
	for (i = 1; i < fncs.nsym; ++i)
	    if (fncs.tbl[i].addr == fncs.tbl[i-1].addr)
		printf("text symbols %s and %s both have address %lx\n",
		    	strings + fncs.tbl[i-1].name,
			strings + fncs.tbl[i].name, fncs.tbl[i].addr);
    if (Debug == 3)
	dump_tbl(&fncs);
}

void
make_ttytbl(void)
{
    struct sym_s *tp;
    DIR *dp;
    struct dirent *dep;
    struct stat statbuf;
    char path[PATH_MAX];

    if (stat("/dev", &statbuf) < 0 || (dp = opendir("/dev")) == NULL) {
        fprintf(stderr, "cannot read /dev directory\n");
	exit(1);
    }
    tp = ttys.tbl = (struct sym_s *)
	xmalloc(statbuf.st_size * sizeof(struct sym_s));
    ttys.strings = NULL;
    ttys.stringsize = statbuf.st_size;
    while ((dep = readdir(dp))) {
	sprintf(path, "/dev/%s", dep->d_name);
	if (lstat(path, &statbuf) >= 0 && S_ISCHR(statbuf.st_mode)) {
	    tp->addr = statbuf.st_rdev;
	    tp->name = strdup(dep->d_name) - ttys.strings;
	    ++tp;
	}
    }
    closedir(dp);
    ttys.nsym = tp - ttys.tbl;

    if (Debug > 1)
	fprintf(stderr, "%d ttys\n", ttys.nsym);

    qsort(ttys.tbl, ttys.nsym, sizeof(struct sym_s), addrcmp);
    if (Debug == 3)
	dump_tbl(&ttys);
}

/*
 * write table tbl to descriptor fd, header structure dbtdl is updated
 */
static int
write_tbl(int fd, struct dbtbl_s *dbtbl, struct tbl_s *tbl)
{
    int i;
    struct sym_s *p;
    char *s;
    char *old_strings;
    int strsize, symsize;

    dbtbl->off = lseek(fd, 0L, SEEK_CUR);
    old_strings = tbl->strings;
    s = tbl->strings = xmalloc(tbl->stringsize);
    for (i = 0, p = tbl->tbl; i < tbl->nsym; i++, p++) {
	strcpy(s, old_strings + p->name);
	p->name = s - tbl->strings;
	s += strlen(s) + 1;
    }
    symsize = tbl->nsym * sizeof(struct sym_s);
    if (write(fd, (char *) tbl->tbl, symsize) != symsize)
	return -1;
    strsize = (s - tbl->strings + 3) & ~3;
    if (write(fd, tbl->strings, strsize) != strsize)
	return -1;
    
    dbtbl->size = strsize + symsize;
    dbtbl->nsym = tbl->nsym;

    return(0);
}
