/*
 * fstat -	identify open files
 *
 *
 * Copyright (c) 1992 Branko Lankester
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <string.h>
#include <dirent.h>
#include <stdio.h>
#include <pwd.h>
#include "psdata.h"


struct inode *ino_table;
struct file *fil_table;

int _read_pipe_fops;
int _write_pipe_fops;
caddr_t kbase;

#define	KWORD(a)	(*((unsigned long *)(kbase + (a))))
#define	KPTR(p)		((void *) ((unsigned long) (p) + kbase))

extern char *optarg;
extern int optind;

char *progname;
int fuser;

int all;
int fflag;
int numeric;
int verbose;
int killflag;

int ndev;
int *devs;

char *lookup_filename();

main(argc, argv)
char **argv;
{
    struct task_struct **p, *task, buf;
    struct file *filep;
    unsigned numlib;
    int n, i, opt;
    int pid = -1, uid = -1;
    char *opts = "p:u:fnvd";
    int dirflag = 0;

    if ((progname = strrchr(*argv, '/')) == NULL)
	progname = *argv;
    else
	++progname;
    
    if (strcmp(progname, "fuser") == 0) {
	opts = "fu:k";
	fuser = 1;
    }

    if (open_psdb()) {
	perror("cannot open psdatabase");
	exit(1);
    }
    while ((opt = getopt(argc, argv, opts)) != EOF) {
	switch (opt) {
	    case 'p': pid = atoi(optarg); break;
	    case 'u': uid = uid_from_user(optarg); break;
	    case 'f': fflag = 1; break;
	    case 'n': numeric = 1; break;
	    case 'v': verbose = 1; break;
	    case 'd': dirflag = 1; break;
	    case 'k': killflag = 1; break;
	    default: usage();
	}
    }
    argc -= optind;
    argv += optind;
    if (argc == 0) {
	if (fuser)
	    usage();
	all = 1;
    }
    if (!fuser) {
	if (numeric)
	    printf("USER     COMMAND    PID    FD  DEV   INUM   SZ|DV    MODE%s\n",
		    (argc ? " NAME" : ""));
	else
	    printf("USER     COMMAND    PID    FD  DEV   INUM   SZ|DV MODE       NAME\n");
    }
    if (fflag) {	/* arguments are file systems */
	if ((devs = malloc(optind * sizeof *devs)) == NULL) {
	    perror("malloc");
	    exit(1);
	}
	while (--argc >= 0)
	    if (devs[ndev] = get_devno(*argv++))
		++ndev;
	if (!ndev)
	    exit(1);
	all = 1;
    } else if (dirflag) { /* arguments are additional directories to scan */
	while (--argc >= 0)
	    add_dir(*argv++);
	all = 1;
    } else {		/* restrict output to certain files */
	while (--argc >= 0)
	    add_file(*argv++);
    }
    if (all && !numeric) {
	add_dir("/dev");
	add_dir("/lib");
    }

    mmap_mem();
    ino_table = KPTR(KWORD(k_addr("_inode_table")));
    fil_table = KPTR(k_addr("_file_table"));
    p = KPTR(k_addr("_task"));
    for (n = NR_TASKS; n > 0; --n, ++p) {
	if (*p) {
#if 0
	    task = KPTR(*p);
#else
	    kmemread(&buf, *p, sizeof buf);
	    task = &buf;
#endif
	    if (task->state == TASK_ZOMBIE)
		continue;
	    if (pid != -1 && pid != task->pid)
		continue;
	    if (uid != -1 && uid != task->uid)
		continue;
	    if (!all)
		show_inode(task, task->root, -1);
	    show_inode(task, task->pwd, -2);
	    show_inode(task, task->executable, -3);
	    if ((numlib = task->numlibraries) > MAX_SHARED_LIBS)
		continue;
	    for (i = 0; i < numlib; ++i)
		show_inode(task, task->libraries[i].library, -4);
	    for (i = 0; i < NR_OPEN; ++i) {
		if (task->filp[i]) {
		    filep = KPTR(task->filp[i]);
		    if (filep < fil_table || filep >= fil_table + NR_FILE) {
			if (verbose)
			    printf("bad file pointer\n");
			break;
		    }
		    show_inode(task, filep->f_inode, i);
		}
	    }
	}
    }
    if (fuser)
	printf("\n");
    exit(0);
}

usage()
{
    if (fuser)
	fprintf(stderr, "usage: fuser [-fk] [-u user] file|dev...\n");
    else
	fprintf(stderr, "usage: fstat [-nfdv] [-p pid] [-u user] [file|dir...]\n");
    exit(1);
}

