1 /* lsof.c - list open files. 2 * 3 * Copyright 2015 The Android Open Source Project 4 5 USE_LSOF(NEWTOY(lsof, "lp*t", TOYFLAG_USR|TOYFLAG_BIN)) 6 7 config 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 24 GLOBALS( 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 32 struct proc_info { 33 char cmd[17]; 34 int pid, uid; 35 }; 36 37 struct 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 49 static 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 82 static void free_info(void *data) 83 { 84 free(((struct file_info *)data)->name); 85 free(data); 86 } 87 88 static 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 109 static 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 128 static 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 138 static 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 151 static 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 170 static 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 217 static 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 247 static 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), "0%03o", 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 289 struct 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 305 static 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 329 static 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 363 static 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 384 static 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 414 static 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 424 void 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