| /* vi: set sw=4 ts=4: */ |
| /* |
| * tiny fuser implementation |
| * |
| * Copyright 2004 Tony J. White |
| * |
| * May be distributed under the conditions of the |
| * GNU Library General Public License |
| */ |
| |
| #include "libbb.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; |
| |
| typedef struct pid_list { |
| struct pid_list *next; |
| pid_t pid; |
| } pid_list; |
| |
| static dev_t find_socket_dev(void) |
| { |
| int fd = socket(AF_INET, SOCK_DGRAM, 0); |
| if (fd >= 0) { |
| struct stat buf; |
| int r = fstat(fd, &buf); |
| close(fd); |
| if (r == 0) |
| return buf.st_dev; |
| } |
| return 0; |
| } |
| |
| static int file_to_dev_inode(const char *filename, dev_t *dev, ino_t *inode) |
| { |
| struct stat f_stat; |
| if (stat(filename, &f_stat)) |
| return 0; |
| *inode = f_stat.st_ino; |
| *dev = f_stat.st_dev; |
| return 1; |
| } |
| |
| static char *parse_net_arg(const char *arg, unsigned *port) |
| { |
| char path[20], tproto[5]; |
| |
| if (sscanf(arg, "%u/%4s", port, tproto) != 2) |
| return NULL; |
| sprintf(path, "/proc/net/%s", tproto); |
| if (access(path, R_OK) != 0) |
| return NULL; |
| return xstrdup(tproto); |
| } |
| |
| static pid_list *add_pid(pid_list *plist, pid_t pid) |
| { |
| pid_list *curr = plist; |
| while (curr != NULL) { |
| if (curr->pid == pid) |
| return plist; |
| curr = curr->next; |
| } |
| curr = xmalloc(sizeof(pid_list)); |
| curr->pid = pid; |
| curr->next = plist; |
| return curr; |
| } |
| |
| static inode_list *add_inode(inode_list *ilist, dev_t dev, ino_t inode) |
| { |
| inode_list *curr = ilist; |
| while (curr != NULL) { |
| if (curr->inode == inode && curr->dev == dev) |
| return ilist; |
| curr = curr->next; |
| } |
| curr = xmalloc(sizeof(inode_list)); |
| curr->dev = dev; |
| curr->inode = inode; |
| curr->next = ilist; |
| return curr; |
| } |
| |
| static inode_list *scan_proc_net(const char *proto, |
| unsigned port, inode_list *ilist) |
| { |
| char path[20], line[MAX_LINE + 1]; |
| char addr[128]; |
| ino_t tmp_inode; |
| dev_t tmp_dev; |
| long long uint64_inode; |
| unsigned tmp_port; |
| FILE *f; |
| |
| tmp_dev = find_socket_dev(); |
| |
| sprintf(path, "/proc/net/%s", proto); |
| f = fopen(path, "r"); |
| if (!f) |
| return ilist; |
| |
| while (fgets(line, MAX_LINE, f)) { |
| if (sscanf(line, "%*d: %64[0-9A-Fa-f]:%x %*x:%*x %*x %*x:%*x " |
| "%*x:%*x %*x %*d %*d %llu", |
| addr, &tmp_port, &uint64_inode) == 3 |
| ) { |
| if (strlen(addr) == 8 && (option_mask32 & OPT_IP6)) |
| continue; |
| if (strlen(addr) > 8 && (option_mask32 & OPT_IP4)) |
| continue; |
| if (tmp_port == port) { |
| tmp_inode = uint64_inode; |
| ilist = add_inode(ilist, tmp_dev, tmp_inode); |
| } |
| } |
| } |
| fclose(f); |
| return ilist; |
| } |
| |
| static int search_dev_inode(inode_list *ilist, dev_t dev, ino_t inode) |
| { |
| while (ilist) { |
| if (ilist->dev == dev) { |
| if (option_mask32 & OPT_MOUNT) |
| return 1; |
| if (ilist->inode == inode) |
| return 1; |
| } |
| ilist = ilist->next; |
| } |
| return 0; |
| } |
| |
| static pid_list *scan_pid_maps(const char *fname, pid_t pid, |
| inode_list *ilist, pid_list *plist) |
| { |
| FILE *file; |
| char line[MAX_LINE + 1]; |
| int major, minor; |
| ino_t inode; |
| long long uint64_inode; |
| dev_t dev; |
| |
| file = fopen(fname, "r"); |
| if (!file) |
| return plist; |
| while (fgets(line, MAX_LINE, file)) { |
| if (sscanf(line, "%*s %*s %*s %x:%x %llu", &major, &minor, &uint64_inode) != 3) |
| continue; |
| inode = uint64_inode; |
| if (major == 0 && minor == 0 && inode == 0) |
| continue; |
| dev = makedev(major, minor); |
| if (search_dev_inode(ilist, dev, inode)) |
| plist = add_pid(plist, pid); |
| } |
| fclose(file); |
| return plist; |
| } |
| |
| static pid_list *scan_link(const char *lname, pid_t pid, |
| inode_list *ilist, pid_list *plist) |
| { |
| ino_t inode; |
| dev_t dev; |
| |
| if (!file_to_dev_inode(lname, &dev, &inode)) |
| return plist; |
| if (search_dev_inode(ilist, dev, inode)) |
| plist = add_pid(plist, pid); |
| return plist; |
| } |
| |
| static pid_list *scan_dir_links(const char *dname, pid_t pid, |
| inode_list *ilist, pid_list *plist) |
| { |
| DIR *d; |
| struct dirent *de; |
| char *lname; |
| |
| d = opendir(dname); |
| if (!d) |
| return plist; |
| while ((de = readdir(d)) != NULL) { |
| lname = concat_subpath_file(dname, de->d_name); |
| if (lname == NULL) |
| continue; |
| plist = scan_link(lname, pid, ilist, plist); |
| free(lname); |
| } |
| closedir(d); |
| return plist; |
| } |
| |
| static pid_list *scan_proc_pids(inode_list *ilist) |
| { |
| DIR *d; |
| struct dirent *de; |
| pid_t pid; |
| pid_list *plist; |
| |
| d = opendir("."); |
| if (!d) |
| return NULL; |
| |
| plist = NULL; |
| while ((de = readdir(d)) != NULL) { |
| pid = (pid_t)bb_strtou(de->d_name, NULL, 10); |
| if (errno) |
| continue; |
| if (chdir(de->d_name) < 0) |
| continue; |
| plist = scan_link("cwd", pid, ilist, plist); |
| plist = scan_link("exe", pid, ilist, plist); |
| plist = scan_link("root", pid, ilist, plist); |
| plist = scan_dir_links("fd", pid, ilist, plist); |
| plist = scan_dir_links("lib", pid, ilist, plist); |
| plist = scan_dir_links("mmap", pid, ilist, plist); |
| plist = scan_pid_maps("maps", pid, ilist, plist); |
| xchdir("/proc"); |
| } |
| closedir(d); |
| return plist; |
| } |
| |
| static int print_pid_list(pid_list *plist) |
| { |
| while (plist != NULL) { |
| printf("%u ", (unsigned)plist->pid); |
| plist = plist->next; |
| } |
| bb_putchar('\n'); |
| return 1; |
| } |
| |
| static int kill_pid_list(pid_list *plist, int sig) |
| { |
| pid_t mypid = getpid(); |
| int success = 1; |
| |
| while (plist != NULL) { |
| if (plist->pid != mypid) { |
| if (kill(plist->pid, sig) != 0) { |
| bb_perror_msg("kill pid %u", (unsigned)plist->pid); |
| success = 0; |
| } |
| } |
| plist = plist->next; |
| } |
| return success; |
| } |
| |
| int fuser_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
| int fuser_main(int argc ATTRIBUTE_UNUSED, char **argv) |
| { |
| pid_list *plist; |
| inode_list *ilist; |
| char **pp; |
| dev_t dev; |
| ino_t inode; |
| unsigned port; |
| int opt; |
| int success; |
| int killsig; |
| /* |
| fuser [options] FILEs or PORT/PROTOs |
| Find processes which use FILEs or PORTs |
| -m Find processes which use same fs as FILEs |
| -4 Search only IPv4 space |
| -6 Search only IPv6 space |
| -s Silent: just exit with 0 if any processes are found |
| -k Kill found processes (otherwise display PIDs) |
| -SIGNAL Signal to send (default: TERM) |
| */ |
| /* Handle -SIGNAL. Oh my... */ |
| killsig = SIGTERM; |
| pp = argv; |
| while (*++pp) { |
| 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" */ |
| opt = get_signum(&arg[1]); |
| if (opt < 0) |
| continue; |
| /* "-SIGNAL" option found. Remove it and bail out */ |
| killsig = opt; |
| do { |
| pp[0] = arg = pp[1]; |
| pp++; |
| } while (arg); |
| break; |
| } |
| |
| opt = getopt32(argv, OPTION_STRING); |
| argv += optind; |
| |
| ilist = NULL; |
| pp = argv; |
| while (*pp) { |
| char *proto = parse_net_arg(*pp, &port); |
| if (proto) { /* PORT/PROTO */ |
| ilist = scan_proc_net(proto, port, ilist); |
| free(proto); |
| } else { /* FILE */ |
| if (!file_to_dev_inode(*pp, &dev, &inode)) |
| bb_perror_msg_and_die("can't open %s", *pp); |
| ilist = add_inode(ilist, dev, inode); |
| } |
| pp++; |
| } |
| |
| plist = scan_proc_pids(ilist); |
| |
| if (!plist) |
| return EXIT_FAILURE; |
| success = 1; |
| if (opt & OPT_KILL) { |
| success = kill_pid_list(plist, killsig); |
| } else if (!(opt & OPT_SILENT)) { |
| success = print_pid_list(plist); |
| } |
| return (success != 1); /* 0 == success */ |
| } |