[comp.sources.unix] v12i049: Who's using that file?

rsalz@uunet.UU.NET (Rich Salz) (10/27/87)

Submitted-by: crash!kenobi!ford
Posting-number: Volume 12, Issue 49
Archive-name: fuser

[  This program takes a filename, searches the kernel's open file
   table, and determines who has that file open.  You can to kill the
   offending process(es).  This is useful for backups, e.g.  A port of
   this to BSD and other Unices would be nice.  --r$  ]

Here is the latest source to my version of the fuser(1M) command, written
and running on the Unix PC.  A man page and makefile are included.

Verify that the macros in "Makefile" are correct for you (LBIN, for example
should be where you want the program to be installed), and run make AS ROOT.
It is important to do the make as root so that the resulting program will
have the set-uid bit set, allowing it to read the kernel's memory.


Some notes to anyone who wants to port this program to something other
than the Unix PC:

The Unix PC has "tunable kernel parameters", which mean that things that
are constants on most older Unix systems are variable.  This version of
fuser reads the values of these variables from the kernel's memory.  I have
tried to make most of this transparent, for ease of porting to a more plain-
vanilla Unix.  For example, I have 'int' varialbes called 'NOFILE', 'NPROC',
etc., which emulate the #defines present in 'standard' Unix.  To make it
run on such systems, it should be possible to remove these variables and
the code that sets them, and the program will use the #defines from
<sys/*.h>.  The only other significant change is that because NOFILE is
variable, the u_ofile field in the user structure is a pointer rather than
an array.  This means that you can delete the code that copies the u_ofile
list since it is already right in the user structure.


					-=] Ford [=-

"GNU does not eliminate			(In Real Life:  Michael Ditto)
all the world's problems,		kenobi!ford@crash.CTS.COM
only some of them." -rms		...!crash!kenobi!ford


Here's the shar file:

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	fuser.1
#	Makefile
#	fuser.c
export PATH; PATH=/bin:$PATH
echo shar: extracting "'fuser.1'" '(1947 characters)'
if test -f 'fuser.1'
then
	echo shar: will not over-write existing file "'fuser.1'"
else
cat << \SHAR_EOF > 'fuser.1'
.TH FUSER 1M
.SH NAME
fuser \- find processes using a file or filesystem
.SH SYNOPSIS
.B fuser
[-ku] files [...]
.SH DESCRIPTION
.I Fuser
searches the kernel's internal tables to find all processes that are
accessing the listed
.I files.
If one of the
.I files
is a block special file (such as a disk) then fuser finds processes that
are accessing any file on that device.
.P
.I Fuser
lists on its standard output the process ID of each process found
to be using any of the files
specified.  The process IDs will be followed by the letters
.B c,
.B r,
or
.B p,
if the file is open as the current directory, root directory, or
parent directory of the process, respectively.  (The kernel has a process'
parent directory "open" for internal use under certain conditions.)
.SH OPTIONS
The -k option causes
.I fuser
to attempt to kill each process found with the SIGKILL signal (normal
permission controls apply; see kill(2)).
.P
The -u option causes
.I fuser
to print, in parentheses, the user ID of the owner of each process listed.
.P
Options may be re-specified between filenames; a '-' argument by itself
turns off the -k and -u options.
.P
The process IDs are written to standard output, one line per file searched
for.  Other output is written to standard error.
.SH EXAMPLES
fuser /tmp/foo
.br
.RS
This prints the process IDs of all processes that have the file /tmp/foo open.
.RE
.P
fuser -u /dev/fp021
.br
.RS
This finds all processes that are accessing any file on the disk /dev/fp021.
The process IDs and user names of the process owners are listed.
This is useful to find out why a disk can not be unmounted.
.RE
.P
fuser -k /dev/fp021
.br
umount /dev/fp021
.br
.RS
If run by super-user, this kills all processes that are preventing
the disk /dev/fp021 from being unmounted, and then unmounts the disk.
.RE
.SH BUGS
If a process's user area is swapped (happens on some versions of UNIX)
fuser will print a warning and ignore that process.
SHAR_EOF
if test 1947 -ne "`wc -c < 'fuser.1'`"
then
	echo shar: error transmitting "'fuser.1'" '(should have been 1947 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'Makefile'" '(164 characters)'
if test -f 'Makefile'
then
	echo shar: will not over-write existing file "'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
SHELL=/bin/sh
INSTALL=ln
LBIN=/usr/lbin
CFLAGS=-O

fuser : fuser.o
	$(CC) $(CFLAGS) -o fuser fuser.o
	chmod 4755 fuser
	chown root fuser
	$(INSTALL) fuser $(LBIN)

SHAR_EOF
if test 164 -ne "`wc -c < 'Makefile'`"
then
	echo shar: error transmitting "'Makefile'" '(should have been 164 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'fuser.c'" '(9081 characters)'
if test -f 'fuser.c'
then
	echo shar: will not over-write existing file "'fuser.c'"
else
cat << \SHAR_EOF > 'fuser.c'
/************************************************************
 *
 * This program was written by me, Mike "Ford" Ditto, and
 * I hereby release it into the public domain in the interest
 * of promoting the development of free, quality software
 * for the hackers and users of the world.
 *
 * Feel free to use, copy, modify, improve, and redistribute
 * this program, but keep in mind the spirit of this
 * contribution; always provide source, and always allow
 * free redistribution (shareware is fine with me).  If
 * you use a significant part of this code in a program of
 * yours, I would appreciate being given the appropriate
 * amount of credit.
 *				-=] Ford [=-
 *
 ************************************************************/

