/* vi: set sw=4 ts=4: */
/*
 * tiny fuser implementation
 *
 * Copyright 2004 Tony J. White
 *
 * Licensed under GPLv2, see file LICENSE in this source tree.
 */
//config:config FUSER
//config:	bool "fuser (7 kb)"
//config:	default y
//config:	help
//config:	fuser lists all PIDs (Process IDs) that currently have a given
//config:	file open. fuser can also list all PIDs that have a given network
//config:	(TCP or UDP) port open.

//applet:IF_FUSER(APPLET(fuser, BB_DIR_USR_BIN, BB_SUID_DROP))

//kbuild:lib-$(CONFIG_FUSER) += fuser.o

//usage:#define fuser_trivial_usage
//usage:       "[-msk46] [-SIGNAL] FILE or PORT/PROTO"
//usage:#define fuser_full_usage "\n\n"
//usage:       "Find processes which use FILEs or PORTs\n"
//usage:     "\n	-m	Find processes which use same fs as FILEs"
//usage:     "\n	-4,-6	Search only IPv4/IPv6 space"
//usage:     "\n	-s	Don't display PIDs"
//usage:     "\n	-k	Kill found processes"
//usage:     "\n	-SIGNAL	Signal to send (default: KILL)"

#include "libbb.h"
#include "common_bufsiz.h"

#define MAX_LINE 255

#define OPTION_STRING "mks64"
enum {
	OPT_MOUNT  = (1 << 0),
	OPT_KILL   = (1 << 1),
	OPT_SILENT = (1 << 2),
	OPT_IP6    = (1 << 3),
	OPT_IP4    = (1 << 4),
};

typedef struct inode_list {
	struct inode_list *next;
	ino_t inode;
	dev_t dev;
} inode_list;

struct globals {
	int recursion_depth;
	pid_t mypid;
	inode_list *inode_list_head;
	smallint kill_failed;
	int killsig;
} FIX_ALIASING;
#define G (*(struct globals*)bb_common_bufsiz1)
#define INIT_G() do { \
	setup_common_bufsiz(); \
	G.mypid = getpid(); \
	G.killsig = SIGKILL; \
} while (0)

static void add_inode(const struct stat *st)
{
	inode_list **curr = &G.inode_list_head;

	while (*curr) {
		if ((*curr)->dev == st->st_dev
		 && (*curr)->inode == st->st_ino
		) {
			return;
		}
		curr = &(*curr)->next;
	}

	*curr = xzalloc(sizeof(inode_list));
	(*curr)->dev = st->st_dev;
	(*curr)->inode = st->st_ino;
}

static smallint search_dev_inode(const struct stat *st)
{
	inode_list *ilist = G.inode_list_head;

	while (ilist) {
		if (ilist->dev == st->st_dev) {
			if (option_mask32 & OPT_MOUNT)
				return 1;
			if (ilist->inode == st->st_ino)
				return 1;
		}
		ilist = ilist->next;
	}
	return 0;
}

enum {
	PROC_NET = 0,
	PROC_DIR,
	PROC_DIR_LINKS,
	PROC_SUBDIR_LINKS,
};

static smallint scan_proc_net_or_maps(const char *path, unsigned port)
{
	FILE *f;
	char line[MAX_LINE + 1], addr[68];
	int major, minor, r;
	long long uint64_inode;
	unsigned tmp_port;
	smallint retval;
	struct stat statbuf;
	const char *fmt;
	void *fag, *sag;

	f = fopen_for_read(path);
	if (!f)
		return 0;

	if (G.recursion_depth == PROC_NET) {
		int fd;

		/* find socket dev */
		statbuf.st_dev = 0;
		fd = socket(AF_INET, SOCK_DGRAM, 0);
		if (fd >= 0) {
			fstat(fd, &statbuf);
			close(fd);
		}

		fmt = "%*d: %64[0-9A-Fa-f]:%x %*x:%*x %*x "
			"%*x:%*x %*x:%*x %*x %*d %*d %llu";
		fag = addr;
		sag = &tmp_port;
	} else {
		fmt = "%*s %*s %*s %x:%x %llu";
		fag = &major;
		sag = &minor;
	}

	retval = 0;
	while (fgets(line, MAX_LINE, f)) {
		r = sscanf(line, fmt, fag, sag, &uint64_inode);
		if (r != 3)
			continue;

		statbuf.st_ino = uint64_inode;
		if (G.recursion_depth == PROC_NET) {
			r = strlen(addr);
			if (r == 8 && (option_mask32 & OPT_IP6))
				continue;
			if (r > 8 && (option_mask32 & OPT_IP4))
				continue;
			if (tmp_port == port)
				add_inode(&statbuf);
		} else {
			if (major != 0 && minor != 0 && statbuf.st_ino != 0) {
				statbuf.st_dev = makedev(major, minor);
				retval = search_dev_inode(&statbuf);
				if (retval)
					break;
			}
		}
	}
	fclose(f);

	return retval;
}

