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  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), "%04o", 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