#include <stdio.h>
#include <fcntl.h>
#include <ctype.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/sysmacros.h>
#include <sys/stat.h>
#include <sys/tune.h>
#include <sys/inode.h>
#include <sys/file.h>
#include <sys/user.h>
#include <sys/proc.h>
#include <sys/signal.h>
#include <a.out.h>

/* get rid of meaningless NOFILE from param.h */
#ifdef NOFILE
#undef NOFILE
#endif

extern char *sbrk();
extern long lseek();
extern void perror(), exit();
extern struct passwd *getpwuid();


char *progname;

#define tuhiaddr (mysyms[0].n_value)
#define inodeaddr (mysyms[1].n_value)
#define fileaddr (mysyms[2].n_value)
#define procaddr (mysyms[3].n_value)
#define nofileaddr (mysyms[4].n_value)

struct nlist mysyms[] =
{
    { "tuhi", },
    { "inode", },
    { "file", },
    { "proc", },
    { "nofile", },
    { (char *)0, },
};

char buf[BUFSIZ];

int kmem, mem, kflag, uflag;
int NINODE, NFILE, NPROC, NOFILE;

struct inode *inode;
struct file *file;
struct proc *proc;


/* main program for fuser(1M), a program which lists */
/* processes that are using the given file(s) */
main(argc, argv)
int argc;
char *argv[];
{
    int status=0;

    progname = *argv;

    setup();

    while (++argv,--argc)
	if ((*argv)[0]=='-')
	{
	    register char c, *i;

	    kflag=uflag=0;

	    i = *argv+1;
	    while (c= *i++) switch(c)
	    {
	    case 'k':
		++kflag;
		break;
	    case 'u':
		++uflag;
		break;
	    default:
		fprintf(stderr, "%s: bad flag `-%c'\n", progname, c);
		fprintf(stderr,
			"Usage: %s [-ku] files [[-] [-ku] files]\n",
			progname);
		return -1;
	    }
	}
	else
	    status += fuser(*argv);

    return status;
}


/* a fast, zeroizing, memory allocator for things */
/* that will never need to be freed */
char *myalloc(nbytes)
long nbytes;
{
    register char *ptr = sbrk((int)nbytes);

    if ((long)ptr < 0L)
    {
	sprintf(buf, "%s: no memory!", progname);
	perror(buf);
	exit(1);
    }

    return ptr;
}


