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,...] [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
24 GLOBALS(
25 struct arg_list *p;
26
27 struct stat *sought_files;
28
29 struct double_list *all_sockets;
30 struct double_list *files;
31 int last_shown_pid;
32 int shown_header;
33 )
34
35 struct proc_info {
36 char cmd[10];
37 int pid;
38 char user[12];
39 };
40
41 struct file_info {
42 char *next, *prev;
43
44 // For output.
45 struct proc_info pi;
46 char* name;
47 char fd[8], rw, locks, type[10], device[32], size_off[32], node[32];
48
49 // For filtering.
50 dev_t st_dev;
51 ino_t st_ino;
52 };
53
print_info(void * data)54 static void print_info(void *data)
55 {
56 struct file_info *fi = data;
57
58 // Filter matches
59 if (toys.optc) {
60 int i;
61
62 for (i = 0; i<toys.optc; i++)
63 if (TT.sought_files[i].st_dev==fi->st_dev)
64 if (TT.sought_files[i].st_ino==fi->st_ino) break;
65
66 if (i==toys.optc) return;
67 }
68
69 if (toys.optflags&FLAG_t) {
70 if (fi->pi.pid != TT.last_shown_pid)
71 printf("%d\n", TT.last_shown_pid = fi->pi.pid);
72 } else {
73 if (!TT.shown_header) {
74 // TODO: llist_traverse to measure the columns first.
75 printf("%-9s %5s %10.10s %4s %7s %18s %9s %10s %s\n", "COMMAND", "PID",
76 "USER", "FD", "TYPE", "DEVICE", "SIZE/OFF", "NODE", "NAME");
77 TT.shown_header = 1;
78 }
79
80 printf("%-9s %5d %10.10s %4s%c%c %7s %18s %9s %10s %s\n",
81 fi->pi.cmd, fi->pi.pid, fi->pi.user,
82 fi->fd, fi->rw, fi->locks, fi->type, fi->device, fi->size_off,
83 fi->node, fi->name);
84 }
85 }
86
free_info(void * data)87 static void free_info(void *data)
88 {
89 free(((struct file_info *)data)->name);
90 free(data);
91 }
92
fill_flags(struct file_info * fi)93 static void fill_flags(struct file_info *fi)
94 {
95 FILE* fp;
96 long long pos;
97 unsigned flags;
98
99 snprintf(toybuf, sizeof(toybuf), "/proc/%d/fdinfo/%s", fi->pi.pid, fi->fd);
100 fp = fopen(toybuf, "r");
101 if (!fp) return;
102
103 if (fscanf(fp, "pos: %lld flags: %o", &pos, &flags) == 2) {
104 flags &= O_ACCMODE;
105 if (flags == O_RDONLY) fi->rw = 'r';
106 else if (flags == O_WRONLY) fi->rw = 'w';
107 else fi->rw = 'u';
108
109 snprintf(fi->size_off, sizeof(fi->size_off), "0t%lld", pos);
110 }
111 fclose(fp);
112 }
113
scan_proc_net_file(char * path,int family,char type,void (* fn)(char *,int,char))114 static void scan_proc_net_file(char *path, int family, char type,
115 void (*fn)(char *, int, char))
116 {
117 FILE *fp = fopen(path, "r");
118 char *line = NULL;
119 size_t line_length = 0;
120
121 if (!fp) return;
122
123 if (!getline(&line, &line_length, fp)) return; // Skip header.
124
125 while (getline(&line, &line_length, fp) > 0) {
126 fn(line, family, type);
127 }
128
129 free(line);
130 fclose(fp);
131 }
132
add_socket(ino_t inode,const char * type)133 static struct file_info *add_socket(ino_t inode, const char *type)
134 {
135 struct file_info *fi = xzalloc(sizeof(struct file_info));
136
137 dlist_add_nomalloc(&TT.all_sockets, (struct double_list *)fi);
138 fi->st_ino = inode;
139 strcpy(fi->type, type);
140 return fi;
141 }
142
scan_unix(char * line,int af,char type)143 static void scan_unix(char *line, int af, char type)
144 {
145 long inode;
146 int path_pos;
147
148 if (sscanf(line, "%*p: %*X %*X %*X %*X %*X %lu %n", &inode, &path_pos) >= 1) {
149 struct file_info *fi = add_socket(inode, "unix");
150 char *name = chomp(line + path_pos);
151
152 fi->name = strdup(*name ? name : "socket");
153 }
154 }
155
scan_netlink(char * line,int af,char type)156 static void scan_netlink(char *line, int af, char type)
157 {
158 unsigned state;
159 long inode;
160 char *netlink_states[] = {
161 "ROUTE", "UNUSED", "USERSOCK", "FIREWALL", "SOCK_DIAG", "NFLOG", "XFRM",
162 "SELINUX", "ISCSI", "AUDIT", "FIB_LOOKUP", "CONNECTOR", "NETFILTER",
163 "IP6_FW", "DNRTMSG", "KOBJECT_UEVENT", "GENERIC", "DM", "SCSITRANSPORT",
164 "ENCRYPTFS", "RDMA", "CRYPTO"
165 };
166
167 if (sscanf(line, "%*p %u %*u %*x %*u %*u %*u %*u %*u %lu", &state, &inode)
168 < 2) {
169 return;
170 }
171
172 struct file_info *fi = add_socket(inode, "netlink");
173 fi->name =
174 strdup(state < ARRAY_LEN(netlink_states) ? netlink_states[state] : "?");
175 }
176
scan_ip(char * line,int af,char type)177 static void scan_ip(char *line, int af, char type)
178 {
179 char *tcp_states[] = {
180 "UNKNOWN", "ESTABLISHED", "SYN_SENT", "SYN_RECV", "FIN_WAIT1", "FIN_WAIT2",
181 "TIME_WAIT", "CLOSE", "CLOSE_WAIT", "LAST_ACK", "LISTEN", "CLOSING"
182 };
183 char local_ip[INET6_ADDRSTRLEN] = {0};
184 char remote_ip[INET6_ADDRSTRLEN] = {0};
185 struct in6_addr local, remote;
186 int local_port, remote_port, state;
187 long inode;
188 int ok;
189
190 if (af == 4) {
191 ok = sscanf(line, " %*d: %x:%x %x:%x %x %*x:%*x %*X:%*X %*X %*d %*d %ld",
192 &(local.s6_addr32[0]), &local_port,
193 &(remote.s6_addr32[0]), &remote_port,
194 &state, &inode) == 6;
195 } else {
196 ok = sscanf(line, " %*d: %8x%8x%8x%8x:%x %8x%8x%8x%8x:%x %x "
197 "%*x:%*x %*X:%*X %*X %*d %*d %ld",
198 &(local.s6_addr32[0]), &(local.s6_addr32[1]),
199 &(local.s6_addr32[2]), &(local.s6_addr32[3]),
200 &local_port,
201 &(remote.s6_addr32[0]), &(remote.s6_addr32[1]),
202 &(remote.s6_addr32[2]), &(remote.s6_addr32[3]),
203 &remote_port, &state, &inode) == 12;
204 }
205 if (!ok) return;
206
207 struct file_info *fi = add_socket(inode, af == 4 ? "IPv4" : "IPv6");
208 inet_ntop(af, &local, local_ip, sizeof(local_ip));
209 inet_ntop(af, &remote, remote_ip, sizeof(remote_ip));
210 if (type == 't') {
211 if (state < 0 || state > TCP_CLOSING) state = 0;
212 fi->name = xmprintf(af == 4 ?
213 "TCP %s:%d->%s:%d (%s)" :
214 "TCP [%s]:%d->[%s]:%d (%s)",
215 local_ip, local_port, remote_ip, remote_port,
216 tcp_states[state]);
217 } else {
218 fi->name = xmprintf(af == 4 ? "%s %s:%d->%s:%d" : "%s [%s]:%d->[%s]:%d",
219 type == 'u' ? "UDP" : "RAW",
220 local_ip, local_port, remote_ip, remote_port);
221 }
222 }
223
find_socket(struct file_info * fi,long inode)224 static int find_socket(struct file_info *fi, long inode)
225 {
226 static int cached;
227 if (!cached) {
228 scan_proc_net_file("/proc/net/tcp", 4, 't', scan_ip);
229 scan_proc_net_file("/proc/net/tcp6", 6, 't', scan_ip);
230 scan_proc_net_file("/proc/net/udp", 4, 'u', scan_ip);
231 scan_proc_net_file("/proc/net/udp6", 6, 'u', scan_ip);
232 scan_proc_net_file("/proc/net/raw", 4, 'r', scan_ip);
233 scan_proc_net_file("/proc/net/raw6", 6, 'r', scan_ip);
234 scan_proc_net_file("/proc/net/unix", 0, 0, scan_unix);
235 scan_proc_net_file("/proc/net/netlink", 0, 0, scan_netlink);
236 cached = 1;
237 }
238 void* list = TT.all_sockets;
239
240 while (list) {
241 struct file_info *s = (struct file_info*) llist_pop(&list);
242
243 if (s->st_ino == inode) {
244 fi->name = s->name ? strdup(s->name) : NULL;
245 strcpy(fi->type, s->type);
246 return 1;
247 }
248 if (list == TT.all_sockets) break;
249 }
250
251 return 0;
252 }
253
fill_stat(struct file_info * fi,const char * path)254 static void fill_stat(struct file_info *fi, const char *path)
255 {
256 struct stat sb;
257 long dev;
258
259 if (stat(path, &sb)) return;
260
261 // Fill TYPE.
262 switch ((sb.st_mode & S_IFMT)) {
263 case S_IFBLK: strcpy(fi->type, "BLK"); break;
264 case S_IFCHR: strcpy(fi->type, "CHR"); break;
265 case S_IFDIR: strcpy(fi->type, "DIR"); break;
266 case S_IFIFO: strcpy(fi->type, "FIFO"); break;
267 case S_IFLNK: strcpy(fi->type, "LINK"); break;
268 case S_IFREG: strcpy(fi->type, "REG"); break;
269 case S_IFSOCK: strcpy(fi->type, "sock"); break;
270 default:
271 snprintf(fi->type, sizeof(fi->type), "0%03o", sb.st_mode & S_IFMT);
272 break;
273 }
274
275 if (S_ISSOCK(sb.st_mode)) find_socket(fi, sb.st_ino);
276
277 // Fill DEVICE.
278 dev = (S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode)) ? sb.st_rdev : sb.st_dev;
279 if (!S_ISSOCK(sb.st_mode))
280 snprintf(fi->device, sizeof(fi->device), "%d,%d",
281 dev_major(dev), dev_minor(dev));
282
283 // Fill SIZE/OFF.
284 if (S_ISREG(sb.st_mode) || S_ISDIR(sb.st_mode))
285 snprintf(fi->size_off, sizeof(fi->size_off), "%lld",
286 (long long)sb.st_size);
287
288 // Fill NODE.
289 snprintf(fi->node, sizeof(fi->node), "%ld", (long)sb.st_ino);
290
291 // Stash st_dev and st_ino for filtering.
292 fi->st_dev = sb.st_dev;
293 fi->st_ino = sb.st_ino;
294 }
295
new_file_info(struct proc_info * pi,const char * fd)296 struct file_info *new_file_info(struct proc_info *pi, const char *fd)
297 {
298 struct file_info *fi = xzalloc(sizeof(struct file_info));
299
300 dlist_add_nomalloc(&TT.files, (struct double_list *)fi);
301
302 fi->pi = *pi;
303
304 // Defaults.
305 strcpy(fi->fd, fd);
306 strcpy(fi->type, "unknown");
307 fi->rw = fi->locks = ' ';
308
309 return fi;
310 }
311
visit_symlink(struct proc_info * pi,char * name,char * path)312 static void visit_symlink(struct proc_info *pi, char *name, char *path)
313 {
314 struct file_info *fi = new_file_info(pi, "");
315
316 // Get NAME.
317 if (name) { // "/proc/pid/[cwd]".
318 snprintf(fi->fd, sizeof(fi->fd), "%s", name);
319 snprintf(toybuf, sizeof(toybuf), "/proc/%d/%s", pi->pid, path);
320 } else { // "/proc/pid/fd/[3]"
321 snprintf(fi->fd, sizeof(fi->fd), "%s", path);
322 fill_flags(fi); // Clobbers toybuf.
323 snprintf(toybuf, sizeof(toybuf), "/proc/%d/fd/%s", pi->pid, path);
324 }
325 // TODO: code called by fill_stat would be easier to write if we didn't
326 // rely on toybuf being preserved here.
327 fill_stat(fi, toybuf);
328 if (!fi->name) { // We already have a name for things like sockets.
329 fi->name = xreadlink(toybuf);
330 if (!fi->name) {
331 fi->name = xmprintf("%s (readlink: %s)", toybuf, strerror(errno));
332 }
333 }
334 }
335
visit_maps(struct proc_info * pi)336 static void visit_maps(struct proc_info *pi)
337 {
338 FILE *fp;
339 unsigned long long offset;
340 char device[10];
341 long inode;
342 char *line = NULL;
343 size_t line_length = 0;
344
345 snprintf(toybuf, sizeof(toybuf), "/proc/%d/maps", pi->pid);
346 fp = fopen(toybuf, "r");
347 if (!fp) return;
348
349 while (getline(&line, &line_length, fp) > 0) {
350 int name_pos;
351
352 if (sscanf(line, "%*x-%*x %*s %llx %s %ld %n",
353 &offset, device, &inode, &name_pos) >= 3) {
354 struct file_info *fi;
355
356 // Ignore non-file maps.
357 if (inode == 0 || !strcmp(device, "00:00")) continue;
358 // TODO: show unique maps even if they have a non-zero offset?
359 if (offset != 0) continue;
360
361 fi = new_file_info(pi, "mem");
362 fi->name = strdup(chomp(line + name_pos));
363 fill_stat(fi, fi->name);
364 }
365 }
366 free(line);
367 fclose(fp);
368 }
369
visit_fds(struct proc_info * pi)370 static void visit_fds(struct proc_info *pi)
371 {
372 DIR *dir;
373 struct dirent *de;
374
375 snprintf(toybuf, sizeof(toybuf), "/proc/%d/fd", pi->pid);
376 if (!(dir = opendir(toybuf))) {
377 struct file_info *fi = new_file_info(pi, "NOFD");
378
379 fi->name = xmprintf("%s (opendir: %s)", toybuf, strerror(errno));
380 return;
381 }
382
383 while ((de = readdir(dir))) {
384 if (*de->d_name == '.') continue;
385 visit_symlink(pi, NULL, de->d_name);
386 }
387
388 closedir(dir);
389 }
390
lsof_pid(int pid)391 static void lsof_pid(int pid)
392 {
393 struct proc_info pi;
394 char *line;
395 struct stat sb;
396
397 // Skip nonexistent pids
398 sprintf(toybuf, "/proc/%d/stat", pid);
399 if (!(line = readfile(toybuf, toybuf, sizeof(toybuf)))) return;
400
401 // Get COMMAND.
402 strcpy(pi.cmd, "?");
403 if (line) {
404 char *open_paren = strchr(toybuf, '(');
405 char *close_paren = strrchr(toybuf, ')');
406
407 if (open_paren && close_paren) {
408 *close_paren = 0;
409 snprintf(pi.cmd, sizeof(pi.cmd), "%s", open_paren + 1);
410 }
411 }
412
413 // We already know PID.
414 pi.pid = pid;
415
416 // Get USER.
417 snprintf(toybuf, sizeof(toybuf), "/proc/%d", pid);
418 if (!stat(toybuf, &sb)) {
419 struct passwd *pw;
420
421 if (!(toys.optflags&FLAG_l) && (pw = getpwuid(sb.st_uid))) {
422 snprintf(pi.user, sizeof(pi.user), "%s", pw->pw_name);
423 } else snprintf(pi.user, sizeof(pi.user), "%u", (unsigned)sb.st_uid);
424 }
425
426 visit_symlink(&pi, "cwd", "cwd");
427 visit_symlink(&pi, "rtd", "root");
428 visit_symlink(&pi, "txt", "exe");
429 visit_maps(&pi);
430 visit_fds(&pi);
431 }
432
scan_proc(struct dirtree * node)433 static int scan_proc(struct dirtree *node)
434 {
435 int pid;
436
437 if (!node->parent) return DIRTREE_RECURSE|DIRTREE_SHUTUP;
438 if ((pid = atol(node->name))) lsof_pid(pid);
439
440 return 0;
441 }
442
lsof_main(void)443 void lsof_main(void)
444 {
445 struct arg_list *pp;
446 int i;
447
448 // lsof will only filter on paths it can stat (because it filters by inode).
449 TT.sought_files = xmalloc(toys.optc*sizeof(struct stat));
450 for (i = 0; i<toys.optc; ++i) xstat(toys.optargs[i], TT.sought_files+i);
451
452 if (!TT.p) dirtree_read("/proc", scan_proc);
453 else for (pp = TT.p; pp; pp = pp->next) {
454 char *start, *end, *next = pp->arg;
455 int length, pid;
456
457 while ((start = comma_iterate(&next, &length))) {
458 pid = strtol(start, &end, 10);
459 if (pid<1 || (*end && *end!=','))
460 error_exit("bad -p '%.*s'", (int)(end-start), start);
461 lsof_pid(pid);
462 }
463 }
464
465 llist_traverse(TT.files, print_info);
466
467 if (CFG_TOYBOX_FREE) {
468 llist_traverse(TT.files, free_info);
469 llist_traverse(TT.all_sockets, free_info);
470 }
471 }
472