blob: db28cca798c59b2fea8d6cd98f39c16e27b7eabc
1 | /* vi: set sw=4 ts=4: */ |
2 | /* |
3 | * tiny fuser implementation |
4 | * |
5 | * Copyright 2004 Tony J. White |
6 | * |
7 | * Licensed under GPLv2, see file LICENSE in this source tree. |
8 | */ |
9 | //config:config FUSER |
10 | //config: bool "fuser" |
11 | //config: default y |
12 | //config: help |
13 | //config: fuser lists all PIDs (Process IDs) that currently have a given |
14 | //config: file open. fuser can also list all PIDs that have a given network |
15 | //config: (TCP or UDP) port open. |
16 | |
17 | //applet:IF_FUSER(APPLET(fuser, BB_DIR_USR_BIN, BB_SUID_DROP)) |
18 | |
19 | //kbuild:lib-$(CONFIG_FUSER) += fuser.o |
20 | |
21 | //usage:#define fuser_trivial_usage |
22 | //usage: "[OPTIONS] FILE or PORT/PROTO" |
23 | //usage:#define fuser_full_usage "\n\n" |
24 | //usage: "Find processes which use FILEs or PORTs\n" |
25 | //usage: "\n -m Find processes which use same fs as FILEs" |
26 | //usage: "\n -4,-6 Search only IPv4/IPv6 space" |
27 | //usage: "\n -s Don't display PIDs" |
28 | //usage: "\n -k Kill found processes" |
29 | //usage: "\n -SIGNAL Signal to send (default: KILL)" |
30 | |
31 | #include "libbb.h" |
32 | #include "common_bufsiz.h" |
33 | |
34 | #define MAX_LINE 255 |
35 | |
36 | #define OPTION_STRING "mks64" |
37 | enum { |
38 | OPT_MOUNT = (1 << 0), |
39 | OPT_KILL = (1 << 1), |
40 | OPT_SILENT = (1 << 2), |
41 | OPT_IP6 = (1 << 3), |
42 | OPT_IP4 = (1 << 4), |
43 | }; |
44 | |
45 | typedef struct inode_list { |
46 | struct inode_list *next; |
47 | ino_t inode; |
48 | dev_t dev; |
49 | } inode_list; |
50 | |
51 | struct globals { |
52 | int recursion_depth; |
53 | pid_t mypid; |
54 | inode_list *inode_list_head; |
55 | smallint kill_failed; |
56 | int killsig; |
57 | } FIX_ALIASING; |
58 | #define G (*(struct globals*)bb_common_bufsiz1) |
59 | #define INIT_G() do { \ |
60 | setup_common_bufsiz(); \ |
61 | G.mypid = getpid(); \ |
62 | G.killsig = SIGKILL; \ |
63 | } while (0) |
64 | |
65 | static void add_inode(const struct stat *st) |
66 | { |
67 | inode_list **curr = &G.inode_list_head; |
68 | |
69 | while (*curr) { |
70 | if ((*curr)->dev == st->st_dev |
71 | && (*curr)->inode == st->st_ino |
72 | ) { |
73 | return; |
74 | } |
75 | curr = &(*curr)->next; |
76 | } |
77 | |
78 | *curr = xzalloc(sizeof(inode_list)); |
79 | (*curr)->dev = st->st_dev; |
80 | (*curr)->inode = st->st_ino; |
81 | } |
82 | |
83 | static smallint search_dev_inode(const struct stat *st) |
84 | { |
85 | inode_list *ilist = G.inode_list_head; |
86 | |
87 | while (ilist) { |
88 | if (ilist->dev == st->st_dev) { |
89 | if (option_mask32 & OPT_MOUNT) |
90 | return 1; |
91 | if (ilist->inode == st->st_ino) |
92 | return 1; |
93 | } |
94 | ilist = ilist->next; |
95 | } |
96 | return 0; |
97 | } |
98 | |
99 | enum { |
100 | PROC_NET = 0, |
101 | PROC_DIR, |
102 | PROC_DIR_LINKS, |
103 | PROC_SUBDIR_LINKS, |
104 | }; |
105 | |
106 | static smallint scan_proc_net_or_maps(const char *path, unsigned port) |
107 | { |
108 | FILE *f; |
109 | char line[MAX_LINE + 1], addr[68]; |
110 | int major, minor, r; |
111 | long long uint64_inode; |
112 | unsigned tmp_port; |
113 | smallint retval; |
114 | struct stat statbuf; |
115 | const char *fmt; |
116 | void *fag, *sag; |
117 | |
118 | f = fopen_for_read(path); |
119 | if (!f) |
120 | return 0; |
121 | |
122 | if (G.recursion_depth == PROC_NET) { |
123 | int fd; |
124 | |
125 | /* find socket dev */ |
126 | statbuf.st_dev = 0; |
127 | fd = socket(AF_INET, SOCK_DGRAM, 0); |
128 | if (fd >= 0) { |
129 | fstat(fd, &statbuf); |
130 | close(fd); |
131 | } |
132 | |
133 | fmt = "%*d: %64[0-9A-Fa-f]:%x %*x:%*x %*x " |
134 | "%*x:%*x %*x:%*x %*x %*d %*d %llu"; |
135 | fag = addr; |
136 | sag = &tmp_port; |
137 | } else { |
138 | fmt = "%*s %*s %*s %x:%x %llu"; |
139 | fag = &major; |
140 | sag = &minor; |
141 | } |
142 | |
143 | retval = 0; |
144 | while (fgets(line, MAX_LINE, f)) { |
145 | r = sscanf(line, fmt, fag, sag, &uint64_inode); |
146 | if (r != 3) |
147 | continue; |
148 | |
149 | statbuf.st_ino = uint64_inode; |
150 | if (G.recursion_depth == PROC_NET) { |
151 | r = strlen(addr); |
152 | if (r == 8 && (option_mask32 & OPT_IP6)) |
153 | continue; |
154 | if (r > 8 && (option_mask32 & OPT_IP4)) |
155 | continue; |
156 | if (tmp_port == port) |
157 | add_inode(&statbuf); |
158 | } else { |
159 | if (major != 0 && minor != 0 && statbuf.st_ino != 0) { |
160 | statbuf.st_dev = makedev(major, minor); |
161 | retval = search_dev_inode(&statbuf); |
162 | if (retval) |
163 | break; |
164 | } |
165 | } |
166 | } |
167 | fclose(f); |
168 | |
169 | return retval; |
170 | } |
171 | |
172 | static smallint scan_recursive(const char *path) |
173 | { |
174 | DIR *d; |
175 | struct dirent *d_ent; |
176 | smallint stop_scan; |
177 | smallint retval; |
178 | |
179 | d = opendir(path); |
180 | if (d == NULL) |
181 | return 0; |
182 | |
183 | G.recursion_depth++; |
184 | retval = 0; |
185 | stop_scan = 0; |
186 | while (!stop_scan && (d_ent = readdir(d)) != NULL) { |
187 | struct stat statbuf; |
188 | pid_t pid; |
189 | char *subpath; |
190 | |
191 | subpath = concat_subpath_file(path, d_ent->d_name); |
192 | if (subpath == NULL) |
193 | continue; /* . or .. */ |
194 | |
195 | switch (G.recursion_depth) { |
196 | case PROC_DIR: |
197 | pid = (pid_t)bb_strtou(d_ent->d_name, NULL, 10); |
198 | if (errno != 0 |
199 | || pid == G.mypid |
200 | /* "this PID doesn't use specified FILEs or PORT/PROTO": */ |
201 | || scan_recursive(subpath) == 0 |
202 | ) { |
203 | break; |
204 | } |
205 | if (option_mask32 & OPT_KILL) { |
206 | if (kill(pid, G.killsig) != 0) { |
207 | bb_perror_msg("kill pid %s", d_ent->d_name); |
208 | G.kill_failed = 1; |
209 | } |
210 | } |
211 | if (!(option_mask32 & OPT_SILENT)) |
212 | printf("%s ", d_ent->d_name); |
213 | retval = 1; |
214 | break; |
215 | |
216 | case PROC_DIR_LINKS: |
217 | switch ( |
218 | index_in_substrings( |
219 | "cwd" "\0" "exe" "\0" |
220 | "root" "\0" "fd" "\0" |
221 | "lib" "\0" "mmap" "\0" |
222 | "maps" "\0", |
223 | d_ent->d_name |
224 | ) |
225 | ) { |
226 | enum { |
227 | CWD_LINK, |
228 | EXE_LINK, |
229 | ROOT_LINK, |
230 | FD_DIR_LINKS, |
231 | LIB_DIR_LINKS, |
232 | MMAP_DIR_LINKS, |
233 | MAPS, |
234 | }; |
235 | case CWD_LINK: |
236 | case EXE_LINK: |
237 | case ROOT_LINK: |
238 | goto scan_link; |
239 | case FD_DIR_LINKS: |
240 | case LIB_DIR_LINKS: |
241 | case MMAP_DIR_LINKS: |
242 | stop_scan = scan_recursive(subpath); |
243 | if (stop_scan) |
244 | retval = stop_scan; |
245 | break; |
246 | case MAPS: |
247 | stop_scan = scan_proc_net_or_maps(subpath, 0); |
248 | if (stop_scan) |
249 | retval = stop_scan; |
250 | default: |
251 | break; |
252 | } |
253 | break; |
254 | case PROC_SUBDIR_LINKS: |
255 | scan_link: |
256 | if (stat(subpath, &statbuf) < 0) |
257 | break; |
258 | stop_scan = search_dev_inode(&statbuf); |
259 | if (stop_scan) |
260 | retval = stop_scan; |
261 | default: |
262 | break; |
263 | } |
264 | free(subpath); |
265 | } |
266 | closedir(d); |
267 | G.recursion_depth--; |
268 | return retval; |
269 | } |
270 | |
271 | int fuser_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
272 | int fuser_main(int argc UNUSED_PARAM, char **argv) |
273 | { |
274 | char **pp; |
275 | |
276 | INIT_G(); |
277 | |
278 | /* Handle -SIGNAL. Oh my... */ |
279 | pp = argv; |
280 | while (*++pp) { |
281 | int sig; |
282 | char *arg = *pp; |
283 | |
284 | if (arg[0] != '-') |
285 | continue; |
286 | if (arg[1] == '-' && arg[2] == '\0') /* "--" */ |
287 | break; |
288 | if ((arg[1] == '4' || arg[1] == '6') && arg[2] == '\0') |
289 | continue; /* it's "-4" or "-6" */ |
290 | sig = get_signum(&arg[1]); |
291 | if (sig < 0) |
292 | continue; |
293 | /* "-SIGNAL" option found. Remove it and bail out */ |
294 | G.killsig = sig; |
295 | do { |
296 | pp[0] = arg = pp[1]; |
297 | pp++; |
298 | } while (arg); |
299 | break; |
300 | } |
301 | |
302 | opt_complementary = "-1"; /* at least one param */ |
303 | getopt32(argv, OPTION_STRING); |
304 | argv += optind; |
305 | |
306 | pp = argv; |
307 | while (*pp) { |
308 | /* parse net arg */ |
309 | unsigned port; |
310 | char path[sizeof("/proc/net/TCP6")]; |
311 | |
312 | strcpy(path, "/proc/net/"); |
313 | if (sscanf(*pp, "%u/%4s", &port, path + sizeof("/proc/net/")-1) == 2 |
314 | && access(path, R_OK) == 0 |
315 | ) { |
316 | /* PORT/PROTO */ |
317 | scan_proc_net_or_maps(path, port); |
318 | } else { |
319 | /* FILE */ |
320 | struct stat statbuf; |
321 | xstat(*pp, &statbuf); |
322 | add_inode(&statbuf); |
323 | } |
324 | pp++; |
325 | } |
326 | |
327 | if (scan_recursive("/proc")) { |
328 | if (!(option_mask32 & OPT_SILENT)) |
329 | bb_putchar('\n'); |
330 | return G.kill_failed; |
331 | } |
332 | |
333 | return EXIT_FAILURE; |
334 | } |
335 |