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