/* fuser.c  -  identify processes using files. Written by Werner Almesberger */


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <dirent.h>
#include <pwd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/sched.h>
#include "signals.h"


#define PROC_BASE  "/proc"
#define UID_UNKNOWN -1
#define NAME_FIELD 20 /* space reserved for file name */

#define REF_FILE   1	/* an open file */
#define REF_ROOT   2	/* current root */
#define REF_CWD    4	/* current directory */
#define REF_EXE    8	/* executable */
#define REF_MMAP  16	/* mmap'ed file or library */

#define FLAG_KILL  1	/* kill process */
#define FLAG_UID   2	/* show uid */
#define FLAG_VERB  4	/* show verbose output */
#define FLAG_DEV   8	/* show all processes using this device */

#define COMM_LEN sizeof(dummy.comm)


typedef struct proc_dsc {
    pid_t pid;
    int ref_set;
    int uid; /* must also accept UID_UNKNOWN */
    struct proc_dsc *next;
} PROC_DSC;

typedef struct file_dsc {
    char *name;
    dev_t dev;
    ino_t ino;
    int flags,sig_num;
    PROC_DSC *procs;
    struct file_dsc *next;
} FILE_DSC;


static FILE_DSC *files = NULL;
static struct task_struct dummy;
static int all = 0,found_proc = 0;


static void check_file(char *path,pid_t pid,int type)
{
    struct stat st1,st2;
    FILE_DSC *file;
    PROC_DSC **proc,*this;

    if (stat(path,&st1) < 0) return;
    for (file = files; file; file = file->next)
	if ((st1.st_ino == file->ino || (file->flags & FLAG_DEV)) &&
	  st1.st_dev == file->dev) {
	    for (proc = &file->procs; *proc; proc = &(*proc)->next)
		if ((*proc)->pid >= pid) break;
	    if ((*proc)->pid == pid) this = *proc;
	    else {
		if (!(this = malloc(sizeof(PROC_DSC)))) {
		    perror("malloc");
		    exit(1);
		}
		this->pid = pid;
		this->ref_set = 0;
		this->uid = UID_UNKNOWN;
		this->next = *proc;
		*proc = this;
		found_proc = 1;
	    }
	    this->ref_set |= type;
	    if ((file->flags & (FLAG_UID | FLAG_VERB)) && this->uid ==
	      UID_UNKNOWN && lstat(path,&st2) >= 0) this->uid = st2.st_uid;
	}
}


static void check_dir(char *rel,pid_t pid,int type)
{
    DIR *dir;
    struct dirent *de;
    char path[PATH_MAX+1];

    if (!(dir = opendir(rel))) return;
    while (de = readdir(dir))
	if (strcmp(de->d_name,".") && strcmp(de->d_name,"..")) {
	    sprintf(path,"%s/%s",rel,de->d_name);
	    check_file(path,pid,type);
	}
    (void) closedir(dir);
}


static void scan_proc(void)
{
    DIR *dir;
    struct dirent *de;
    char path[PATH_MAX+1];
    pid_t pid;

    if (!(dir = opendir(PROC_BASE))) {
	perror(PROC_BASE);
	exit(1);
    }
    while (de = readdir(dir))
	if (pid = atoi(de->d_name)) {
	    sprintf(path,"%s/%d",PROC_BASE,pid);
	    if (chdir(path) >= 0) {
		check_file("root",pid,REF_ROOT);
		check_file("cwd",pid,REF_CWD);
		check_file("exe",pid,REF_EXE);
		check_dir("lib",pid,REF_MMAP);
		check_dir("mmap",pid,REF_MMAP);
		check_dir("fd",pid,REF_FILE);
	    }
	}
    (void) closedir(dir);
}


