xref: /original-bsd/usr.bin/fstat/fstat.c (revision 07d71086)
1 /*
2  * Copyright (c) 1987 Regents of the University of California.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms are permitted
6  * provided that the above copyright notice and this paragraph are
7  * duplicated in all such forms and that any documentation,
8  * advertising materials, and other materials related to such
9  * distribution and use acknowledge that the software was developed
10  * by the University of California, Berkeley.  The name of the
11  * University may not be used to endorse or promote products derived
12  * from this software without specific prior written permission.
13  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
14  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
15  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
16  */
17 
18 #ifndef lint
19 char copyright[] =
20 "@(#) Copyright (c) 1987 Regents of the University of California.\n\
21  All rights reserved.\n";
22 #endif /* not lint */
23 
24 #ifndef lint
25 static char sccsid[] = "@(#)fstat.c	5.19 (Berkeley) 08/15/89";
26 #endif /* not lint */
27 
28 /*
29  *  fstat
30  */
31 #include <machine/pte.h>
32 
33 #include <sys/param.h>
34 #include <sys/dir.h>
35 #include <sys/user.h>
36 #include <sys/proc.h>
37 #include <sys/text.h>
38 #include <sys/stat.h>
39 #include <sys/time.h>
40 #include <sys/vnode.h>
41 #include <sys/socket.h>
42 #include <sys/socketvar.h>
43 #include <sys/domain.h>
44 #include <sys/protosw.h>
45 #include <sys/unpcb.h>
46 #include <sys/vmmac.h>
47 #define	KERNEL
48 #include <sys/file.h>
49 #undef	KERNEL
50 #include <ufs/inode.h>
51 #include <net/route.h>
52 #include <netinet/in.h>
53 #include <netinet/in_pcb.h>
54 #include <stdio.h>
55 #include <ctype.h>
56 #include <nlist.h>
57 #include <pwd.h>
58 #include <strings.h>
59 #include <paths.h>
60 
61 #define	TEXT	-2
62 #define	WD	-1
63 
64 #define	VP	if (vflg) (void)printf
65 
66 typedef struct devs {
67 	struct devs *next;
68 	dev_t dev;
69 	int inum;
70 	char *name;
71 } DEVS;
72 DEVS *devs;
73 
74 static struct nlist nl[] = {
75 	{ "_proc" },
76 #define	X_PROC		0
77 	{ "_Usrptmap" },
78 #define	X_USRPTMA	1
79 	{ "_nproc" },
80 #define	X_NPROC		2
81 	{ "_usrpt" },
82 #define	X_USRPT		3
83 	{ "_ufs_vnodeops" },
84 #define	X_UFSOPS	4
85 	{ "_blk_vnodeops" },
86 #define	X_BLKOPS	5
87 	{ "" },
88 };
89 
90 struct proc *mproc;
91 struct pte *Usrptma, *usrpt;
92 
93 union {
94 	struct user user;
95 	char upages[UPAGES][NBPG];
96 } user;
97 
98 extern int errno;
99 static int fflg, vflg;
100 static int kmem, mem, nproc, swap;
101 static char *uname;
102 
103 off_t lseek();
104 
105 main(argc, argv)
106 	int argc;
107 	char **argv;
108 {
109 	extern char *optarg;
110 	extern int optind;
111 	register struct passwd *passwd;
112 	register int pflg, pid, uflg, uid;
113 	int ch, size;
114 	struct passwd *getpwnam(), *getpwuid();
115 	long lgetw();
116 	char *malloc();
117 
118 	pflg = uflg = 0;
119 	while ((ch = getopt(argc, argv, "p:u:v")) != EOF)
120 		switch((char)ch) {
121 		case 'p':
122 			if (pflg++)
123 				usage();
124 			if (!isdigit(*optarg)) {
125 				fputs("fstat: -p option requires a process id.\n", stderr);
126 				usage();
127 			}
128 			pid = atoi(optarg);
129 			break;
130 		case 'u':
131 			if (uflg++)
132 				usage();
133 			if (!(passwd = getpwnam(optarg))) {
134 				(void)fprintf(stderr, "%s: unknown uid\n",
135 				    optarg);
136 				exit(1);
137 			}
138 			uid = passwd->pw_uid;
139 			uname = passwd->pw_name;
140 			break;
141 		case 'v':	/* undocumented: print read error messages */
142 			vflg++;
143 			break;
144 		case '?':
145 		default:
146 			usage();
147 		}
148 
149 	if (*(argv += optind)) {
150 		for (; *argv; ++argv) {
151 			if (getfname(*argv))
152 				fflg = 1;
153 		}
154 		if (!fflg)	/* file(s) specified, but none accessable */
155 			exit(1);
156 	}
157 
158 	openfiles();
159 
160 	if (nlist(_PATH_UNIX, nl) == -1 || !nl[0].n_type) {
161 		(void)fprintf(stderr, "%s: no namelist\n", _PATH_UNIX);
162 		exit(1);
163 	}
164 	Usrptma = (struct pte *)nl[X_USRPTMA].n_value;
165 	usrpt = (struct pte *) nl[X_USRPT].n_value;
166 	nproc = (int)lgetw((off_t)nl[X_NPROC].n_value);
167 
168 	(void)lseek(kmem, lgetw((off_t)nl[X_PROC].n_value), L_SET);
169 	size = nproc * sizeof(struct proc);
170 	if ((mproc = (struct proc *)malloc((u_int)size)) == NULL) {
171 		(void)fprintf(stderr, "fstat: out of space.\n");
172 		exit(1);
173 	}
174 	if (read(kmem, (char *)mproc, size) != size)
175 		rerr1("proc table", _PATH_KMEM);
176 
177 	(void)printf("USER\t CMD\t      PID    FD\tDEVICE\tINODE\t  SIZE TYPE%s\n",
178 	    fflg ? " NAME" : "");
179 	for (; nproc--; ++mproc) {
180 		if (mproc->p_stat == 0)
181 			continue;
182 		if (pflg && mproc->p_pid != pid)
183 			continue;
184 		if (uflg)  {
185 			if (mproc->p_uid != uid)
186 				continue;
187 		}
188 		else
189 			uname = (passwd = getpwuid(mproc->p_uid)) ?
190 			    passwd->pw_name : "unknown";
191 		if (mproc->p_stat != SZOMB && getu() == 0)
192 			continue;
193 		dotext();
194 		readf();
195 	}
196 	exit(0);
197 }
198 
199 static
200 getu()
201 {
202 	struct pte *pteaddr, apte;
203 	struct pte arguutl[UPAGES+CLSIZE];
204 	register int i;
205 	int ncl;
206 
207 	if ((mproc->p_flag & SLOAD) == 0) {
208 		if (swap < 0)
209 			return(0);
210 		(void)lseek(swap, (off_t)dtob(mproc->p_swaddr), L_SET);
211 		if (read(swap, (char *)&user.user, sizeof(struct user))
212 		    != sizeof(struct user)) {
213 			VP("fstat: can't read u for pid %d from %s\n",
214 			    mproc->p_pid, _PATH_DRUM);
215 			return(0);
216 		}
217 		return(1);
218 	}
219 	pteaddr = &Usrptma[btokmx(mproc->p_p0br) + mproc->p_szpt - 1];
220 	(void)lseek(kmem, (off_t)pteaddr, L_SET);
221 	if (read(kmem, (char *)&apte, sizeof(apte)) != sizeof(apte)) {
222 		VP("fstat: can't read indir pte to get u for pid %d from %s\n",
223 		    mproc->p_pid, _PATH_DRUM);
224 		return(0);
225 	}
226 	(void)lseek(mem, (off_t)ctob(apte.pg_pfnum+1) - (UPAGES+CLSIZE)
227 	    * sizeof(struct pte), L_SET);
228 	if (read(mem, (char *)arguutl, sizeof(arguutl)) != sizeof(arguutl)) {
229 		VP("fstat: can't read page table for u of pid %d from %s\n",
230 		    mproc->p_pid, _PATH_KMEM);
231 		return(0);
232 	}
233 	ncl = (sizeof(struct user) + NBPG*CLSIZE - 1) / (NBPG*CLSIZE);
234 	while (--ncl >= 0) {
235 		i = ncl * CLSIZE;
236 		(void)lseek(mem, (off_t)ctob(arguutl[CLSIZE+i].pg_pfnum), L_SET);
237 		if (read(mem, user.upages[i], CLSIZE*NBPG) != CLSIZE*NBPG) {
238 			VP("fstat: can't read page %u of u of pid %d from %s\n",
239 			    arguutl[CLSIZE+i].pg_pfnum, mproc->p_pid,
240 			    _PATH_MEM);
241 			return(0);
242 		}
243 	}
244 	return(1);
245 }
246 
247 static
248 dotext()
249 {
250 	struct text text;
251 
252 	(void)lseek(kmem, (off_t)mproc->p_textp, L_SET);
253 	if (read(kmem, (char *) &text, sizeof(text)) != sizeof(text)) {
254 		rerr1("text table", _PATH_KMEM);
255 		return;
256 	}
257 	if (text.x_flag)
258 		vtrans(DTYPE_VNODE, text.x_vptr, TEXT);
259 }
260 
261 static
262 vtrans(ftype, g, fno)
263 	int ftype, fno;
264 	struct vnode *g;		/* if ftype is vnode */
265 {
266 	struct vnode vnode;
267 	struct inode inode;
268 	dev_t idev;
269 	char *comm, *vtype();
270 	char *name = (char *)NULL;	/* set by devmatch() on a match */
271 
272 	if (g || fflg) {
273 		(void)lseek(kmem, (off_t)g, L_SET);
274 		if (read(kmem, (char *)&vnode, sizeof(vnode)) != sizeof(vnode)) {
275 			rerr2((int)g, "vnode");
276 			return;
277 		}
278 		if (vnode.v_op != (struct vnodeops *)nl[X_UFSOPS].n_value &&
279 		    (vnode.v_op != (struct vnodeops *)nl[X_BLKOPS].n_value ||
280 		     vnode.v_data == (qaddr_t)0)) {
281 			if (fflg)
282 				return;
283 			ftype = -1;
284 		} else {
285 			(void)lseek(kmem, (off_t)vnode.v_data, L_SET);
286 			if (read(kmem, (char *)&inode, sizeof(inode))
287 			    != sizeof(inode)) {
288 				rerr2((int)vnode.v_data, "inode");
289 				return;
290 			}
291 			idev = inode.i_dev;
292 			if (fflg && !devmatch(idev, inode.i_number, &name))
293 				return;
294 		}
295 	}
296 	if (mproc->p_pid == 0)
297 		comm = "swapper";
298 	else if (mproc->p_pid == 2)
299 		comm = "pagedaemon";
300 	else
301 		comm = user.user.u_comm;
302 	(void)printf("%-8.8s %-10.10s %5d  ", uname, comm, mproc->p_pid);
303 
304 	switch(fno) {
305 	case WD:
306 		(void)printf("  wd"); break;
307 	case TEXT:
308 		(void)printf("text"); break;
309 	default:
310 		(void)printf("%4d", fno);
311 	}
312 
313 	if (g == 0) {
314 		(void)printf("* (deallocated)\n");
315 		return;
316 	}
317 
318 	switch(ftype) {
319 
320 	case DTYPE_VNODE:
321 		(void)printf("\t%2d, %2d\t%5lu\t", major(inode.i_dev),
322 		    minor(inode.i_dev), inode.i_number);
323 		switch(inode.i_mode & IFMT) {
324 		case IFSOCK:
325 			(void)printf("     0\t");
326 			break;
327 		case IFBLK:
328 		case IFCHR:
329 			(void)printf("%2d, %2d\t", major(inode.i_rdev),
330 			    minor(inode.i_rdev));
331 			break;
332 		default:
333 			(void)printf("%6ld\t", inode.i_size);
334 		}
335 		(void)printf("%3s %s\n", vtype(vnode.v_type), name ? name : "");
336 		break;
337 
338 	case -1: /* DTYPE_VNODE for a remote filesystem */
339 		(void)printf(" from remote filesystem\t");
340 		(void)printf("%3s %s\n", vtype(vnode.v_type), name ? name : "");
341 		break;
342 
343 	case DTYPE_SOCKET:
344 		socktrans((struct socket *)g);
345 		break;
346 
347 #ifdef DTYPE_PORT
348 	case DTYPE_PORT:
349 		(void)printf("* (fifo / named pipe)\n");
350 		break;
351 #endif
352 
353 	default:
354 		(void)printf("* (unknown file type)\n");
355 	}
356 }
357 
358 static char *
359 vtype(type)
360 	enum vtype type;
361 {
362 	switch(type) {
363 	case VCHR:
364 		return("chr");
365 	case VDIR:
366 		return("dir");
367 	case VBLK:
368 		return("blk");
369 	case VREG:
370 		return("reg");
371 	case VLNK:
372 		return("lnk");
373 	case VSOCK:
374 		return("soc");
375 	case VBAD:
376 		return("bad");
377 	default:
378 		return("unk");
379 	}
380 	/*NOTREACHED*/
381 }
382 
383 static
384 socktrans(sock)
385 	struct socket *sock;
386 {
387 	static char *stypename[] = {
388 		"unused",	/* 0 */
389 		"stream", 	/* 1 */
390 		"dgram",	/* 2 */
391 		"raw",		/* 3 */
392 		"rdm",		/* 4 */
393 		"seqpak"	/* 5 */
394 	};
395 #define	STYPEMAX 5
396 	struct socket	so;
397 	struct protosw	proto;
398 	struct domain	dom;
399 	struct inpcb	inpcb;
400 	struct unpcb	unpcb;
401 	int len;
402 	char dname[32], *strcpy();
403 
404 	/* fill in socket */
405 	(void)lseek(kmem, (off_t)sock, L_SET);
406 	if (read(kmem, (char *)&so, sizeof(struct socket))
407 	    != sizeof(struct socket)) {
408 		rerr2((int)sock, "socket");
409 		return;
410 	}
411 
412 	/* fill in protosw entry */
413 	(void)lseek(kmem, (off_t)so.so_proto, L_SET);
414 	if (read(kmem, (char *)&proto, sizeof(struct protosw))
415 	    != sizeof(struct protosw)) {
416 		rerr2((int)so.so_proto, "protosw");
417 		return;
418 	}
419 
420 	/* fill in domain */
421 	(void)lseek(kmem, (off_t)proto.pr_domain, L_SET);
422 	if (read(kmem, (char *)&dom, sizeof(struct domain))
423 	    != sizeof(struct domain)) {
424 		rerr2((int)proto.pr_domain, "domain");
425 		return;
426 	}
427 
428 	/*
429 	 * grab domain name
430 	 * kludge "internet" --> "inet" for brevity
431 	 */
432 	if (dom.dom_family == AF_INET)
433 		(void)strcpy(dname, "inet");
434 	else {
435 		(void)lseek(kmem, (off_t)dom.dom_name, L_SET);
436 		if ((len = read(kmem, dname, sizeof(dname) - 1)) < 0) {
437 			rerr2((int)dom.dom_name, "char");
438 			dname[0] = '\0';
439 		}
440 		else
441 			dname[len] = '\0';
442 	}
443 
444 	if ((u_short)so.so_type > STYPEMAX)
445 		(void)printf("* (%s unk%d %x", dname, so.so_type, so.so_state);
446 	else
447 		(void)printf("* (%s %s %x", dname, stypename[so.so_type],
448 		    so.so_state);
449 
450 	/*
451 	 * protocol specific formatting
452 	 *
453 	 * Try to find interesting things to print.  For tcp, the interesting
454 	 * thing is the address of the tcpcb, for udp and others, just the
455 	 * inpcb (socket pcb).  For unix domain, its the address of the socket
456 	 * pcb and the address of the connected pcb (if connected).  Otherwise
457 	 * just print the protocol number and address of the socket itself.
458 	 * The idea is not to duplicate netstat, but to make available enough
459 	 * information for further analysis.
460 	 */
461 	switch(dom.dom_family) {
462 	case AF_INET:
463 		getinetproto(proto.pr_protocol);
464 		if (proto.pr_protocol == IPPROTO_TCP ) {
465 			if (so.so_pcb) {
466 				(void)lseek(kmem, (off_t)so.so_pcb, L_SET);
467 				if (read(kmem, (char *)&inpcb, sizeof(struct inpcb))
468 				    != sizeof(struct inpcb)){
469 					rerr2((int)so.so_pcb, "inpcb");
470 					return;
471 				}
472 				(void)printf(" %x", (int)inpcb.inp_ppcb);
473 			}
474 		}
475 		else if (so.so_pcb)
476 			(void)printf(" %x", (int)so.so_pcb);
477 		break;
478 	case AF_UNIX:
479 		/* print address of pcb and connected pcb */
480 		if (so.so_pcb) {
481 			(void)printf(" %x", (int)so.so_pcb);
482 			(void)lseek(kmem, (off_t)so.so_pcb, L_SET);
483 			if (read(kmem, (char *)&unpcb, sizeof(struct unpcb))
484 			    != sizeof(struct unpcb)){
485 				rerr2((int)so.so_pcb, "unpcb");
486 				return;
487 			}
488 			if (unpcb.unp_conn) {
489 				char shoconn[4], *cp;
490 
491 				cp = shoconn;
492 				if (!(so.so_state & SS_CANTRCVMORE))
493 					*cp++ = '<';
494 				*cp++ = '-';
495 				if (!(so.so_state & SS_CANTSENDMORE))
496 					*cp++ = '>';
497 				*cp = '\0';
498 				(void)printf(" %s %x", shoconn,
499 				    (int)unpcb.unp_conn);
500 			}
501 		}
502 		break;
503 	default:
504 		/* print protocol number and socket address */
505 		(void)printf(" %d %x", proto.pr_protocol, (int)sock);
506 	}
507 	(void)printf(")\n");
508 }
509 
510 /*
511  * getinetproto --
512  *	print name of protocol number
513  */
514 static
515 getinetproto(number)
516 	int number;
517 {
518 	char *cp;
519 
520 	switch(number) {
521 	case IPPROTO_IP:
522 		cp = "ip"; break;
523 	case IPPROTO_ICMP:
524 		cp ="icmp"; break;
525 	case IPPROTO_GGP:
526 		cp ="ggp"; break;
527 	case IPPROTO_TCP:
528 		cp ="tcp"; break;
529 	case IPPROTO_EGP:
530 		cp ="egp"; break;
531 	case IPPROTO_PUP:
532 		cp ="pup"; break;
533 	case IPPROTO_UDP:
534 		cp ="udp"; break;
535 	case IPPROTO_IDP:
536 		cp ="idp"; break;
537 	case IPPROTO_RAW:
538 		cp ="raw"; break;
539 	default:
540 		(void)printf(" %d", number);
541 		return;
542 	}
543 	(void)printf(" %s", cp);
544 }
545 
546 static
547 readf()
548 {
549 	struct file lfile;
550 	int i;
551 
552 	vtrans(DTYPE_VNODE, user.user.u_cdir, WD);
553 	for (i = 0; i < NOFILE; i++) {
554 		if (user.user.u_ofile[i] == 0)
555 			continue;
556 		(void)lseek(kmem, (off_t)user.user.u_ofile[i], L_SET);
557 		if (read(kmem, (char *)&lfile, sizeof(lfile))
558 		    != sizeof(lfile)) {
559 			rerr1("file", _PATH_KMEM);
560 			continue;
561 		}
562 		vtrans(lfile.f_type, (struct vnode *)lfile.f_data, i);
563 	}
564 }
565 
566 static
567 devmatch(idev, inum, name)
568 	dev_t idev;
569 	ino_t inum;
570 	char  **name;
571 {
572 	register DEVS *d;
573 
574 	for (d = devs; d; d = d->next)
575 		if (d->dev == idev && (d->inum == 0 || d->inum == inum)) {
576 			*name = d->name;
577 			return(1);
578 		}
579 	return(0);
580 }
581 
582 static
583 getfname(filename)
584 	char *filename;
585 {
586 	struct stat statbuf;
587 	DEVS *cur;
588 	char *malloc();
589 
590 	if (stat(filename, &statbuf)) {
591 		(void)fprintf(stderr, "fstat: %s: %s\n", strerror(errno),
592 		    filename);
593 		return(0);
594 	}
595 	if ((cur = (DEVS *)malloc(sizeof(DEVS))) == NULL) {
596 		(void)fprintf(stderr, "fstat: out of space.\n");
597 		exit(1);
598 	}
599 	cur->next = devs;
600 	devs = cur;
601 
602 	/* if file is block special, look for open files on it */
603 	if ((statbuf.st_mode & S_IFMT) != S_IFBLK) {
604 		cur->inum = statbuf.st_ino;
605 		cur->dev = statbuf.st_dev;
606 	}
607 	else {
608 		cur->inum = 0;
609 		cur->dev = statbuf.st_rdev;
610 	}
611 	cur->name = filename;
612 	return(1);
613 }
614 
615 static
616 openfiles()
617 {
618 	if ((kmem = open(_PATH_KMEM, O_RDONLY, 0)) < 0) {
619 		(void)fprintf(stderr, "fstat: %s: %s\n",
620 		    strerror(errno), _PATH_KMEM);
621 		exit(1);
622 	}
623 	if ((mem = open(_PATH_MEM, O_RDONLY, 0)) < 0) {
624 		(void)fprintf(stderr, "fstat: %s: %s\n",
625 		    strerror(errno), _PATH_MEM);
626 		exit(1);
627 	}
628 	if ((swap = open(_PATH_DRUM, O_RDONLY, 0)) < 0) {
629 		(void)fprintf(stderr, "fstat: %s: %s\n",
630 		    strerror(errno), _PATH_DRUM);
631 		exit(1);
632 	}
633 }
634 
635 static
636 rerr1(what, fromwhat)
637 	char *what, *fromwhat;
638 {
639 	VP("error reading %s from %s", what, fromwhat);
640 }
641 
642 static
643 rerr2(address, what)
644 	int address;
645 	char *what;
646 {
647 	VP("error %d reading %s at %x from %s\n", errno, what, address,
648 	    _PATH_KMEM);
649 }
650 
651 static long
652 lgetw(loc)
653 	off_t loc;
654 {
655 	long word;
656 
657 	(void)lseek(kmem, (off_t)loc, L_SET);
658 	if (read(kmem, (char *)&word, sizeof(word)) != sizeof(word))
659 		rerr2((int)loc, "word");
660 	return(word);
661 }
662 
663 static
664 usage()
665 {
666 	(void)fprintf(stderr,
667 	    "usage: fstat [-u user] [-p pid] [filename ...]\n");
668 	exit(1);
669 }
670