lsof.c revision 11d6079ed3cb067dc66a795a1c7565c13afd5a00
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,...] [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
24GLOBALS(
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
34struct proc_info {
35  char cmd[10];
36  int pid;
37  char user[12];
38};
39
40struct 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
59static 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
69static 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
80static 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
103static 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
124static 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
147static 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
162static 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
184static 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
232static 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
245static 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
287struct 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
303static 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
327static 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
361static 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
382static 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
428static 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
437void 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