static void show_files(void)
{
    FILE_DSC *file;
    PROC_DSC *proc;
    FILE *f;
    struct passwd *pw;
    char *name,*scan;
    char tmp[10],path[PATH_MAX+1],comm[COMM_LEN+1];
    int length,header,first,dummy;

    header = 1;
    for (file = files; file; file = file->next)
	if (file->procs || all) {
	    length = 0;
	    for (scan = file->name; *scan; scan++)
		if (*scan == '\\') {
		    printf("\\\\");
		    length += 2;
		}
		else if (*scan > ' ' && *scan <= '~') {
			putchar(*scan);
			length++;
		    }
		    else {
			printf("\\%03o",*scan);
			length += 4;
		    }
	    putchar(':');
	    while (length < NAME_FIELD) {
		putchar(' ');
		length++;
	    }
	    first = 1;
	    for (proc = file->procs; proc; proc = proc->next)
		if (!(file->flags & FLAG_VERB)) {
		    if (proc->ref_set & REF_FILE) printf("%6d",proc->pid);
		    if (proc->ref_set & REF_ROOT) printf("%6dr",proc->pid);
		    if (proc->ref_set & REF_CWD) printf("%6dc",proc->pid);
		    if (proc->ref_set & REF_EXE) printf("%6de",proc->pid);
		    if (proc->ref_set & REF_MMAP) printf("%6dm",proc->pid);
		    if (file->flags & FLAG_UID && proc->uid != UID_UNKNOWN)
			if (pw = getpwuid(proc->uid))
			    printf("(%s)",pw->pw_name);
			else printf("(%d)",proc->uid);
		}
		else {
		    if (header) {
			if (length > NAME_FIELD)
			    printf("\n%*s",NAME_FIELD+1,"");
			printf(" USER       PID ACCESS COMMAND\n");
			header = first = 0;
		    }
		    sprintf(path,PROC_BASE "/%d/stat",proc->pid);
		    strcpy(comm,"???");
		    if (f = fopen(path,"r")) {
			(void) fscanf(f,"%d (%[^)]",&dummy,comm);
			(void) fclose(f);
		    }
		    if (proc->uid == UID_UNKNOWN) name = "???";
		    else if (pw = getpwuid(proc->uid)) name = pw->pw_name;
			else sprintf(name = tmp,"%d",proc->uid);
		    if (!first) printf("%*s",NAME_FIELD+1,"");
		    else if (length > NAME_FIELD)
			    printf("\n%*s",NAME_FIELD+1,"");
		    first = 0;
		    printf(" %-8s %5d %c%c%c%c%c  ",name,proc->pid,
		      proc->ref_set & REF_FILE ? 'f' : '.',proc->ref_set &
		      REF_ROOT ? 'r' : '.',proc->ref_set & REF_CWD ? 'c' : '.',
		      proc->ref_set & REF_EXE ? 'e' : '.',proc->ref_set &
		      REF_MMAP ? 'm' : '.');
		    for (scan = comm; *scan; scan++)
			if (*scan == '\\') printf("\\\\");
			else if (*scan > ' ' && *scan <= '~') putchar(*scan);
			    else printf("\\%03o",(unsigned char) *scan);
		    putchar('\n');
		}
	    if (!(file->flags & FLAG_VERB) || first) putchar('\n');
	    if (file->flags & FLAG_KILL)
		for (proc = file->procs; proc; proc = proc->next)
		    if (kill(proc->pid,file->sig_num) < 0) {
			sprintf(tmp,"kill %d",proc->pid);
			perror(tmp);
		    }
	}
}


static void usage(void)
{
    fprintf(stderr,"usage: fuser [ -a | -q ] [ -signal ] [ -kmuv ] filename "
      "... [ - ] [ -signal ]\n%13s[ -kmuv ] filename ...\n","");
    fprintf(stderr,"       fuser -l\n\n");
    fprintf(stderr,"    -a      display unused files too\n");
    fprintf(stderr,"    -k      kill processes accessing that file\n");
    fprintf(stderr,"    -l      list signal names\n");
    fprintf(stderr,"    -m      mounted FS\n");
    fprintf(stderr,"    -s      silent operation\n");
    fprintf(stderr,"    -signal send signal instead of SIGKILL\n");
    fprintf(stderr,"    -u      display user ids\n");
    fprintf(stderr,"    -v      verbose output\n");
    fprintf(stderr,"    -       reset options\n\n");
    exit(1);
}


int main(int argc,char **argv)
{
    struct stat st;
    FILE_DSC *new,*last;
    char path[PATH_MAX+1];
    int flags,silent,sig_number,no_files;

    flags = silent = 0;
    sig_number = SIGKILL;
    no_files = 1;
    last = NULL;
    if (argc < 2) usage();
    if (argc == 2 && !strcmp(argv[1],"-l")) {
	list_signals();
	return 0;
    }
    while (--argc) {
	argv++;
	if (**argv == '-')
	    if (!argv[0][1]) {
		flags = 0;
		sig_number = SIGKILL;
	    }
	    else while (*++*argv)
		    switch (**argv) {
			case 'a':
			    all = 1;
			    break;
			case 'k':
			    flags |= FLAG_KILL;
			    break;
			case 'm':
			    flags |= FLAG_DEV;
			    break;
			case 's':
			    silent = 1;
			    break;
			case 'u':
			    flags |= FLAG_UID;
			    break;
			case 'v':
			    flags |= FLAG_VERB;
			    break;
			default:
			    if (isupper(**argv)) {
				sig_number = get_signal(*argv,"fuser");
				argv[0][1] = 0;
				break;
			    }
			    usage();
		    }
	else {
	    no_files = 0;
	    if (stat(*argv,&st) < 0) {
		perror(*argv);
		continue;
	    }
	    if (!(new = malloc(sizeof(FILE_DSC)))) {
		perror("malloc");
		exit(1);
	    }
	    if (!(new->name = strdup(*argv))) {
		perror("strdup");
		exit(1);
	    }
	    new->flags = flags;
	    new->sig_num = sig_number;
	    new->procs = NULL;
	    new->next = NULL;
	    new->dev = st.st_dev;
	    new->ino = st.st_ino;
	    if (flags & FLAG_DEV)
		if (S_ISBLK(st.st_mode)) new->dev = st.st_rdev;
		else if (S_ISDIR(st.st_mode)) {
			sprintf(path,"%s/.",*argv);
			if (stat(*argv,&st) >= 0) new->dev = st.st_dev;
		    }
	    if (last) last->next = new;
	    else files = new;
	    last = new;
	}
    }
    if (no_files || (all && silent)) usage();
    scan_proc();
    if (!silent) show_files();
    return found_proc ? 0 : 1;
}
