/*	Copyright (c) 1984 1988 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#ident	"$Header: fuser.c 1.15 90/04/25 $"

/*			The last swap based fuser for the 3B5
			and the 3B2 is rev 1.17 */

#include <nlist.h>
#include <fcntl.h>
#include <pwd.h>
#include <stdio.h>

#include <sys/param.h>
#include <sys/types.h>
#include <sys/devsw.h>

#include <sys/ufsdir.h>
#include <sys/timer.h>
#include <sys/vnode.h>
#include <sys/vm.h>

#include <sys/signal.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#define INKERNEL
#include <sys/file.h>
#include <sys/ufsinode.h>
#include <sys/snode.h>
#include <sys/user.h>
#undef INKERNEL
#include <sys/proc.h>
#include <sys/var.h>
#include <sys/mount.h>

/* symbol names */
#define V_STR		"v"
#define PROC_STR	"proc"
#define FILE_STR	"file"
#define INODE_STR	"inode"
#define	VNODEOPS_STR	"nfs_vnodeops"
#define	NOFILE_STR	"Nofile"

#define MEMF "/dev/kmem"
#define SYSTEM "/unix"

/*	types of files, etc */
#define LOCAL	1
#define	REMOTE	2
#define	RESRC	3

#define	TYP_LOCAL_FILE		1
#define	TYP_LOCAL_FILSYS	2
#define	TYP_REMOTE_FILE		3
#define TYP_REMOTE_FILSYS	4
#define PROCSZ (((unsigned)v.ve_proc - proc_adr) / sizeof(struct proc))
#define INODESZ (((unsigned)v.ve_inode - inode_adr) / sizeof(struct inode))
#define FMIN(X, Y)  ((X < Y) ? X : Y)
#define valid_file(X) (X != 0)

void exit(), perror(), free();
extern char *malloc();
extern int errno;

int file_type, mypid, gun = 0, usrid = 0, cflag = 0, fflag = 0;

struct file_info {
	struct vnode vnode;
	struct inode inode;
	unsigned vn_addr;
} fia, fib;

int mdev = -1;
int nfs_vnodeops_addr;


struct nlist nl[] = {
	{V_STR},
	{PROC_STR},
	{FILE_STR},
	{INODE_STR},
	{VNODEOPS_STR},
	{NOFILE_STR},
	{""}
};

copylval(symbol, ptr)
char *symbol;
unsigned *ptr;
{		/* Copies the lvalue of the UNIX symbol "symbol" into
		 * the variable pointed to by "ptr". The lvalue of
		 * "symbol" is read from SYSTEM.
		 */
	int i = 0;

	while(nl[i].n_name[0] != '\0') {
		if(!strcmp(symbol,nl[i].n_name)) {
			if(nl[i].n_value == 0) {
				return(-1);
			}
			*ptr = nl[i].n_value;
			return(0);
		}
		i++;
	}
	return(1);
}

copyrval(symbol, ptr, size, memdev)
char *symbol, *ptr;
int memdev, size;
{	/* Copies the rvalue of the UNIX symbol "symbol" into the structure
	 * pointed to by "ptr". The rvalue is read from memory at the location
	 * specified by the value of "symbol" in the name list file "nl_file".
	 */
	int mem;
	unsigned lval;

	if(copylval(symbol, &lval)) {
		printf("Copylval: did not find '%s'\n",symbol);
		exit(1);
	}
	if(memdev >= 0) mem = memdev;
	else if((mem = open(MEMF, O_RDONLY)) == -1)
	{	perror(MEMF);
		exit(1);
	}
	rread(mem, (long) lval, ptr, size);
	if(memdev != mem) close(mem);
}

file_to_vnode(file_adr, in, memdev)
int memdev;
unsigned *in;
struct file *file_adr;
{	/* Takes a pointer to one of the system's file entries
	 * and copies the address of vnode to which it refers into the area
	 * pointed to by "in". "file_adr" is a system pointer, usually
	 * from the user area. */
	int mem;
	struct file f;
	union /* used to convert pointers to numbers */
	{	char	*ptr;
		unsigned addr; /* must be same length as char * */
	} convert;

