v18i056: Find who has files open on BSD, Sun, etc.

Rich Salz rsalz at uunet.uu.net
Thu Mar 23 09:06:51 AEST 1989


Submitted-by: abe at mace.cc.purdue.edu (Vic Abell)
Posting-number: Volume 18, Issue 56
Archive-name: ofiles

[  Just the thing to see who's preventing you from unmounting that disk...
   --r$  ]

Show owner of open file or network connection.  Reports owner, process ID,
access type, command and inode number.  The update looks up the owner of a
network connection from its Protocol Control Block address (netstat -A).
The program also underwent a general cleanup and lint was removed.  The
man page was redone.  Ofiles compiles and runs on 4.3BSD, DYNIX 3.0.12
(Balance) and 3.0.14 (Symmetry), SunOS 4.0 and ULTRIX 2.2.

Vic Abell, Purdue University Computing Center, abe at mace.cc.purdue.edu

#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	README
#	Makefile
#	ofiles.c
#	ofiles.8l
# By:	Vic Abell (Purdue University)
echo shar: extracting README '(1315 characters)'
sed 's/^XX//' << \SHAR_EOF > README
XXOfiles shows the owner of an open file or network connection.
XX
XXC. Spencer is the original author of ofiles.  Michael Ditto, Tom Dunigan,
XXAlexander Dupuy, Gary Nebbett and Richard Tobin made contributions.  Mike
XXSpitzer, Ray Moody and Vik Lall of the Purdue University Computing Center
XX(PUCC) adapted ofiles to a number of different UNIX environments.  I added
XXthe network connection option.
XX
XXOfiles compiles and runs on 4.3BSD, DYNIX 3.0.12 (Balance) and 3.0.14
XX(Symmetry), SunOS 4.0 and ULTRIX 2.2.  It is free of lint on those systems
XXaccording to the lint libraries in use at PUCC.
XX
XXSpecial notes:
XX
XX	1.  A "generic" Makefile is included.  You may have to adjust it
XX	    to your local conditions.
XX
XX	2.  SunOS 4.0 loading requires "-lkvm", so modify the Makefile
XX	    accordingly when you port ofiles to your Sun system.
XX
XX	3.  Ofiles needs permission to read /dev/drum (swap files), /dev/kmem
XX	    and /dev/mem.  PUCC uses a "setgid kmem" approach - i. e.,
XX	    /dev/{drum,kmem,mem} are in the kmem group and ofiles has mode
XX	    2755, group kmem.  The Makefile install rule implements this
XX	    convention.
XX
XXThe ofiles distribution includes:
XX
XX	README		this file
XX	Makefile	a generic Makefile
XX	ofiles.c	source
XX	ofiles.8l	man page
XX
XXVic Abell, abe at mace.cc.purdue.edu
XXPurdue University Computing Center
XXMarch 22, 1989
SHAR_EOF
if test 1315 -ne "`wc -c README`"
then
echo shar: error transmitting README '(should have been 1315 characters)'
fi
echo shar: extracting Makefile '(738 characters)'
sed 's/^XX//' << \SHAR_EOF > Makefile
XX#
XX#	Makefile for ofiles
XX#
XX
XXPROG=	ofiles
XXBIN=	${DESTDIR}/usr/local/etc
XX
XXI=/usr/include
XXS=/usr/include/sys
XXL=/usr/include/local
XX
XXINCLUDE=
XXDEBUG=	-O
XXCDEFS=  
XXCFLAGS= ${DEBUG} ${CDEFS} ${INCLUDE}
XX
XXHDR=	
XXONEC=	ofiles.c
XXOTHER=	
XXSOURCE=	Makefile ${HDR} ${ONEC} ${OTHER}
XX
XXall: ${PROG}
XX
XX# SunOS 4.0 needs -lkvm in the following rule
XX
XX${PROG}:
XX	${CC} -o $@ ${CFLAGS} ${ONEC} 
XX
XXclean: FRC
XX	rm -f Makefile.bak ${PROG} *.o a.out core errs tags
XX
XXinstall: all FRC
XX	install -cs -m 2755 -g kmem ${PROG} ${BIN}
XX
XXlint: ${HDR} ${ONEC} FRC
XX	lint ${CDEFS} ${INCLUDE} ${ONEC}
XX
XXprint: source FRC
XX	lpr -J'${PROG} source' ${SOURCE}
XX
XXsource: ${SOURCE}
XX
XXspotless: clean
XX	rcsclean ${SOURCE}
XX
XXtags: ${HDR} ${ONEC}
XX	ctags -t ${HDR} ${ONEC}
XX
XX${SOURCE}:
XX	co -q $@
XX
XXFRC:
XX
SHAR_EOF
if test 738 -ne "`wc -c Makefile`"
then
echo shar: error transmitting Makefile '(should have been 738 characters)'
fi
echo shar: extracting ofiles.c '(24404 characters)'
sed 's/^XX//' << \SHAR_EOF > ofiles.c
XX/*	ofiles.c
XX *
XX *	ofiles [-d ] [-k nlist core] [-n] [-p] args
XX *
XX *	Show owner of open file or network connection.
XX *
XX *	Reports owner, process ID, access type, command and inode number.
XX *
XX *	-d		select verbose debugging output
XX *
XX *	-k nlist core	specifies alternative name list and core files
XX *			(DYNIX only)
XX *
XX *	-n		interpret names as network connection, hexadecimal
XX *			Protocol Control Block (PCB) addresses
XX *
XX *	-p  		gives brief (pids only) report
XX *
XX *	names		file names, file system names or network connection
XX *			PCB addresses
XX *
XX *	Stat each file or file system argument and scan the process table,
XX *	looking for a match in the associated user structure's file lists.
XX *
XX *	Follow each PCB arg to the Internet Protocol Control Block (INPCB),
XX *	thence to the socket; then scan the file table to find the file
XX *	structure address associated with the socket; finally, scan the
XX *	process table, looking for a nacth in the associated user structure's
XX *	file lists.
XX *
XX *	Doesn't handlle remote NFS files.
XX */
XX
XX/*
XX *	Authors:
XX *
XX *	The orignal author is:
XX *
XX *		C. Spencer
XX *
XX *	Contributors include:
XX *
XX *		Michael Ditto
XX *		Tom Dunigan
XX *		Alexander Dupuy
XX *		Greg Nebbett
XX *		Richard Tobin
XX *
XX *	From the Purdue University Computing Center:
XX *
XX *		Mike Spitzer 		converted to 4.3BSD, DYNIX 3.0.1[24]
XX *		Ray Moody		SunOS 4.0 and ULTRIX 2.2
XX *		Vik Lall
XX *
XX *		Vic Abell		added socket option and removed lint
XX *				
XX */
XX
XX#ifndef lint
XXstatic char rcsid[]="$Header: /usr/src/local/etc/ofiles/RCS/ofiles.c,v 1.5 89/01/26 16:20:11 vik Exp $";
XX#endif /* lint */
XX
XX#include <sys/param.h>
XX#include <sys/dir.h>
XX#include <sys/user.h>
XX
XX#ifdef DYNIX
XX#define KERNEL
XX#include <sys/file.h>
XX#include <sys/vnode.h>
XX#include <sys/inode.h>
XX#undef KERNEL
XX#else
XX#define KERNEL
XX#include <sys/file.h>
XX#ifndef sun
XX#include <sys/inode.h>
XX#endif
XX#undef KERNEL
XX#endif
XX
XX#include <machine/pte.h>
XX#if !defined(ultrix) && !defined(sun)
XX#include <machine/machparam.h>
XX#endif
XX#include <sys/proc.h>
XX#include <nlist.h>
XX#include <sys/stat.h>
XX#include <pwd.h>
XX#include <fstab.h>
XX#include <sys/vmmac.h>
XX#include <stdio.h>
XX
XX#ifdef sun
XX#include <sys/vnode.h>
XX#include "/usr/src/sys/specfs/snode.h"
XX#include <ufs/inode.h>
XX#include <kvm.h>
XXkvm_t	*kd;
XX#endif
XX
XX#ifdef ultrix
XX#include <sys/gnode.h>
XX#include <sys/gnode_common.h>
XX#include <machine/param.h>
XX#endif
XX
XX#include <sys/socket.h>
XX#include <sys/socketvar.h>
XX#include <net/route.h>
XX#include <netinet/in.h>
XX#include <netinet/in_pcb.h>
XX#include <netinet/tcp.h>
XX#include <netinet/tcp_fsm.h>
XX#include <netinet/tcp_timer.h>
XX#include <netinet/tcp_var.h>
XX
XX#define CDIR	01
XX#define OFILE	02
XX#define RDIR	04
XX#define SHFILE	010
XX#define EXFILE	020
XX#define SOCKET  040
XX
XXchar *namelist;
XXchar *corefile;
XXint k_opt = 0;
XXint n_opt = 0;
XX
XX#ifdef	ultrix
XX#define	ls_t	long
XX#else
XX#define ls_t	off_t
XX#endif
XX
XX#ifdef	sun
XXchar	*sprintf();
XX#endif
XX
XXls_t lseek(), vtophys();
XX
XXvoid eread(), eseek();
XX#ifdef	ultrix
XXvoid exit(), nlist(), perror();
XX#endif
XX
XXint nproc;		/* number of entries in proc table 		*/
XXint mem;		/* fd for /dev/mem				*/
XXint kmem;
XXint swap;		/* fd for /dev/swap				*/
XXlong procbase;
XXint ppid = -1;		/* previously display PID */
XX
XXint dirinode;		/* directory (CDIR or RDIR) inode */
XXint opninode;		/* save inode of open file */
XXint pids_only = 0;	/* if non-zero, only output process ids	*/
XX
XXchar *progname;
XXstruct nlist nl[] = {
XX#define	X_PROC		0
XX	{ "_proc" },
XX#define X_NPROC 	1
XX	{"_nproc"},
XX#define X_USRPTMA	2
XX    	{"_Usrptmap"},	
XX#define X_USRPT		3
XX	{"_usrpt"},
XX#define X_SYSMAP	4
XX	{"_Sysmap"},
XX#define	SFIL		5
XX	{ "_file" },
XX#define	SNFILE		6
XX	{ "_nfile" },
XX	{ "" },
XX};
XX
XX#ifndef	DYNIX
XXstruct pte *usrpt, *Usrptma;
XX#endif
XX
XXint debug;
XX
XXmain(argc, argv)
XX	int 	argc;
XX	char	*argv[];
XX{
XX
XX#ifdef ultrix
XX	struct gnode *i, *getinode();
XX#else
XX	struct inode *i, *getinode();
XX#endif
XX	struct stat s;
XX	struct user *u, *getuser();
XX	struct proc p;
XX	register int filen, flags, procn;
XX	register char *filename, *fsname;
XX	struct fstab *fs, *getfsfile(), *getfsspec();
XX	char *getsockfp(), *rindex();
XX	struct file *fp;
XX	int ax, err, findf, nmct;
XX	char *ap;
XX	int exitval = 0;
XX
XX#ifdef	lint
XX/*
XX * The following code satisfies lint for KERNEL symbols.
XX * This program is lint-free under 4.3BSD, DYNIX 3.0.1[24], SunOS 4.0
XX * and ULTRIX 2.2, using the lint libraries of the systems at the
XX * Purdue University Computing Center.
XX */
XX#if	!defined(ultrix) && !defined(DYNIX) && !defined(sun)
XX	long lintlong;
XX#endif
XX#ifdef	ultrix
XX	struct nch *lintnch;
XX	float lintfloat;
XX#endif
XX#if	!defined(DYNIX)
XX	file = fileNFILE = NULL;
XX	fp = file;
XX	fp = fileNFILE;
XX	nfile = 0;
XX	filen = nfile;
XX#endif
XX#if	!defined(ultrix) && !defined(DYNIX) && !defined(sun)
XX	inode = inodeNINODE = rootdir = NULL;
XX	i = inode;
XX	i = inodeNINODE;
XX	i = rootdir;
XX	ninode = 0;
XX	nextinodeid = 0l;
XX	lintlong = nextinodeid;
XX	nextinodeid = lintlong;
XX	filen = ninode;
XX#endif
XX#ifdef	sun
XX	tcp_ttl = 0;
XX	filen  = tcp_ttl;
XX#endif
XX#ifdef	ultrix
XX	nch = NULL;
XX	lintnch = nch;
XX	nch = lintnch;
XX	nchsize = 0;
XX	filen = nchsize;
XX	tcp_alpha = tcp_beta = 0.0;
XX	lintfloat = tcp_alpha;
XX	lintfloat = tcp_beta;
XX	tcp_alpha = lintfloat;
XX#endif
XX#endif	/* lint */
XX
XX	if ((progname = rindex(argv[0], '/')))
XX		progname++;
XX	else
XX		progname = argv[0];
XX
XX	if (argc == 1) {
XX
XXusage:
XX
XX#ifdef	DYNIX
XX		(void) fprintf(stderr,
XX			"usage: %s [-d ] [-k nlist core] [-n] [-p] names\n",
XX			progname);
XX#else
XX		(void) fprintf(stderr,
XX			"usage: %s [-d ] [-n] [-p] names\n", progname);
XX#endif
XX		(void) fprintf(stderr, "\t-d    = select verbose debugging output\n");
XX#ifdef	DYNIX
XX		(void) fprintf(stderr,
XX			"\t-k    = use specified nlist and core files\n");
XX#endif
XX		(void) fprintf(stderr,
XX			"\t-n    = interpret names as network connection, hexadecimal,\n");
XX		(void) fprintf(stderr,
XX			"\t        Protocol Control Block (PCB) addresses, as supplied\n");
XX		(void) fprintf(stderr,
XX			"\t        by netstat's -A option\n");
XX		(void) fprintf(stderr, "\t-p    = print only process IDs\n");
XX		(void) fprintf(stderr,
XX			"\tnames = file names or PCB addresses\n");
XX		exit(exitval);
XX	}
XX
XX	/* check for switches */
XX	for (err = 0, ax = 1; ax < argc; ax++) {
XX		ap = argv[ax];
XX		if (*ap++ != '-')
XX			break;
XX		while (*ap) {
XX			switch (*ap++) {
XX
XX			case 'd':
XX				debug = 1;
XX				break;
XX#ifdef	DYNIX
XX			case 'k':
XX				if ((ax + 2) >= argc) {
XX					(void) fprintf(stderr,
XX						"%s: no nlist/core after -k\n",
XX						progname);
XX					err++;
XX				} else {
XX					namelist = argv[++ax];
XX					corefile = argv[++ax];
XX					k_opt = 1;
XX					continue;
XX				}
XX				break;
XX#endif
XX			case 'n':
XX				n_opt++;
XX				break;
XX
XX			case 'p':
XX				pids_only = 1;
XX				break;
XX
XX			default:
XX				(void) fprintf(stderr,
XX					"%s: unknown switch - %c\n",
XX					progname, *(ap - 1));
XX				err++;
XX			}
XX		}
XX	}
XX	if (ax >= argc) {
XX		(void) fprintf(stderr, "%s: no names specified\n", progname);
XX		err++;
XX	}
XX	if (err) {
XX		exitval = 1;
XX		goto usage;
XX	}
XX
XX#ifdef sun
XX    	if ((kd = kvm_open (NULL, NULL, NULL, O_RDONLY)) == 0) {
XX		(void) fprintf(stderr, "%s: can't access memory: ",
XX			progname);
XX        	perror ("");
XX		exit(1);
XX	}
XX#endif
XX	if ((mem = open("/dev/mem", 0)) < 0) {
XX		(void) fprintf(stderr, "%s: /dev/mem: ", progname);
XX		perror("");
XX		exit(1);
XX	}
XX	if (k_opt) {
XX		if ((kmem = open(corefile, 0)) < 0) {
XX			(void) fprintf(stderr, "%s: %s: ",
XX				progname, corefile);
XX			perror("");
XX			exit(1);
XX		}
XX	} else {
XX		if ((kmem = open("/dev/kmem", 0)) < 0) {
XX			(void) fprintf(stderr, "%s: /dev/kmem: ", progname);
XX			perror("");
XX			exit(1);
XX		}
XX	}
XX	if (!k_opt) 
XX		if ((swap = open("/dev/drum", 0)) < 0) {
XX			(void) fprintf(stderr, "%s: /dev/drum: ", progname);
XX			perror("");
XX			exit(1);
XX		}
XX
XX	getsyms();
XX
XX	for (err = 0, nmct = argc - ax; ax < argc; ax++) {
XX		/* if -n, then arg is a PCB */
XX		if (n_opt) {
XX			if ((filename = getsockfp(argv[ax], &fp)) == NULL) {
XX				err++;
XX				continue;
XX			}
XX			fsname = "";
XX		} else {
XX			/* assume arg is  a filesystem */
XX			if ((fs = getfsfile(argv[ax])) != NULL) {
XX				fsname = argv[ax];
XX				if (strcmp(".", argv[ax]) == 0)
XX					filename = argv[ax];
XX				else
XX					filename = fs->fs_spec;
XX			/* maybe it's the device name for a filesystem */
XX			} else if ((fs = getfsspec(argv[ax])) != NULL) {
XX				filename = argv[ax];
XX				fsname = fs->fs_file;
XX			/* probably a regular file */
XX			} else {
XX				filename = argv[ax];
XX				fsname = "";
XX			}
XX			if (stat(filename, &s)) {
XX				(void) fprintf(stderr, "%s: can't stat %s: ",
XX					progname, filename);
XX				perror("");
XX				err++;
XX				continue;
XX			}
XX			if (debug)
XX				(void) printf(
XX					"stat dev %x ino %d mode %x rdev %x\n",
XX					s.st_dev, s.st_ino, s.st_mode,
XX					s.st_rdev);
XX		}
XX		if (! pids_only) {
XX			(void) printf("%s\t%s", filename, fsname);
XX			if (!n_opt) {
XX				if ((s.st_mode & S_IFMT) == S_IFDIR)
XX					(void) printf("(directory)");
XX			}
XX			(void) printf("\n%-8.8s  %5s  %-6.6s  FD  %-14.14s",
XX				"USER", "PID", "TYPE", "CMD");
XX			if (!n_opt)
XX				(void) printf("  INODE");
XX			(void) printf("\n");
XX		}
XX		for (findf = procn = 0; procn < nproc; procn++) {
XX			procslot(procn, &p);
XX			flags = 0;
XX			if (p.p_stat == 0 || p.p_stat == SZOMB) 
XX				continue;
XX#ifdef sun
XX        		u = kvm_getu(kd, &p);
XX#else
XX			u = getuser(&p);
XX#endif
XX
XX			if ( u == (struct user *)NULL)
XX				continue;
XX			if (debug)
XX				(void) printf("pid %d uid %d cmd %s\n", p.p_pid,
XX					p.p_uid, u->u_comm);
XX			if (!n_opt) {
XX				i = getinode(u->u_rdir, "rdir");
XX				if (check(&s, i)) {
XX					dirinode = s.st_ino;
XX					gotone(u, &p, -1, RDIR);
XX					findf++;
XX				}
XX				i = getinode(u->u_cdir, "cdir");
XX				if (check(&s, i)) {
XX					dirinode = s.st_ino;
XX					gotone(u, &p, -1, CDIR);
XX					findf++;
XX				}
XX			}
XX#ifdef	DYNIX
XX			for (filen = 0; filen < u->u_nofile; filen++)
XX#else
XX			for (filen = 0; filen < NOFILE; filen++)
XX#endif	
XX			{
XX				struct file f;
XX
XX				flags = 0;
XX				if (n_opt) {
XX#ifdef	DYNIX
XX					if (u->u_lofile[filen].of_file != fp)
XX#else
XX					if (u->u_ofile[filen] != fp)
XX#endif
XX						continue;
XX				} else {
XX#ifdef	DYNIX
XX					if (u->u_lofile[filen].of_file == NULL)
XX						continue;
XX#else
XX					if (u->u_ofile[filen] == NULL)
XX						continue;
XX#endif
XX				}
XX
XX				if (k_opt)
XX#ifdef	DYNIX
XX					eseek(kmem, vtophys((ls_t)u->u_lofile[filen].of_file), 0, "file");
XX#else
XX				eseek(kmem, vtophys((ls_t)u->u_ofile[filen]), 0, "file");
XX#endif 
XX				else
XX#ifdef	DYNIX
XX					eseek(kmem, (ls_t)u->u_lofile[filen].of_file, 0, "file");
XX#else
XX				eseek(kmem, (ls_t)u->u_ofile[filen], 0, "file");
XX#endif
XX				eread(kmem, (char *)&f, sizeof(f), "file");
XX
XX				if (f.f_count > 0) {
XX					if (n_opt && f.f_type == DTYPE_SOCKET) {
XX						gotone(u, &p, filen, SOCKET);
XX						findf++;
XX						continue;
XX					}
XX#if	defined(DYNIX) || defined(sun)
XX					if (f.f_type != DTYPE_VNODE)
XX#else
XX					if (f.f_type != DTYPE_INODE)
XX#endif
XX						continue;
XX#if	defined(DYNIX) || defined(sun)
XX					i = getinode((struct vnode *)f.f_data,
XX						"ofile");
XX#else
XX#ifdef	ultrix
XX					i = getinode((struct gnode *)f.f_data,
XX						"ofile");
XX#else
XX					i = getinode((struct inode *)f.f_data,
XX						"ofile");
XX#endif
XX#endif
XX					if (check(&s, i)) {
XX#if !defined(ultrix)
XX						opninode = i->i_number;
XX#else
XX						opninode = (int)i->g_req.gr_number;
XX#endif
XX						flags |= OFILE;
XX						if (f.f_flag & FEXLOCK) {
XX							flags |= EXFILE;
XX						}
XX						if (f.f_flag & FSHLOCK) {
XX							flags |= SHFILE;
XX						}
XX						gotone(u, &p, filen, flags);
XX						findf++;
XX					}
XX				}
XX			}
XX		}
XX		if (findf)
XX			nmct--;
XX		if (! pids_only) {
XX	    		(void) printf("\n");
XX			(void) fflush(stdout);
XX		}
XX	}
XX	if (pids_only && ppid != -1) {
XX		(void) printf("\n");
XX		(void) fflush(stdout);
XX	}
XX	exitval = (err || nmct) ? 1 : 0;
XX	exit(exitval);
XX}		
XX
XX/*
XX * print the name of the user owning process "p" and the pid of that process
XX */
XXgotone(u, p, fd, f)
XX	struct user *u;
XX	struct proc *p;
XX	int fd;
XX	int f;
XX{
XX	char *ty, tybuf[8], *strcat(), *strcpy();
XX	struct passwd *getpwuid();
XX	register struct passwd *pw;
XX
XX	/* only print pids and return */
XX	if (pids_only) {
XX		if (ppid != p->p_pid) {
XX			if (ppid != -1)
XX				(void) printf(" ");
XX			(void) printf("%d", p->p_pid);
XX			(void) fflush(stdout);
XX			ppid = p->p_pid;
XX		}
XX		return;
XX	}
XX	pw = getpwuid((int)p->p_uid);
XX	if (pw)
XX		(void) printf("%-8.8s  ", pw->pw_name );
XX	else
XX		(void) printf("%-8d  ", p->p_uid);
XX	(void) printf("%5d  ", p->p_pid);
XX	if (f & OFILE) {
XX		(void) strcpy(tybuf, "file");
XX		ty = tybuf;
XX		if (f & SHFILE)
XX			(void) strcat(ty, "/s");
XX		else if (f & EXFILE)
XX			(void) strcat(ty, "/x");
XX	} else if (f & CDIR)
XX		ty = "cwd";
XX	else if (f & RDIR)
XX		ty = "rdir";
XX	else if (f & SOCKET)
XX		ty = "sock";
XX	else
XX		ty = "";
XX	(void) printf("%-6.6s  ", ty);
XX	if (fd >= 0)
XX		(void) printf("%2d  ", fd);
XX	else
XX		(void) printf("    ");
XX	(void) printf("%-14.14s", u->u_comm);
XX	if (f & OFILE)
XX		(void) printf("  %5d", opninode);
XX	else if (f & (CDIR|RDIR))
XX		(void) printf("  %5d", dirinode);
XX	(void) printf("\n");
XX	return;
XX}
XX
XX/*
XX * is inode "i" on device "s"? returns TRUE or FALSE 
XX */
XXcheck(s, i)
XX	struct stat *s;
XX#ifdef ultrix
XX	struct gnode *i;
XX#else
XX	struct inode *i;
XX#endif
XX{
XX	if (s == (struct stat *)NULL)
XX		return 0;
XX#ifdef ultrix
XX	if (i == (struct gnode *)NULL)
XX		return 0;
XX	if ((s->st_mode & S_IFMT) == S_IFBLK && s->st_rdev == i->g_dev)
XX			return 1;
XX	else if ((s->st_dev == i->g_dev) && (s->st_ino == i->g_req.gr_number))
XX			return 1;
XX#else
XX	if (i == (struct inode *)NULL) return 0;
XX	if ((s->st_mode & S_IFMT) == S_IFBLK && s->st_rdev == i->i_dev)
XX			return 1;
XX	else if ((s->st_dev == i->i_dev) && (s->st_ino == i->i_number))
XX			return 1;
XX#endif
XX#ifdef sun
XX	else if (s->st_rdev == i->i_dev && i->i_number == 0)
XX			return 1;
XX#endif
XX	else return 0;
XX}
XX
XX
XX/* 
XX *	getinode - read an inode from from mem at address "addr"
XX * 	      return pointer to inode struct. 
XX */
XX#if defined(DYNIX) || defined(sun)
XXstruct inode *
XXgetinode(ip, s)
XX	struct vnode *ip;
XX	char *s;
XX{
XX	static struct inode i;
XX	static struct vnode v;
XX#ifdef	sun
XX	struct snode sn;
XX#endif
XX
XX	if (ip == NULL)
XX		return(NULL);
XX	if (k_opt)
XX		eseek(kmem, vtophys((ls_t)ip), 0, "vnode");
XX	else
XX		eseek(kmem, (ls_t)ip, 0, "vnode");
XX	eread(kmem, (char *)&v, sizeof(v), "vnode");
XX	if (debug)
XX		(void) printf("vnode %s at %x %x dev=%x vtype=%d inode@%x\n",
XX	 		s, ip, v.v_flag, v.v_rdev, v.v_type, v.v_data);
XX	if (k_opt)
XX		eseek(kmem, vtophys((ls_t)v.v_data), 0, "inode");
XX	else
XX		eseek(kmem, (ls_t)v.v_data, 0, "inode");
XX#ifdef	sun
XX	if (v.v_type == VBLK || v.v_type == VCHR || v.v_type == VFIFO) {
XX		eread(kmem, (char *)&sn, sizeof(sn), "snode");
XX		if (debug)
XX			(void) printf(
XX			    "snode %s at %x %x dev=%x realvp=%x bdevvp=%x\n",
XX			    s, ip, sn.s_vnode.v_type, sn.s_dev,
XX			    sn.s_realvp, sn.s_bdevvp);
XX		if (sn.s_realvp || sn.s_bdevvp) {
XX			eseek(kmem,
XX				(sn.s_realvp) ? (ls_t)sn.s_realvp
XX					      : (ls_t)sn.s_bdevvp,
XX				0, "rvnode");
XX			eread(kmem, (char *)&v, sizeof(v), "rvnode");
XX			eseek(kmem, (ls_t)v.v_data, 0, "rinode");
XX		}
XX	}
XX#endif
XX	eread(kmem, (char *)&i, sizeof(i), "inode");
XX	if (debug)
XX		(void) printf("inode %s at %x %x dev=%x inode=%d vtype=%x\n",
XX		    s, v.v_data, i.i_flag, i.i_dev, i.i_number,
XX		    i.i_vnode.v_type);
XX	return &i;
XX}
XX
XX#else
XX/* ARGSUSED */
XX
XX#ifdef ultrix
XXstruct gnode *
XXgetinode(ip, s)
XX	struct gnode *ip;
XX#else
XX
XXstruct inode *
XXgetinode(ip, s)
XX	struct inode *ip;
XX#endif
XX
XX	char *s;
XX{
XX#if defined(ultrix) 
XX	static struct gnode i;
XX#else
XX	static struct inode i;
XX#endif
XX
XX	eseek(kmem, (ls_t)ip, 0, "inode");
XX	eread(kmem, (char *)&i, sizeof(i), "inode");
XX	return &i;
XX}
XX#endif
XX
XX#if !defined(sun)
XX/* 
XX * get user page for proc "p" from core or swap
XX * return pointer to user struct
XX */
XX#ifdef	DYNIX
XXstruct user *
XXgetuser(p)
XX	struct proc *p;
XX{
XX	int btr;
XX	ls_t sp;
XX	static struct user *u = NULL;
XX	char *valloc();
XX
XX	if (u == NULL) {
XX		if ((u = (struct user *) valloc(ctob(UPAGES))) == NULL) {
XX			(void) fprintf(stderr,
XX				"%s: can't allocate space for user structure\n",				progname);
XX			exit(1);
XX		}
XX	}
XX	btr = ctob(UPAGES);
XX	if ((p->p_flag & SLOAD) == 0) {
XX		if (k_opt)
XX			return (struct user *)NULL;
XX		sp = (ls_t) dtob(p->p_swaddr);
XX		if (lseek(swap, sp, 0) != sp) {
XX			if (debug) {
XX				(void) fprintf(stderr,
XX					"%s: error seeking to swap for %d: ",
XX					progname, p->p_pid);
XX				perror("");
XX			}
XX			return (struct user *)NULL;
XX		}
XX		if (read(swap, (char*)u, btr) != btr) {
XX			if (debug) {
XX				(void) fprintf(stderr,
XX				    "%s: error reading swap for %d: ",
XX				    progname, p->p_pid);
XX				perror("");
XX			}
XX			return (struct user *)NULL;
XX		}
XX		if (debug)
XX			(void) printf("read swap\n");
XX	} else {
XX		if (k_opt)
XX			(void) lseek(kmem, vtophys((ls_t)p->p_uarea), L_SET);
XX		else
XX			(void) lseek(kmem, (ls_t)p->p_uarea, L_SET);
XX		if (read(kmem, (char *)u, btr) != btr)
XX			return (struct user *)NULL;
XX	}
XX	return u;
XX}
XX#else
XXstruct user *
XXgetuser(p)
XX	struct proc *p;
XX{
XX	struct pte *ptep, apte;
XX	struct pte mypgtbl[NBPG/sizeof(struct pte)];
XX	int upage;
XX	char *up;
XX	static struct user user;
XX
XX	/* easy way */
XX	if ((p->p_flag & SLOAD) == 0) {
XX		if (k_opt)
XX			return (struct user *)NULL;
XX		(void) lseek(swap, (ls_t)dtob(p->p_swaddr), 0);
XX		if (read(swap, (char *)&user, sizeof(struct user))==0) {
XX			(void) fprintf(stderr,
XX				"%s: can't get swapped user page\n",
XX				progname);
XX			return (struct user *)NULL;
XX		}
XX		if (debug)
XX			(void) printf("read swap\n");
XX	} else { 	/* boo */
XX		ptep = &Usrptma[btokmx(p->p_p0br) + p->p_szpt - 1];
XX
XX		/* get the page table for the user page */
XX		(void) lseek(kmem, (ls_t)ptep, 0);
XX		if (read(kmem, (char *)&apte, sizeof(apte)) == 0) {
XX			(void) fprintf(stderr,
XX				"%s: can't get user page table\n",
XX				progname);
XX			return (struct user *)NULL;
XX		}
XX
XX		/* now get this user's page table */
XX		eseek(mem, (ls_t)ctob(apte.pg_pfnum) ,0, "page tbl");
XX		if (read(mem, (char *)mypgtbl, sizeof(mypgtbl)) == 0) {
XX			(void) fprintf(stderr,
XX				"%s: can't get mypgtbl.\n", progname);
XX			return (struct user *)NULL;
XX		}
XX		/* now collect various pages of u area */
XX		for (upage = 0, up = (char *)&user; upage < sizeof(struct user)/NBPG; upage++) {
XX			eseek(mem, (ls_t)ctob(mypgtbl[NPTEPG-UPAGES+upage].pg_pfnum), 0, "u page");
XX			if (read(mem, up, NBPG) == 0) {
XX				(void) fprintf(stderr,
XX				    "%s: can't get page %d of user area.\n",
XX				    progname, upage);
XX				return(NULL);
XX			}
XX			up += NBPG;
XX		}
XX	}
XX	return &user;
XX}
XX#endif
XX
XX#endif
XX/*
XX * read with error checking
XX */
XXvoid
XXeread(fd, p, size, s)
XX	int fd;
XX	char *p;
XX	int size;
XX	char *s;
XX{
XX	char buf[100];
XX	if (read(fd, p, size) != size) {
XX		if (!k_opt) {
XX			(void) fprintf(stderr, "%s: eread ", progname);
XX			perror("");
XX		}
XX		(void) sprintf(buf, "read error for %s\n", s);
XX		error(buf);
XX	}
XX}
XX
XX/*
XX * seek with error checking
XX */
XXvoid
XXeseek(fd, off, whence, s)
XX	int fd;
XX	ls_t off;
XX	int whence;
XX	char *s;
XX{
XX	ls_t ret;
XX	char buf[100];
XX
XX	if (( ret = lseek(fd, off, whence)) != off) {
XX		(void) sprintf(buf, "seek for %s failed, wanted %o, got %o.\n",
XX			s, off, ret);
XX		error(buf);
XX	}
XX}
XX
XX/*
XX * print mesg "s", don't exit if we are processing a core, 
XX * so that corrupt entries don't prevent further uncorrupted
XX * entries from showing up.
XX */
XXerror(s)
XX	char *s;
XX{
XX	if (s && !k_opt)
XX		(void) fprintf(stderr, "%s: %s", progname, s);
XX	if (!k_opt)
XX		exit(1);
XX}
XX
XX/*
XX * get some symbols form the kernel
XX */
XXgetsyms()
XX{
XX	register i;
XX
XX	if (k_opt) {
XX#ifdef	ultrix
XX		(void) nlist(namelist, nl);
XX#else	/* not ultrix */
XX		if (nlist(namelist, nl) == -1) {
XX			(void) fprintf(stderr,
XX				"%s: can't get name list from %s\n",
XX				progname, namelist);
XX			exit(1);
XX		}
XX#endif	/* ultrix */
XX	} else {
XX#ifdef	ultrix
XX		(void) nlist("/vmunix", nl);
XX#else	/* not ultrix */
XX#ifdef	DYNIX
XX		if (nlist("/dynix", nl) == -1)
XX#else	/* not DYNIX */
XX		if (nlist("/vmunix", nl) == -1)
XX#endif	/* DYNIX */
XX		{
XX			(void) fprintf(stderr,
XX				"%s: can't get name list from %s\n",
XX#ifdef	DYNIX
XX				progname, "/dynix");
XX#else	/* not DYNIX */
XX				progname, "/vmunix");
XX#endif	/* DYNIX */
XX			exit(1);
XX		}
XX#endif	/* ultrix */
XX	}
XX
XX	for (i = 0; i < (sizeof(nl)/sizeof(nl[0]))-1; i++)
XX		if (nl[i].n_value == 0) {
XX			(void) fprintf(stderr, "%s: can't nlist symbol %s\n",
XX				progname, nl[i].n_name);
XX			exit(1);
XX		}
XX
XX		eseek(kmem, (ls_t)(nl[X_PROC].n_value), 0, "procbase 1");
XX		eread(kmem, (char *)&procbase, sizeof(procbase), "procbase 1");
XX		eseek(kmem, (ls_t)(nl[X_NPROC].n_value), 0, "nproc");
XX		eread(kmem, (char *)&nproc, sizeof(nproc), "nproc");
XX
XX#ifndef	DYNIX
XX	Usrptma = (struct pte *)nl[X_USRPTMA].n_value;
XX	usrpt = (struct pte *)nl[X_USRPT].n_value;	/* used by <vmmac.h>*/
XX#endif
XX	return;
XX}
XX
XX/*
XX * read proc table entry "n" into buffer "p"
XX */
XXprocslot(n, p)
XX	int n;
XX	struct proc *p;
XX{
XX	if (k_opt)
XX		eseek(kmem, vtophys((ls_t)(procbase + (long)(n * sizeof(struct proc)))), 0, "proc");
XX	else
XX		eseek(kmem, (ls_t)(procbase + (long)(n * sizeof(struct proc))), 0, "proc");
XX	eread(kmem, (char *)p, sizeof(struct proc), "proc");
XX	return;
XX}
XX
XX/*
XX * When looking at kernel data space through /dev/mem or
XX * with a core file, do virtual memory mapping.
XX */
XXls_t
XXvtophys(vaddr)
XX	ls_t vaddr;
XX{
XX	u_int paddr;
XX	
XX#ifdef	i386
XX	if (vaddr < 8192)
XX		return vaddr;
XX#endif
XX	paddr = nl[X_SYSMAP].n_value;
XX	(void) lseek(kmem, (ls_t)paddr, 0);
XX	(void) read(kmem, (char *)&paddr, sizeof paddr);
XX	paddr = (int)((int *)paddr + (vaddr / NBPG));
XX	(void) lseek(kmem, (ls_t)paddr, 0);
XX	(void) read(kmem, (char *)&paddr, sizeof paddr);
XX#ifndef	i386
XX# define	PTBITS	0x1ff	/* 512 byte pages */
XX#else
XX# define	PTBITS	0xfff	/* 4096 byte pages */
XX#endif
XX
XX	return ((ls_t)(paddr & ~PTBITS) | (vaddr & PTBITS));
XX}
XX
XX/*
XX * get file pointer for socket
XX */
XXchar *
XXgetsockfp(cba, pfp)
XX	char *cba;
XX	struct file **pfp;
XX{
XX	register char *cp;
XX	struct file *socktofile();
XX	struct inpcb inpcb;
XX	static char nmbuf[128];
XX	struct socket sock;
XX	struct tcpcb tcpcb;
XX	long x;
XX
XX/*
XX * Convert PCB address from ASCII to hex.
XX */
XX	for (cp = cba, x = 0l; *cp; cp++) {
XX		x <<= 4;
XX		if (*cp >= '0' && *cp <= '9')
XX			x += *cp - '0';
XX		else if (*cp >= 'a' && *cp <= 'f')
XX			x += *cp - 'a' + 10;
XX		else if (*cp >= 'A' && *cp <= 'F')
XX			x += *cp - 'A' + 10;
XX		else {
XX			(void) fprintf(stderr,
XX				"%s: non-hex address, %s\n", progname, cba);
XX			return(NULL);
XX		}
XX	}
XX/*
XX * Read PCB and make sure it is in LISTEN or ESTABLISHED state.
XX */
XX	eseek(kmem, (ls_t)x, 0, "tcpcb");
XX	eread(kmem, (char *)&tcpcb, sizeof(tcpcb), "tcpcb");
XX	if (tcpcb.t_state < TCPS_LISTEN || tcpcb.t_state > TCPS_ESTABLISHED) {
XX		(void) fprintf(stderr,
XX			"%s: PCB %x not in LISTEN to ESTABLISHED state\n",
XX			progname, x);
XX		return(NULL);
XX	}
XX	if (tcpcb.t_inpcb == (struct inpcb *)0) {
XX		(void) fprintf(stderr,
XX			"%s: PCB %x has no INPCB\n",
XX			progname, x);
XX		return(NULL);
XX	}
XX/*
XX * Read INPCB for PCB and make sure it points back to the PCB.
XX */
XX	eseek(kmem, (ls_t)tcpcb.t_inpcb, 0, "inpcb");
XX	eread(kmem, (char *)&inpcb, sizeof(inpcb), "inpcb");
XX	if ((caddr_t)x != inpcb.inp_ppcb) {
XX		(void) fprintf(stderr,
XX			"%s: INPCB for PCB %x not linked to it\n",
XX			progname, x);
XX		return(NULL);
XX	}
XX/*
XX * Read the socket and make sure it points back to the INPCB.
XX */
XX	eseek(kmem, (ls_t)inpcb.inp_socket, 0, "socket");
XX	eread(kmem, (char *)&sock, sizeof(sock), "socket");
XX	if (sock.so_pcb != (caddr_t)tcpcb.t_inpcb) {
XX		(void) fprintf(stderr,
XX			"%s: socket not linked to INPCB for PCB %x\n",
XX			progname, x);
XX		return(NULL);
XX	}
XX/*
XX * Find the file structure that is linked to the socket.
XX */
XX	if ((*pfp = socktofile((caddr_t)inpcb.inp_socket)) == NULL) {
XX		(void) fprintf(stderr,
XX			"%s: no file structure for socket of PCB %x\n",
XX			progname, x);
XX		return(NULL);
XX	}
XX/*
XX * Construct a pseudo file name and return it.
XX */
XX	(void) sprintf(nmbuf,
XX		"file %lx of socket %lx of INPCB %lx of PCB %lx",
XX		(long)*pfp, (long)inpcb.inp_socket, (long)tcpcb.t_inpcb, x);
XX	return(nmbuf);
XX}
XX
XX/*
XX * Convert a socket address to a file address.
XX */
XXstruct file *
XXsocktofile(s)
XX	caddr_t s;
XX{
XX	register struct file *afile;
XX	char *calloc();
XX	register struct file *fp;
XX	static struct file *ftp;
XX	static int nfile = -1;
XX	static struct file *xfile = NULL;
XX
XX/*
XX * Read the size of file table, allocate space
XX * for it, and read the file table pointer (once).
XX */
XX	if (nfile < 0) {
XX		eseek(kmem, (ls_t)(nl[SNFILE].n_value), 0, "_nfile");
XX		eread(kmem, (char *)&nfile, sizeof(nfile), "_nfile");
XX		xfile = (struct file *) calloc((unsigned)nfile, sizeof (struct file));
XX		eseek(kmem, (ls_t)(nl[SFIL].n_value), 0, "_file");
XX		eread(kmem, (char *)&ftp, sizeof(ftp), "_file");
XX	}
XX/*
XX * Read the file table and search for an in-use
XX * socket file with a matching data address.
XX */
XX	eseek(kmem, (ls_t)ftp, 0, "_file");
XX	eread(kmem, (char *)xfile, nfile * sizeof(struct file), "_file");
XX	for (fp = xfile, afile = ftp; fp < &xfile[nfile]; fp++, afile++) {
XX		if (fp->f_count && fp->f_type == DTYPE_SOCKET
XX		&&  s == fp->f_data)
XX			return(afile);
XX	}
XX	return(NULL);
XX}
SHAR_EOF
if test 24404 -ne "`wc -c ofiles.c`"
then
echo shar: error transmitting ofiles.c '(should have been 24404 characters)'
fi
echo shar: extracting ofiles.8l '(5223 characters)'
sed 's/^XX//' << \SHAR_EOF > ofiles.8l
XX.TH OFILES 8L LOCAL
XX.SH NAME
XXofiles \- show owner of open file or network connection
XX.SH SYNOPSIS
XX.B ofiles
XX[
XX.B \-d
XX] [
XX.B \-k
XX.I nlist
XX.I core
XX] [
XX.B \-n
XX] [
XX.B \-p
XX]
XX.I names
XX.SH DESCRIPTION
XX.I Ofiles
XXdisplays the owner, process identification (PID), type, command and
XXthe number of the inode associated with an open instance of a file
XXor a network connection.
XX.PP
XXAn open file may be a regular file, a file system or a directory;
XXit is specified by its path name.
XXWhen the path name refers to a file system,
XX.I ofiles
XXwill display the owners of all open instances of files in the system.
XX.PP
XXAn open network connection is specified by the kernel address of its
XXProtocol Control Block (PCB), as displayed by
XX.IR netstat (8),
XXwhen its
XX.B \-A
XXoption is specified.
XX.SH OPTIONS
XX.I Ofiles
XXdisplays information about its usage if no options are specified.
XX.TP \w'-kXnlistXcore'u+4
XX.BI \-d
XXThis option selects verbose, debugging output.
XX.TP
XX.BI \-k \ nlist\ core
XXThis option may be used only on DYNIX hosts.
XXIt sets optional name list and core file paths.
XX.IP
XX.I Nlist
XXis the path to the file from which
XX.I ofiles
XXshould obtain the addresses of kernel symbols,
XXinstead of from
XX.IR /dynix .
XX.IP
XX.I Core
XXis the path to the file from which
XX.I ofiles
XXshould obtain the value of kernel symbols,
XXinstead of from
XX.IR /dev/mem .
XX.IP
XXThis option is useful for debugging system crash dumps.
XX.TP
XX.B \-n
XXThis option specifies that the
XX.I name
XXarguments identify network connections by their hexadecimal, Protocol
XXControl Block (PCB) addresses.
XXPCB addresses can be obtained via the
XX.B \-A
XXoption of
XX.IR netstat (1).
XX.IP
XXThis option makes it possible to determine the local processes that
XXare using network connections in the LISTEN through ESTABLISHED states.
XX.TP
XX.B \-p 
XXThis option specifies that
XX.I ofiles
XXshould print process identifiers only \- e. g., so that the output may
XXbe piped to
XX.IR kill (1).
XX.TP
XX.I names
XXThese are path names of files, directories and file systems;
XXor, if the
XX.B \-n
XXoption has been specified, network connections, identified by their
XXhexadecimal Protocol Control Block (PCB) addresses.
XX.SH OUTPUT
XX.I Ofiles
XXdisplays for each
XX.IR name :
XX.TP \w'name/linkages'u+4
XX.I name/linkages
XXfor file paths, an interpretation of the type of name \- file, directory
XXor file system;
XXfor network connections, the kernel address linkages, starting with the file
XXstructure and proceeding through the socket structure and the Internet
XXProtocol Control Block (INPCB) structure to the PCB
XX.TP
XX.B USER
XXthe login name of the user of the process that has
XX.I name
XXopen
XX.TP
XX.B PID
XXthe identifier of the process that has
XX.I name
XXopen
XX.TP
XX.B TYPE
XXa file type explanation:
XX.RS
XX.TP \w'file'u+4
XX.B cwd 
XXif
XX.I name
XXis the current working directory of the process
XX.TP
XX.B file
XXif
XX.I name
XXis being used as a regular file by the process, optionally followed by:
XX.RS
XX.TP
XX.B /s
XXif the process has a shared lock on the file
XX.TP
XX.B /x
XXif the process has an exclusive lock on the file
XX.RE
XX.TP
XX.B rdir
XXif 
XX.I name
XXis the root directory of the process
XX.TP
XX.B sock
XXif
XX.I name
XXis a socket
XX.RE
XX.TP
XX.B FD
XXthe file descriptor number, local to the process
XX.TP
XX.B CMD
XXthe user command that opened
XX.I name
XX.TP
XX.B INODE
XXthe inode number of the file
XX.SH EXAMPLES
XXThis example shows the use of
XX.I ofiles
XXto discover the owner of the open, regular file,
XX.IR /usr/spool/lpd/lock .
XX.PP
XX.RS
XX.nf
XX% ofiles /usr/spool/lpd/lock
XX.br
XX/usr/spool/lpd/lock	
XX.br
XXUSER	PID	TYPE  	FD	CMD	INODE
XX.br
XXroot	110	file/x	 3	lpd	26683
XX.fi
XX.RE
XX.PP
XXThis example shows the use of
XX.IR netstat (1),
XX.IR grep (1)
XXand
XX.I ofiles
XXto identify the local endpoint of the ``smtp'' network connection.
XXThe first column of output from
XX.I netstat
XXis the PCB address; it is used as the
XX.I name
XXargument to
XX.IR ofiles ,
XXalong with the
XX.B \-n
XXoption.
XX.PP
XX.RS
XX.nf
XX% netstat -aA | grep smtp
XX.br
XX80f6770c	tcp	0	0	*.smtp	*.*	LISTEN
XX.br
XX% ofiles -n 80f6770c
XX.br
XXfile 80102b64 of socket 80f6758c of INPCB 80f6780c of PCB 80f6770c	
XX.br
XXUSER	PID	TYPE	FD	CMD
XX.br
XXroot	105	sock	 5	sendmail 
XX.fi
XX.RE
XX.SH DIAGNOSTICS
XXErrors are identified with messages on the standard error file.
XX.PP
XX.I Ofiles
XXreturns a one (1) if any error was detected, including the failure to
XXlocate any
XX.IR names .
XXIt returns a zero (0) if no errors were detected and if it was able to
XXdisplay owner information about all the specified
XX.IR names .
XX.SH BUGS
XX.I Ofiles
XXcan't identify SunOS 4.0 stream files, so it doesn't follow their file
XXstructure pointers correctly when reading their inodes.
XXThat results in the display of erroneous inode numbers for stream files.
XX.PP
XXThe
XX.B \-n
XXoption limits its search to network connections in the LISTEN through
XXESTABLISHED states.
XX.PP
XXSince
XX.I ofiles
XXreads kernel memory in its search for open files and network connections,
XXrapid changes in kernel memory may produce unsatisfactory results.
XX.SH AUTHORS
XXC. Spencer is the original author.
XXMichael Ditto, Tom Dunigan, Alexander Dupuy, Gary Nebbett and Richard Tobin
XXcontributed.
XX.PP
XXMichael Spitzer, Ray Moody, and Vik Lall of the Purdue University Computing
XXCenter converted the program to a variety of UNIX environments.
XX.PP
XXVic Abell of the Purdue University Computing Center added the
XX.B \-n
XXoption.
XX.SH SEE ALSO
XXinode(5),
XXmount(1),
XXkill(1),
XXtcp(4).
SHAR_EOF
if test 5223 -ne "`wc -c ofiles.8l`"
then
echo shar: error transmitting ofiles.8l '(should have been 5223 characters)'
fi
#	End of shell archive
exit 0

-- 
Please send comp.sources.unix-related mail to rsalz at uunet.uu.net.



More information about the Comp.sources.unix mailing list