toybox/toys/pending/lsof.c
<<
>>
Prefs
   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  struct dev_ino di;
  46};
  47
  48static 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
  79static void free_info(void *data)
  80{
  81  free(((struct file_info *)data)->name);
  82  free(data);
  83}
  84
  85static 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
 105static 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
 124static 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
 134static 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
 147static 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
 166static 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
 213static 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
 243static 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
 285struct 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
 301static 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
 325static 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
 357static 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
 378static 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
 408static 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
 418void 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