	convert.ptr = (char *) file_adr;
	if(convert.addr == 0) return(-1);
	if(memdev >= 0) mem = memdev;
	else if((mem = open(MEMF, O_RDONLY)) == -1)
	{	perror(MEMF);
		return(-1);
	}

	/* read the file table entry */
	rread(mem, (long) convert.addr, (char *) &f, sizeof f);
	if(memdev != mem) close(mem);
	if(f.f_flag) {
		*in = (unsigned)f.f_vnode;
		return(0);
	}
	else return(-1);
}

struct ofile_tab *
get_ofile(tab_addr, memdev, in_core)
struct ofile_tab *tab_addr;
int memdev, in_core;
{
	static struct ofile *ofile_ext;
	static struct ofile_tab *ofile_tab;
	static unsigned int szofile_tab;
	static unsigned int szofile_ext;
	int  size;
	int mem;

	if(memdev >= 0) mem = memdev;
	else if((mem = open(MEMF, O_RDONLY)) == -1)
	{	perror(MEMF);
		return((struct ofile_tab *)-1);
	}

	if (ofile_tab == 0) {
		long Nofile_addr;
		long Nofile;

		copylval(NOFILE_STR, &Nofile_addr);
		rread(mem, Nofile_addr, (char *)&Nofile, sizeof(Nofile));
		szofile_tab = sizeof(struct ofile_tab) +
				(Nofile - 1) * sizeof(struct ofile);
		ofile_tab = (struct ofile_tab *)malloc(szofile_tab);
		if (!ofile_tab) {
			perror("malloc of ofile_tab");
			exit(1);
		}
		szofile_ext = 0;
		ofile_ext = 0;
	}
	/*
	 * None of this stuff ever gets swapped out.
	 */
	rread(mem, (long)tab_addr, (char *)ofile_tab, szofile_tab);

	if ((long)ofile_tab->oft_ofile !=
	     (long)tab_addr + ((long)ofile_tab->oft_lofile - (long)ofile_tab)) {
		if (szofile_ext < ofile_tab->oft_nofile * sizeof(struct ofile)) {
			szofile_ext = ofile_tab->oft_nofile *
					sizeof(struct ofile);
			if (ofile_ext)
				ofile_ext = (struct ofile *)realloc(szofile_ext);
			else
				ofile_ext = (struct ofile *)malloc(szofile_ext);
		}
		rread(mem, (long)ofile_tab->oft_ofile, (char *)ofile_ext,
			szofile_ext);
		ofile_tab->oft_ofile = ofile_ext;
	} else
		ofile_tab->oft_ofile = ofile_tab->oft_lofile;

	return(ofile_tab);
}

struct var v;
unsigned proc_adr;
unsigned file_adr, inode_adr;
struct proc p, my_p;

