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