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