main(argc, argv)	/* exits with errcnt <= 254 */
int argc;
char **argv;
{
	int mem, newfile = 0, errcnt = 0, tmperr;
	register i, j, k, l;
	struct user *ublock;
	char *ub;
	struct ofile *ofile_ptr;
	struct ofile_tab *ofile_tab, *get_ofile();
	union /* used to convert pointers to numbers */
	{
		char     *ptr;
		unsigned addr;
	} convert;
	int status;

	/* once only code */
	if(argc < 2) {
		fprintf(stderr, "Usage:  %s [-cfku] files [-] [[-cfku] files]\n"
				, argv[0]);
		exit(1);
	}

	/* open file to access memory */
	if((mem = open(MEMF, O_RDONLY)) == -1) {
		tmperr = errno;
		fprintf(stderr, "%s: ", argv[0]);
		errno = tmperr;
		perror(MEMF);
		exit(1);
	}
	/* get values of system variables */
	if(nlist(SYSTEM, nl) == -1) {
		perror("nlist:");
		exit(1);
	}

	/* get values of system variables and address of process table */
	(void) copyrval(V_STR, (char *) &v, sizeof v, mem);
	ub = malloc(ctob(UPAGES) + 511);
	ub = (char *)(((int)ub + 511) &~ 511);
	ublock = UBTOUSER(ub);
	(void) copyrval(PROC_STR, (char *) &proc_adr, sizeof proc_adr, mem);
	(void) copyrval(FILE_STR, (char *)&file_adr, sizeof file_adr, mem);
	(void) copyrval(INODE_STR, (char *)&inode_adr, sizeof inode_adr, mem);
	if(copylval(VNODEOPS_STR, &nfs_vnodeops_addr)) {
		nfs_vnodeops_addr = 0;
	}

	mypid = getpid();
	/*
	 * find my proc slot in case I have to look for remote files
	 */
	for(j = 0; j < PROCSZ; j++) {
		/* read in the per-process info */
		rread(mem, (long) (proc_adr + j * sizeof my_p),
				(char *) &my_p, sizeof my_p);
		if(my_p.p_pid == mypid) { 
			break;
		}
	}
	/* for each argunent on the command line */
	for(i = 1; i < argc; i++) {
		if(argv[i][0] == '-') {
			/* options processing */
			if(newfile) {
				gun = 0;
				usrid = 0;
				cflag = 0;
				fflag = 0;
			}
			newfile = 0;
			for(j = 1; argv[i][j] != '\0'; j++)
			switch(argv[i][j]) {
			case 'k':
				gun++; 
				break;
			case 'u':
				usrid++; 
				break;
			case 'c':
				cflag++;
				fflag = 0;
				break;
			case 'f':
				fflag++;
				cflag = 0;
				break;
			default:
				fprintf(stderr,
					"Illegal option %c ignored.\n",
						argv[i][j]);
			}
			continue;
		} else
			newfile = 1;

		fflush(stdout);
	
		/* First print file name on stderr (so stdout (pids) can
		 * be piped to kill) */
		fprintf(stderr, "%s: ", argv[i]);

		/* then convert the path into an vnode */
		if(path_to_vnode(argv[i], mem) != 0) {
			errcnt++;
			continue;
		}
		/* then for each process */
		for(j = 0; j < PROCSZ; j++) {
			/* read in the per-process info */
			rread(mem, (long) (proc_adr + j * sizeof p),
					(char *) &p, sizeof p);
			if(p.p_stat == 0 
			|| p.p_stat == SZOMB 
			|| p.p_stat == SIDL) {
				continue;
			}

			/* and the user area */
			read_user(&p, ub, mem);

			fib.vn_addr = (unsigned)ublock->u_cdir;
			if((status = ckuse("c", mem)) == -1) {
				errcnt++;
				continue;
			}
			/* killed by report(), so uarea is gone */
			if(status == 9) {
				continue;
			}
	
			fib.vn_addr = (unsigned)ublock->u_rdir;
			if((status = ckuse("r", mem)) == -1) {
				errcnt++;
				continue;
			}
			/* killed by report(), so uarea is gone */
			if(status == 9) {
				continue;
			}

			/* then, for each file */
			ofile_tab = get_ofile(ublock->u_ofile_tab, mem,
						p.p_flag & SLOAD);
			ofile_ptr = ofile_tab->oft_ofile;

			l = ofile_tab->oft_lastfile;
			for(k = 0; k <= l; k++){
				if(!valid_file(ofile_ptr[k].of_file))
					continue;
				if(file_to_vnode(ofile_ptr[k].of_file, 
					&fib.vn_addr, mem))
					continue;
				if((status = ckuse(strncmp(ublock->u_comm,
				"server",6) == 0 ? "S" : "", mem)) == -1) {
					errcnt++;
					break;
				}
				/* killed by report(), so uarea is gone */
				if(status == 9) {
					continue;
				}
			}

			/* check for text files */
			if(cktxt(mem, ublock) == -1) {
				errcnt++;
				continue;
			}
		}

		fprintf(stderr,"\n");
	}

	/* newfile is set when a file is found.  if it isn't set here,
	 * then the user did not use correct syntax  */
	if (! newfile) {
		fprintf(stderr, "fuser: missing file name\n");
		fprintf(stderr, "Usage: %s [-ku] files [-] [[-ku] files]\n", argv[0]);
		errcnt=(errcnt ? errcnt : 1);
	}
	if(errcnt > 254)
		errcnt = 254;	/* return is unsigned char */
	exit(errcnt);
}

