/*
 *	$Source: /afs/athena.mit.edu/contrib/watchmaker/src/ofiles/RCS/ofiles.c,v $
 *	$Author: epeisach $
 *	$Header: /afs/athena.mit.edu/contrib/watchmaker/src/ofiles/RCS/ofiles.c,v 1.6 91/01/30 10:33:58 epeisach Exp $
 */

#ifndef lint
static char *rcsid_ofiles_c = "$Header: /afs/athena.mit.edu/contrib/watchmaker/src/ofiles/RCS/ofiles.c,v 1.6 91/01/30 10:33:58 epeisach Exp $";
#endif lint

#include "./ofiles.h"
#include <sys/unpcb.h>
#include <sys/domain.h>

char *AFtype(), *PFtype();

#define INET_INFO

#define STAT_TIMEOUT 5			/* time out stat() calls after 5 sec */
#ifndef STAT_TIMEOUT
#define safe_stat stat
#endif

/*
 * File descriptors.
 */

int             mem;			/* fd for /dev/mem */
int             kmem;			/* fd for /dev/kmem */
int             swap;			/* fd for /dev/drum */

#ifndef ultrix
FILE           *mtab;			/* stdio file for /etc/mtab */
#else
struct fs_data mountbuffer[NMOUNT];
#endif

/*
 * Kernel tables.
 */

long            procbase;		/* address of process table */
int             nproc;			/* number of entries in proc table */
struct pte     *Usrptma,		/* user page table map */
               *usrpt;			/* user page table  */

int             inodes[NOFILE];		/* inodes of open files */

/*
 * Program options.
 */

char           *progname;
int             terse = 0;		/* if non-zero, only output pids */
int             debug = 1;		/* set debugging output level */
int		all = 0;		/* print all pids */
int             filesonly = 0;		/* match args as files, not mntents */
int             inode = 0;		/* if non-zero, inode to match */

/*
 * Main routine - option processing.
 */

main (argc, argv)
int             argc;
char           *argv[];
{
    extern char    *optarg;
    extern int      optind;
    int             option;
    int             usage = 0;

    progname = argv[0];

    while ((option = getopt (argc, argv, "apfd:")) != EOF)
    {
	switch (option)
	{
	  case 'p':
	    terse = 1;
	    break;

          case 'a':
	    all = 1;
	    break;

          case 'f':
	    filesonly = 1;
	    break;

	  case 'd':
	    debug = atoi (optarg);
	    break;

	  case '?':
	    usage = 1;
	}
    }
    if (!(all || (argc -= optind)) || usage)
    {
	fprintff (stderr, "usage:\t%s %s\n\t%s %s",
		  progname, "[-fp] [-d debug-level] files...",
		  progname, "[-d debug-level] process-ids...\n");
	exit (2);
    }
    argv += optind;

    if ((mem = open ("/dev/mem", 0)) < 0)
	fail ("can't open /dev/mem");
    if ((kmem = open ("/dev/kmem", 0)) < 0)
	fail ("can't open /dev/kmem");
    if ((swap = open ("/dev/drum", 0)) < 0)
	fail ("can't open /dev/drum");

#ifndef ultrix
    if ((mtab = setmntent (MOUNTED, "r")) == 0)
	fail ("can't open /etc/mtab");
#endif

    getsyms ();

    /*
     * Check for process-id vs. file{,system}
     */

    if (all)
	    return(allpids(argc,argv));
    
    if (!filesonly && !terse && numeric (*argv))
	return (checkpids (argc, argv));
    
    return (checkfiles (argc, argv));
}


/*
 * Print information about which processes are using file{,systems}.
 */

