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