cktxt(mem, ub)
int mem;
struct user *ub;
{
	struct mfile mf;
	union /* used to convert pointers to numbers */
	{
		char     *ptr;
		unsigned addr;
	} convert;

	convert.ptr = (char *)ub->u_mmap[0].mm_handle;
	if(convert.addr == 0)
		return(0);
	rread(mem, convert.addr, (char *)&mf, sizeof(struct mfile));
	fib.vn_addr = (unsigned)mf.mf_vp;
	return(ckuse("t", mem));
}

ckuse(fc, mem)
char *fc;
int mem;
{
	struct vnode a_vnode, b_vnode;

	if (fib.vn_addr == NULL)
		return(0);

	rread(mem, (long)fib.vn_addr, (char *)&fib.vnode, sizeof(fib.vnode));
	switch(file_type) {
	case TYP_LOCAL_FILE:
		if(fib.vnode.v_op == (struct vnodeops *)nfs_vnodeops_addr) {
			return(0);
		}
		rread(mem, (long)fib.vnode.v_data, (char *)&fib.inode, 
			sizeof(fib.inode));
		if(fia.inode.i_number == fib.inode.i_number) {
			rread(mem, (long)fia.inode.i_vnode.v_devvp, &a_vnode,
				sizeof(struct vnode));
			rread(mem, (long)fib.inode.i_vnode.v_devvp, &b_vnode,
				sizeof(struct vnode));
			if(a_vnode.v_rdev == b_vnode.v_rdev) {
				return(report(fc));
			}
		}
		break;
	case TYP_LOCAL_FILSYS:
		if(fib.vnode.v_op == (struct vnodeops *)nfs_vnodeops_addr) {
			return(0);
		}
		rread(mem, (long)fib.vnode.v_data, (char *)&fib.inode, 
			sizeof(fib.inode));
		if(fib.inode.i_vnode.v_devvp == (struct vnode *)NULL) {
			return(0);
		}
		rread(mem, (long)fib.inode.i_vnode.v_devvp, &b_vnode,
			sizeof(struct vnode));
		if(cflag) {
			rread(mem, (long)fia.inode.i_vnode.v_devvp, &a_vnode,
				sizeof(struct vnode));
			if(a_vnode.v_rdev == b_vnode.v_rdev) {
				return(report(fc));
			}
			return(0);
		}
		if(fia.inode.i_rdev == b_vnode.v_rdev) {
			return(report(fc));
		}
		break;
	case TYP_REMOTE_FILE:
		if(fib.vnode.v_op != (struct vnodeops *)nfs_vnodeops_addr) {
			return(0);
		}
		if(fia.vn_addr == fib.vn_addr) {
			return(report(fc));
		}
		break;
	case TYP_REMOTE_FILSYS:
		if(fib.vnode.v_op != (struct vnodeops *)nfs_vnodeops_addr) {
			return(0);
		}
		rread(mem, (long)fia.vn_addr, (char *)&fia.vnode, 
			sizeof(fia.vnode));
		if(fia.vnode.v_vfsp == fib.vnode.v_vfsp) {
			return(report(fc));
		}
		break;
	}
	return(0);
}

