| /* lsof.c - list open files. |
| * |
| * Copyright 2015 The Android Open Source Project |
| |
| USE_LSOF(NEWTOY(lsof, "lp*t", TOYFLAG_USR|TOYFLAG_BIN)) |
| |
| config LSOF |
| bool "lsof" |
| default n |
| help |
| usage: lsof [-lt] [-p PID1,PID2,...] [NAME]... |
| |
| Lists open files. If names are given on the command line, only |
| those files will be shown. |
| |
| -l list uids numerically |
| -p for given comma-separated pids only (default all pids) |
| -t terse (pid only) output |
| */ |
| |
| #define FOR_lsof |
| #include "toys.h" |
| |
| GLOBALS( |
| struct arg_list *p; |
| |
| struct stat *sought_files; |
| |
| struct double_list *all_sockets; |
| struct double_list *files; |
| int last_shown_pid; |
| int shown_header; |
| ) |
| |
| struct proc_info { |
| char cmd[10]; |
| int pid; |
| char user[12]; |
| }; |
| |
| struct file_info { |
| char *next, *prev; |
| |
| // For output. |
| struct proc_info pi; |
| char* name; |
| char fd[8], rw, locks, type[10], device[32], size_off[32], node[32]; |
| |
| // For filtering. |
| dev_t st_dev; |
| ino_t st_ino; |
| }; |
| |
| static void print_info(void *data) |
| { |
| struct file_info *fi = data; |
| |
| // Filter matches |
| if (toys.optc) { |
| int i; |
| |
| for (i = 0; i<toys.optc; i++) |
| if (TT.sought_files[i].st_dev==fi->st_dev) |
| if (TT.sought_files[i].st_ino==fi->st_ino) break; |
| |
| if (i==toys.optc) return; |
| } |
| |
| if (toys.optflags&FLAG_t) { |
| if (fi->pi.pid != TT.last_shown_pid) |
| printf("%d\n", TT.last_shown_pid = fi->pi.pid); |
| } else { |
| if (!TT.shown_header) { |
| // TODO: llist_traverse to measure the columns first. |
| printf("%-9s %5s %10.10s %4s %7s %18s %9s %10s %s\n", "COMMAND", "PID", |
| "USER", "FD", "TYPE", "DEVICE", "SIZE/OFF", "NODE", "NAME"); |
| TT.shown_header = 1; |
| } |
| |
| printf("%-9s %5d %10.10s %4s%c%c %7s %18s %9s %10s %s\n", |
| fi->pi.cmd, fi->pi.pid, fi->pi.user, |
| fi->fd, fi->rw, fi->locks, fi->type, fi->device, fi->size_off, |
| fi->node, fi->name); |
| } |
| } |
| |
| static void free_info(void *data) |
| { |
| free(((struct file_info *)data)->name); |
| free(data); |
| } |
| |
| static void fill_flags(struct file_info *fi) |
| { |
| FILE* fp; |
| long long pos; |
| unsigned flags; |
| |
| snprintf(toybuf, sizeof(toybuf), "/proc/%d/fdinfo/%s", fi->pi.pid, fi->fd); |
| fp = fopen(toybuf, "r"); |
| if (!fp) return; |
| |
| if (fscanf(fp, "pos: %lld flags: %o", &pos, &flags) == 2) { |
| flags &= O_ACCMODE; |
| if (flags == O_RDONLY) fi->rw = 'r'; |
| else if (flags == O_WRONLY) fi->rw = 'w'; |
| else fi->rw = 'u'; |
| |
| snprintf(fi->size_off, sizeof(fi->size_off), "0t%lld", pos); |
| } |
| fclose(fp); |
| } |
| |
| static void scan_proc_net_file(char *path, int family, char type, |
| void (*fn)(char *, int, char)) |
| { |
| FILE *fp = fopen(path, "r"); |
| char *line = NULL; |
| size_t line_length = 0; |
| |
| if (!fp) return; |
| |
| if (!getline(&line, &line_length, fp)) return; // Skip header. |
| |
| while (getline(&line, &line_length, fp) > 0) { |
| fn(line, family, type); |
| } |
| |
| free(line); |
| fclose(fp); |
| } |
| |
| static struct file_info *add_socket(ino_t inode, const char *type) |
| { |
| struct file_info *fi = xzalloc(sizeof(struct file_info)); |
| |
| dlist_add_nomalloc(&TT.all_sockets, (struct double_list *)fi); |
| fi->st_ino = inode; |
| strcpy(fi->type, type); |
| return fi; |
| } |
| |
| static void scan_unix(char *line, int af, char type) |
| { |
| long inode; |
| int path_pos; |
| |
| if (sscanf(line, "%*p: %*X %*X %*X %*X %*X %lu %n", &inode, &path_pos) >= 1) { |
| struct file_info *fi = add_socket(inode, "unix"); |
| char *name = chomp(line + path_pos); |
| |
| fi->name = strdup(*name ? name : "socket"); |
| } |
| } |
| |
| static void scan_netlink(char *line, int af, char type) |
| { |
| unsigned state; |
| long inode; |
| char *netlink_states[] = { |
| "ROUTE", "UNUSED", "USERSOCK", "FIREWALL", "SOCK_DIAG", "NFLOG", "XFRM", |
| "SELINUX", "ISCSI", "AUDIT", "FIB_LOOKUP", "CONNECTOR", "NETFILTER", |
| "IP6_FW", "DNRTMSG", "KOBJECT_UEVENT", "GENERIC", "DM", "SCSITRANSPORT", |
| "ENCRYPTFS", "RDMA", "CRYPTO" |
| }; |
| |
| if (sscanf(line, "%*p %u %*u %*x %*u %*u %*u %*u %*u %lu", &state, &inode) |
| < 2) { |
| return; |
| } |
| |
| struct file_info *fi = add_socket(inode, "netlink"); |
| fi->name = |
| strdup(state < ARRAY_LEN(netlink_states) ? netlink_states[state] : "?"); |
| } |
| |
| static void scan_ip(char *line, int af, char type) |
| { |
| char *tcp_states[] = { |
| "UNKNOWN", "ESTABLISHED", "SYN_SENT", "SYN_RECV", "FIN_WAIT1", "FIN_WAIT2", |
| "TIME_WAIT", "CLOSE", "CLOSE_WAIT", "LAST_ACK", "LISTEN", "CLOSING" |
| }; |
| char local_ip[INET6_ADDRSTRLEN] = {0}; |
| char remote_ip[INET6_ADDRSTRLEN] = {0}; |
| struct in6_addr local, remote; |
| int local_port, remote_port, state; |
| long inode; |
| int ok; |
| |
| if (af == 4) { |
| ok = sscanf(line, " %*d: %x:%x %x:%x %x %*x:%*x %*X:%*X %*X %*d %*d %ld", |
| &(local.s6_addr32[0]), &local_port, |
| &(remote.s6_addr32[0]), &remote_port, |
| &state, &inode) == 6; |
| } else { |
| ok = sscanf(line, " %*d: %8x%8x%8x%8x:%x %8x%8x%8x%8x:%x %x " |
| "%*x:%*x %*X:%*X %*X %*d %*d %ld", |
| &(local.s6_addr32[0]), &(local.s6_addr32[1]), |
| &(local.s6_addr32[2]), &(local.s6_addr32[3]), |
| &local_port, |
| &(remote.s6_addr32[0]), &(remote.s6_addr32[1]), |
| &(remote.s6_addr32[2]), &(remote.s6_addr32[3]), |
| &remote_port, &state, &inode) == 12; |
| } |
| if (!ok) return; |
| |
| struct file_info *fi = add_socket(inode, af == 4 ? "IPv4" : "IPv6"); |
| inet_ntop(af, &local, local_ip, sizeof(local_ip)); |
| inet_ntop(af, &remote, remote_ip, sizeof(remote_ip)); |
| if (type == 't') { |
| if (state < 0 || state > TCP_CLOSING) state = 0; |
| fi->name = xmprintf(af == 4 ? |
| "TCP %s:%d->%s:%d (%s)" : |
| "TCP [%s]:%d->[%s]:%d (%s)", |
| local_ip, local_port, remote_ip, remote_port, |
| tcp_states[state]); |
| } else { |
| fi->name = xmprintf(af == 4 ? "%s %s:%d->%s:%d" : "%s [%s]:%d->[%s]:%d", |
| type == 'u' ? "UDP" : "RAW", |
| local_ip, local_port, remote_ip, remote_port); |
| } |
| } |
| |
| static int find_socket(struct file_info *fi, long inode) |
| { |
| static int cached; |
| if (!cached) { |
| scan_proc_net_file("/proc/net/tcp", 4, 't', scan_ip); |
| scan_proc_net_file("/proc/net/tcp6", 6, 't', scan_ip); |
| scan_proc_net_file("/proc/net/udp", 4, 'u', scan_ip); |
| scan_proc_net_file("/proc/net/udp6", 6, 'u', scan_ip); |
| scan_proc_net_file("/proc/net/raw", 4, 'r', scan_ip); |
| scan_proc_net_file("/proc/net/raw6", 6, 'r', scan_ip); |
| scan_proc_net_file("/proc/net/unix", 0, 0, scan_unix); |
| scan_proc_net_file("/proc/net/netlink", 0, 0, scan_netlink); |
| cached = 1; |
| } |
| void* list = TT.all_sockets; |
| |
| while (list) { |
| struct file_info *s = (struct file_info*) llist_pop(&list); |
| |
| if (s->st_ino == inode) { |
| fi->name = s->name ? strdup(s->name) : NULL; |
| strcpy(fi->type, s->type); |
| return 1; |
| } |
| if (list == TT.all_sockets) break; |
| } |
| |
| return 0; |
| } |
| |
| static void fill_stat(struct file_info *fi, const char *path) |
| { |
| struct stat sb; |
| long dev; |
| |
| if (stat(path, &sb)) return; |
| |
| // Fill TYPE. |
| switch ((sb.st_mode & S_IFMT)) { |
| case S_IFBLK: strcpy(fi->type, "BLK"); break; |
| case S_IFCHR: strcpy(fi->type, "CHR"); break; |
| case S_IFDIR: strcpy(fi->type, "DIR"); break; |
| case S_IFIFO: strcpy(fi->type, "FIFO"); break; |
| case S_IFLNK: strcpy(fi->type, "LINK"); break; |
| case S_IFREG: strcpy(fi->type, "REG"); break; |
| case S_IFSOCK: strcpy(fi->type, "sock"); break; |
| default: |
| snprintf(fi->type, sizeof(fi->type), "0%03o", sb.st_mode & S_IFMT); |
| break; |
| } |
| |
| if (S_ISSOCK(sb.st_mode)) find_socket(fi, sb.st_ino); |
| |
| // Fill DEVICE. |
| dev = (S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode)) ? sb.st_rdev : sb.st_dev; |
| if (!S_ISSOCK(sb.st_mode)) |
| snprintf(fi->device, sizeof(fi->device), "%d,%d", |
| dev_major(dev), dev_minor(dev)); |
| |
| // Fill SIZE/OFF. |
| if (S_ISREG(sb.st_mode) || S_ISDIR(sb.st_mode)) |
| snprintf(fi->size_off, sizeof(fi->size_off), "%lld", |
| (long long)sb.st_size); |
| |
| // Fill NODE. |
| snprintf(fi->node, sizeof(fi->node), "%ld", (long)sb.st_ino); |
| |
| // Stash st_dev and st_ino for filtering. |
| fi->st_dev = sb.st_dev; |
| fi->st_ino = sb.st_ino; |
| } |
| |
| struct file_info *new_file_info(struct proc_info *pi, const char *fd) |
| { |
| struct file_info *fi = xzalloc(sizeof(struct file_info)); |
| |
| dlist_add_nomalloc(&TT.files, (struct double_list *)fi); |
| |
| fi->pi = *pi; |
| |
| // Defaults. |
| strcpy(fi->fd, fd); |
| strcpy(fi->type, "unknown"); |
| fi->rw = fi->locks = ' '; |
| |
| return fi; |
| } |
| |
| static void visit_symlink(struct proc_info *pi, char *name, char *path) |
| { |
| struct file_info *fi = new_file_info(pi, ""); |
| |
| // Get NAME. |
| if (name) { // "/proc/pid/[cwd]". |
| snprintf(fi->fd, sizeof(fi->fd), "%s", name); |
| snprintf(toybuf, sizeof(toybuf), "/proc/%d/%s", pi->pid, path); |
| } else { // "/proc/pid/fd/[3]" |
| snprintf(fi->fd, sizeof(fi->fd), "%s", path); |
| fill_flags(fi); // Clobbers toybuf. |
| snprintf(toybuf, sizeof(toybuf), "/proc/%d/fd/%s", pi->pid, path); |
| } |
| // TODO: code called by fill_stat would be easier to write if we didn't |
| // rely on toybuf being preserved here. |
| fill_stat(fi, toybuf); |
| if (!fi->name) { // We already have a name for things like sockets. |
| fi->name = xreadlink(toybuf); |
| if (!fi->name) { |
| fi->name = xmprintf("%s (readlink: %s)", toybuf, strerror(errno)); |
| } |
| } |
| } |
| |
| static void visit_maps(struct proc_info *pi) |
| { |
| FILE *fp; |
| unsigned long long offset; |
| char device[10]; |
| long inode; |
| char *line = NULL; |
| size_t line_length = 0; |
| |
| snprintf(toybuf, sizeof(toybuf), "/proc/%d/maps", pi->pid); |
| fp = fopen(toybuf, "r"); |
| if (!fp) return; |
| |
| while (getline(&line, &line_length, fp) > 0) { |
| int name_pos; |
| |
| if (sscanf(line, "%*x-%*x %*s %llx %s %ld %n", |
| &offset, device, &inode, &name_pos) >= 3) { |
| struct file_info *fi; |
| |
| // Ignore non-file maps. |
| if (inode == 0 || !strcmp(device, "00:00")) continue; |
| // TODO: show unique maps even if they have a non-zero offset? |
| if (offset != 0) continue; |
| |
| fi = new_file_info(pi, "mem"); |
| fi->name = strdup(chomp(line + name_pos)); |
| fill_stat(fi, fi->name); |
| } |
| } |
| free(line); |
| fclose(fp); |
| } |
| |
| static void visit_fds(struct proc_info *pi) |
| { |
| DIR *dir; |
| struct dirent *de; |
| |
| snprintf(toybuf, sizeof(toybuf), "/proc/%d/fd", pi->pid); |
| if (!(dir = opendir(toybuf))) { |
| struct file_info *fi = new_file_info(pi, "NOFD"); |
| |
| fi->name = xmprintf("%s (opendir: %s)", toybuf, strerror(errno)); |
| return; |
| } |
| |
| while ((de = readdir(dir))) { |
| if (*de->d_name == '.') continue; |
| visit_symlink(pi, NULL, de->d_name); |
| } |
| |
| closedir(dir); |
| } |
| |
| static void lsof_pid(int pid) |
| { |
| struct proc_info pi; |
| char *line; |
| struct stat sb; |
| |
| // Skip nonexistent pids |
| sprintf(toybuf, "/proc/%d/stat", pid); |
| if (!(line = readfile(toybuf, toybuf, sizeof(toybuf)))) return; |
| |
| // Get COMMAND. |
| strcpy(pi.cmd, "?"); |
| if (line) { |
| char *open_paren = strchr(toybuf, '('); |
| char *close_paren = strrchr(toybuf, ')'); |
| |
| if (open_paren && close_paren) { |
| *close_paren = 0; |
| snprintf(pi.cmd, sizeof(pi.cmd), "%s", open_paren + 1); |
| } |
| } |
| |
| // We already know PID. |
| pi.pid = pid; |
| |
| // Get USER. |
| snprintf(toybuf, sizeof(toybuf), "/proc/%d", pid); |
| if (!stat(toybuf, &sb)) { |
| struct passwd *pw; |
| |
| if (!(toys.optflags&FLAG_l) && (pw = getpwuid(sb.st_uid))) { |
| snprintf(pi.user, sizeof(pi.user), "%s", pw->pw_name); |
| } else snprintf(pi.user, sizeof(pi.user), "%u", (unsigned)sb.st_uid); |
| } |
| |
| visit_symlink(&pi, "cwd", "cwd"); |
| visit_symlink(&pi, "rtd", "root"); |
| visit_symlink(&pi, "txt", "exe"); |
| visit_maps(&pi); |
| visit_fds(&pi); |
| } |
| |
| static int scan_proc(struct dirtree *node) |
| { |
| int pid; |
| |
| if (!node->parent) return DIRTREE_RECURSE|DIRTREE_SHUTUP; |
| if ((pid = atol(node->name))) lsof_pid(pid); |
| |
| return 0; |
| } |
| |
| void lsof_main(void) |
| { |
| struct arg_list *pp; |
| int i; |
| |
| // lsof will only filter on paths it can stat (because it filters by inode). |
| TT.sought_files = xmalloc(toys.optc*sizeof(struct stat)); |
| for (i = 0; i<toys.optc; ++i) xstat(toys.optargs[i], TT.sought_files+i); |
| |
| if (!TT.p) dirtree_read("/proc", scan_proc); |
| else for (pp = TT.p; pp; pp = pp->next) { |
| char *start, *end, *next = pp->arg; |
| int length, pid; |
| |
| while ((start = comma_iterate(&next, &length))) { |
| pid = strtol(start, &end, 10); |
| if (pid<1 || (*end && *end!=',')) |
| error_exit("bad -p '%.*s'", (int)(end-start), start); |
| lsof_pid(pid); |
| } |
| } |
| |
| llist_traverse(TT.files, print_info); |
| |
| if (CFG_TOYBOX_FREE) { |
| llist_traverse(TT.files, free_info); |
| llist_traverse(TT.all_sockets, free_info); |
| } |
| } |