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);
}
}
}
}