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