xref: /original-bsd/usr.bin/w/w.c (revision 3f839ad3)
1 static char *sccsid = "@(#)w.c	4.8 (Berkeley) 11/19/82";
2 /*
3  * w - print system status (who and what)
4  *
5  * This program is similar to the systat command on Tenex/Tops 10/20
6  * It needs read permission on /dev/mem, /dev/kmem, and /dev/drum.
7  */
8 #include <sys/param.h>
9 #include <nlist.h>
10 #include <stdio.h>
11 #include <ctype.h>
12 #include <utmp.h>
13 #include <sys/stat.h>
14 #include <sys/dir.h>
15 #include <sys/user.h>
16 #include <sys/proc.h>
17 #include <sys/pte.h>
18 #include <sys/vm.h>
19 
20 #define NMAX sizeof(utmp.ut_name)
21 #define LMAX sizeof(utmp.ut_line)
22 
23 #define ARGWIDTH	33	/* # chars left on 80 col crt for args */
24 
25 struct pr {
26 	short	w_pid;			/* proc.p_pid */
27 	char	w_flag;			/* proc.p_flag */
28 	short	w_size;			/* proc.p_size */
29 	long	w_seekaddr;		/* where to find args */
30 	long	w_lastpg;		/* disk address of stack */
31 	int	w_igintr;		/* INTR+3*QUIT, 0=die, 1=ign, 2=catch */
32 	time_t	w_time;			/* CPU time used by this process */
33 	time_t	w_ctime;		/* CPU time used by children */
34 	dev_t	w_tty;			/* tty device of process */
35 	char	w_comm[15];		/* user.u_comm, null terminated */
36 	char	w_args[ARGWIDTH+1];	/* args if interesting process */
37 } *pr;
38 int	nproc;
39 
40 struct	nlist nl[] = {
41 	{ "_proc" },
42 #define	X_PROC		0
43 	{ "_swapdev" },
44 #define	X_SWAPDEV	1
45 	{ "_Usrptmap" },
46 #define	X_USRPTMA	2
47 	{ "_usrpt" },
48 #define	X_USRPT		3
49 	{ "_nswap" },
50 #define	X_NSWAP		4
51 	{ "_avenrun" },
52 #define	X_AVENRUN	5
53 	{ "_boottime" },
54 #define	X_BOOTTIME	6
55 	{ "_nproc" },
56 #define	X_NPROC		7
57 	{ 0 },
58 };
59 
60 FILE	*ps;
61 FILE	*ut;
62 FILE	*bootfd;
63 int	kmem;
64 int	mem;
65 int	swap;			/* /dev/kmem, mem, and swap */
66 int	nswap;
67 dev_t	tty;
68 char	doing[520];		/* process attached to terminal */
69 time_t	proctime;		/* cpu time of process in doing */
70 double	avenrun[3];
71 struct	proc *aproc;
72 
73 #define	DIV60(t)	((t + 30) / 60)    /* x/60 rounded */
74 #define	TTYEQ		(tty == pr[i].w_tty)
75 #define IGINT		(1+3*1)		/* ignoring both SIGINT & SIGQUIT */
76 
77 char	*getargs();
78 char	*fread();
79 char	*ctime();
80 char	*rindex();
81 FILE	*popen();
82 struct	tm *localtime();
83 
84 int	debug;			/* true if -d flag: debugging output */
85 int	header = 1;		/* true if -h flag: don't print heading */
86 int	lflag = 1;		/* true if -l flag: long style output */
87 int	login;			/* true if invoked as login shell */
88 int	idle;			/* number of minutes user is idle */
89 int	nusers;			/* number of users logged in now */
90 char *	sel_user;		/* login of particular user selected */
91 char firstchar;			/* first char of name of prog invoked as */
92 time_t	jobtime;		/* total cpu time visible */
93 time_t	now;			/* the current time of day */
94 struct	tm *nowt;		/* current time as time struct */
95 struct	timeval boottime;
96 time_t	uptime;			/* time of last reboot & elapsed time since */
97 int	np;			/* number of processes currently active */
98 struct	utmp utmp;
99 struct	proc mproc;
100 struct	user up;
101 char	fill[512];
102 
103 main(argc, argv)
104 	char **argv;
105 {
106 	int days, hrs, mins;
107 	register int i, j;
108 	char *cp;
109 	register int curpid, empty;
110 	char obuf[BUFSIZ];
111 
112 	setbuf(stdout, obuf);
113 	login = (argv[0][0] == '-');
114 	cp = rindex(argv[0], '/');
115 	firstchar = login ? argv[0][1] : (cp==0) ? argv[0][0] : cp[1];
116 	cp = argv[0];	/* for Usage */
117 
118 	while (argc > 1) {
119 		if (argv[1][0] == '-') {
120 			for (i=1; argv[1][i]; i++) {
121 				switch(argv[1][i]) {
122 
123 				case 'd':
124 					debug++;
125 					break;
126 
127 				case 'h':
128 					header = 0;
129 					break;
130 
131 				case 'l':
132 					lflag++;
133 					break;
134 
135 				case 's':
136 					lflag = 0;
137 					break;
138 
139 				case 'u':
140 				case 'w':
141 					firstchar = argv[1][i];
142 					break;
143 
144 				default:
145 					printf("Bad flag %s\n", argv[1]);
146 					exit(1);
147 				}
148 			}
149 		} else {
150 			if (!isalnum(argv[1][0]) || argc > 2) {
151 				printf("Usage: %s [ -hlsuw ] [ user ]\n", cp);
152 				exit(1);
153 			} else
154 				sel_user = argv[1];
155 		}
156 		argc--; argv++;
157 	}
158 
159 	if ((kmem = open("/dev/kmem", 0)) < 0) {
160 		fprintf(stderr, "No kmem\n");
161 		exit(1);
162 	}
163 	nlist("/vmunix", nl);
164 	if (nl[0].n_type==0) {
165 		fprintf(stderr, "No namelist\n");
166 		exit(1);
167 	}
168 
169 	if (firstchar != 'u')
170 		readpr();
171 
172 	ut = fopen("/etc/utmp","r");
173 	if (header) {
174 		/* Print time of day */
175 		time(&now);
176 		nowt = localtime(&now);
177 		prtat(nowt);
178 
179 		/*
180 		 * Print how long system has been up.
181 		 * (Found by looking for "boottime" in kernel)
182 		 */
183 		lseek(kmem, (long)nl[X_BOOTTIME].n_value, 0);
184 		read(kmem, &boottime, sizeof (boottime));
185 
186 		uptime = now - boottime.tv_sec;
187 		uptime += 30;
188 		days = uptime / (60*60*24);
189 		uptime %= (60*60*24);
190 		hrs = uptime / (60*60);
191 		uptime %= (60*60);
192 		mins = uptime / 60;
193 
194 		printf("  up");
195 		if (days > 0)
196 			printf(" %d day%s,", days, days>1?"s":"");
197 		if (hrs > 0 && mins > 0) {
198 			printf(" %2d:%02d,", hrs, mins);
199 		} else {
200 			if (hrs > 0)
201 				printf(" %d hr%s,", hrs, hrs>1?"s":"");
202 			if (mins > 0)
203 				printf(" %d min%s,", mins, mins>1?"s":"");
204 		}
205 
206 		/* Print number of users logged in to system */
207 		while (fread(&utmp, sizeof(utmp), 1, ut)) {
208 			if (utmp.ut_name[0] != '\0')
209 				nusers++;
210 		}
211 		rewind(ut);
212 		printf("  %d users", nusers);
213 
214 		/*
215 		 * Print 1, 5, and 15 minute load averages.
216 		 * (Found by looking in kernel for avenrun).
217 		 */
218 		printf(",  load average:");
219 		lseek(kmem, (long)nl[X_AVENRUN].n_value, 0);
220 		read(kmem, avenrun, sizeof(avenrun));
221 		for (i = 0; i < (sizeof(avenrun)/sizeof(avenrun[0])); i++) {
222 			if (i > 0)
223 				printf(",");
224 			printf(" %.2f", avenrun[i]);
225 		}
226 		printf("\n");
227 		if (firstchar == 'u')
228 			exit(0);
229 
230 		/* Headers for rest of output */
231 		if (lflag)
232 			printf("User     tty       login@  idle   JCPU   PCPU  what\n");
233 		else
234 			printf("User    tty  idle  what\n");
235 		fflush(stdout);
236 	}
237 
238 
239 	for (;;) {	/* for each entry in utmp */
240 		if (fread(&utmp, sizeof(utmp), 1, ut) == NULL) {
241 			fclose(ut);
242 			exit(0);
243 		}
244 		if (utmp.ut_name[0] == '\0')
245 			continue;	/* that tty is free */
246 		if (sel_user && strcmpn(utmp.ut_name, sel_user, NMAX) != 0)
247 			continue;	/* we wanted only somebody else */
248 
249 		gettty();
250 		jobtime = 0;
251 		proctime = 0;
252 		strcpy(doing, "-");	/* default act: normally never prints */
253 		empty = 1;
254 		curpid = -1;
255 		idle = findidle();
256 		for (i=0; i<np; i++) {	/* for each process on this tty */
257 			if (!(TTYEQ))
258 				continue;
259 			jobtime += pr[i].w_time + pr[i].w_ctime;
260 			proctime += pr[i].w_time;
261 			if (debug) {
262 				printf("\t\t%d\t%s", pr[i].w_pid, pr[i].w_args);
263 				if ((j=pr[i].w_igintr) > 0)
264 					if (j==IGINT)
265 						printf(" &");
266 					else
267 						printf(" & %d %d", j%3, j/3);
268 				printf("\n");
269 			}
270 			if (empty && pr[i].w_igintr!=IGINT) {
271 				empty = 0;
272 				curpid = -1;
273 			}
274 			if(pr[i].w_pid>curpid && (pr[i].w_igintr!=IGINT || empty)){
275 				curpid = pr[i].w_pid;
276 				strcpy(doing, lflag ? pr[i].w_args : pr[i].w_comm);
277 #ifdef notdef
278 				if (doing[0]==0 || doing[0]=='-' && doing[1]<=' ' || doing[0] == '?') {
279 					strcat(doing, " (");
280 					strcat(doing, pr[i].w_comm);
281 					strcat(doing, ")");
282 				}
283 #endif
284 			}
285 		}
286 		putline();
287 	}
288 }
289 
290 /* figure out the major/minor device # pair for this tty */
291 gettty()
292 {
293 	char ttybuf[20];
294 	struct stat statbuf;
295 
296 	ttybuf[0] = 0;
297 	strcpy(ttybuf, "/dev/");
298 	strcat(ttybuf, utmp.ut_line);
299 	stat(ttybuf, &statbuf);
300 	tty = statbuf.st_rdev;
301 }
302 
303 /*
304  * putline: print out the accumulated line of info about one user.
305  */
306 putline()
307 {
308 	register int tm;
309 
310 	/* print login name of the user */
311 	printf("%-*.*s ", NMAX, NMAX, utmp.ut_name);
312 
313 	/* print tty user is on */
314 	if (lflag)
315 		/* long form: all (up to) LMAX chars */
316 		printf("%-*.*s", LMAX, LMAX, utmp.ut_line);
317 	else {
318 		/* short form: 2 chars, skipping 'tty' if there */
319 		if (utmp.ut_line[0]=='t' && utmp.ut_line[1]=='t' && utmp.ut_line[2]=='y')
320 			printf("%-2.2s", &utmp.ut_line[3]);
321 		else
322 			printf("%-2.2s", utmp.ut_line);
323 	}
324 
325 	if (lflag)
326 		/* print when the user logged in */
327 		prtat(localtime(&utmp.ut_time));
328 
329 	/* print idle time */
330 	prttime(idle," ");
331 
332 	if (lflag) {
333 		/* print CPU time for all processes & children */
334 		prttime(jobtime," ");
335 		/* print cpu time for interesting process */
336 		prttime(proctime," ");
337 	}
338 
339 	/* what user is doing, either command tail or args */
340 	printf(" %-.32s\n",doing);
341 	fflush(stdout);
342 }
343 
344 /* find & return number of minutes current tty has been idle */
345 findidle()
346 {
347 	struct stat stbuf;
348 	long lastaction, diff;
349 	char ttyname[20];
350 
351 	strcpy(ttyname, "/dev/");
352 	strcatn(ttyname, utmp.ut_line, LMAX);
353 	stat(ttyname, &stbuf);
354 	time(&now);
355 	lastaction = stbuf.st_atime;
356 	diff = now - lastaction;
357 	diff = DIV60(diff);
358 	if (diff < 0) diff = 0;
359 	return(diff);
360 }
361 
362 /*
363  * prttime prints a time in hours and minutes.
364  * The character string tail is printed at the end, obvious
365  * strings to pass are "", " ", or "am".
366  */
367 prttime(tim, tail)
368 	time_t tim;
369 	char *tail;
370 {
371 	register int didhrs = 0;
372 
373 	if (tim >= 60) {
374 		printf("%3d:", tim/60);
375 		didhrs++;
376 	} else {
377 		printf("    ");
378 	}
379 	tim %= 60;
380 	if (tim > 0 || didhrs) {
381 		printf(didhrs&&tim<10 ? "%02d" : "%2d", tim);
382 	} else {
383 		printf("  ");
384 	}
385 	printf("%s", tail);
386 }
387 
388 /* prtat prints a 12 hour time given a pointer to a time of day */
389 prtat(p)
390 	struct tm *p;
391 {
392 	register int t, pm;
393 
394 	t = p -> tm_hour;
395 	pm = (t > 11);
396 	if (t > 11)
397 		t -= 12;
398 	if (t == 0)
399 		t = 12;
400 	prttime(t*60 + p->tm_min, pm ? "pm" : "am");
401 }
402 
403 /*
404  * readpr finds and reads in the array pr, containing the interesting
405  * parts of the proc and user tables for each live process.
406  */
407 readpr()
408 {
409 	int pn, mf, addr, c;
410 	int szpt, pfnum, i;
411 	struct pte *Usrptma, *usrpt, *pte, apte;
412 	struct dblock db;
413 
414 	Usrptma = (struct pte *) nl[X_USRPTMA].n_value;
415 	usrpt = (struct pte *) nl[X_USRPT].n_value;
416 	if((mem = open("/dev/mem", 0)) < 0) {
417 		fprintf(stderr, "No mem\n");
418 		exit(1);
419 	}
420 	if ((swap = open("/dev/drum", 0)) < 0) {
421 		fprintf(stderr, "No drum\n");
422 		exit(1);
423 	}
424 	/*
425 	 * read mem to find swap dev.
426 	 */
427 	lseek(kmem, (long)nl[X_SWAPDEV].n_value, 0);
428 	read(kmem, &nl[X_SWAPDEV].n_value, sizeof(nl[X_SWAPDEV].n_value));
429 	/*
430 	 * Find base of swap
431 	 */
432 	lseek(kmem, (long)nl[X_NSWAP].n_value, 0);
433 	read(kmem, &nswap, sizeof(nswap));
434 	/*
435 	 * Locate proc table
436 	 */
437 	lseek(kmem, (long)nl[X_NPROC].n_value, 0);
438 	read(kmem, &nproc, sizeof(nproc));
439 	pr = (struct pr *)calloc(nproc, sizeof (struct pr));
440 	np = 0;
441 	lseek(kmem, (long)nl[X_PROC].n_value, 0);
442 	read(kmem, &aproc, sizeof(aproc));
443 	for (pn=0; pn<nproc; pn++) {
444 		lseek(kmem, (int)(aproc + pn), 0);
445 		read(kmem, &mproc, sizeof mproc);
446 		/* decide if it's an interesting process */
447 		if (mproc.p_stat==0 || mproc.p_pgrp==0)
448 			continue;
449 		/* find & read in the user structure */
450 		if ((mproc.p_flag & SLOAD) == 0) {
451 			/* not in memory - get from swap device */
452 			addr = mproc.p_swaddr<<9;
453 			lseek(swap, (long)addr, 0);
454 			if (read(swap, &up, sizeof(up)) != sizeof(up)) {
455 				continue;
456 			}
457 		} else {
458 			int p0br, cc;
459 #define INTPPG (NBPG / sizeof (int))
460 			struct pte pagetbl[NBPG / sizeof (struct pte)];
461 			/* loaded, get each page from memory separately */
462 			szpt = mproc.p_szpt;
463 			p0br = (int)mproc.p_p0br;
464 			pte = &Usrptma[btokmx(mproc.p_p0br) + szpt-1];
465 			lseek(kmem, (long)pte, 0);
466 			if (read(kmem, &apte, sizeof(apte)) != sizeof(apte))
467 				continue;
468 			lseek(mem, ctob(apte.pg_pfnum), 0);
469 			if (read(mem,pagetbl,sizeof(pagetbl)) != sizeof(pagetbl))
470 cont:
471 				continue;
472 			for(cc=0; cc<UPAGES; cc++) {	/* get u area */
473 				int upage = pagetbl[NPTEPG-UPAGES+cc].pg_pfnum;
474 				lseek(mem,ctob(upage),0);
475 				if (read(mem,((int *)&up)+INTPPG*cc,NBPG) != NBPG)
476 					goto cont;
477 			}
478 			szpt = up.u_pcb.pcb_szpt;
479 			pr[np].w_seekaddr = ctob(apte.pg_pfnum);
480 		}
481 		vstodb(0, CLSIZE, &up.u_smap, &db, 1);
482 		pr[np].w_lastpg = ctob(db.db_base);
483 		if (up.u_ttyp == NULL)
484 			continue;
485 
486 		/* save the interesting parts */
487 		pr[np].w_pid = mproc.p_pid;
488 		pr[np].w_flag = mproc.p_flag;
489 		pr[np].w_size = mproc.p_dsize + mproc.p_ssize;
490 		pr[np].w_igintr = (((int)up.u_signal[2]==1) + 2*((int)up.u_signal[2]>1) + 3*((int)up.u_signal[3]==1)) + 6*((int)up.u_signal[3]>1);
491 		pr[np].w_time =
492 		    up.u_ru.ru_utime.tv_sec + up.u_ru.ru_stime.tv_sec;
493 		pr[np].w_ctime =
494 		    up.u_cru.ru_utime.tv_sec + up.u_cru.ru_stime.tv_sec;
495 		pr[np].w_tty = up.u_ttyd;
496 		up.u_comm[14] = 0;	/* Bug: This bombs next field. */
497 		strcpy(pr[np].w_comm, up.u_comm);
498 		/*
499 		 * Get args if there's a chance we'll print it.
500 		 * Cant just save pointer: getargs returns static place.
501 		 * Cant use strcpyn: that crock blank pads.
502 		 */
503 		pr[np].w_args[0] = 0;
504 		strcatn(pr[np].w_args,getargs(&pr[np]),ARGWIDTH);
505 		if (pr[np].w_args[0]==0 || pr[np].w_args[0]=='-' && pr[np].w_args[1]<=' ' || pr[np].w_args[0] == '?') {
506 			strcat(pr[np].w_args, " (");
507 			strcat(pr[np].w_args, pr[np].w_comm);
508 			strcat(pr[np].w_args, ")");
509 		}
510 		np++;
511 	}
512 }
513 
514 /*
515  * getargs: given a pointer to a proc structure, this looks at the swap area
516  * and tries to reconstruct the arguments. This is straight out of ps.
517  */
518 char *
519 getargs(p)
520 	struct pr *p;
521 {
522 	int c, addr, nbad;
523 	static int abuf[CLSIZE*NBPG/sizeof(int)];
524 	struct pte pagetbl[NPTEPG];
525 	register int *ip;
526 	register char *cp, *cp1;
527 
528 	if ((p->w_flag & SLOAD) == 0) {
529 		lseek(swap, p->w_lastpg, 0);
530 		if (read(swap, abuf, sizeof(abuf)) != sizeof(abuf))
531 			return(p->w_comm);
532 	} else {
533 		c = p->w_seekaddr;
534 		lseek(mem,c,0);
535 		if (read(mem,pagetbl,NBPG) != NBPG)
536 			return(p->w_comm);
537 		if (pagetbl[NPTEPG-CLSIZE-UPAGES].pg_fod==0 && pagetbl[NPTEPG-CLSIZE-UPAGES].pg_pfnum) {
538 			lseek(mem,ctob(pagetbl[NPTEPG-CLSIZE-UPAGES].pg_pfnum),0);
539 			if (read(mem,abuf,sizeof(abuf)) != sizeof(abuf))
540 				return(p->w_comm);
541 		} else {
542 			lseek(swap, p->w_lastpg, 0);
543 			if (read(swap, abuf, sizeof(abuf)) != sizeof(abuf))
544 				return(p->w_comm);
545 		}
546 	}
547 	abuf[sizeof(abuf)/sizeof(abuf[0])-1] = 0;
548 	for (ip = &abuf[sizeof(abuf)/sizeof(abuf[0])-2]; ip > abuf;) {
549 		/* Look from top for -1 or 0 as terminator flag. */
550 		if (*--ip == -1 || *ip == 0) {
551 			cp = (char *)(ip+1);
552 			if (*cp==0)
553 				cp++;
554 			nbad = 0;	/* up to 5 funny chars as ?'s */
555 			for (cp1 = cp; cp1 < (char *)&abuf[sizeof(abuf)/sizeof(abuf[0])]; cp1++) {
556 				c = *cp1&0177;
557 				if (c==0)  /* nulls between args => spaces */
558 					*cp1 = ' ';
559 				else if (c < ' ' || c > 0176) {
560 					if (++nbad >= 5) {
561 						*cp1++ = ' ';
562 						break;
563 					}
564 					*cp1 = '?';
565 				} else if (c=='=') {	/* Oops - found an
566 							 * environment var, back
567 							 * over & erase it. */
568 					*cp1 = 0;
569 					while (cp1>cp && *--cp1!=' ')
570 						*cp1 = 0;
571 					break;
572 				}
573 			}
574 			while (*--cp1==' ')	/* strip trailing spaces */
575 				*cp1 = 0;
576 			return(cp);
577 		}
578 	}
579 	return (p->w_comm);
580 }
581 
582 /*
583  * Given a base/size pair in virtual swap area,
584  * return a physical base/size pair which is the
585  * (largest) initial, physically contiguous block.
586  */
587 vstodb(vsbase, vssize, dmp, dbp, rev)
588 	register int vsbase;
589 	int vssize;
590 	struct dmap *dmp;
591 	register struct dblock *dbp;
592 {
593 	register int blk = DMMIN;
594 	register swblk_t *ip = dmp->dm_map;
595 
596 	if (vsbase < 0 || vsbase + vssize > dmp->dm_size)
597 		panic("vstodb");
598 	while (vsbase >= blk) {
599 		vsbase -= blk;
600 		if (blk < DMMAX)
601 			blk *= 2;
602 		ip++;
603 	}
604 	if (*ip <= 0 || *ip + blk > nswap)
605 		panic("vstodb *ip");
606 	dbp->db_size = min(vssize, blk - vsbase);
607 	dbp->db_base = *ip + (rev ? blk - (vsbase + dbp->db_size) : vsbase);
608 }
609 
610 panic(cp)
611 	char *cp;
612 {
613 
614 	/* printf("%s\n", cp); */
615 }
616 
617 min(a, b)
618 {
619 
620 	return (a < b ? a : b);
621 }
622