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

#define __KERNEL__
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <stdio.h>
#include <signal.h>
#include <pwd.h>
#include <stdlib.h>
#include <getopt.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/sysmacros.h>
#include <arpa/inet.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include "ps.h"
#include "psdata.h"

void *ptr_min;
void *ptr_max;

int _read_pipe_fops;
int _write_pipe_fops;

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(int ino, int dev);
int uid_from_user(char *user);
void usage(void);
int get_devno(char *s);
void add_file(char *name);
void add_dir(char *name);
void show_inode(struct task_struct *task, struct inode *ino, int type);
void print_sock(struct inode *ino);
void sock_init(void);

int
main(int argc, char *argv[])
{
    struct task_struct **p, *task, buf;
    struct file *filep, *tmp;
    struct vm_area_struct vm, *vmm;
    int n, i, opt;
    int pid = -1, uid = -1;
    char *comm = NULL;
    char *opts = "c: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 'c': comm = optarg; break;
	    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_file("/");
	add_dir("/dev");
	add_dir("/lib");
    }

    mmap_init();
    ptr_min = KPTR(4096);
    ptr_max = KPTR(KWORD(k_addr("_high_memory")));
    p = KPTR(k_addr("_task"));
    for (n = NR_TASKS; n > 0; --n, ++p) {
	if (*p) {
	    kmemread(&buf, (unsigned long) *p, sizeof buf);
	    task = &buf;
	    if (task->state == TASK_ZOMBIE)
		continue;
	    if (pid != -1 && pid != task->pid)
		continue;
	    if (uid != -1 && uid != task->uid)
		continue;
	    if (comm && strcmp(comm, task->comm) != 0)
		continue;
	    if (!all)
		show_inode(task, task->fs->root, -1);
	    show_inode(task, task->fs->pwd, -2);
	    for (vmm = task->mm->mmap; vmm; vmm = vm.vm_next) {
		kmemread(&vm, (unsigned long) vmm, sizeof vm);
		if (vm.vm_inode)
		    show_inode(task, vm.vm_inode, -4);
	    }
	    for (i = 0; i < NR_OPEN; ++i) {
		if ((tmp = task->files->fd[i])) {
		    if (mmap_page((unsigned long) tmp) < 0 && verbose)
			    printf("mmap_page failed\n");
		    filep = KPTR(tmp);
		    if (filep < (struct file *) ptr_min || filep >= (struct file *) ptr_max) {
			if (verbose)
			    printf("bad file pointer\n");
			break;
		    }
		    show_inode(task, filep->f_inode, i);
		}
	    }
	}
    }
    if (fuser)
	printf("\n");
    exit(0);
}

void
usage(void)
{
    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",
    "mmap",
};

void
show_inode(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;

    if (mmap_page((unsigned long) ino) < 0 && verbose)
	    printf("mmap_page failed\n");
    inop = KPTR(ino);
    if (inop < (struct inode *) ptr_min || inop >= (struct inode *) ptr_max) {
	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_pipe && (inop->i_ino == 0 || numeric)) {
	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->files->fd[type]))->f_op == _read_pipe_fops)
	    rw = "<-";
	else if ((long)((struct file *)KPTR(task->files->fd[type]))->f_op == _write_pipe_fops)
	    rw = "->";
	printf("* (pipe %s %#lx %d %dr %dw)\n",
	       rw, (unsigned long) PIPE_BASE(*inop),
	       PIPE_SIZE(*inop), PIPE_READERS(*inop), PIPE_WRITERS(*inop));
	return;
    }
    if (S_ISSOCK(inop->i_mode)) {
	print_sock(ino);
	return;
    }
    printf(" %2d,%-2d %5ld ", 
	major(inop->i_dev), minor(inop->i_dev), inop->i_ino);

    if (S_ISCHR(inop->i_mode) || S_ISBLK(inop->i_mode))
	printf("%3d,%3d ", major(inop->i_rdev), minor(inop->i_rdev));
    else
	printf("%7ld ", (long) 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];

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

    if (lstat(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;
}

void
add_dir(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(int ino, int 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 <linux/net.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <linux/timer.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <net/inet/protocol.h>
#include <net/inet/sock.h>
#include <net/inet/ip.h>
#include <net/inet/tcp.h>
#include <net/unix/unix.h>

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

struct socket * find_sock(struct inode *ino);

int _unix_proto_ops;
int _inet_proto_ops;

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

char *tcp_states[] = {
  "0",
  "ESTABLISHED",
  "SYN_SENT",
  "SYN_RECV",
#if 0
  "CLOSING",
#endif
  "FIN_WAIT1",
  "FIN_WAIT2",
  "TIME_WAIT",
  "CLOSE",
  "CLOSE_WAIT",
  "LAST_ACK",
  "LISTEN"
};

char *sock_states[] = {
    "0",
    "UNCONNECTED",
    "CONNECTING",
    "CONNECTED",
    "DISCONNECTING"
};

struct socket *Sockets;

char *
sock_name(char *buf, unsigned long addr, short unsigned int port)
{
    struct in_addr in_addr;

    if (addr)
	memcpy(&in_addr, &addr, sizeof(in_addr));
    if (!addr && !port)
	sprintf(buf, "*:*");
    else if (!addr)
	sprintf(buf, "*:%d", ntohs(port));
    else if (!port)
	sprintf(buf, "%s:*", inet_ntoa(in_addr));
    else
	sprintf(buf, "%s:%d", inet_ntoa(in_addr), ntohs(port));
    return buf;
}

void
print_sock(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("???");
    }
#if 0
    printf(" (flags: %x)", sock->flags);
#endif

    if (af == AF_INET) {
	struct sock sk;
	char sbuf[128], dbuf[128]; 

	if (sock->data) {
	    kmemread(&sk, (unsigned long) sock->data, sizeof sk);
	    printf(" %s <-> %s",
		sock_name(sbuf, sk.saddr, sk.dummy_th.source),
		sock_name(dbuf, sk.daddr, sk.dummy_th.dest));
	    if (sock->type == SOCK_STREAM &&
		    sk.state < sizeof tcp_states / sizeof *tcp_states)
		printf(" %s", tcp_states[sk.state]);
	}
    }
    else if (af == AF_UNIX) {
	struct unix_proto_data data;
	if (sock->data) {
	    kmemread(&data, (unsigned long) sock->data, sizeof data);
	    if (data.sockaddr_len)
		printf(" %.*s", data.sockaddr_len,
		       (char *) &data.sockaddr_un.sun_path);
	    if (data.peerupd) {
		kmemread(&data, (unsigned long) data.peerupd, sizeof data);
		if (data.sockaddr_len)
		    printf(" [%.*s]", data.sockaddr_len,
			   (char *) &data.sockaddr_un.sun_path);
	    }
	    if (sock->flags & SO_ACCEPTCON)
		printf(" ACCEPTING");
	    else if (sock->state < sizeof sock_states / sizeof *sock_states)
		printf(" %s", sock_states[sock->state]);
	}
    }

    printf(")\n");
}

struct socket *
find_sock(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;
}

void
sock_init(void)
{
    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(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(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;
}
