1 /* lsof.c - list open files.
2 *
3 * Copyright 2015 The Android Open Source Project
4
5 USE_LSOF(NEWTOY(lsof, "lp*t", TOYFLAG_USR|TOYFLAG_BIN))
6
7 config LSOF
8 bool "lsof"
9 default n
10 help
11 usage: lsof [-lt] [-p PID1,PID2,...] [FILE...]
12
13 List all open files belonging to all active processes, or processes using
14 listed FILE(s).
15
16 -l list uids numerically
17 -p for given comma-separated pids only (default all pids)
18 -t terse (pid only) output
19 */
20
21 #define FOR_lsof
22 #include "toys.h"
23
24 GLOBALS(
25 struct arg_list *p;
26
27 struct stat *sought_files;
28 struct double_list *all_sockets, *files;
29 int last_shown_pid, shown_header;
30 )
31
32 struct proc_info {
33 char cmd[17];
34 int pid, uid;
35 };
36
37 struct file_info {
38 char *next, *prev;
39
40 // For output.
41 struct proc_info pi;
42 char *name, fd[8], rw, locks, type[10], device[32], size_off[32], node[32];
43
44 // For filtering.
45 struct dev_ino di;
46 };
47
print_info(void * data)48 static void print_info(void *data)
49 {
50 struct file_info *fi = data;
51
52 // Filter matches
53 if (toys.optc) {
54 int i;
55
56 for (i = 0; i<toys.optc; i++)
57 if (same_dev_ino(TT.sought_files+i, &fi->di)) break;
58 if (i==toys.optc) return;
59 }
60
61 if (FLAG(t)) {
62 if (fi->pi.pid != TT.last_shown_pid)
63 printf("%d\n", TT.last_shown_pid = fi->pi.pid);
64 } else {
65 if (!TT.shown_header) {
66 // TODO: llist_traverse to measure the columns first.
67 printf("%-9s %5s %10.10s %4s %7s %18s %9s %10s %s\n", "COMMAND", "PID",
68 "USER", "FD", "TYPE", "DEVICE", "SIZE/OFF", "NODE", "NAME");
69 TT.shown_header = 1;
70 }
71
72 #ifdef TOYBOX_OH_ADAPT
73 if (FLAG(l)) {
74 printf("%-9s %5d %10d %4s%c%c %7s %18s %9s %10s %s\n",
75 fi->pi.cmd, fi->pi.pid, fi->pi.uid,
76 fi->fd, fi->rw, fi->locks, fi->type, fi->device, fi->size_off,
77 fi->node, fi->name);
78 return;
79 }
80 #endif
81 printf("%-9s %5d %10.10s %4s%c%c %7s %18s %9s %10s %s\n",
82 fi->pi.cmd, fi->pi.pid, getusername(fi->pi.uid),
83 fi->fd, fi->rw, fi->locks, fi->type, fi->device, fi->size_off,
84 fi->node, fi->name);
85 }
86 }
87
free_info(void * data)88 static void free_info(void *data)
89 {
90 free(((struct file_info *)data)->name);
91 free(data);
92 }
93
fill_flags(struct file_info * fi)94 static void fill_flags(struct file_info *fi)
95 {
96 FILE* fp;
97 long long pos;
98 unsigned flags;
99
100 snprintf(toybuf, sizeof(toybuf), "/proc/%d/fdinfo/%s", fi->pi.pid, fi->fd);
101 if (!(fp = fopen(toybuf, "r"))) return;
102
103 if (fscanf(fp, "pos: %lld flags: %o", &pos, &flags) == 2) {
104 flags &= O_ACCMODE;
105 if (flags == O_RDONLY) fi->rw = 'r';
106 else if (flags == O_WRONLY) fi->rw = 'w';
107 else fi->rw = 'u';
108
109 snprintf(fi->size_off, sizeof(fi->size_off), "0t%lld", pos);
110 }
111 fclose(fp);
112 }
113
scan_proc_net_file(char * path,int family,char type,void (* fn)(char *,int,char))114 static void scan_proc_net_file(char *path, int family, char type,
115 void (*fn)(char *, int, char))
116 {
117 FILE *fp = fopen(path, "r");
118 char *line = NULL;
119 size_t line_length = 0;
120
121 if (!fp) return;
122
123 if (getline(&line, &line_length, fp)) return; // Skip header.
124
125 while (getline(&line, &line_length, fp) > 0) {
126 fn(line, family, type);
127 }
128
129 free(line);
130 fclose(fp);
131 }
132
add_socket(ino_t inode,const char * type)133 static struct file_info *add_socket(ino_t inode, const char *type)
134 {
135 struct file_info *fi = xzalloc(sizeof(struct file_info));
136
137 dlist_add_nomalloc(&TT.all_sockets, (struct double_list *)fi);
138 fi->di.ino = inode;
139 strcpy(fi->type, type);
140 return fi;
141 }
142
scan_unix(char * line,int af,char type)143 static void scan_unix(char *line, int af, char type)
144 {
145 long inode;
146 int path_pos;
147
148 if (sscanf(line, "%*p: %*X %*X %*X %*X %*X %lu %n", &inode, &path_pos) >= 1) {
149 struct file_info *fi = add_socket(inode, "unix");
150 char *name = chomp(line + path_pos);
151
152 fi->name = strdup(*name ? name : "socket");
153 }
154 }
155
scan_netlink(char * line,int af,char type)156 static void scan_netlink(char *line, int af, char type)
157 {
158 unsigned state;
159 long inode;
160 char *netlink_states[] = {
161 "ROUTE", "UNUSED", "USERSOCK", "FIREWALL", "SOCK_DIAG", "NFLOG", "XFRM",
162 "SELINUX", "ISCSI", "AUDIT", "FIB_LOOKUP", "CONNECTOR", "NETFILTER",
163 "IP6_FW", "DNRTMSG", "KOBJECT_UEVENT", "GENERIC", "DM", "SCSITRANSPORT",
164 "ENCRYPTFS", "RDMA", "CRYPTO"
165 };
166
167 if (sscanf(line, "%*p %u %*u %*x %*u %*u %*u %*u %*u %lu", &state, &inode)<2)
168 return;
169
170 struct file_info *fi = add_socket(inode, "netlink");
171 fi->name =
172 strdup(state < ARRAY_LEN(netlink_states) ? netlink_states[state] : "?");
173 }
174
scan_ip(char * line,int af,char type)175 static void scan_ip(char *line, int af, char type)
176 {
177 char *tcp_states[] = {
178 "UNKNOWN", "ESTABLISHED", "SYN_SENT", "SYN_RECV", "FIN_WAIT1", "FIN_WAIT2",
179 "TIME_WAIT", "CLOSE", "CLOSE_WAIT", "LAST_ACK", "LISTEN", "CLOSING"
180 };
181 char local_ip[INET6_ADDRSTRLEN] = {0};
182 char remote_ip[INET6_ADDRSTRLEN] = {0};
183 struct in6_addr local, remote;
184 int local_port, remote_port, state;
185 long inode;
186 int ok;
187
188 if (af == 4) {
189 ok = sscanf(line, " %*d: %x:%x %x:%x %x %*x:%*x %*X:%*X %*X %*d %*d %ld",
190 &(local.s6_addr32[0]), &local_port,
191 &(remote.s6_addr32[0]), &remote_port,
192 &state, &inode) == 6;
193 } else {
194 ok = sscanf(line, " %*d: %8x%8x%8x%8x:%x %8x%8x%8x%8x:%x %x "
195 "%*x:%*x %*X:%*X %*X %*d %*d %ld",
196 &(local.s6_addr32[0]), &(local.s6_addr32[1]),
197 &(local.s6_addr32[2]), &(local.s6_addr32[3]),
198 &local_port,
199 &(remote.s6_addr32[0]), &(remote.s6_addr32[1]),
200 &(remote.s6_addr32[2]), &(remote.s6_addr32[3]),
201 &remote_port, &state, &inode) == 12;
202 }
203 if (!ok) return;
204
205 struct file_info *fi = add_socket(inode, af == 4 ? "IPv4" : "IPv6");
206 inet_ntop(af, &local, local_ip, sizeof(local_ip));
207 inet_ntop(af, &remote, remote_ip, sizeof(remote_ip));
208 if (type == 't') {
209 if (state < 0 || state > TCP_CLOSING) state = 0;
210 fi->name = xmprintf(af == 4 ?
211 "TCP %s:%d->%s:%d (%s)" :
212 "TCP [%s]:%d->[%s]:%d (%s)",
213 local_ip, local_port, remote_ip, remote_port,
214 tcp_states[state]);
215 } else {
216 fi->name = xmprintf(af == 4 ? "%s %s:%d->%s:%d" : "%s [%s]:%d->[%s]:%d",
217 type == 'u' ? "UDP" : "RAW",
218 local_ip, local_port, remote_ip, remote_port);
219 }
220 }
221
find_socket(struct file_info * fi,long inode)222 static int find_socket(struct file_info *fi, long inode)
223 {
224 static int cached;
225 if (!cached) {
226 scan_proc_net_file("/proc/net/tcp", 4, 't', scan_ip);
227 scan_proc_net_file("/proc/net/tcp6", 6, 't', scan_ip);
228 scan_proc_net_file("/proc/net/udp", 4, 'u', scan_ip);
229 scan_proc_net_file("/proc/net/udp6", 6, 'u', scan_ip);
230 scan_proc_net_file("/proc/net/raw", 4, 'r', scan_ip);
231 scan_proc_net_file("/proc/net/raw6", 6, 'r', scan_ip);
232 scan_proc_net_file("/proc/net/unix", 0, 0, scan_unix);
233 scan_proc_net_file("/proc/net/netlink", 0, 0, scan_netlink);
234 cached = 1;
235 }
236 void* list = TT.all_sockets;
237
238 while (list) {
239 struct file_info *s = (struct file_info *)llist_pop(&list);
240
241 if (s->di.ino == inode) {
242 fi->name = s->name ? strdup(s->name) : NULL;
243 strcpy(fi->type, s->type);
244 return 1;
245 }
246 if (list == TT.all_sockets) break;
247 }
248
249 return 0;
250 }
251
fill_stat(struct file_info * fi,const char * path)252 static void fill_stat(struct file_info *fi, const char *path)
253 {
254 struct stat sb;
255 long dev;
256
257 if (stat(path, &sb)) return;
258
259 // Fill TYPE.
260 switch ((sb.st_mode & S_IFMT)) {
261 case S_IFBLK: strcpy(fi->type, "BLK"); break;
262 case S_IFCHR: strcpy(fi->type, "CHR"); break;
263 case S_IFDIR: strcpy(fi->type, "DIR"); break;
264 case S_IFIFO: strcpy(fi->type, "FIFO"); break;
265 case S_IFLNK: strcpy(fi->type, "LINK"); break;
266 case S_IFREG: strcpy(fi->type, "REG"); break;
267 case S_IFSOCK: strcpy(fi->type, "sock"); break;
268 default:
269 snprintf(fi->type, sizeof(fi->type), "%04o", sb.st_mode & S_IFMT);
270 break;
271 }
272
273 if (S_ISSOCK(sb.st_mode)) find_socket(fi, sb.st_ino);
274
275 // Fill DEVICE.
276 dev = (S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode)) ? sb.st_rdev : sb.st_dev;
277 if (!S_ISSOCK(sb.st_mode))
278 snprintf(fi->device, sizeof(fi->device), "%d,%d",
279 dev_major(dev), dev_minor(dev));
280
281 // Fill SIZE/OFF.
282 if (S_ISREG(sb.st_mode) || S_ISDIR(sb.st_mode))
283 snprintf(fi->size_off, sizeof(fi->size_off), "%lld",
284 (long long)sb.st_size);
285
286 // Fill NODE.
287 snprintf(fi->node, sizeof(fi->node), "%ld", (long)sb.st_ino);
288
289 // Stash st_dev and st_ino for filtering.
290 fi->di.dev = sb.st_dev;
291 fi->di.ino = sb.st_ino;
292 }
293
new_file_info(struct proc_info * pi,const char * fd)294 struct file_info *new_file_info(struct proc_info *pi, const char *fd)
295 {
296 struct file_info *fi = xzalloc(sizeof(struct file_info));
297
298 dlist_add_nomalloc(&TT.files, (struct double_list *)fi);
299
300 fi->pi = *pi;
301
302 // Defaults.
303 strcpy(fi->fd, fd);
304 strcpy(fi->type, "unknown");
305 fi->rw = fi->locks = ' ';
306
307 return fi;
308 }
309
visit_symlink(struct proc_info * pi,char * name,char * path)310 static void visit_symlink(struct proc_info *pi, char *name, char *path)
311 {
312 struct file_info *fi = new_file_info(pi, "");
313
314 // Get NAME.
315 if (name) { // "/proc/pid/[cwd]".
316 snprintf(fi->fd, sizeof(fi->fd), "%s", name);
317 snprintf(toybuf, sizeof(toybuf), "/proc/%d/%s", pi->pid, path);
318 } else { // "/proc/pid/fd/[3]"
319 snprintf(fi->fd, sizeof(fi->fd), "%s", path);
320 fill_flags(fi); // Clobbers toybuf.
321 snprintf(toybuf, sizeof(toybuf), "/proc/%d/fd/%s", pi->pid, path);
322 }
323 // TODO: code called by fill_stat would be easier to write if we didn't
324 // rely on toybuf being preserved here.
325 fill_stat(fi, toybuf);
326 if (!fi->name) { // We already have a name for things like sockets.
327 fi->name = xreadlink(toybuf);
328 if (!fi->name) {
329 fi->name = xmprintf("%s (readlink: %s)", toybuf, strerror(errno));
330 }
331 }
332 }
333
visit_maps(struct proc_info * pi)334 static void visit_maps(struct proc_info *pi)
335 {
336 FILE *fp;
337 unsigned long long offset;
338 long inode;
339 char *line = NULL, device[10];
340 size_t line_length = 0;
341 struct file_info *fi;
342
343 snprintf(toybuf, sizeof(toybuf), "/proc/%d/maps", pi->pid);
344 fp = fopen(toybuf, "r");
345 if (!fp) return;
346
347 while (getline(&line, &line_length, fp) > 0) {
348 int name_pos;
349
350 if (sscanf(line, "%*x-%*x %*s %llx %s %ld %n",
351 &offset, device, &inode, &name_pos) >= 3) {
352 struct file_info *fi;
353
354 // Ignore non-file maps.
355 if (inode == 0 || !strcmp(device, "00:00")) continue;
356 // TODO: show unique maps even if they have a non-zero offset?
357 if (offset != 0) continue;
358
359 fi = new_file_info(pi, "mem");
360 fi->name = strdup(chomp(line + name_pos));
361 fill_stat(fi, fi->name);
362 }
363 }
364 free(line);
365 fclose(fp);
366 }
367
visit_fds(struct proc_info * pi)368 static void visit_fds(struct proc_info *pi)
369 {
370 DIR *dir;
371 struct dirent *de;
372
373 snprintf(toybuf, sizeof(toybuf), "/proc/%d/fd", pi->pid);
374 if (!(dir = opendir(toybuf))) {
375 struct file_info *fi = new_file_info(pi, "NOFD");
376
377 fi->name = xmprintf("%s (opendir: %s)", toybuf, strerror(errno));
378 return;
379 }
380
381 while ((de = readdir(dir))) {
382 if (*de->d_name == '.') continue;
383 visit_symlink(pi, NULL, de->d_name);
384 }
385
386 closedir(dir);
387 }
388
lsof_pid(int pid,struct stat * st)389 static void lsof_pid(int pid, struct stat *st)
390 {
391 struct proc_info pi;
392 struct stat sb;
393 char *s;
394
395 pi.pid = pid;
396
397 // Skip nonexistent pids
398 sprintf(toybuf, "/proc/%d/stat", pid);
399 if (!readfile(toybuf, toybuf, sizeof(toybuf)-1) || !(s = strchr(toybuf, '(')))
400 return;
401 memcpy(pi.cmd, s+1, sizeof(pi.cmd)-1);
402 pi.cmd[sizeof(pi.cmd)-1] = 0;
403 if ((s = strrchr(pi.cmd, ')'))) *s = 0;
404
405 // Get USER.
406 if (!st) {
407 snprintf(toybuf, sizeof(toybuf), "/proc/%d", pid);
408 if (stat(toybuf, st = &sb)) return;
409 }
410 pi.uid = st->st_uid;
411
412 visit_symlink(&pi, "cwd", "cwd");
413 visit_symlink(&pi, "rtd", "root");
414 visit_symlink(&pi, "txt", "exe");
415 visit_maps(&pi);
416 visit_fds(&pi);
417 }
418
scan_proc(struct dirtree * node)419 static int scan_proc(struct dirtree *node)
420 {
421 int pid;
422
423 if (!node->parent) return DIRTREE_RECURSE|DIRTREE_SHUTUP;
424 if ((pid = atol(node->name))) lsof_pid(pid, &node->st);
425
426 return 0;
427 }
428
lsof_main(void)429 void lsof_main(void)
430 {
431 struct arg_list *pp;
432 int i, pid;
433
434 // lsof will only filter on paths it can stat (because it filters by inode).
435 if (toys.optc) {
436 TT.sought_files = xmalloc(toys.optc*sizeof(struct stat));
437 for (i = 0; i<toys.optc; ++i) xstat(toys.optargs[i], TT.sought_files+i);
438 }
439
440 if (!TT.p) dirtree_read("/proc", scan_proc);
441 else for (pp = TT.p; pp; pp = pp->next) {
442 char *start, *end, *next = pp->arg;
443
444 while ((start = comma_iterate(&next, &i))) {
445 if ((pid = strtol(start, &end, 10))<1 || (*end && *end!=','))
446 error_msg("bad -p '%.*s'", (int)(end-start), start);
447 lsof_pid(pid, 0);
448 }
449 }
450
451 llist_traverse(TT.files, print_info);
452
453 if (CFG_TOYBOX_FREE) {
454 llist_traverse(TT.files, free_info);
455 llist_traverse(TT.all_sockets, free_info);
456 }
457 }
458