report(fc)
char *fc;
{
	if (p.p_pid == mypid) {
		return(1);
	}
	fprintf(stdout, " %7d", (int) p.p_pid);
	fflush(stdout);
	switch(fc[0]) {
	case 'c':	/* current dir */
	case 'p':	/* parent dir */
	case 'r':	/* root dir */
	case 't':	/* text file busy */
	case 's':	/* sleeping on receive descriptor */
	case 'S':	/* Server */
		fprintf(stderr,"%c", fc[0]);
	}
	if(usrid) 
		puname();
	if((fc[0] != 'S') && (gun)) {
		if(kill((int) p.p_pid, 9)) {
			return(-1);
		}
		return(9);
	}
	return(1);
}

path_to_vnode(path, memdev)
char *path;
int memdev;
{

	static struct user *my_ublock;
	static char *my_ub;
	static int mem;
	static int fd = 0;
	struct stat s, parent_s;
	struct ofile *ofile_ptr;
	char parent_path[MAXPATHLEN];
	int i;


	file_type = 0;

	if(memdev >= 0) mem = memdev;
	else if ((mem = open(MEMF, O_RDONLY)) == -1) {
		perror(MEMF);
		return(-1);
	}
	if(my_ub == 0) {
		my_ub = malloc(ctob(UPAGES) + 511);
		my_ub = (char *)(((int)my_ub + 511) &~ 511);
		my_ublock = UBTOUSER(my_ub);
	}
	if(fd) {
		close(fd);
		fd = 0;
	}
	if(stat(path, &s) == -1) {
		fprintf(stderr, " couldn't stat file.\n");
		return(-1);
	}
	if ((s.st_mode & S_IFMT) == S_IFDIR) {
		strcpy(parent_path, path);
		strcat(parent_path, "/..");
		if(stat(parent_path, &parent_s) == -1) {
			fprintf(stderr, " couldn't stat file.\n");
			return(-1);
		}
	} else {
		parent_s.st_dev = s.st_dev;
	}

	if(major(s.st_dev) == 255) {
		struct ofile_tab *ofile_tab, *get_ofile();

		/* open the file so that we can find it's vnode */
		if((fd = open(path, 0)) == NULL) {
			fprintf(stderr, " could not open.\n");
			return(-1);
		}

		/* read in our u-area to find vnode in our fd table */
		read_user(&my_p, my_ub, mem);
		ofile_tab = get_ofile(my_ublock->u_ofile_tab, mem, 1);
		ofile_ptr = ofile_tab->oft_ofile;
		if(!valid_file(ofile_ptr[fd].of_file)) {
			fprintf(stderr, " could not open.\n");
			return(-1);
		}
		if(file_to_vnode(ofile_ptr[fd].of_file, &fia.vn_addr, mem)) {
			fprintf(stderr, " could not open.\n");
			return(-1);
		}
		rread(mem, (long)fia.vn_addr, (char *)&fia.vnode,
			sizeof(fia.vnode));

		/*
		 * This means the file is on a remote system.  If cflag
		 * is set then we are looking for all the users under
		 * this directory otherwise it's just a regular file.
		 */
		if (cflag) {
			if (parent_s.st_dev == s.st_dev) {
				fprintf(stderr, " is not a mount point\n");
				return(-1);
			}
			file_type = TYP_REMOTE_FILSYS;
		} else {
			file_type = TYP_REMOTE_FILE;
		}
	} else {
		/*
		 * This just a local file.
		 */
		if (get_vnode(s, &fia, mem) == -1 ) {
			return(-1);
		}

		rread(mem, (long)fia.vnode.v_data, (char *)&fia.inode, 
				sizeof(fia.inode));
		if (((s.st_mode & IFMT) == IFBLK) && (!fflag)) {
			/*
			 * Want to find all files below this one
			 */
			file_type = TYP_LOCAL_FILSYS;
			cflag = 0;
		} else if ((s.st_dev != parent_s.st_dev) && (cflag)) {
			/*
			 * Want to find all users using a file below
			 * this directory.
			 */
			file_type = TYP_LOCAL_FILSYS;
		} else if (cflag) {
			/*
			 * Picked the -c but the file is not a mount point.
			 */
			fprintf(stderr, " is not a mount point\n");
			return(-1);
		} else {
			/*
			 * Want to find just this particular file.
			 */
			file_type = TYP_LOCAL_FILE;
		}
	}
	return(0);
}