checkfiles (argc, argv)
int             argc;
char          **argv;
{
    char            filename[MNTMAXSTR],
                    fsname[MNTMAXSTR];
    struct stat     stats;
    struct mntent  *mnt;
    int             err = 0;

    for (; argc--; argv++)
    {

	/*
	 * Check for filesystem/mountpoint vs. file.
	 */

	if (!filesonly && (mnt = getmntname (*argv)) != NULL)
	{
	    inode = 0;			/* match any inode on this fs */
	    (void) strcpy (filename, mnt->mnt_dir);
	    (void) strcpy (fsname, mnt->mnt_fsname);
	}
	else
	{
	    inode = 1;			/* match only one inode */
	    (void) strcpy (filename, *argv);
	}

	if (safe_stat (filename, &stats))
	{				/* find out what it is we want */
	    error (filename);
	    err = 1;
	    continue;
	}

	if (inode)			/* looking for file */
	{
	    inode = stats.st_ino;
	    if ((mnt = getmntfile (&stats)) != NULL)
		(void) strcpy (fsname, mnt->mnt_fsname);
	    else
		(void) strcpy (fsname, "unknown");
	}

	if (!terse)
	{
	    printff ("%s:\t%s\n", fsname, filename);

	    if (debug > 1)
		if (ispecial (stats.st_mode))
		    printff (" %s device %d/%d\n", itype (stats.st_mode),
			     major (stats.st_rdev), minor (stats.st_rdev));
		else if (nfsdev (stats.st_dev))
		    printff (" inode %d on remote fs %d (%s)\n",
			     stats.st_ino, minor (stats.st_dev),
			     itype (stats.st_mode));
		else
		    printff (" inode %d on block device %x (%s)\n",
			  stats.st_ino, stats.st_dev, itype (stats.st_mode));

	    printff ("user\t process command\ttype\tinode(s)\n");
	}

	lookforfiles (&stats);
    }

    return (err);
}

/*
 * Print information about processes which are using files of interest.
 */

lookforfiles (filestats)
struct stat    *filestats;
{
    struct proc     p;
    struct user    *u;
    struct text     t;
    register int    procn,
                    flags,
                    filen;

    for (procn = 0; procn < nproc; procn++)
    {
	procslot (procn, &p);
	if (p.p_stat == 0)
	    continue;

	flags = 0;

	if (debug > 2)
	    printff ("uid %d pid %d\n", p.p_uid, p.p_pid);

	if (p.p_textp != NULL)
	{
	    eseek (kmem, (long) p.p_textp, 0, "text");
	    eread (kmem, (char *) &t, sizeof (struct text), "text");

	    if (check (filestats, getfileid (t.x_vptr, "text")))
		flags |= TEXT;
	}
	
	u = NULL;

	if (p.p_stat == SZOMB)
	{
	    if (debug > 2)
		printff ("zombie\n");
	}
	else if ((u = getuser (&p)) != NULL)
	{
	    if (debug > 2)
		printff ("command %s\n", u->u_comm);

	    if (u->u_rdir != NULL)
		if (check (filestats, getfileid (u->u_rdir, "rdir")))
		    flags |= RDIR;

	    if (u->u_cdir != NULL)
		if (check (filestats, getfileid (u->u_cdir, "cdir")))
		    flags |= CDIR;

	    for (filen = 0; filen < NOFILE; filen++)
	    {
		struct file     f;
		union fileid   *fid;

		inodes[filen] = 0;

		if (u->u_ofile[filen] == NULL)
		    continue;

		eseek (kmem, (long) u->u_ofile[filen], 0, "file");
		eread (kmem, (char *) &f, sizeof (f), "file");

		if (f.f_count > 0)
		{
		    if (f.f_type != DTYPE_VNODE)
			continue;

		    fid = getfileid ((struct vnode *) f.f_data, "ofile");
		    if (check (filestats, fid))
		    {
			flags |= OFILE;
			if (!ispecial (filestats->st_mode)) {
			    inodes[filen] = fid->file.nodeid;
			}
		    }
		}
	    }
	}

	if (flags)
	    gotone (u, &p, flags);
    }
}

/*
 * Print the name of the user owning process "p" and the pid of that process.
 */

gotone (u, p, f)
struct user    *u;
struct proc    *p;
int             f;
{
    struct passwd  *getpwuid ();
    register struct passwd *pw;
    register int    filen;

    if (terse)				/* only print pids and return */
    {
	printff ("%d ", p->p_pid);
	(void) fflush (stdout);
	return;
    }

    pw = getpwuid (p->p_uid);
    if (pw)
	printff ("%-8.8s", pw->pw_name);
    else
	printff ("(%d)\t", p->p_uid);

    printff (" %d\t", p->p_pid);

    printff (" %-14.14s\t", (u) ? u->u_comm : "(zombie)");

    if (f & TEXT)
	putchar ('t');			/* proc's shared text is on device */
    if (f & RDIR)
	putchar ('r');			/* proc's root dir is on device */
    if (f & CDIR)
	putchar ('c');			/* proc's current dir is on device */
    if (f & OFILE)
	putchar ('f');			/* proc has file(s) open on device */
    printff ("\t");

    if (f & OFILE)			/* list inodes of open file(s) */
	for (filen = 0; filen < NOFILE; filen++)
	    if (inodes[filen] > 0)
		printff ("%d ", inodes[filen]);

    printff ("\n");
}