/* one-time setup of main data structures from the kernel */
setup()
{
    struct tunable tune;

    if ( (kmem=open("/dev/kmem", O_RDONLY)) < 0 )
    {
	sprintf(buf, "%s: can't open /dev/kmem", progname);
	perror(buf);
	exit(1);
    }

    if ( (mem=open("/dev/mem", O_RDONLY)) < 0 )
    {
	sprintf(buf, "%s: can't open /dev/mem", progname);
	perror(buf);
	exit(1);
    }

    if (nlist("/unix", mysyms))
    {
	sprintf(buf, "%s: can't nlist /unix", progname);
	perror(buf);
	exit(1);
    }

    setuid(getuid());

    kcopy((char *)&NOFILE, nofileaddr, (long) sizeof NOFILE);

#ifdef DEBUG
    fprintf(stderr, "tuhi:	0x%08lx\n", tuhiaddr);
#endif DEBUG
    kcopy((char *)&tune, tuhiaddr, (long) sizeof tune);

    /* do indirection on these addresses, since they */
    /* are just pointers in the kernel */
    kcopy((char *)&inodeaddr, inodeaddr, (long) sizeof inodeaddr);
    kcopy((char *)&fileaddr, fileaddr, (long) sizeof fileaddr);
    kcopy((char *)&procaddr, procaddr, (long) sizeof procaddr);

#ifdef DEBUG
    fprintf(stderr, "inode:	0x%08lx\n", inodeaddr);
    fprintf(stderr, "file:	0x%08lx\n", fileaddr);
    fprintf(stderr, "proc:	0x%08lx\n", procaddr);
#endif DEBUG

    NINODE = tune.ninode;
    NFILE = tune.nfile;
    NPROC = tune.nproc;

#ifdef DEBUG
    fprintf(stderr, "NOFILE:	%d\n", NOFILE);
    fprintf(stderr, "NINODE:	%d\n", NINODE);
    fprintf(stderr, "NFILE:	%d\n", NFILE);
    fprintf(stderr, "NPROC:	%d\n", NPROC);
#endif DEBUG

    inode = (struct inode *)myalloc((long) sizeof (struct inode) * NINODE);
    file = (struct file *)myalloc((long) sizeof (struct file) * NFILE);
    proc = (struct proc *)myalloc((long) sizeof (struct proc) * NPROC);

    kcopy((char *)inode, inodeaddr, (long) sizeof (struct inode) * NINODE);
    kcopy((char *)file, fileaddr, (long) sizeof (struct file) * NFILE);
    kcopy((char *)proc, procaddr, (long) sizeof (struct proc) * NPROC);
}


/* copy bytes from physical address space to this process */
pcopy(caddr, paddr, nbytes)
char *caddr;
long paddr;
long nbytes;
{
    if ( lseek(mem, paddr, 0)<0L ||
	read(mem, caddr, (unsigned)nbytes) != nbytes )
    {
	sprintf(buf, "%s: can't read /dev/mem", progname);
	perror(buf);
	exit(1);
    }
}


/* copy bytes from kernel address space to this process */
kcopy(caddr, kaddr, nbytes)
char *caddr;
long kaddr;
long nbytes;
{
    if ( lseek(kmem, kaddr, 0)<0L ||
	read(kmem, caddr, (unsigned)nbytes) != nbytes )
    {
	sprintf(buf, "%s: can't read /dev/kmem", progname);
	perror(buf);
	exit(1);
    }
}


/* Return a pointer to a local copy of the user structure */
/* for process number `procidx'.  Returns NULL if procidx */
/* refers to an invalid (not-in-use or otherwise) slot. */
struct user *getuser(procidx)
int procidx;
{
    static struct user **users;
    struct file **ofile;
    long upage;

    if (!proc[procidx].p_stat ||
	proc[procidx].p_stat == SIDL ||
	proc[procidx].p_stat == SZOMB)
	return 0;

    if (!(proc[procidx].p_flag & SLOAD))
    {
	/* can't handle swapped process yet */
	fprintf(stderr, "%s: can't handle swapped process %d (flag=%05x)\n",
		progname, proc[procidx].p_pid, proc[procidx].p_flag);
	return 0;
    }

    if (!users)
	users = (struct user **)myalloc((long) sizeof (struct user *) * NPROC);

    if (!users[procidx])
    {
	upage = (long)ctob(proc[procidx].p_addr[0]);

	/* allocate and copy in the user structure */
	users[procidx] = (struct user *)myalloc((long) sizeof (struct user));
	pcopy((char *)(users[procidx]),
	      upage + U_OFFSET,
	      (long) sizeof (struct user));

	/* allocate and copy in the list of file pointers */
	ofile = (struct file **)myalloc((long) sizeof (struct file *) * NOFILE);
	pcopy((char *)ofile,
	      upage+(long)(users[procidx]->u_ofile)-VPG_BASE,
	      (long) sizeof (struct file *) * NOFILE);
	users[procidx]->u_ofile = ofile;
    }

    return users[procidx];
}


