| /* 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; |
| } |