get_vnode(s, fi, memdev)
struct stat s;
struct file_info *fi;
int memdev;
{
	static struct inode *inode_buf=0;
	static int i, mem;
	struct vnode idevvnode;

	if(!inode_buf) {
		inode_buf = (struct inode *)malloc(sizeof(struct inode) * 
						   v.v_inode);
		if(memdev >= 0) mem = memdev;
		else if ((mem = open(MEMF, O_RDONLY)) == -1) {
			perror(MEMF);
			return(-1);
		}
		rread(mem, (long)inode_adr, (char *)inode_buf, 
		      (int)(sizeof(struct inode) * v.v_inode));
	}

	for (i=0; i<INODESZ; i++) {
		rread(mem, inode_buf[i].i_devvp, &idevvnode,
						sizeof (idevvnode));
		if ((s.st_dev == idevvnode.v_rdev) && 
		    (s.st_ino == inode_buf[i].i_number)) {
			fi->vn_addr = (unsigned)((unsigned)inode_adr + 
					   (unsigned)&(inode_buf[i].i_vnode) - 
					   (unsigned)inode_buf);
			rread(mem, (long)fi->vn_addr, (char *)&fi->vnode,
				sizeof(fi->vnode));
			return(0);
		}
	}
	fprintf(stderr, " couldn't find inode for file.\n");
	return(-1);
}

puname()
{	struct passwd *getpwuid(), *pid;

	pid = getpwuid((int)p.p_uid);
	if(pid == NULL) return;
	fprintf(stderr, "(%s)", pid->pw_name);
}

read_user(pr, ub, memdev)
struct proc *pr;
char *ub;
int memdev;
{	/* Copies the system's user area (in memory) pointed
	 * to by "pr" into the structure pointed to by "u"
	 */

	int mem;
	int size;
	long u_addr;

	if (pr->p_flag & SLOAD) {	/* proc is in core */
		if(memdev >= 0) mem = memdev;
		else if((mem = open(MEMF, O_RDONLY)) == -1) {
			perror(MEMF);
			exit(1);
		}
		u_addr = (int)pr->p_uarea;
		size = sizeof(struct user);
		rread(mem, u_addr, (char *)UBTOUSER(ub), size);
	} else {			/* proc is swapped out */
		if(mdev < 0)
			if((mdev = open("/dev/swap", O_RDONLY)) == -1) {
				perror("/dev/swap");
				exit(1);
			}
		u_addr = (long) dtob(pr->p_swaddr);
		size = ctob(UPAGES);
		rread(mdev, u_addr, ub, size);
	}
}

rread(device, position, buffer, count)
char *buffer;
int count, device;
long position;
{	/* Seeks to "position" on device "device" and reads "count"
	 * bytes into "buffer". Zeroes out the buffer on errors.
	 */
	int i, tmperr;
	long lseek();

	if(lseek(device, position, 0) == (long) -1) {
		tmperr = errno;
		fprintf(stderr, "Seek error for file number %d: ", device);
		errno = tmperr;
		perror("");
		for(i = 0; i < count; buffer++, i++) *buffer = '\0';
		return;
	}
	if(read(device, buffer, (unsigned) count) == -1) {
		tmperr = errno;
		fprintf(stderr, "Read error for file number %d: ", device);
		errno = tmperr;
		perror("");
		for(i = 0; i < count; buffer++, i++) *buffer = '\0';
	}
}