static smallint scan_recursive(const char *path)
{
	DIR *d;
	struct dirent *d_ent;
	smallint stop_scan;
	smallint retval;

	d = opendir(path);
	if (d == NULL)
		return 0;

	G.recursion_depth++;
	retval = 0;
	stop_scan = 0;
	while (!stop_scan && (d_ent = readdir(d)) != NULL) {
		struct stat statbuf;
		pid_t pid;
		char *subpath;

		subpath = concat_subpath_file(path, d_ent->d_name);
		if (subpath == NULL)
			continue; /* . or .. */

		switch (G.recursion_depth) {
		case PROC_DIR:
			pid = (pid_t)bb_strtou(d_ent->d_name, NULL, 10);
			if (errno != 0
			 || pid == G.mypid
			/* "this PID doesn't use specified FILEs or PORT/PROTO": */
			 || scan_recursive(subpath) == 0
			) {
				break;
			}
			if (option_mask32 & OPT_KILL) {
				if (kill(pid, G.killsig) != 0) {
					bb_perror_msg("kill pid %s", d_ent->d_name);
					G.kill_failed = 1;
				}
			}
			if (!(option_mask32 & OPT_SILENT))
				printf("%s ", d_ent->d_name);
			retval = 1;
			break;

		case PROC_DIR_LINKS:
			switch (
				index_in_substrings(
					"cwd"  "\0" "exe"  "\0"
					"root" "\0" "fd"   "\0"
					"lib"  "\0" "mmap" "\0"
					"maps" "\0",
					d_ent->d_name
				)
			) {
			enum {
				CWD_LINK,
				EXE_LINK,
				ROOT_LINK,
				FD_DIR_LINKS,
				LIB_DIR_LINKS,
				MMAP_DIR_LINKS,
				MAPS,
			};
			case CWD_LINK:
			case EXE_LINK:
			case ROOT_LINK:
				goto scan_link;
			case FD_DIR_LINKS:
			case LIB_DIR_LINKS:
			case MMAP_DIR_LINKS:
				stop_scan = scan_recursive(subpath);
				if (stop_scan)
					retval = stop_scan;
				break;
			case MAPS:
				stop_scan = scan_proc_net_or_maps(subpath, 0);
				if (stop_scan)
					retval = stop_scan;
			default:
				break;
			}
			break;
		case PROC_SUBDIR_LINKS:
  scan_link:
			if (stat(subpath, &statbuf) < 0)
				break;
			stop_scan = search_dev_inode(&statbuf);
			if (stop_scan)
				retval = stop_scan;
		default:
			break;
		}
		free(subpath);
	}
	closedir(d);
	G.recursion_depth--;
	return retval;
}

int fuser_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int fuser_main(int argc UNUSED_PARAM, char **argv)
{
	char **pp;

	INIT_G();

	/* Handle -SIGNAL. Oh my... */
	pp = argv;
	while (*++pp) {
		int sig;
		char *arg = *pp;

		if (arg[0] != '-')
			continue;
		if (arg[1] == '-' && arg[2] == '\0') /* "--" */
			break;
		if ((arg[1] == '4' || arg[1] == '6') && arg[2] == '\0')
			continue; /* it's "-4" or "-6" */
		sig = get_signum(&arg[1]);
		if (sig < 0)
			continue;
		/* "-SIGNAL" option found. Remove it and bail out */
		G.killsig = sig;
		do {
			pp[0] = arg = pp[1];
			pp++;
		} while (arg);
		break;
	}

	getopt32(argv, "^" OPTION_STRING "\0" "-1"/*at least one arg*/);
	argv += optind;

	pp = argv;
	while (*pp) {
		/* parse net arg */
		unsigned port;
		char path[sizeof("/proc/net/TCP6")];

		strcpy(path, "/proc/net/");
		if (sscanf(*pp, "%u/%4s", &port, path + sizeof("/proc/net/")-1) == 2
		 && access(path, R_OK) == 0
		) {
			/* PORT/PROTO */
			scan_proc_net_or_maps(path, port);
		} else {
			/* FILE */
			struct stat statbuf;
			xstat(*pp, &statbuf);
			add_inode(&statbuf);
		}
		pp++;
	}

	if (scan_recursive("/proc")) {
		if (!(option_mask32 & OPT_SILENT))
			bb_putchar('\n');
		return G.kill_failed;
	}

	return EXIT_FAILURE;
}