char *types[] = {
    "",
    "root",
    "wd",
    "text",
    "lib",
};

show_inode(task, ino, type)
struct task_struct *task;
struct inode *ino;
int type;
{
    char *s;
    char mode[16];
    struct inode *inop;
    int i;
    static int pid = -1;

    if (!ino)
	return;

    inop = KPTR(ino);
    if (inop < ino_table || inop >= ino_table + NR_INODE) {
	if (verbose)
	    printf("bad inode pointer\n");
	return;
    }
    if (fflag) {
	for (i = 0; i < ndev; ++i)
	    if (inop->i_dev == devs[i])
		break;
	if (i == ndev)
	    return;
    }
    if ((s = lookup_filename(inop->i_ino, inop->i_dev)) == NULL && !all)
	return;
    if (fuser) {
	if (task->pid == pid)
	    return;
	pid = task->pid;
	printf("%d ", pid);
	if (killflag && pid != getpid() && kill(pid, 9) == -1) {
	    fprintf(stderr, "kill %d: ", pid);
	    perror("");
	}
	return;
    }
    printf("%-8.8s %-8.8s %5d ",
	user_from_uid(task->euid), task->comm, task->pid);

    if (type < 0) {
	printf("%5s", types[-type]);
    } else
	printf("%5d", type);
    
    if (inop->i_mode == 0 && inop->i_pipe) {
       char *rw = "?";
       if (!_read_pipe_fops) {
           _read_pipe_fops = k_addr("_read_pipe_fops");
           _write_pipe_fops = k_addr("_write_pipe_fops");
       }
       if ((long)((struct file *)KPTR(task->filp[i]))->f_op == _read_pipe_fops)
           rw = "<-";
       else if ((long)((struct file *)KPTR(task->filp[i]))->f_op == _write_pipe_fops)
           rw = "->";
       printf("* (pipe %s %#x %d %dr %dw)\n", rw, PIPE_BASE(*inop),
               PIPE_SIZE(*inop),
               PIPE_READERS(*inop), PIPE_WRITERS(*inop));
	return;
    }
    if (S_ISSOCK(inop->i_mode)) {
	print_sock(ino);
	/*printf("* (socket)\n");*/
	return;
    }
    printf(" %2d,%-2d %5d ", 
	inop->i_dev >> 8, inop->i_dev & 0xff, inop->i_ino);

    if (S_ISCHR(inop->i_mode) || S_ISBLK(inop->i_mode))
	printf("%3d,%3d ", inop->i_rdev >> 8, inop->i_rdev & 0xff);
    else
	printf("%7d ", inop->i_size);

    if (numeric)
	printf("%7o ", inop->i_mode);
    else {
	strmode(inop->i_mode, mode);
	printf("%s", mode);
    }

    if (s) {
	printf("%s\n", s);
    } else
	printf("\n");
}


#define HASH(x)		((x) & 0xff)

struct fnhash {
    struct fnhash *next;
    int dev;
    int ino;
    char name[1];	/* variable size */
} *hasht[256];

add_file(name)
char *name;
{
    struct fnhash *p;
    struct stat st;

    if (stat(name, &st) == -1)
	return;

    if ((p = malloc(sizeof(struct fnhash) + strlen(name))) == NULL) {
	perror("malloc");
	return;
    }
    strcpy(p->name, name);
    p->dev = st.st_dev;
    p->ino = st.st_ino;
    p->next = hasht[HASH(st.st_ino)];
    hasht[HASH(st.st_ino)] = p;
}

add_dir(name)
char *name;
{
    DIR *dp;
    struct dirent *de;
    static char path[256], *t;
    struct stat st;

    if (stat(name, &st) == -1) {
	perror(name);
	return;
    }
    add_file(name);
    if ((st.st_mode & S_IFMT) != S_IFDIR)
	return;
    if ((dp = opendir(name)) == NULL) {
	perror(name);
	return;
    }
    strcpy(path, name);
    t = path + strlen(path);
    if (strcmp(path, "/") != 0)
	*t++ = '/';
    while ((de = readdir(dp)) != NULL) {
	if (de->d_name[0] == '.' && (de->d_name[1] == '\0' ||
		de->d_name[1] == '.' && de->d_name[2] == '\0'))
	    continue;
	strcpy(t, de->d_name);
	add_file(path);
    }
    closedir(dp);
}

char *
lookup_filename(ino, dev)
{
    struct fnhash *p;

    p = hasht[HASH(ino)];
    while (p != NULL) {
	if (p->ino == ino && p->dev == dev)
	    return p->name;
	p = p->next;
    }
    return NULL;
}


