1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 2009 Stanislav Sedov <stas@FreeBSD.org>
5 * Copyright (c) 1988, 1993
6 *	The Regents of the University of California.  All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of the University nor the names of its contributors
17 *    may be used to endorse or promote products derived from this software
18 *    without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD$");
35
36#include <sys/param.h>
37#include <sys/user.h>
38#include <sys/stat.h>
39#include <sys/socket.h>
40#include <sys/socketvar.h>
41#include <sys/sysctl.h>
42#include <sys/queue.h>
43#include <sys/un.h>
44
45#include <netinet/in.h>
46
47#include <arpa/inet.h>
48
49#include <assert.h>
50#include <ctype.h>
51#include <err.h>
52#include <libprocstat.h>
53#include <limits.h>
54#include <pwd.h>
55#include <stdint.h>
56#include <stdio.h>
57#include <stdlib.h>
58#include <stddef.h>
59#include <string.h>
60#include <unistd.h>
61#include <netdb.h>
62
63#include "functions.h"
64
65static int 	fsflg,	/* show files on same filesystem as file(s) argument */
66		pflg,	/* show files open by a particular pid */
67		sflg,	/* show socket details */
68		uflg;	/* show files open by a particular (effective) user */
69static int 	checkfile; /* restrict to particular files or filesystems */
70static int	nflg;	/* (numerical) display f.s. and rdev as dev_t */
71static int	mflg;	/* include memory-mapped files */
72static int	vflg;	/* be verbose */
73
74typedef struct devs {
75	struct devs	*next;
76	uint64_t	fsid;
77	uint64_t	ino;
78	const char	*name;
79} DEVS;
80
81static DEVS *devs;
82static char *memf, *nlistf;
83
84static int	getfname(const char *filename);
85static void	dofiles(struct procstat *procstat, struct kinfo_proc *p);
86static void	print_access_flags(int flags);
87static void	print_file_info(struct procstat *procstat,
88    struct filestat *fst, const char *uname, const char *cmd, int pid);
89static void	print_pipe_info(struct procstat *procstat,
90    struct filestat *fst);
91static void	print_pts_info(struct procstat *procstat,
92    struct filestat *fst);
93static void	print_sem_info(struct procstat *procstat,
94    struct filestat *fst);
95static void	print_shm_info(struct procstat *procstat,
96    struct filestat *fst);
97static void	print_socket_info(struct procstat *procstat,
98    struct filestat *fst);
99static void	print_vnode_info(struct procstat *procstat,
100    struct filestat *fst);
101static void	usage(void) __dead2;
102
103int
104do_fstat(int argc, char **argv)
105{
106	struct kinfo_proc *p;
107	struct passwd *passwd;
108	struct procstat *procstat;
109	int arg, ch, what;
110	int cnt, i;
111
112	arg = 0;
113	what = KERN_PROC_PROC;
114	nlistf = memf = NULL;
115	while ((ch = getopt(argc, argv, "fmnp:su:vN:M:")) != -1)
116		switch((char)ch) {
117		case 'f':
118			fsflg = 1;
119			break;
120		case 'M':
121			memf = optarg;
122			break;
123		case 'N':
124			nlistf = optarg;
125			break;
126		case 'm':
127			mflg = 1;
128			break;
129		case 'n':
130			nflg = 1;
131			break;
132		case 'p':
133			if (pflg++)
134				usage();
135			if (!isdigit(*optarg)) {
136				warnx("-p requires a process id");
137				usage();
138			}
139			what = KERN_PROC_PID;
140			arg = atoi(optarg);
141			break;
142		case 's':
143			sflg = 1;
144			break;
145		case 'u':
146			if (uflg++)
147				usage();
148			if (!(passwd = getpwnam(optarg)))
149				errx(1, "%s: unknown uid", optarg);
150			what = KERN_PROC_UID;
151			arg = passwd->pw_uid;
152			break;
153		case 'v':
154			vflg = 1;
155			break;
156		case '?':
157		default:
158			usage();
159		}
160
161	if (*(argv += optind)) {
162		for (; *argv; ++argv) {
163			if (getfname(*argv))
164				checkfile = 1;
165		}
166		if (!checkfile)	/* file(s) specified, but none accessible */
167			exit(1);
168	}
169
170	if (fsflg && !checkfile) {
171		/* -f with no files means use wd */
172		if (getfname(".") == 0)
173			exit(1);
174		checkfile = 1;
175	}
176
177	if (memf != NULL)
178		procstat = procstat_open_kvm(nlistf, memf);
179	else
180		procstat = procstat_open_sysctl();
181	if (procstat == NULL)
182		errx(1, "procstat_open()");
183	p = procstat_getprocs(procstat, what, arg, &cnt);
184	if (p == NULL)
185		errx(1, "procstat_getprocs()");
186
187	/*
188	 * Print header.
189	 */
190	if (nflg)
191		printf("%s",
192"USER     CMD          PID   FD  DEV    INUM       MODE SZ|DV R/W");
193	else
194		printf("%s",
195"USER     CMD          PID   FD MOUNT      INUM MODE         SZ|DV R/W");
196	if (checkfile && fsflg == 0)
197		printf(" NAME\n");
198	else
199		putchar('\n');
200
201	/*
202	 * Go through the process list.
203	 */
204	for (i = 0; i < cnt; i++) {
205		if (p[i].ki_stat == SZOMB)
206			continue;
207		dofiles(procstat, &p[i]);
208	}
209	procstat_freeprocs(procstat, p);
210	procstat_close(procstat);
211	return (0);
212}
213
214static void
215dofiles(struct procstat *procstat, struct kinfo_proc *kp)
216{
217	const char *cmd;
218	const char *uname;
219	struct filestat *fst;
220	struct filestat_list *head;
221	int pid;
222
223	uname = user_from_uid(kp->ki_uid, 0);
224	pid = kp->ki_pid;
225	cmd = kp->ki_comm;
226
227	head = procstat_getfiles(procstat, kp, mflg);
228	if (head == NULL)
229		return;
230	STAILQ_FOREACH(fst, head, next)
231		print_file_info(procstat, fst, uname, cmd, pid);
232	procstat_freefiles(procstat, head);
233}
234
235
236static void
237print_file_info(struct procstat *procstat, struct filestat *fst,
238    const char *uname, const char *cmd, int pid)
239{
240	struct vnstat vn;
241	DEVS *d;
242	const char *filename;
243	int error, fsmatch = 0;
244	char errbuf[_POSIX2_LINE_MAX];
245
246	filename = NULL;
247	if (checkfile != 0) {
248		if (fst->fs_type != PS_FST_TYPE_VNODE &&
249		    fst->fs_type != PS_FST_TYPE_FIFO)
250			return;
251		error = procstat_get_vnode_info(procstat, fst, &vn, errbuf);
252		if (error != 0)
253			return;
254
255		for (d = devs; d != NULL; d = d->next)
256			if (d->fsid == vn.vn_fsid) {
257				fsmatch = 1;
258				if (d->ino == vn.vn_fileid) {
259					filename = d->name;
260					break;
261				}
262			}
263		if (fsmatch == 0 || (filename == NULL && fsflg == 0))
264			return;
265	}
266
267	/*
268	 * Print entry prefix.
269	 */
270	printf("%-8.8s %-10s %5d", uname, cmd, pid);
271	if (fst->fs_uflags & PS_FST_UFLAG_TEXT)
272		printf(" text");
273	else if (fst->fs_uflags & PS_FST_UFLAG_CDIR)
274		printf("   wd");
275	else if (fst->fs_uflags & PS_FST_UFLAG_RDIR)
276		printf(" root");
277	else if (fst->fs_uflags & PS_FST_UFLAG_TRACE)
278		printf("   tr");
279	else if (fst->fs_uflags & PS_FST_UFLAG_MMAP)
280		printf(" mmap");
281	else if (fst->fs_uflags & PS_FST_UFLAG_JAIL)
282		printf(" jail");
283	else if (fst->fs_uflags & PS_FST_UFLAG_CTTY)
284		printf(" ctty");
285	else
286		printf(" %4d", fst->fs_fd);
287
288	/*
289	 * Print type-specific data.
290	 */
291	switch (fst->fs_type) {
292	case PS_FST_TYPE_FIFO:
293	case PS_FST_TYPE_VNODE:
294		print_vnode_info(procstat, fst);
295		break;
296	case PS_FST_TYPE_SOCKET:
297		print_socket_info(procstat, fst);
298		break;
299	case PS_FST_TYPE_PIPE:
300		print_pipe_info(procstat, fst);
301		break;
302	case PS_FST_TYPE_PTS:
303		print_pts_info(procstat, fst);
304		break;
305	case PS_FST_TYPE_SHM:
306		print_shm_info(procstat, fst);
307		break;
308	case PS_FST_TYPE_SEM:
309		print_sem_info(procstat, fst);
310		break;
311	case PS_FST_TYPE_DEV:
312		break;
313	default:
314		if (vflg)
315			fprintf(stderr,
316			    "unknown file type %d for file %d of pid %d\n",
317			    fst->fs_type, fst->fs_fd, pid);
318	}
319	if (filename && !fsflg)
320		printf("  %s", filename);
321	putchar('\n');
322}
323
324static char *
325addr_to_string(struct sockaddr_storage *ss, char *buffer, int buflen)
326{
327	char buffer2[INET6_ADDRSTRLEN];
328	struct sockaddr_in6 *sin6;
329	struct sockaddr_in *sin;
330	struct sockaddr_un *sun;
331
332	switch (ss->ss_family) {
333	case AF_LOCAL:
334		sun = (struct sockaddr_un *)ss;
335		if (strlen(sun->sun_path) == 0)
336			strlcpy(buffer, "-", buflen);
337		else
338			strlcpy(buffer, sun->sun_path, buflen);
339		break;
340
341	case AF_INET:
342		sin = (struct sockaddr_in *)ss;
343		if (sin->sin_addr.s_addr == INADDR_ANY)
344			snprintf(buffer, buflen, "%s:%d", "*",
345			    ntohs(sin->sin_port));
346		else if (inet_ntop(AF_INET, &sin->sin_addr, buffer2,
347		    sizeof(buffer2)) != NULL)
348			snprintf(buffer, buflen, "%s:%d", buffer2,
349		            ntohs(sin->sin_port));
350		break;
351
352	case AF_INET6:
353		sin6 = (struct sockaddr_in6 *)ss;
354		if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr))
355			snprintf(buffer, buflen, "%s.%d", "*",
356			    ntohs(sin6->sin6_port));
357		else if (inet_ntop(AF_INET6, &sin6->sin6_addr, buffer2,
358		    sizeof(buffer2)) != NULL)
359			snprintf(buffer, buflen, "%s.%d", buffer2,
360			    ntohs(sin6->sin6_port));
361		else
362			strlcpy(buffer, "-", buflen);
363		break;
364
365	default:
366		strlcpy(buffer, "", buflen);
367		break;
368	}
369	return buffer;
370}
371
372
373static void
374print_socket_info(struct procstat *procstat, struct filestat *fst)
375{
376	static const char *stypename[] = {
377		"unused",	/* 0 */
378		"stream",	/* 1 */
379		"dgram",	/* 2 */
380		"raw",		/* 3 */
381		"rdm",		/* 4 */
382		"seqpak"	/* 5 */
383	};
384#define STYPEMAX 5
385	struct sockstat sock;
386	struct protoent *pe;
387	char errbuf[_POSIX2_LINE_MAX];
388	char src_addr[PATH_MAX], dst_addr[PATH_MAX];
389	struct sockaddr_un *sun;
390	int error;
391	static int isopen;
392
393	error = procstat_get_socket_info(procstat, fst, &sock, errbuf);
394	if (error != 0) {
395		printf("* error");
396		return;
397	}
398	if (sock.type > STYPEMAX)
399		printf("* %s ?%d", sock.dname, sock.type);
400	else
401		printf("* %s %s", sock.dname, stypename[sock.type]);
402
403	/*
404	 * protocol specific formatting
405	 *
406	 * Try to find interesting things to print.  For tcp, the interesting
407	 * thing is the address of the tcpcb, for udp and others, just the
408	 * inpcb (socket pcb).  For unix domain, its the address of the socket
409	 * pcb and the address of the connected pcb (if connected).  Otherwise
410	 * just print the protocol number and address of the socket itself.
411	 * The idea is not to duplicate netstat, but to make available enough
412	 * information for further analysis.
413	 */
414	switch (sock.dom_family) {
415	case AF_INET:
416	case AF_INET6:
417		if (!isopen)
418			setprotoent(++isopen);
419		if ((pe = getprotobynumber(sock.proto)) != NULL)
420			printf(" %s", pe->p_name);
421		else
422			printf(" %d", sock.proto);
423		if (sock.proto == IPPROTO_TCP ) {
424			if (sock.inp_ppcb != 0)
425				printf(" %lx", (u_long)sock.inp_ppcb);
426		}
427		else if (sock.so_pcb != 0)
428			printf(" %lx", (u_long)sock.so_pcb);
429		if (!sflg)
430			break;
431		printf(" %s <-> %s",
432		    addr_to_string(&sock.sa_local, src_addr, sizeof(src_addr)),
433		    addr_to_string(&sock.sa_peer, dst_addr, sizeof(dst_addr)));
434		break;
435	case AF_UNIX:
436		/* print address of pcb and connected pcb */
437		if (sock.so_pcb != 0) {
438			printf(" %lx", (u_long)sock.so_pcb);
439			if (sock.unp_conn) {
440				char shoconn[4], *cp;
441
442				cp = shoconn;
443				if (!(sock.so_rcv_sb_state & SBS_CANTRCVMORE))
444					*cp++ = '<';
445				*cp++ = '-';
446				if (!(sock.so_snd_sb_state & SBS_CANTSENDMORE))
447					*cp++ = '>';
448				*cp = '\0';
449				printf(" %s %lx", shoconn,
450				    (u_long)sock.unp_conn);
451			}
452		}
453		if (!sflg)
454			break;
455		sun = (struct sockaddr_un *)&sock.sa_local;
456		/*
457		 * While generally we like to print two addresses,
458		 * local and peer, for sockets, it turns out to be
459		 * more useful to print the first non-null address for
460		 * local sockets, as typically they aren't bound and
461		 *  connected, and the path strings can get long.
462		 */
463		if (sun->sun_path[0] != 0)
464			addr_to_string(&sock.sa_local,
465			    src_addr, sizeof(src_addr));
466		else
467			addr_to_string(&sock.sa_peer,
468			    src_addr, sizeof(src_addr));
469		printf(" %s", src_addr);
470		break;
471	default:
472		/* print protocol number and socket address */
473		printf(" %d %lx", sock.proto, (u_long)sock.so_addr);
474	}
475}
476
477static void
478print_pipe_info(struct procstat *procstat, struct filestat *fst)
479{
480	struct pipestat ps;
481	char errbuf[_POSIX2_LINE_MAX];
482	int error;
483
484	error = procstat_get_pipe_info(procstat, fst, &ps, errbuf);
485	if (error != 0) {
486		printf("* error");
487		return;
488	}
489	printf("* pipe %8lx <-> %8lx", (u_long)ps.addr, (u_long)ps.peer);
490	printf(" %6zd", ps.buffer_cnt);
491	print_access_flags(fst->fs_fflags);
492}
493
494static void
495print_pts_info(struct procstat *procstat, struct filestat *fst)
496{
497	struct ptsstat pts;
498	char errbuf[_POSIX2_LINE_MAX];
499	int error;
500
501	error = procstat_get_pts_info(procstat, fst, &pts, errbuf);
502	if (error != 0) {
503		printf("* error");
504		return;
505	}
506	printf("* pseudo-terminal master ");
507	if (nflg || !*pts.devname) {
508		printf("%#10jx", (uintmax_t)pts.dev);
509	} else {
510		printf("%10s", pts.devname);
511	}
512	print_access_flags(fst->fs_fflags);
513}
514
515static void
516print_sem_info(struct procstat *procstat, struct filestat *fst)
517{
518	struct semstat sem;
519	char errbuf[_POSIX2_LINE_MAX];
520	char mode[15];
521	int error;
522
523	error = procstat_get_sem_info(procstat, fst, &sem, errbuf);
524	if (error != 0) {
525		printf("* error");
526		return;
527	}
528	if (nflg) {
529		printf("             ");
530		(void)snprintf(mode, sizeof(mode), "%o", sem.mode);
531	} else {
532		printf(" %-15s", fst->fs_path != NULL ? fst->fs_path : "-");
533		strmode(sem.mode, mode);
534	}
535	printf(" %10s %6u", mode, sem.value);
536	print_access_flags(fst->fs_fflags);
537}
538
539static void
540print_shm_info(struct procstat *procstat, struct filestat *fst)
541{
542	struct shmstat shm;
543	char errbuf[_POSIX2_LINE_MAX];
544	char mode[15];
545	int error;
546
547	error = procstat_get_shm_info(procstat, fst, &shm, errbuf);
548	if (error != 0) {
549		printf("* error");
550		return;
551	}
552	if (nflg) {
553		printf("             ");
554		(void)snprintf(mode, sizeof(mode), "%o", shm.mode);
555	} else {
556		printf(" %-15s", fst->fs_path != NULL ? fst->fs_path : "-");
557		strmode(shm.mode, mode);
558	}
559	printf(" %10s %6ju", mode, shm.size);
560	print_access_flags(fst->fs_fflags);
561}
562
563static void
564print_vnode_info(struct procstat *procstat, struct filestat *fst)
565{
566	struct vnstat vn;
567	char errbuf[_POSIX2_LINE_MAX];
568	char mode[15];
569	const char *badtype;
570	int error;
571
572	badtype = NULL;
573	error = procstat_get_vnode_info(procstat, fst, &vn, errbuf);
574	if (error != 0)
575		badtype = errbuf;
576	else if (vn.vn_type == PS_FST_VTYPE_VBAD)
577		badtype = "bad";
578	else if (vn.vn_type == PS_FST_VTYPE_VNON)
579		badtype = "none";
580	if (badtype != NULL) {
581		printf(" -         -  %10s    -", badtype);
582		return;
583	}
584
585	if (nflg)
586		printf(" %#5jx", (uintmax_t)vn.vn_fsid);
587	else if (vn.vn_mntdir != NULL)
588		(void)printf(" %-8s", vn.vn_mntdir);
589
590	/*
591	 * Print access mode.
592	 */
593	if (nflg)
594		(void)snprintf(mode, sizeof(mode), "%o", vn.vn_mode);
595	else {
596		strmode(vn.vn_mode, mode);
597	}
598	(void)printf(" %6jd %10s", (intmax_t)vn.vn_fileid, mode);
599
600	if (vn.vn_type == PS_FST_VTYPE_VBLK || vn.vn_type == PS_FST_VTYPE_VCHR) {
601		if (nflg || !*vn.vn_devname)
602			printf(" %#6jx", (uintmax_t)vn.vn_dev);
603		else {
604			printf(" %6s", vn.vn_devname);
605		}
606	} else
607		printf(" %6ju", (uintmax_t)vn.vn_size);
608	print_access_flags(fst->fs_fflags);
609}
610
611static void
612print_access_flags(int flags)
613{
614	char rw[3];
615
616	rw[0] = '\0';
617	if (flags & PS_FST_FFLAG_READ)
618		strcat(rw, "r");
619	if (flags & PS_FST_FFLAG_WRITE)
620		strcat(rw, "w");
621	printf(" %2s", rw);
622}
623
624int
625getfname(const char *filename)
626{
627	struct stat statbuf;
628	DEVS *cur;
629
630	if (stat(filename, &statbuf)) {
631		warn("%s", filename);
632		return (0);
633	}
634	if ((cur = malloc(sizeof(DEVS))) == NULL)
635		err(1, NULL);
636	cur->next = devs;
637	devs = cur;
638
639	cur->ino = statbuf.st_ino;
640	cur->fsid = statbuf.st_dev;
641	cur->name = filename;
642	return (1);
643}
644
645static void
646usage(void)
647{
648	(void)fprintf(stderr,
649 "usage: fstat [-fmnv] [-M core] [-N system] [-p pid] [-u user] [file ...]\n");
650	exit(1);
651}
652