7465d455499641f05a0e064d27e6091d80c5558a
[platform/upstream/busybox.git] / procps / fuser.c
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 tarball for details.
8  */
9
10 #include "libbb.h"
11
12 #define MAX_LINE 255
13
14 #define OPTION_STRING "mks64"
15 enum {
16         OPT_MOUNT  = (1 << 0),
17         OPT_KILL   = (1 << 1),
18         OPT_SILENT = (1 << 2),
19         OPT_IP6    = (1 << 3),
20         OPT_IP4    = (1 << 4),
21 };
22
23 typedef struct inode_list {
24         struct inode_list *next;
25         ino_t inode;
26         dev_t dev;
27 } inode_list;
28
29 typedef struct pid_list {
30         struct pid_list *next;
31         pid_t pid;
32 } pid_list;
33
34
35 struct globals {
36         pid_list *pid_list_head;
37         inode_list *inode_list_head;
38 };
39 #define G (*(struct globals*)&bb_common_bufsiz1)
40 #define INIT_G() do { } while (0)
41
42
43 static dev_t find_socket_dev(void)
44 {
45         int fd = socket(AF_INET, SOCK_DGRAM, 0);
46         if (fd >= 0) {
47                 struct stat buf;
48                 int r = fstat(fd, &buf);
49                 close(fd);
50                 if (r == 0)
51                         return buf.st_dev;
52         }
53         return 0;
54 }
55
56 static char *parse_net_arg(const char *arg, unsigned *port)
57 {
58         char path[20], tproto[5];
59
60         if (sscanf(arg, "%u/%4s", port, tproto) != 2)
61                 return NULL;
62         sprintf(path, "/proc/net/%s", tproto);
63         if (access(path, R_OK) != 0)
64                 return NULL;
65         return xstrdup(path);
66 }
67
68 static void add_pid(const pid_t pid)
69 {
70         pid_list **curr = &G.pid_list_head;
71
72         while (*curr) {
73                 if ((*curr)->pid == pid)
74                         return;
75                 curr = &(*curr)->next;
76         }
77
78         *curr = xzalloc(sizeof(pid_list));
79         (*curr)->pid = pid;
80 }
81
82 static void add_inode(const struct stat *st)
83 {
84         inode_list **curr = &G.inode_list_head;
85
86         while (*curr) {
87                 if ((*curr)->dev == st->st_dev
88                  && (*curr)->inode == st->st_ino
89                 ) {
90                         return;
91                 }
92                 curr = &(*curr)->next;
93         }
94
95         *curr = xzalloc(sizeof(inode_list));
96         (*curr)->dev = st->st_dev;
97         (*curr)->inode = st->st_ino;
98 }
99
100 static void scan_proc_net(const char *path, unsigned port)
101 {
102         char line[MAX_LINE + 1];
103         long long uint64_inode;
104         unsigned tmp_port;
105         FILE *f;
106         struct stat st;
107
108         st.st_dev = find_socket_dev();
109
110         f = fopen_for_read(path);
111         if (!f)
112                 return;
113
114         while (fgets(line, MAX_LINE, f)) {
115                 char addr[68];
116                 if (sscanf(line, "%*d: %64[0-9A-Fa-f]:%x %*x:%*x %*x %*x:%*x "
117                                 "%*x:%*x %*x %*d %*d %llu",
118                                 addr, &tmp_port, &uint64_inode) == 3
119                 ) {
120                         int len = strlen(addr);
121                         if (len == 8 && (option_mask32 & OPT_IP6))
122                                 continue;
123                         if (len > 8 && (option_mask32 & OPT_IP4))
124                                 continue;
125                         if (tmp_port == port) {
126                                 st.st_ino = uint64_inode;
127                                 add_inode(&st);
128                         }
129                 }
130         }
131         fclose(f);
132 }
133
134 static int search_dev_inode(const struct stat *st)
135 {
136         inode_list *ilist = G.inode_list_head;
137
138         while (ilist) {
139                 if (ilist->dev == st->st_dev) {
140                         if (option_mask32 & OPT_MOUNT)
141                                 return 1;
142                         if (ilist->inode == st->st_ino)
143                                 return 1;
144                 }
145                 ilist = ilist->next;
146         }
147         return 0;
148 }
149
150 static void scan_pid_maps(const char *fname, pid_t pid)
151 {
152         FILE *file;
153         char line[MAX_LINE + 1];
154         int major, minor;
155         long long uint64_inode;
156         struct stat st;
157
158         file = fopen_for_read(fname);
159         if (!file)
160                 return;
161
162         while (fgets(line, MAX_LINE, file)) {
163                 if (sscanf(line, "%*s %*s %*s %x:%x %llu", &major, &minor, &uint64_inode) != 3)
164                         continue;
165                 st.st_ino = uint64_inode;
166                 if (major == 0 && minor == 0 && st.st_ino == 0)
167                         continue;
168                 st.st_dev = makedev(major, minor);
169                 if (search_dev_inode(&st))
170                         add_pid(pid);
171         }
172         fclose(file);
173 }
174
175 static void scan_link(const char *lname, pid_t pid)
176 {
177         struct stat st;
178
179         if (stat(lname, &st) >= 0) {
180                 if (search_dev_inode(&st))
181                         add_pid(pid);
182         }
183 }
184
185 static void scan_dir_links(const char *dname, pid_t pid)
186 {
187         DIR *d;
188         struct dirent *de;
189         char *lname;
190
191         d = opendir(dname);
192         if (!d)
193                 return;
194
195         while ((de = readdir(d)) != NULL) {
196                 lname = concat_subpath_file(dname, de->d_name);
197                 if (lname == NULL)
198                         continue;
199                 scan_link(lname, pid);
200                 free(lname);
201         }
202         closedir(d);
203 }
204
205 /* NB: does chdir internally */
206 static void scan_proc_pids(void)
207 {
208         DIR *d;
209         struct dirent *de;
210         pid_t pid;
211
212         xchdir("/proc");
213         d = opendir("/proc");
214         if (!d)
215                 return;
216
217         while ((de = readdir(d)) != NULL) {
218                 pid = (pid_t)bb_strtou(de->d_name, NULL, 10);
219                 if (errno)
220                         continue;
221                 if (chdir(de->d_name) < 0)
222                         continue;
223                 scan_link("cwd", pid);
224                 scan_link("exe", pid);
225                 scan_link("root", pid);
226
227                 scan_dir_links("fd", pid);
228                 scan_dir_links("lib", pid);
229                 scan_dir_links("mmap", pid);
230
231                 scan_pid_maps("maps", pid);
232                 xchdir("/proc");
233         }
234         closedir(d);
235 }
236
237 int fuser_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
238 int fuser_main(int argc UNUSED_PARAM, char **argv)
239 {
240         pid_list *plist;
241         pid_t mypid;
242         char **pp;
243         struct stat st;
244         unsigned port;
245         int opt;
246         int exitcode;
247         int killsig;
248 /*
249 fuser [OPTIONS] FILE or PORT/PROTO
250 Find processes which use FILEs or PORTs
251         -m      Find processes which use same fs as FILEs
252         -4      Search only IPv4 space
253         -6      Search only IPv6 space
254         -s      Don't display PIDs
255         -k      Kill found processes
256         -SIGNAL Signal to send (default: KILL)
257 */
258         /* Handle -SIGNAL. Oh my... */
259         killsig = SIGKILL; /* yes, the default is not SIGTERM */
260         pp = argv;
261         while (*++pp) {
262                 char *arg = *pp;
263                 if (arg[0] != '-')
264                         continue;
265                 if (arg[1] == '-' && arg[2] == '\0') /* "--" */
266                         break;
267                 if ((arg[1] == '4' || arg[1] == '6') && arg[2] == '\0')
268                         continue; /* it's "-4" or "-6" */
269                 opt = get_signum(&arg[1]);
270                 if (opt < 0)
271                         continue;
272                 /* "-SIGNAL" option found. Remove it and bail out */
273                 killsig = opt;
274                 do {
275                         pp[0] = arg = pp[1];
276                         pp++;
277                 } while (arg);
278                 break;
279         }
280
281         opt_complementary = "-1"; /* at least one param */
282         opt = getopt32(argv, OPTION_STRING);
283         argv += optind;
284
285         pp = argv;
286         while (*pp) {
287                 char *path = parse_net_arg(*pp, &port);
288                 if (path) { /* PORT/PROTO */
289                         scan_proc_net(path, port);
290                         free(path);
291                 } else { /* FILE */
292                         xstat(*pp, &st);
293                         add_inode(&st);
294                 }
295                 pp++;
296         }
297
298         scan_proc_pids(); /* changes dir to "/proc" */
299
300         mypid = getpid();
301         plist = G.pid_list_head;
302         while (1) {
303                 if (!plist)
304                         return EXIT_FAILURE;
305                 if (plist->pid != mypid)
306                         break;
307                 plist = plist->next;
308         }
309
310         exitcode = EXIT_SUCCESS;
311         do {
312                 if (plist->pid != mypid) {
313                         if (opt & OPT_KILL) {
314                                 if (kill(plist->pid, killsig) != 0) {
315                                         bb_perror_msg("kill pid %u", (unsigned)plist->pid);
316                                         exitcode = EXIT_FAILURE;
317                                 }
318                         }
319                         if (!(opt & OPT_SILENT)) {
320                                 printf("%u ", (unsigned)plist->pid);
321                         }
322                 }
323                 plist = plist->next;
324         } while (plist);
325
326         if (!(opt & (OPT_SILENT))) {
327                 bb_putchar('\n');
328         }
329
330         return exitcode;
331 }