#include <linux/socket.h>
#include "/usr/src/linux/net/kern_sock.h"
#include "/usr/src/linux/net/tcp/timer.h"
#include "/usr/src/linux/net/tcp/tcp.h"
#include "/usr/src/linux/net/tcp/sock.h"

#define	SOCK_INODE(x)	((x)->dummy)

struct socket * find_sock();

int _unix_proto_ops;
int _inet_proto_ops;

char *af_name[] = {
    "????",
    "unix",
    "inet",
};
struct socket *sockets;

print_sock(ino)
struct inode *ino;
{
    struct socket *sock;
    int af = 0;

    if (sockets == NULL)
	sock_init();
    if ((sock = find_sock(ino)) == NULL) {
	printf("* (socket ???)\n");
	return;
    }
    if ((long) sock->ops == _unix_proto_ops)
      af = AF_UNIX;
    else if (_inet_proto_ops != -1 && (long) sock->ops == _inet_proto_ops)
      af = AF_INET;

    printf("* (%s ", af_name[af]);
    switch (sock->type) {
	case SOCK_STREAM: printf("stream"); break;
	case SOCK_DGRAM: printf("dgram"); break;
	case SOCK_RAW: printf("raw"); break;
	case SOCK_RDM: printf("rdm"); break;
	case SOCK_SEQPACKET: printf("seqpak"); break;
	case SOCK_PACKET: printf("packet"); break;
	default: printf("???");
    }
    printf(" %x", sock->flags);
    if (af == AF_INET) {
      struct sock sk;
      if (sock->data) {
          kmemread(&sk, sock->data, sizeof sk);
          printf(" %d", sk.num);
      }
    }
    printf(")\n");
}

struct socket *
find_sock(ino)
struct inode *ino;
{
    struct socket *sock;

    for (sock = sockets; sock < sockets+NSOCKETS; ++sock) {
	if (sock->state != SS_FREE && SOCK_INODE(sock) == ino)
	    return sock;
    }
    return NULL;
}

sock_init()
{
    sockets = KPTR(k_addr("_sockets"));
    _unix_proto_ops = k_addr("_unix_proto_ops");
    Debug = 0;
    _inet_proto_ops = k_addr("_inet_proto_ops");
}


int
uid_from_user(user)
char *user;
{
    struct passwd *pw;

    if (*user == '#')
	return(atoi(user + 1));
    if ((pw = getpwnam(user)) == NULL) {
	fprintf(stderr, "%s: no such user: %s\n", progname, user);
	exit(1);
    }
    return(pw->pw_uid);
}

#include <mntent.h>

int
get_devno(s)
char *s;
{
    struct stat st;
    struct mntent *mnt;
    static FILE *f = NULL;

    if (stat(s, &st) == -1) {
	perror(s);
	return 0;
    }
    if ((st.st_mode & S_IFMT) == S_IFBLK)
	return st.st_rdev;

    if ((st.st_mode & S_IFMT) != S_IFDIR) {
	fprintf(stderr, "%s: Not a directory\n", s);
	return 0;
    }
    if (f == NULL && (f = setmntent(MOUNTED, "r")) == NULL) {
	perror(MOUNTED);
	return 0;
    }
    rewind(f);
    while ((mnt = getmntent(f)) != NULL)
	if (strcmp(mnt->mnt_dir, s) == 0)
	    break;

    if (mnt == NULL) {
	fprintf(stderr, "%s: Not a mount point\n", s);
	return 0;
    }
    if (stat(mnt->mnt_fsname, &st) == -1) {
	perror(mnt->mnt_fsname);
	return 0;
    }
    return st.st_rdev;
}


unsigned long end_mem;

mmap_mem()
{
    extern int kmemfd;

#if 0
    end_mem = get_kword(k_addr("_high_memory"));
#else
    end_mem = get_kword(k_addr("_memory_start"));
#endif
    if (kbase == NULL) {
	if ((kbase = (caddr_t) malloc(end_mem)) == NULL || ((long) kbase & 0xfff)) {
	    fprintf(stderr, "malloc failed: %x\n", kbase);
	    exit(1);
	}
    }
    kbase = mmap(kbase, end_mem, PROT_READ, MAP_SHARED|MAP_FIXED, kmemfd, 0L);
    if (kbase == (caddr_t) -1) {
	perror("mmap");
	exit(1);
    }
}

munmap_mem()
{
    if (munmap(kbase, end_mem) == -1) {
	perror("munmap");
    }
}
