[comp.sys.att] Unix PC fuser

ford@kenobi.UUCP (Mike Ditto) (10/04/87)

Here is the fuser(1M) command I wrote upon discovering its absence in the
Unix PC development set.  It is a compatible replacement for the standard
System V command, which is documented under fuser(1M) in the Unix Programmer's
Manual, and in the Unix PC Development Set manuals.

There is one known limitation of this version:  It will not work properly
if a process is actually "swapped out".  Normal virtual-memory paging doesn't
cause any problem, but if the kernel decides to move a process' user area
to the swap area, that process will not be checked by fuser.  Note, however,
that I HAVE NEVER SEEN THIS HAPPEN on the Unix PC, and I did try to make it
happen (by running lots of emacses, etc.).  It never did.  It may be that
the Unix PC never really "swaps" a process in that way.  The only reason this
version does not handle this condition is that the way to handle it is not
documented, and since I could not make it happen, I could not determine the
procedure empirically.  I don't think it will ever be a problem anyway.


Compilation instructions:

Just compile it and make it setuid-root.  If you want a makefile, use this:
($INSTALL can be "/bin/cp", $LBIN can be "/usr/bin", use whatever you like.)

fuser : fuser.c
	$(CC) $(CFLAGS) -o fuser fuser.c
	$(INSTALL) fuser $(LBIN)
	chmod 4755 $(LBIN)/fuser	# note that you MUST be root 
	chown root $(LBIN)/fuser	# when you do this chmod & chown



"GNU does not eliminate			-=] Mike "Ford" Ditto [=-
all the world's problems,		kenobi!ford@crash.CTS.COM
only some of them."			...!crash!kenobi!ford


Here it is, "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 quality free 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]=='-')
	{
	    if ((*argv)[1])
	    {
		register char c, *i;

		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
		kflag=uflag=0;
	}
	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)
		    if (kill(proc[i].p_pid, SIGKILL))
		    {
			sprintf(buf, "%s: can't kill process %d",
				progname, proc[i].p_pid);
			perror(buf);
			exit(1);
		    }
	    }
	}
}