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