1/* vi: set sw=4 ts=4: */
2/*
3 * tiny fuser implementation
4 *
5 * Copyright 2004 Tony J. White
6 *
7 * May be distributed under the conditions of the
8 * GNU Library General Public License
9 */
10
11#include "libbb.h"
12
13#define FUSER_PROC_DIR "/proc"
14#define FUSER_MAX_LINE 255
15
16#define FUSER_OPT_MOUNT  1
17#define FUSER_OPT_KILL   2
18#define FUSER_OPT_SILENT 4
19#define FUSER_OPT_IP6    8
20#define FUSER_OPT_IP4    16
21
22typedef struct inode_list {
23	ino_t inode;
24	dev_t dev;
25	struct inode_list *next;
26} inode_list;
27
28typedef struct pid_list {
29	pid_t pid;
30	struct pid_list *next;
31} pid_list;
32
33static int fuser_option(char *option)
34{
35	int opt = 0;
36
37	if (!option[0])
38		return 0;
39	if (option[0] != '-')
40		return 0;
41	++option;
42	while (*option != '\0') {
43		if (*option == 'm') opt |= FUSER_OPT_MOUNT;
44		else if (*option == 'k') opt |= FUSER_OPT_KILL;
45		else if (*option == 's') opt |= FUSER_OPT_SILENT;
46		else if (*option == '6') opt |= FUSER_OPT_IP6;
47		else if (*option == '4') opt |= FUSER_OPT_IP4;
48		else
49			bb_error_msg_and_die("unsupported option '%c'", *option);
50		++option;
51	}
52	return opt;
53}
54
55static int fuser_file_to_dev_inode(const char *filename,
56	 dev_t *dev, ino_t *inode)
57{
58	struct stat f_stat;
59	if ((stat(filename, &f_stat)) < 0)
60		return 0;
61	*inode = f_stat.st_ino;
62	*dev = f_stat.st_dev;
63	return 1;
64}
65
66static int fuser_find_socket_dev(dev_t *dev)
67{
68	int fd = socket(PF_INET, SOCK_DGRAM,0);
69	struct stat buf;
70
71	if (fd >= 0 && (fstat(fd, &buf)) == 0) {
72		*dev = buf.st_dev;
73		close(fd);
74		return 1;
75	}
76	return 0;
77}
78
79static int fuser_parse_net_arg(const char *filename,
80	const char **proto, int *port)
81{
82	char path[sizeof(FUSER_PROC_DIR)+12], tproto[5];
83
84	if ((sscanf(filename, "%d/%4s", port, tproto)) != 2)
85		return 0;
86	sprintf(path, FUSER_PROC_DIR "/net/%s", tproto);
87	if ((access(path, R_OK)) != 0)
88		return 0;
89	*proto = xstrdup(tproto);
90	return 1;
91}
92
93static int fuser_add_pid(pid_list *plist, pid_t pid)
94{
95	pid_list *curr = NULL, *last = NULL;
96
97	if (plist->pid == 0)
98		plist->pid = pid;
99	curr = plist;
100	while (curr != NULL) {
101		if (curr->pid == pid)
102			return 1;
103		last = curr;
104		curr = curr->next;
105	}
106	curr = xzalloc(sizeof(pid_list));
107	last->next = curr;
108	curr->pid = pid;
109	/*curr->next = NULL;*/
110	return 1;
111}
112
113static int fuser_add_inode(inode_list *ilist, dev_t dev, ino_t inode)
114{
115	inode_list *curr = NULL, *last = NULL;
116
117	if (!ilist->inode && !ilist->dev) {
118		ilist->dev = dev;
119		ilist->inode = inode;
120	}
121	curr = ilist;
122	while (curr != NULL) {
123		if (curr->inode == inode && curr->dev == dev)
124			return 1;
125		last = curr;
126		curr = curr->next;
127	}
128	curr = xzalloc(sizeof(inode_list));
129	last->next = curr;
130	curr->dev = dev;
131	curr->inode = inode;
132	/*curr->next = NULL;*/
133	return 1;
134}
135
136static int fuser_scan_proc_net(int opts, const char *proto,
137	int port, inode_list *ilist)
138{
139	char path[sizeof(FUSER_PROC_DIR)+12], line[FUSER_MAX_LINE+1];
140	char addr[128];
141	ino_t tmp_inode;
142	dev_t tmp_dev;
143	long long uint64_inode;
144	int tmp_port;
145	FILE *f;
146
147	if (!fuser_find_socket_dev(&tmp_dev))
148		tmp_dev = 0;
149	sprintf(path, FUSER_PROC_DIR "/net/%s", proto);
150
151	f = fopen(path, "r");
152	if (!f)
153		return 0;
154	while (fgets(line, FUSER_MAX_LINE, f)) {
155		if (sscanf(line, "%*d: %64[0-9A-Fa-f]:%x %*x:%*x %*x %*x:%*x "
156				"%*x:%*x %*x %*d %*d %llu",
157				addr, &tmp_port, &uint64_inode) == 3
158		) {
159			if (strlen(addr) == 8 && (opts & FUSER_OPT_IP6))
160				continue;
161			if (strlen(addr) > 8 && (opts & FUSER_OPT_IP4))
162				continue;
163			if (tmp_port == port) {
164				tmp_inode = uint64_inode;
165				fuser_add_inode(ilist, tmp_dev, tmp_inode);
166			}
167		}
168	}
169	fclose(f);
170	return 1;
171}
172
173static int fuser_search_dev_inode(int opts, inode_list *ilist,
174	dev_t dev, ino_t inode)
175{
176	inode_list *curr;
177	curr = ilist;
178
179	while (curr) {
180		if ((opts & FUSER_OPT_MOUNT) && curr->dev == dev)
181			return 1;
182		if (curr->inode == inode && curr->dev == dev)
183			return 1;
184		curr = curr->next;
185	}
186	return 0;
187}
188
189static int fuser_scan_pid_maps(int opts, const char *fname, pid_t pid,
190	inode_list *ilist, pid_list *plist)
191{
192	FILE *file;
193	char line[FUSER_MAX_LINE + 1];
194	int major, minor;
195	ino_t inode;
196	long long uint64_inode;
197	dev_t dev;
198
199	file = fopen(fname, "r");
200	if (!file)
201		return 0;
202	while (fgets(line, FUSER_MAX_LINE, file)) {
203		if (sscanf(line, "%*s %*s %*s %x:%x %llu", &major, &minor, &uint64_inode) != 3)
204			continue;
205		inode = uint64_inode;
206		if (major == 0 && minor == 0 && inode == 0)
207			continue;
208		dev = makedev(major, minor);
209		if (fuser_search_dev_inode(opts, ilist, dev, inode)) {
210			fuser_add_pid(plist, pid);
211		}
212	}
213	fclose(file);
214	return 1;
215}
216
217static int fuser_scan_link(int opts, const char *lname, pid_t pid,
218	inode_list *ilist, pid_list *plist)
219{
220	ino_t inode;
221	dev_t dev;
222
223	if (!fuser_file_to_dev_inode(lname, &dev, &inode))
224		return 0;
225	if (fuser_search_dev_inode(opts, ilist, dev, inode))
226		fuser_add_pid(plist, pid);
227	return 1;
228}
229
230static int fuser_scan_dir_links(int opts, const char *dname, pid_t pid,
231	inode_list *ilist, pid_list *plist)
232{
233	DIR *d;
234	struct dirent *de;
235	char *lname;
236
237	d = opendir(dname);
238	if (!d)
239		return 0;
240	while ((de = readdir(d)) != NULL) {
241		lname = concat_subpath_file(dname, de->d_name);
242		if (lname == NULL)
243			continue;
244		fuser_scan_link(opts, lname, pid, ilist, plist);
245		free(lname);
246	}
247	closedir(d);
248	return 1;
249}
250
251static int fuser_scan_proc_pids(int opts, inode_list *ilist, pid_list *plist)
252{
253	DIR *d;
254	struct dirent *de;
255	pid_t pid;
256	char *dname;
257
258	d = opendir(FUSER_PROC_DIR);
259	if (!d)
260		return 0;
261	while ((de = readdir(d)) != NULL) {
262		pid = (pid_t)atoi(de->d_name);
263		if (!pid)
264			continue;
265		dname = concat_subpath_file(FUSER_PROC_DIR, de->d_name);
266		if (chdir(dname) < 0) {
267			free(dname);
268			continue;
269		}
270		free(dname);
271		fuser_scan_link(opts, "cwd", pid, ilist, plist);
272		fuser_scan_link(opts, "exe", pid, ilist, plist);
273		fuser_scan_link(opts, "root", pid, ilist, plist);
274		fuser_scan_dir_links(opts, "fd", pid, ilist, plist);
275		fuser_scan_dir_links(opts, "lib", pid, ilist, plist);
276		fuser_scan_dir_links(opts, "mmap", pid, ilist, plist);
277		fuser_scan_pid_maps(opts, "maps", pid, ilist, plist);
278		chdir("..");
279	}
280	closedir(d);
281	return 1;
282}
283
284static int fuser_print_pid_list(pid_list *plist)
285{
286	pid_list *curr = plist;
287
288	if (plist == NULL)
289		return 0;
290	while (curr != NULL) {
291		if (curr->pid > 0)
292			printf("%d ", curr->pid);
293		curr = curr->next;
294	}
295	puts("");
296	return 1;
297}
298
299static int fuser_kill_pid_list(pid_list *plist, int sig)
300{
301	pid_list *curr = plist;
302	pid_t mypid = getpid();
303	int success = 1;
304
305	if (plist == NULL)
306		return 0;
307	while (curr != NULL) {
308		if (curr->pid > 0 && curr->pid != mypid) {
309			if (kill(curr->pid, sig) != 0) {
310				bb_perror_msg("kill pid '%d'", curr->pid);
311				success = 0;
312			}
313		}
314		curr = curr->next;
315	}
316	return success;
317}
318
319int fuser_main(int argc, char **argv);
320int fuser_main(int argc, char **argv)
321{
322	/*static -- huh???*/ int opt = 0; /* FUSER_OPT_ */
323
324	int port, i, optn;
325	int* fni; /* file name indexes of argv */
326	int fnic = 0;  /* file name index count */
327	const char *proto;
328	dev_t dev;
329	ino_t inode;
330	pid_list *pids;
331	inode_list *inodes;
332	int killsig = SIGTERM;
333	int success = 1;
334
335	if (argc < 2)
336		bb_show_usage();
337
338	fni = xmalloc(sizeof(int));
339	for (i = 1; i < argc; i++) {
340		optn = fuser_option(argv[i]);
341		if (optn)
342			opt |= optn;
343		else if (argv[i][0] == '-') {
344			killsig = get_signum(argv[i]+1);
345			if (killsig < 0)
346				killsig = SIGTERM;
347		} else {
348			fni = xrealloc(fni, sizeof(int) * (fnic+2));
349			fni[fnic++] = i;
350		}
351	}
352
353	if (!fnic)
354		return 1;
355
356	inodes = xmalloc(sizeof(inode_list));
357	for (i = 0; i < fnic; i++) {
358		if (fuser_parse_net_arg(argv[fni[i]], &proto, &port)) {
359			fuser_scan_proc_net(opt, proto, port, inodes);
360		} else {
361			if (!fuser_file_to_dev_inode(argv[fni[i]], &dev, &inode)) {
362				if (ENABLE_FEATURE_CLEAN_UP)
363					free(inodes);
364				bb_perror_msg_and_die("cannot open '%s'", argv[fni[i]]);
365			}
366			fuser_add_inode(inodes, dev, inode);
367		}
368	}
369	pids = xmalloc(sizeof(pid_list));
370	success = fuser_scan_proc_pids(opt, inodes, pids);
371	/* if the first pid in the list is 0, none have been found */
372	if (pids->pid == 0)
373		success = 0;
374	if (success) {
375		if (opt & FUSER_OPT_KILL) {
376			success = fuser_kill_pid_list(pids, killsig);
377		} else if (!(opt & FUSER_OPT_SILENT)) {
378			success = fuser_print_pid_list(pids);
379		}
380	}
381	if (ENABLE_FEATURE_CLEAN_UP) {
382		free(pids);
383		free(inodes);
384	}
385	/* return 0 on (success == 1) 1 otherwise */
386	return (success != 1);
387}
388