/* find all users of the file `name' */
fuser(name)
char *name;
{
    register i;
    int filesys;
    struct stat Stat;

    if (stat(name, &Stat))
    {
	sprintf(buf, "%s: can't stat %s", progname, name);
	perror(buf);
	return 1;
    }

    /* see if we are looking for a whole filesystem */
    filesys = ((Stat.st_mode&S_IFMT) == S_IFBLK);

#ifdef DEBUG
    if (filesys)
	fprintf(stderr, "looking for files on dev=%d,%d\n",
		bmajor(Stat.st_rdev), minor(Stat.st_rdev));
    else
	fprintf(stderr, "looking for dev=%d,%d, ino=%d\n",
		bmajor(Stat.st_dev), minor(Stat.st_dev), Stat.st_ino);
#endif DEBUG

    for ( i=0 ; i<NINODE ; ++i )
    {
	if ( inode[i].i_count &&
	     (filesys
		 ? (brdev(inode[i].i_dev) == Stat.st_rdev)
		 : (brdev(inode[i].i_dev) == Stat.st_dev &&
		    inode[i].i_number == Stat.st_ino)) )
	{
#ifdef DEBUG
	    fprintf(stderr, "Found it!  inode[%d], i_size is %ld\n",
		   i, inode[i].i_size);
#endif DEBUG

	    iuser((struct inode *)inodeaddr + i);
	}
    }

    putchar('\n');

    return 0;
}


#define CHECK(kaddr, type) if (kaddr==kinode) { if (++flag==1) printf(" %d", proc[i].p_pid); if (type) putchar(type); }

/* find all users of the inode at kernel address `kinode' */
iuser(kinode)
struct inode *kinode;
{
    register int i, j;
    int flag;
    struct user *user;
    struct passwd *pwd;

#ifdef DEBUG
    fprintf(stderr, "Looking for users of inode at kernel address 0x%08lx\n",
	    kinode);
#endif DEBUG

    for ( i=0 ; i<NPROC ; ++i )
	if (user = getuser(i))
	{
#ifdef DEBUG
	    fprintf(stderr, "%03d: pid=%5d addr[0]=%05x addr[1]=%05x swaddr=%05x\n",
		    i, proc[i].p_pid, proc[i].p_addr[0], proc[i].p_addr[1],
		    proc[i].p_swaddr);
#endif DEBUG

#ifdef DEBUG
	    fprintf(stderr, "	user = 0x%08lx\n", user);
	    fprintf(stderr, "	user->u_ofile = 0x%08lx\n", user->u_ofile);
#endif DEBUG

	    fflush(stderr);
	    flag=0;
	    CHECK(user->u_cdir, 'c');
	    CHECK(user->u_rdir, 'r');
	    CHECK(user->u_pdir, 'p');
	    for ( j=0 ; !flag && j<NOFILE ; ++j )
		if (user->u_ofile[j])
		    CHECK(file[user->u_ofile[j]-(struct file *)fileaddr].f_inode, 0);
	    fflush(stdout);

	    if (flag)
	    {
		if (uflag)
		{
		    if ( (pwd=getpwuid((int)proc[i].p_uid)) )
			fprintf(stderr, "(%s)", pwd->pw_name);
		    else
			fprintf(stderr, "(%d)", proc[i].p_uid);
		}
		if (kflag && proc[i].p_pid)
		    if (kill(proc[i].p_pid, SIGKILL))
		    {
			sprintf(buf, "%s: can't kill process %d",
				progname, proc[i].p_pid);
			perror(buf);
		    }
	    }
	}
}
SHAR_EOF
if test 9081 -ne "`wc -c < 'fuser.c'`"
then
	echo shar: error transmitting "'fuser.c'" '(should have been 9081 characters)'
fi
fi # end of overwriting check
#	End of shell archive
exit 0