/*
 * Check if file id matches stats we are searching for.
 */

check (filestats, fid)
struct stat    *filestats;
union fileid   *fid;
{
    if (fid == NULL)
	return (0);

    if (ispecial (filestats->st_mode))
    {
	if (filestats->st_rdev == fid->special.dev
	    && itype (filestats->st_mode) == vtype (fid->special.type))
	    return (1);			/* for special devices */
	else
	    return (0);
    }

    if (filestats->st_dev == fid->file.fsid)
    {
	if (!inode)			/* if we'll match any file */
	    return (1);

	if (inode == fid->file.nodeid)
	    return (1);
#ifdef sun				/* not sure I understand this */
	else if (fid->file.nodeid == 0)
	    return (1);
#endif
    }

    return (0);
}

/*
 * Print information about selected processes.
 */

struct proclist
{
    struct proclist *next;
    int             pid;
}              *procs;

struct statlist
{
    struct statlist *next;
    char            filename[MNTMAXSTR],
                    fsname[MNTMAXSTR];
    struct stat     status;
}              *stats;

checkpids (argc, argv)
int             argc;
char          **argv;
{
    struct passwd  *getpwuid ();
    struct mntent  *mnt;
    register struct statlist *statp;
    register struct proclist *procp;
    register struct passwd *pw;
    struct proc     p;
    struct user    *u;
    struct text     t;
    int             matched;
    register int    procn,
                    filen;
#ifdef ultrix
    int loc = 0, ret;

    ret = getmountent(&loc, mountbuffer, NMOUNT);
    if (ret == 0) {
	perror("getmountent");
	exit(4);
    }
    for(mnt = mountbuffer; mnt < &mountbuffer[ret]; mnt++)
#else
    while (mnt = getmntent (mtab))
#endif
    {
	statp = (struct statlist *) malloc (sizeof (struct statlist));
	statp->next = stats;
	stats = statp;

	(void) strcpy (statp->filename, mnt->mnt_dir);
	(void) strcpy (statp->fsname, mnt->mnt_fsname);

	if (safe_stat (statp->filename, &statp->status))
	{				/* find out what it is */
	    error (statp->filename);
	    continue;
	}
    }

    for (; argc--; argv++)
    {
	procp = (struct proclist *) malloc (sizeof (struct proclist));
	procp->next = procs;
	procs = procp;
	procp->pid = atoi (*argv);
    }

    printff ("user\t process command\n");

    for (procn = 0; procn < nproc; procn++)
    {
	matched = 0;

	procslot (procn, &p);
	if (p.p_stat == 0)
	    continue;

	if (debug > 2)
	    printff ("uid %d pid %d\n", p.p_uid, p.p_pid);

	u = NULL;

	if (p.p_stat == SZOMB)
	{
	    if (debug > 2)
		printff ("zombie\n");
	}
	else if ((u = getuser (&p)) != NULL)
	{
	    if (debug > 2)
		printff ("command %s\n", u->u_comm);
	}

	for (procp = procs; procp; procp = procp->next)
	{
	    if (procp->pid != p.p_pid)
		continue;

	    procp->pid = 0;		/* clear matched processes */
	    if (matched)
		continue;		/* only print them once */
	    matched = 1;

	    pw = getpwuid (p.p_uid);
	    if (pw)
		printff ("%-8.8s", pw->pw_name);
	    else
		printff ("(%d)\t", p.p_uid);

	    printff (" %d\t", p.p_pid);

	    printff (" %-14.14s\n", (u) ? u->u_comm : "(zombie)");

	    if (p.p_textp != NULL)
	    {
		eseek (kmem, (long) p.p_textp, 0, "text");
		eread (kmem, (char *) &t, sizeof (struct text), "text");
	    
		printff (" text = ");
		printfile (t.x_vptr, stats);
	    }

	    if (u == NULL)
		continue;
	    
	    if (u->u_rdir != NULL)
	    {
		printff (" root = ");
		printfile (u->u_rdir, stats);
	    }
	    if (u->u_cdir != NULL)
	    {
		printff (" cwd = ");
		printfile (u->u_cdir, stats);
	    }

	    for (filen = 0; filen < NOFILE; filen++)
	    {
		struct file     f;

		inodes[filen] = 0;

		if (u->u_ofile[filen] == NULL)
		    continue;

		eseek (kmem, (long) u->u_ofile[filen], 0, "file");
		eread (kmem, (char *) &f, sizeof (f), "file");

		if (f.f_count > 0)
		{
		    printff (" fd #%-2d = ", filen);

		    switch (f.f_type)
		    {
		      case DTYPE_SOCKET:
			printsocket ((struct socket *) f.f_data);
			break;

		      case DTYPE_VNODE:
			printfile ((struct vnode *) f.f_data, stats);
			break;

#ifdef REMOTE
		      case DTYPE_REMOTE:
			printff("(remotefs fd)\n");
			break;
#endif REMOTE
			
#ifdef CHAOS
		      case DTYPE_CHAOS:
			printff("(chaos fd)\n");
			break;
#endif CHAOS
			
		      default:
			printff ("(unknown fd type)");
		    }
		}
	    }
	}
    }

    matched = 0;

    for (procp = procs; procp; procp = procp->next)
    {
	if (procp->pid != 0)
	    fprintff (stderr, "no such process: %d\n", procp->pid);
	matched = 1;
    }

    return (matched);
}

