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