allpids (argc, argv)
int             argc;
char          **argv;
{
	struct passwd  *getpwuid ();
	struct mntent  *mnt;
	register struct statlist *statp;
	register struct passwd *pw;
	struct proc     p;
	struct user    *u;
	struct text     t;
	register int    procn, 	filen;
	char	buff[256];

#ifdef ultrix
    int loc = 0, ret;

    ret = getmountent(&loc, mountbuffer, NMOUNT);
    if (ret == 0) {
	perror("getmountent");
	exit(5);
    }
    for(mnt = mountbuffer; mnt < &mountbuffer[ret]; mnt++) {
#else
	while (mnt = getmntent (mtab)) {
#endif
		statp = (struct statlist *) malloc (sizeof (struct statlist));
		statp->next = stats;
		stats = statp;

		(void) strcpy (statp->filename, mnt->mnt_dir);
		(void) strcpy (statp->fsname, mnt->mnt_fsname);

			
		/* find out what it is */
		if (safe_stat (statp->filename, &statp->status)) {
			error (statp->filename);
			continue;
		}
	}

	printff ("user\t process command\n");
	
	for (procn = 0; procn < nproc; procn++) {

		procslot (procn, &p);
		if (p.p_stat == 0)
			continue;

		u = NULL;

		if (p.p_stat == SZOMB) {
			if (debug > 2)
				printff ("zombie\n");
		} else if ((u = getuser (&p)) != NULL) {
			if (debug > 2)
				printff ("command %s\n", u->u_comm);
		}

		pw = getpwuid (p.p_uid);
		if (pw)
			sprintff (buff, "%-8.8s %-14.14s\t%d\t",
				  pw->pw_name, (u) ? u->u_comm : "(zombie)",
				  p.p_pid);
		else
			sprintff (buff, "(%d)\t %-14.14s\t%d\t",
				  p.p_uid, (u) ? u->u_comm : "(zombie)",
				  p.p_pid);

		if (p.p_textp != NULL) {
			eseek (kmem, (long) p.p_textp, 0, "text");
			eread (kmem, (char *) &t, sizeof (struct text),
			       "text");
	    
			printff ("%s tx ", buff);
			printfile (t.x_vptr, stats);
		}

		if (u == NULL)
			continue;
	    
		if (u->u_rdir != NULL) {
			printff (" %s rt ", buff);
			printfile (u->u_rdir, stats);
		}
		if (u->u_cdir != NULL) {
			printff ("%s cd ", buff);
			printfile (u->u_cdir, stats);
		}

		for (filen = 0; filen < NOFILE; filen++) {
			struct file     f;

			inodes[filen] = 0;

			if (u->u_ofile[filen] == NULL)
				continue;

			eseek (kmem, (long) u->u_ofile[filen], 0, "file");
			eread (kmem, (char *) &f, sizeof (f), "file");

			if (f.f_count > 0) {
				printff ("%s %-2d ", buff, filen);
				
				switch (f.f_type) {

				case DTYPE_SOCKET:
					printsocket ((struct socket *)
						     f.f_data);
					break;

				case DTYPE_VNODE:
					printfile ((struct vnode *) f.f_data,
						   stats);
					break;

#ifdef REMOTE
				case DTYPE_REMOTE:
					printff("(remotefs fd)\n");
					break;
#endif REMOTE
			
#ifdef CHAOS
				case DTYPE_CHAOS:
					printff("(chaos fd)\n");
					break;
#endif CHAOS
			
				default:
					printff ("(unknown fd type)");
				}
			}
		}
	}

    return (0);
}


/*
 * Read a vnode from /dev/kmem, return fileid (dev,ino).
 */

union fileid   *getfileid (node, s)
struct vnode   *node;
char           *s;
{
    struct vnode    v;
    static union xnode
    {
#ifdef ultrix
	struct gnode    i;
#else
	struct inode    i;
#endif
	struct rnode    r;
    }               x;

    union fileid    fid;

    if (node == NULL)
	return (NULL);

    eseek (kmem, (long) node, 0, "vnode");
    eread (kmem, (char *) &v, sizeof (v), "vnode");

    if (vspecial (v.v_type))
    {
	fid.special.dev = v.v_rdev;
	fid.special.type = v.v_type;
	if (debug > 3)
	    printff (" %s %s device %d/%d\n",
		     s, vtype (v.v_type), major (v.v_rdev), minor (v.v_rdev));
	return (&fid);
    }

    if (debug > 3)
	printff (" %s %s", s, vtype (v.v_type));

#ifndef ultrix
    eseek (kmem, (long) v.v_data, 0, "inode/rnode");
    eread (kmem, (char *) &x, sizeof (x), "inode/rnode");
#else
    bcopy(&v, &x, sizeof(struct gnode));
#endif

#ifndef ultrix
    if (x.r.r_nfsattr.na_type == (enum nfsftype) v.v_type)
    {					/* best test I could find for nfs */
	struct vfs      vf;
	struct mntinfo  mi;

	eseek (kmem, (long) v.v_vfsp, 0, "vfs");
	eread (kmem, (char *) &vf, sizeof (vf), "vfs");

	eseek (kmem, (long) vf.vfs_data, 0, "mntinfo");
	eread (kmem, (char *) &mi, sizeof (mi), "mntinfo");

	fid.file.fsid = makedev (0xff, mi.mi_mntno);
	fid.file.nodeid = x.r.r_nfsattr.na_nodeid;

	if (debug > 3)
	    printff (" inode %d on nfs fs %x (%s)\n",
		     x.r.r_nfsattr.na_nodeid, x.r.r_nfsattr.na_fsid,
		     rtype (x.r.r_nfsattr.na_type));
    }
    else
#endif
    {
	fid.file.fsid = x.i.i_dev;
	fid.file.nodeid = x.i.i_number;

	if (debug > 3)
	    printff (" inode %d on device %x (%s)\n",
		     x.i.i_number, x.i.i_dev, itype (x.i.i_mode));
    }
    return (&fid);
}

/*
 * Print a description of a socket.
 */

printsocket (sock)
struct socket  *sock;
{
    struct socket   s;
    struct protosw  psw;
    struct domain   dom;
#ifdef INET_INFO
    struct inpcb    pcb;
#endif

    if (sock == NULL)
	return;

    if (debug > 1)
	    printf("addr: 0x%lx", sock);
    
    eseek (kmem, (long) sock, 0, "socket");
    eread (kmem, (char *) &s, sizeof (s), "socket");

    eseek (kmem, (long) s.so_proto, 0, "protosw");
    eread (kmem, (char *) &psw, sizeof (psw), "protosw");

    if (psw.pr_domain) {
	    eseek (kmem, (long) psw.pr_domain, 0, "domain");
	    eread (kmem, (char *) &dom, sizeof (dom), "domain");
    }

    printf("(");
    if (psw.pr_domain)
	    printf("%s ", AFtype(dom.dom_family));
    printf("%s", PFtype(psw.pr_type));
    switch (s.so_type)
    {
      case SOCK_STREAM:
	printff (" stream");
	break;
      case SOCK_DGRAM:
	printff (" datagram");
	break;
      case SOCK_RAW:
	printff (" raw");
	break;
      case SOCK_RDM:
	printff (" reliable datagram");
	break;
      case SOCK_SEQPACKET:
	printff (" sequenced packet");
	break;
    }

    printff (" socket)\n");
#ifdef INET_INFO
    if (psw.pr_domain && dom.dom_family == AF_INET) {
	struct servent *service;
	char	*type;

	eseek (kmem, (long) s.so_pcb, 0, "in_pcb");
	eread (kmem, (char *) &pcb, sizeof (pcb), "in_pcb");

	type = (s.so_type == SOCK_STREAM) ? "tcp" : "udp";

	printff("\t\t");
	if (pcb.inp_laddr.s_addr)
		printff(" %s", inet_ntoa(pcb.inp_laddr));
	if (service = getservbyport((int) pcb.inp_lport, type))
		printff(" %s", service->s_name);
	else
		printff(" %d", pcb.inp_lport);

	printff(" ->");
	
	if (pcb.inp_faddr.s_addr)
		printff(" %s", inet_ntoa(pcb.inp_faddr));
	if (service = getservbyport((int) pcb.inp_fport, type))
		printff(" %s", service->s_name);
	else
		printff(" %d", pcb.inp_fport);
	printff("\n");
        }
#endif
    if (psw.pr_domain && dom.dom_family == AF_UNIX) {
	    struct unpcb    upcb;

	    eseek (kmem, (long) s.so_pcb, 0, "unpcb");
	    eread (kmem, (char *) &upcb, sizeof (upcb), "unpcb");

	    if (upcb.unp_vnode) {
		    printf("\t\t");
		    printfile(upcb.unp_vnode,stats);
	    }
    }
	    
}

/*
 * Print a description of a vnode.
 */

printfile (node, fsstats)
struct vnode   *node;
register struct statlist *fsstats;
{
    struct vnode    v;
    static union xnode
    {
#ifdef ultrix
	struct gnode    i;
#else
	struct inode    i;
#endif
	struct rnode    r;
    }               x;

    if (node == NULL)
	return;

    eseek (kmem, (long) node, 0, "vnode");
    eread (kmem, (char *) &v, sizeof (v), "vnode");

#ifndef ultrix
    eseek (kmem, (long) v.v_data, 0, "inode/rnode");
    eread (kmem, (char *) &x, sizeof (x), "inode/rnode");
#else
    bcopy(&v, &x, sizeof(struct gnode));
#endif

#ifdef ultrix
    if (ispecial (v.g_mode))
#else
    if (vspecial (v.v_type))
#endif
    {
#ifdef ultrix
	printff ("%s device", itype (v.g_mode));
#else
	printff ("%s device", vtype (v.v_type));
#endif

	if (printdevice (&v, &x.i))
	    printff (" %d/%d\n", major (v.v_rdev), minor (v.v_rdev));
	return;
    }

#ifdef ultrix
    printff ("%s", itype (v.g_mode));
#else
    printff ("%s", vtype (v.v_type));
#endif

#ifndef ultrix
    if (x.r.r_nfsattr.na_type == (enum nfsftype) v.v_type)
    {					/* best test I could find for nfs */
	struct vfs      vf;
	struct mntinfo  mi;
	int             fsdev;

	eseek (kmem, (long) v.v_vfsp, 0, "vfs");
	eread (kmem, (char *) &vf, sizeof (vf), "vfs");

	eseek (kmem, (long) vf.vfs_data, 0, "mntinfo");
	eread (kmem, (char *) &mi, sizeof (mi), "mntinfo");

	fsdev = makedev (0xff, mi.mi_mntno);

	while (fsstats)
	{
	    if (fsstats->status.st_dev == fsdev)
	    {
		printff (" inode %d on remote fs %d (%s)\n",
			 x.r.r_nfsattr.na_nodeid, mi.mi_mntno,
			 fsstats->filename);
		return;
	    }
	    fsstats = fsstats->next;
	}

	printff (" inode %d on nfs fs %x\n",
		 x.r.r_nfsattr.na_nodeid, x.r.r_nfsattr.na_fsid);
	return;
    }
    else
#endif
    {
	while (fsstats)
	{
	    if (fsstats->status.st_dev == x.i.i_dev)
	    {
		printff (" inode %d on local fs %x (%s)\n",
			 x.i.i_number, x.i.i_dev, fsstats->filename);
		return;
	    }
	    fsstats = fsstats->next;
	}

	printff (" inode %d on block device %x\n",
		 x.i.i_number, x.i.i_dev);
    }
}

/*
 * Scan the /dev directory and print matching device name for number.
 */

char           *dev = "/dev/";

printdevice (node, ino)
struct vnode   *node;
struct inode   *ino;
{
    struct stat     status;
    register struct direct *entry;
    register DIR   *devdir;
    static char     devname[32];

    if ((devdir = opendir (dev)) == NULL)
	return (1);

    while ((entry = readdir (devdir)) != NULL)
    {
#ifdef notdef
	if (entry->d_ino != ino->i_number)
	    continue;
#endif

	(void) strcpy (devname, dev);
	(void) strcat (devname, entry->d_name);
	if (stat (devname, &status) < 0)
	    continue;
#ifdef notdef
	if (status.st_dev == ino->i_dev && status.st_ino == ino->i_number)
#else
	if (status.st_rdev == node->v_rdev)
#endif
	{
	    closedir (devdir);
	    printff (" %s\n", devname);
	    return (0);
	}
    }
    closedir (devdir);

    return (1);
}


/*
 * Routines for returning readable strings for types. Returned string pointers
 * can be tested for equality.
 */

char           *none = "none";
char           *regular = "file";
char           *directory = "directory";
char           *block = "block special";
char           *character = "character special";
char           *symbolic = "symbolic link";
char           *unixsocket = "socket";
char           *fifo = "fifo";


char           *vtype (type)
enum vtype      type;
{
    switch (type)
    {
      case VNON:
	return (none);
      case VREG:
	return (regular);
      case VDIR:
	return (directory);
      case VBLK:
	return (block);
      case VCHR:
	return (character);
      case VLNK:
	return (symbolic);
      case VSOCK:
	return (unixsocket);
      case VBAD:
	return ("bad type");
#ifdef S_IFIFO
      case VFIFO:
	return (fifo);
#endif
      default:
	printf("----- %d -----", type);
	return ("unknown vnode type");
    }
}

char           *rtype (type)
enum nfsftype   type;
{
    switch (type)
    {
      case NFNON:
	return (none);
      case NFREG:
	return (regular);
      case NFDIR:
	return (directory);
      case NFBLK:
	return (block);
      case NFCHR:
	return (character);
      case NFLNK:
	return (symbolic);
      default:
	return ("unknown rnode type");
    }
}

char           *itype (mode)
unsigned        mode;
{
    switch (mode & S_IFMT)
    {
      case S_IFREG:
	return (regular);
      case S_IFDIR:
	return (directory);
      case S_IFBLK:
	return (block);
      case S_IFCHR:
	return (character);
      case S_IFLNK:
	return (symbolic);
      case S_IFSOCK:
	return (unixsocket);
#ifdef S_IFIFO
      case S_IFIFO:
	return (fifo);
#endif
      default:
	printf("Failed for mode inode %d\n", mode&S_IFMT);
	return ("unknown inode type");
    }
}

char *PFtype(pf)
	unsigned	pf;
{
	switch (pf) {
	case PF_UNIX:
		return ("unix-domain");
		break;
	case PF_INET:
		return ("internet");
		break;
	case PF_IMPLINK:
		return ("arpanet imp");
		break;
	case PF_PUP:
		return ("pup");
		break;
	case PF_CHAOS:
		return ("chaos");
		break;
	case PF_NS:
		return ("xns");
		break;
	case PF_NBS:
		return ("nbs");
		break;
	case PF_ECMA:
		return ("ecma");
		break;
	case PF_CCITT:
		return ("ccitt");
		break;
	case PF_SNA:
		return ("sna");
		break;
#ifdef PF_NIT
	case PF_NIT:
		return ("nit");
		break;
#endif
	default:
		return ("unspecified");
	}
}

char *AFtype(af)
	unsigned	af;
{
	switch (af) {
	case AF_UNIX:
		return ("unix-domain");
		break;
	case AF_INET:
		return ("internet");
		break;
	case AF_IMPLINK:
		return ("arpanet imp");
		break;
	case AF_PUP:
		return ("pup");
		break;
	case AF_CHAOS:
		return ("chaos");
		break;
	case AF_NS:
		return ("xns");
		break;
	case AF_NBS:
		return ("nbs");
		break;
	case AF_ECMA:
		return ("ecma");
		break;
	case AF_CCITT:
		return ("ccitt");
		break;
	case AF_SNA:
		return ("sna");
		break;
#ifdef AF_NIT
	case AF_NIT:
		return ("nit");
		break;
#endif
	default:
		return ("unspecified");
	}
}

/*
 * Get the mount entry for a directory or block device name.
 */

struct mntent  *getmntname (name)
char           *name;
{
    register struct mntent *mnt;
#ifdef ultrix
    int loc = 0, ret;

    ret = getmountent(&loc, mountbuffer, NMOUNT);
    if (ret == 0) {
	perror("getmountent");
	exit(3);
    }
    for(mnt = mountbuffer; mnt < &mountbuffer[ret]; mnt++) 
#else
    rewind (mtab);
    while ((mnt = getmntent (mtab)) != 0)
#endif
    {
	if (strcmp (name, mnt->mnt_fsname) == 0)
	    return (mnt);
	if (strcmp (name, mnt->mnt_dir) == 0)
	    return (mnt);
    }
    return (NULL);
}

/*
 * Get the mount entry for a device number (in a struct stat).
 */

struct mntent  *getmntfile (filestats)
struct stat    *filestats;
{
    register struct mntent *mnt;
    struct stat     dirstats;
#ifdef ultrix
    int loc = 0, ret;

    ret = getmountent(&loc, mountbuffer, NMOUNT);
    if (ret == 0) {
	perror("getmountent");
	exit(3);
    }
    for(mnt = mountbuffer; mnt < &mountbuffer[ret]; mnt++) 
	if ((stat (mnt->mnt_dir, &dirstats) >= 0) &&
	    (filestats->st_dev == dirstats.st_dev))
	    return (mnt);
#else
    rewind (mtab);
    while ((mnt = getmntent (mtab)) != 0)
	if ((stat (mnt->mnt_dir, &dirstats) >= 0) &&
	    (filestats->st_dev == dirstats.st_dev))
	    return (mnt);
#endif
    return (NULL);
}


/*
 * Test for numeric (pid) arguments.
 */

numeric (string)
char           *string;
{
    while (*string)
	if (*string < '0' || *string > '9')
	    return (0);
	else
	    string++;

    return (1);
}

#ifdef STAT_TIMEOUT

#include <setjmp.h>
#include <sys/signal.h>

extern int      errno;
jmp_buf         jmpbuf;

/* ARGSUSED */
wakeup (sig)
int             sig;
{
    (void) alarm (0);
    (void) signal (SIGALRM, SIG_IGN);
    longjmp (jmpbuf, 1);
}

/*
 * Stat which won't hang on remote filesystems (if mounted soft or intr).
 */

safe_stat (path, buf)
char           *path;
struct stat    *buf;
{
    int             err;
    static int      old_errno;

    old_errno = errno;
    if (setjmp (jmpbuf))
    {
	(void) alarm (0);
	(void) signal (SIGALRM, SIG_IGN);
	errno = ETIMEDOUT;
	return (-1);
    }
    (void) signal (SIGALRM, wakeup);
    (void) alarm (STAT_TIMEOUT);
    err = stat (path, buf);
    if (err)
	old_errno = errno;
    (void) alarm (0);
    (void) signal (SIGALRM, SIG_IGN);
    errno = old_errno;
    return (err);
}

#endif
