|
|
1.1 ! root 1: /* ! 2: * Copyright (c) 1980 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) 1980 Regents of the University of California.\n\ ! 21: All rights reserved.\n"; ! 22: #endif /* not lint */ ! 23: ! 24: #ifndef lint ! 25: static char sccsid[] = "@(#)sysline.c 5.12 (Berkeley) 6/29/88"; ! 26: #endif /* not lint */ ! 27: ! 28: /* ! 29: * sysline - system status display on 25th line of terminal ! 30: * j.k.foderaro ! 31: * ! 32: * Prints a variety of information on the special status line of terminals ! 33: * that have a status display capability. Cursor motions, status commands, ! 34: * etc. are gleamed from /etc/termcap. ! 35: * By default, all information is printed, and flags are given on the command ! 36: * line to disable the printing of information. The information and ! 37: * disabling flags are: ! 38: * ! 39: * flag what ! 40: * ----- ---- ! 41: * time of day ! 42: * load average and change in load average in the last 5 mins ! 43: * number of user logged on ! 44: * -p # of processes the users owns which are runnable and the ! 45: * number which are suspended. Processes whose parent is 1 ! 46: * are not counted. ! 47: * -l users who've logged on and off. ! 48: * -m summarize new mail which has arrived ! 49: * ! 50: * <other flags> ! 51: * -r use non reverse video ! 52: * -c turn off 25th line for 5 seconds before redisplaying. ! 53: * -b beep once one the half hour, twice on the hour ! 54: * +N refresh display every N seconds. ! 55: * -i print pid first thing ! 56: * -e do simple print designed for an emacs buffer line ! 57: * -w do the right things for a window ! 58: * -h print hostname between time and load average ! 59: * -D print day/date before time of day ! 60: * -d debug mode - print status line data in human readable format ! 61: * -q quiet mode - don't output diagnostic messages ! 62: * -s print Short (left-justified) line if escapes not allowed ! 63: * -j Print left Justified line regardless ! 64: */ ! 65: ! 66: #define BSD4_2 /* for 4.2 BSD */ ! 67: #define WHO /* turn this on always */ ! 68: #define HOSTNAME /* 4.1a or greater, with hostname() */ ! 69: #define RWHO /* 4.1a or greater, with rwho */ ! 70: #define VMUNIX /* turn this on if you are running on vmunix */ ! 71: #define NEW_BOOTTIME /* 4.1c or greater */ ! 72: ! 73: #define NETPREFIX "ucb" ! 74: #define DEFDELAY 60 /* update status once per minute */ ! 75: #define MAILDIR "/usr/spool/mail" ! 76: /* ! 77: * if MAXLOAD is defined, then if the load average exceeded MAXLOAD ! 78: * then the process table will not be scanned and the log in/out data ! 79: * will not be checked. The purpose of this is to reduced the load ! 80: * on the system when it is loaded. ! 81: */ ! 82: #define MAXLOAD 6.0 ! 83: ! 84: #include <stdio.h> ! 85: #include <sys/param.h> ! 86: #include <sys/signal.h> ! 87: #include <utmp.h> ! 88: #include <ctype.h> ! 89: #ifndef BSD4_2 ! 90: #include <unctrl.h> ! 91: #endif ! 92: #include <sys/time.h> ! 93: #include <sys/stat.h> ! 94: #ifdef VMUNIX ! 95: #include <nlist.h> ! 96: #include <sys/vtimes.h> ! 97: #include <sys/proc.h> ! 98: #endif ! 99: #ifdef pdp11 ! 100: #include <a.out.h> ! 101: #include <sys/proc.h> ! 102: #endif ! 103: #include <curses.h> ! 104: #undef nl ! 105: #ifdef TERMINFO ! 106: #include <term.h> ! 107: #endif TERMINFO ! 108: ! 109: #ifdef RWHO ! 110: #include <protocols/rwhod.h> ! 111: ! 112: #define DOWN_THRESHOLD (11 * 60) ! 113: #define RWHOLEADER "/usr/spool/rwho/whod." ! 114: ! 115: struct remotehost { ! 116: char *rh_host; ! 117: int rh_file; ! 118: } remotehost[10]; ! 119: int nremotes = 0; ! 120: #endif RWHO ! 121: ! 122: struct nlist nl[] = { ! 123: #ifdef NEW_BOOTTIME ! 124: { "_boottime" }, /* After 4.1a the label changed to "boottime" */ ! 125: #else ! 126: { "_bootime" }, /* Under 4.1a and earlier it is "bootime" */ ! 127: #endif ! 128: #define NL_BOOT 0 ! 129: { "_proc" }, ! 130: #define NL_PROC 1 ! 131: { "_avenrun" }, ! 132: #define NL_AVEN 2 ! 133: #ifdef VMUNIX ! 134: { "_nproc" }, ! 135: #define NL_NPROC 3 ! 136: #endif ! 137: 0 ! 138: }; ! 139: ! 140: /* stuff for the kernel */ ! 141: int kmem; /* file descriptor for /dev/kmem */ ! 142: struct proc *proc, *procNPROC; ! 143: int nproc; ! 144: int procadr; ! 145: double avenrun[3]; /* used for storing load averages */ ! 146: ! 147: /* ! 148: * In order to determine how many people are logged on and who has ! 149: * logged in or out, we read in the /etc/utmp file. We also keep track of ! 150: * the previous utmp file. ! 151: */ ! 152: int ut = -1; /* the file descriptor */ ! 153: struct utmp *new, *old; ! 154: char *status; /* per tty status bits, see below */ ! 155: int nentries; /* number of utmp entries */ ! 156: /* string lengths for printing */ ! 157: #define LINESIZE (sizeof old->ut_line) ! 158: #define NAMESIZE (sizeof old->ut_name) ! 159: /* ! 160: * Status codes to say what has happened to a particular entry in utmp. ! 161: * NOCH means no change, ON means new person logged on, ! 162: * OFF means person logged off. ! 163: */ ! 164: #define NOCH 0 ! 165: #define ON 0x1 ! 166: #define OFF 0x2 ! 167: ! 168: #ifdef WHO ! 169: char whofilename[100]; ! 170: char whofilename2[100]; ! 171: #endif ! 172: ! 173: #ifdef HOSTNAME ! 174: char hostname[MAXHOSTNAMELEN+1]; /* one more for null termination */ ! 175: #endif ! 176: ! 177: char lockfilename[100]; /* if exists, will prevent us from running */ ! 178: ! 179: /* flags which determine which info is printed */ ! 180: int mailcheck = 1; /* m - do biff like checking of mail */ ! 181: int proccheck = 1; /* p - give information on processes */ ! 182: int logcheck = 1; /* l - tell who logs in and out */ ! 183: int hostprint = 0; /* h - print out hostname */ ! 184: int dateprint = 0; /* h - print out day/date */ ! 185: int quiet = 0; /* q - hush diagnostic messages */ ! 186: ! 187: /* flags which determine how things are printed */ ! 188: int clr_bet_ref = 0; /* c - clear line between refeshes */ ! 189: int reverse = 1; /* r - use reverse video */ ! 190: int shortline = 0; /* s - short (left-justified) if escapes not allowed */ ! 191: int leftline = 0; /* j - left-justified even if escapes allowed */ ! 192: ! 193: /* flags which have terminal do random things */ ! 194: int beep = 0; /* b - beep every half hour and twice every hour */ ! 195: int printid = 0; /* i - print pid of this process at startup */ ! 196: int synch = 1; /* synchronize with clock */ ! 197: ! 198: /* select output device (status display or straight output) */ ! 199: int emacs = 0; /* e - assume status display */ ! 200: int window = 0; /* w - window mode */ ! 201: int dbug = 0; /* d - debug */ ! 202: ! 203: /* ! 204: * used to turn off reverse video every REVOFF times ! 205: * in an attempt to not wear out the phospher. ! 206: */ ! 207: #define REVOFF 5 ! 208: int revtime = 1; ! 209: ! 210: /* used by mail checker */ ! 211: off_t mailsize = 0; ! 212: off_t linebeg = 0; /* place where we last left off reading */ ! 213: ! 214: /* things used by the string routines */ ! 215: int chars; /* number of printable characters */ ! 216: char *sp; ! 217: char strarr[512]; /* big enough now? */ ! 218: /* flags to stringdump() */ ! 219: char sawmail; /* remember mail was seen to print bells */ ! 220: char mustclear; /* status line messed up */ ! 221: ! 222: /* strings which control status line display */ ! 223: #ifdef TERMINFO ! 224: char *rev_out, *rev_end, *arrows; ! 225: char *tparm(); ! 226: #else ! 227: char to_status_line[64]; ! 228: char from_status_line[64]; ! 229: char dis_status_line[64]; ! 230: char clr_eol[64]; ! 231: char rev_out[20], rev_end[20]; ! 232: char *arrows, *bell = "\007"; ! 233: int eslok; /* escapes on status line okay (reverse, cursor addressing) */ ! 234: int hasws = 0; /* is "ws" explicitly defined? */ ! 235: int columns; ! 236: #define tparm(cap, parm) tgoto((cap), 0, (parm)) ! 237: char *tgoto(); ! 238: #endif ! 239: ! 240: /* to deal with window size changes */ ! 241: #ifdef SIGWINCH ! 242: int sigwinch(); ! 243: char winchanged; /* window size has changed since last update */ ! 244: #endif ! 245: ! 246: /* random globals */ ! 247: char *username; ! 248: char *ourtty; /* keep track of what tty we're on */ ! 249: struct stat stbuf, mstbuf; /* mstbuf for mail check only */ ! 250: unsigned delay = DEFDELAY; ! 251: uid_t uid; ! 252: double loadavg = 0.0; /* current load average */ ! 253: int users = 0; ! 254: ! 255: char *getenv(); ! 256: char *ttyname(); ! 257: char *strcpy1(); ! 258: char *sysrup(); ! 259: char *calloc(); ! 260: char *malloc(); ! 261: int outc(); ! 262: int erroutc(); ! 263: ! 264: main(argc,argv) ! 265: register char **argv; ! 266: { ! 267: int clearbotl(); ! 268: register char *cp; ! 269: char *home; ! 270: extern char *index(); ! 271: ! 272: #ifdef HOSTNAME ! 273: gethostname(hostname, sizeof hostname - 1); ! 274: if ((cp = index(hostname, '.')) != NULL) ! 275: *cp = '\0'; ! 276: #endif ! 277: ! 278: for (argv++; *argv != 0; argv++) ! 279: switch (**argv) { ! 280: case '-': ! 281: for (cp = *argv + 1; *cp; cp++) { ! 282: switch(*cp) { ! 283: case 'r' : /* turn off reverse video */ ! 284: reverse = 0; ! 285: break; ! 286: case 'c': ! 287: clr_bet_ref = 1; ! 288: break; ! 289: case 'h': ! 290: hostprint = 1; ! 291: break; ! 292: case 'D': ! 293: dateprint = 1; ! 294: break; ! 295: #ifdef RWHO ! 296: case 'H': ! 297: if (argv[1] == 0) ! 298: break; ! 299: argv++; ! 300: if (strcmp(hostname, *argv) && ! 301: strcmp(&hostname[sizeof NETPREFIX - 1], *argv)) ! 302: remotehost[nremotes++].rh_host = *argv; ! 303: break; ! 304: #endif RWHO ! 305: case 'm': ! 306: mailcheck = 0; ! 307: break; ! 308: case 'p': ! 309: proccheck = 0; ! 310: break; ! 311: case 'l': ! 312: logcheck = 0; ! 313: break; ! 314: case 'b': ! 315: beep = 1; ! 316: break; ! 317: case 'i': ! 318: printid = 1; ! 319: break; ! 320: case 'w': ! 321: window = 1; ! 322: break; ! 323: case 'e': ! 324: emacs = 1; ! 325: break; ! 326: case 'd': ! 327: dbug = 1; ! 328: break; ! 329: case 'q': ! 330: quiet = 1; ! 331: break; ! 332: case 's': ! 333: shortline = 1; ! 334: break; ! 335: case 'j': ! 336: leftline = 1; ! 337: break; ! 338: default: ! 339: fprintf(stderr, ! 340: "sysline: bad flag: %c\n", *cp); ! 341: } ! 342: } ! 343: break; ! 344: case '+': ! 345: delay = atoi(*argv + 1); ! 346: if (delay < 10) ! 347: delay = 10; ! 348: else if (delay > 500) ! 349: delay = 500; ! 350: synch = 0; /* no more sync */ ! 351: break; ! 352: default: ! 353: fprintf(stderr, "sysline: illegal argument %s\n", ! 354: argv[0]); ! 355: } ! 356: if (emacs) { ! 357: reverse = 0; ! 358: columns = 79; ! 359: } else /* if not to emacs window, initialize terminal dependent info */ ! 360: initterm(); ! 361: #ifdef SIGWINCH ! 362: /* ! 363: * When the window size changes and we are the foreground ! 364: * process (true if -w), we get this signal. ! 365: */ ! 366: signal(SIGWINCH, sigwinch); ! 367: #endif ! 368: getwinsize(); /* get window size from ioctl */ ! 369: ! 370: /* immediately fork and let the parent die if not emacs mode */ ! 371: if (!emacs && !window && !dbug) { ! 372: if (fork()) ! 373: exit(0); ! 374: /* pgrp should take care of things, but ignore them anyway */ ! 375: signal(SIGINT, SIG_IGN); ! 376: signal(SIGQUIT, SIG_IGN); ! 377: #ifdef VMUNIX ! 378: signal(SIGTTOU, SIG_IGN); ! 379: #endif ! 380: } ! 381: /* ! 382: * When we logoff, init will do a "vhangup()" on this ! 383: * tty which turns off I/O access and sends a SIGHUP ! 384: * signal. We catch this and thereby clear the status ! 385: * display. Note that a bug in 4.1bsd caused the SIGHUP ! 386: * signal to be sent to the wrong process, so you had to ! 387: * `kill -HUP' yourself in your .logout file. ! 388: * Do the same thing for SIGTERM, which is the default kill ! 389: * signal. ! 390: */ ! 391: signal(SIGHUP, clearbotl); ! 392: signal(SIGTERM, clearbotl); ! 393: /* ! 394: * This is so kill -ALRM to force update won't screw us up.. ! 395: */ ! 396: signal(SIGALRM, SIG_IGN); ! 397: ! 398: uid = getuid(); ! 399: ourtty = ttyname(2); /* remember what tty we are on */ ! 400: if (printid) { ! 401: printf("%d\n", getpid()); ! 402: fflush(stdout); ! 403: } ! 404: dup2(2, 1); ! 405: ! 406: if ((home = getenv("HOME")) == 0) ! 407: home = ""; ! 408: strcpy1(strcpy1(whofilename, home), "/.who"); ! 409: strcpy1(strcpy1(whofilename2, home), "/.sysline"); ! 410: strcpy1(strcpy1(lockfilename, home), "/.syslinelock"); ! 411: ! 412: if ((kmem = open("/dev/kmem",0)) < 0) { ! 413: fprintf(stderr, "Can't open kmem.\n"); ! 414: exit(1); ! 415: } ! 416: readnamelist(); ! 417: if (proccheck) ! 418: initprocread(); ! 419: if (mailcheck) ! 420: if ((username = getenv("USER")) == 0) ! 421: mailcheck = 0; ! 422: else { ! 423: chdir(MAILDIR); ! 424: if (stat(username, &mstbuf) >= 0) ! 425: mailsize = mstbuf.st_size; ! 426: else ! 427: mailsize = 0; ! 428: } ! 429: ! 430: while (emacs || window || isloggedin()) ! 431: if (access(lockfilename, 0) >= 0) ! 432: sleep(60); ! 433: else { ! 434: prtinfo(); ! 435: sleep(delay); ! 436: if (clr_bet_ref) { ! 437: tputs(dis_status_line, 1, outc); ! 438: fflush(stdout); ! 439: sleep(5); ! 440: } ! 441: revtime = (1 + revtime) % REVOFF; ! 442: } ! 443: clearbotl(); ! 444: /*NOTREACHED*/ ! 445: } ! 446: ! 447: isloggedin() ! 448: { ! 449: /* ! 450: * you can tell if a person has logged out if the owner of ! 451: * the tty has changed ! 452: */ ! 453: struct stat statbuf; ! 454: ! 455: return fstat(2, &statbuf) == 0 && statbuf.st_uid == uid; ! 456: } ! 457: ! 458: readnamelist() ! 459: { ! 460: time_t bootime, clock, nintv, time(); ! 461: ! 462: #ifdef pdp11 ! 463: nlist("/unix", nl); ! 464: #else ! 465: nlist("/vmunix", nl); ! 466: #endif ! 467: if (nl[0].n_value == 0) { ! 468: if (!quiet) ! 469: fprintf(stderr, "No namelist\n"); ! 470: return; ! 471: } ! 472: lseek(kmem, (long)nl[NL_BOOT].n_value, 0); ! 473: read(kmem, &bootime, sizeof(bootime)); ! 474: (void) time(&clock); ! 475: nintv = clock - bootime; ! 476: if (nintv <= 0L || nintv > 60L*60L*24L*365L) { ! 477: if (!quiet) ! 478: fprintf(stderr, ! 479: "Time makes no sense... namelist must be wrong\n"); ! 480: nl[NL_PROC].n_value = nl[NL_AVEN].n_value = 0; ! 481: } ! 482: } ! 483: ! 484: readutmp(nflag) ! 485: char nflag; ! 486: { ! 487: static time_t lastmod; /* initially zero */ ! 488: static off_t utmpsize; /* ditto */ ! 489: struct stat st; ! 490: ! 491: if (ut < 0 && (ut = open("/etc/utmp", 0)) < 0) { ! 492: fprintf(stderr, "sysline: Can't open utmp.\n"); ! 493: exit(1); ! 494: } ! 495: if (fstat(ut, &st) < 0 || st.st_mtime == lastmod) ! 496: return 0; ! 497: lastmod = st.st_mtime; ! 498: if (utmpsize != st.st_size) { ! 499: utmpsize = st.st_size; ! 500: nentries = utmpsize / sizeof (struct utmp); ! 501: if (old == 0) { ! 502: old = (struct utmp *)calloc(utmpsize, 1); ! 503: new = (struct utmp *)calloc(utmpsize, 1); ! 504: } else { ! 505: old = (struct utmp *)realloc((char *)old, utmpsize); ! 506: new = (struct utmp *)realloc((char *)new, utmpsize); ! 507: free(status); ! 508: } ! 509: status = malloc(nentries * sizeof *status); ! 510: if (old == 0 || new == 0 || status == 0) { ! 511: fprintf(stderr, "sysline: Out of memory.\n"); ! 512: exit(1); ! 513: } ! 514: } ! 515: lseek(ut, 0L, 0); ! 516: (void) read(ut, (char *) (nflag ? new : old), utmpsize); ! 517: return 1; ! 518: } ! 519: ! 520: /* ! 521: * read in the process table locations and sizes, and allocate space ! 522: * for storing the process table. This is done only once. ! 523: */ ! 524: initprocread() ! 525: { ! 526: ! 527: if (nl[NL_PROC].n_value == 0) ! 528: return; ! 529: #ifdef VMUNIX ! 530: lseek(kmem, (long)nl[NL_PROC].n_value, 0); ! 531: read(kmem, &procadr, sizeof procadr); ! 532: lseek(kmem, (long)nl[NL_NPROC].n_value, 0); ! 533: read(kmem, &nproc, sizeof nproc); ! 534: #endif ! 535: #ifdef pdp11 ! 536: procadr = nl[NL_PROC].n_value; ! 537: nproc = NPROC; /* from param.h */ ! 538: #endif ! 539: if ((proc = (struct proc *) calloc(nproc, sizeof (struct proc))) == 0) { ! 540: fprintf(stderr, "Out of memory.\n"); ! 541: exit(1); ! 542: } ! 543: procNPROC = proc + nproc; ! 544: } ! 545: ! 546: /* ! 547: * read in the process table. This assumes that initprocread has alread been ! 548: * called to set up storage. ! 549: */ ! 550: readproctab() ! 551: { ! 552: ! 553: if (nl[NL_PROC].n_value == 0) ! 554: return (0); ! 555: lseek(kmem, (long)procadr, 0); ! 556: read(kmem, (char *)proc, nproc * sizeof (struct proc)); ! 557: return (1); ! 558: } ! 559: ! 560: prtinfo() ! 561: { ! 562: int on, off; ! 563: register i; ! 564: char fullprocess; ! 565: ! 566: stringinit(); ! 567: #ifdef SIGWINCH ! 568: if (winchanged) { ! 569: winchanged = 0; ! 570: getwinsize(); ! 571: mustclear = 1; ! 572: } ! 573: #endif ! 574: #ifdef WHO ! 575: /* check for file named .who in the home directory */ ! 576: whocheck(); ! 577: #endif ! 578: timeprint(); ! 579: /* ! 580: * if mail is seen, don't print rest of info, just the mail ! 581: * reverse new and old so that next time we run, we won't lose log ! 582: * in and out information ! 583: */ ! 584: if (mailcheck && (sawmail = mailseen())) ! 585: goto bottom; ! 586: #ifdef HOSTNAME ! 587: #ifdef RWHO ! 588: for (i = 0; i < nremotes; i++) { ! 589: char *tmp; ! 590: ! 591: stringspace(); ! 592: tmp = sysrup(remotehost + i); ! 593: stringcat(tmp, strlen(tmp)); ! 594: } ! 595: #endif ! 596: /* ! 597: * print hostname info if requested ! 598: */ ! 599: if (hostprint) { ! 600: stringspace(); ! 601: stringcat(hostname, -1); ! 602: } ! 603: #endif ! 604: /* ! 605: * print load average and difference between current load average ! 606: * and the load average 5 minutes ago ! 607: */ ! 608: if (nl[NL_AVEN].n_value != 0) { ! 609: double diff; ! 610: ! 611: stringspace(); ! 612: #ifdef VMUNIX ! 613: lseek(kmem, (long)nl[NL_AVEN].n_value, 0); ! 614: read(kmem, avenrun, sizeof avenrun); ! 615: #endif ! 616: #ifdef pdp11 ! 617: loadav(avenrun); ! 618: #endif ! 619: if ((diff = avenrun[0] - avenrun[1]) < 0.0) ! 620: stringprt("%.1f %.1f", avenrun[0], diff); ! 621: else ! 622: stringprt("%.1f +%.1f", avenrun[0], diff); ! 623: loadavg = avenrun[0]; /* remember load average */ ! 624: } ! 625: /* ! 626: * print log on and off information ! 627: */ ! 628: stringspace(); ! 629: fullprocess = 1; ! 630: #ifdef MAXLOAD ! 631: if (loadavg > MAXLOAD) ! 632: fullprocess = 0; /* too loaded to run */ ! 633: #endif ! 634: /* ! 635: * Read utmp file (logged in data) only if we are doing a full ! 636: * process, or if this is the first time and we are calculating ! 637: * the number of users. ! 638: */ ! 639: on = off = 0; ! 640: if (users == 0) { /* first time */ ! 641: if (readutmp(0)) ! 642: for (i = 0; i < nentries; i++) ! 643: if (old[i].ut_name[0]) ! 644: users++; ! 645: } else if (fullprocess && readutmp(1)) { ! 646: struct utmp *tmp; ! 647: ! 648: users = 0; ! 649: for (i = 0; i < nentries; i++) { ! 650: if (strncmp(old[i].ut_name, ! 651: new[i].ut_name, NAMESIZE) == 0) ! 652: status[i] = NOCH; ! 653: else if (old[i].ut_name[0] == '\0') { ! 654: status[i] = ON; ! 655: on++; ! 656: } else if (new[i].ut_name[0] == '\0') { ! 657: status[i] = OFF; ! 658: off++; ! 659: } else { ! 660: status[i] = ON | OFF; ! 661: on++; ! 662: off++; ! 663: } ! 664: if (new[i].ut_name[0]) ! 665: users++; ! 666: } ! 667: tmp = new; ! 668: new = old; ! 669: old = tmp; ! 670: } ! 671: /* ! 672: * Print: ! 673: * 1. number of users ! 674: * 2. a * for unread mail ! 675: * 3. a - if load is too high ! 676: * 4. number of processes running and stopped ! 677: */ ! 678: stringprt("%du", users); ! 679: if (mailsize > 0 && mstbuf.st_mtime >= mstbuf.st_atime) ! 680: stringcat("*", -1); ! 681: if (!fullprocess && (proccheck || logcheck)) ! 682: stringcat("-", -1); ! 683: if (fullprocess && proccheck && readproctab()) { ! 684: register struct proc *p; ! 685: int procrun, procstop; ! 686: ! 687: /* ! 688: * We are only interested in processes which have the same ! 689: * uid as us, and whose parent process id is not 1. ! 690: */ ! 691: procrun = procstop = 0; ! 692: for (p = proc; p < procNPROC; p++) { ! 693: if (p->p_stat == 0 || p->p_pgrp == 0 || ! 694: p->p_uid != uid || p->p_ppid == 1) ! 695: continue; ! 696: switch (p->p_stat) { ! 697: case SSTOP: ! 698: procstop++; ! 699: break; ! 700: case SSLEEP: ! 701: /* ! 702: * Sleep can mean waiting for a signal or just ! 703: * in a disk or page wait queue ready to run. ! 704: * We can tell if it is the later by the pri ! 705: * being negative. ! 706: */ ! 707: if (p->p_pri < PZERO) ! 708: procrun++; ! 709: break; ! 710: case SWAIT: ! 711: case SRUN: ! 712: case SIDL: ! 713: procrun++; ! 714: } ! 715: } ! 716: if (procrun > 0 || procstop > 0) { ! 717: stringspace(); ! 718: if (procrun > 0 && procstop > 0) ! 719: stringprt("%dr %ds", procrun, procstop); ! 720: else if (procrun > 0) ! 721: stringprt("%dr", procrun); ! 722: else ! 723: stringprt("%ds", procstop); ! 724: } ! 725: } ! 726: /* ! 727: * If anyone has logged on or off, and we are interested in it, ! 728: * print it out. ! 729: */ ! 730: if (logcheck) { ! 731: /* old and new have already been swapped */ ! 732: if (on) { ! 733: stringspace(); ! 734: stringcat("on:", -1); ! 735: for (i = 0; i < nentries; i++) ! 736: if (status[i] & ON) { ! 737: stringprt(" %.8s", old[i].ut_name); ! 738: ttyprint(old[i].ut_line); ! 739: } ! 740: } ! 741: if (off) { ! 742: stringspace(); ! 743: stringcat("off:", -1); ! 744: for (i = 0; i < nentries; i++) ! 745: if (status[i] & OFF) { ! 746: stringprt(" %.8s", new[i].ut_name); ! 747: ttyprint(new[i].ut_line); ! 748: } ! 749: } ! 750: } ! 751: bottom: ! 752: /* dump out what we know */ ! 753: stringdump(); ! 754: } ! 755: ! 756: timeprint() ! 757: { ! 758: long curtime; ! 759: struct tm *tp, *localtime(); ! 760: static int beepable = 1; ! 761: ! 762: /* always print time */ ! 763: time(&curtime); ! 764: tp = localtime(&curtime); ! 765: if (dateprint) ! 766: stringprt("%.11s", ctime(&curtime)); ! 767: stringprt("%d:%02d", tp->tm_hour > 12 ? tp->tm_hour - 12 : ! 768: (tp->tm_hour == 0 ? 12 : tp->tm_hour), tp->tm_min); ! 769: if (synch) /* sync with clock */ ! 770: delay = 60 - tp->tm_sec; ! 771: /* ! 772: * Beepable is used to insure that we get at most one set of beeps ! 773: * every half hour. ! 774: */ ! 775: if (beep) ! 776: if (beepable) { ! 777: if (tp->tm_min == 30) { ! 778: tputs(bell, 1, outc); ! 779: fflush(stdout); ! 780: beepable = 0; ! 781: } else if (tp->tm_min == 0) { ! 782: tputs(bell, 1, outc); ! 783: fflush(stdout); ! 784: sleep(2); ! 785: tputs(bell, 1, outc); ! 786: fflush(stdout); ! 787: beepable = 0; ! 788: } ! 789: } else ! 790: if (tp->tm_min != 0 && tp->tm_min != 30) ! 791: beepable = 1; ! 792: } ! 793: ! 794: /* ! 795: * whocheck -- check for file named .who and print it on the who line first ! 796: */ ! 797: whocheck() ! 798: { ! 799: int chss; ! 800: register char *p; ! 801: char buff[81]; ! 802: int whofile; ! 803: ! 804: if ((whofile = open(whofilename, 0)) < 0 && ! 805: (whofile = open(whofilename2, 0)) < 0) ! 806: return; ! 807: chss = read(whofile, buff, sizeof buff - 1); ! 808: close(whofile); ! 809: if (chss <= 0) ! 810: return; ! 811: buff[chss] = '\0'; ! 812: /* ! 813: * Remove all line feeds, and replace by spaces if they are within ! 814: * the message, else replace them by nulls. ! 815: */ ! 816: for (p = buff; *p;) ! 817: if (*p == '\n') ! 818: if (p[1]) ! 819: *p++ = ' '; ! 820: else ! 821: *p = '\0'; ! 822: else ! 823: p++; ! 824: stringcat(buff, p - buff); ! 825: stringspace(); ! 826: } ! 827: ! 828: /* ! 829: * ttyprint -- given the name of a tty, print in the string buffer its ! 830: * short name surrounded by parenthesis. ! 831: * ttyxx is printed as (xx) ! 832: * console is printed as (cty) ! 833: */ ! 834: ttyprint(name) ! 835: char *name; ! 836: { ! 837: char buff[11]; ! 838: ! 839: if (strncmp(name, "tty", 3) == 0) ! 840: stringprt("(%.*s)", LINESIZE - 3, name + 3); ! 841: else if (strcmp(name, "console") == 0) ! 842: stringcat("(cty)", -1); ! 843: else ! 844: stringprt("(%.*s)", LINESIZE, name); ! 845: } ! 846: ! 847: /* ! 848: * mail checking function ! 849: * returns 0 if no mail seen ! 850: */ ! 851: mailseen() ! 852: { ! 853: FILE *mfd; ! 854: register n; ! 855: register char *cp; ! 856: char lbuf[100], sendbuf[100], *bufend; ! 857: char seenspace; ! 858: int retval = 0; ! 859: ! 860: if (stat(username, &mstbuf) < 0) { ! 861: mailsize = 0; ! 862: return 0; ! 863: } ! 864: if (mstbuf.st_size <= mailsize || (mfd = fopen(username,"r")) == NULL) { ! 865: mailsize = mstbuf.st_size; ! 866: return 0; ! 867: } ! 868: fseek(mfd, mailsize, 0); ! 869: while ((n = readline(mfd, lbuf, sizeof lbuf)) >= 0 && ! 870: strncmp(lbuf, "From ", 5) != 0) ! 871: ; ! 872: if (n < 0) { ! 873: stringcat("Mail has just arrived", -1); ! 874: goto out; ! 875: } ! 876: retval = 1; ! 877: /* ! 878: * Found a From line, get second word, which is the sender, ! 879: * and print it. ! 880: */ ! 881: for (cp = lbuf + 5; *cp && *cp != ' '; cp++) /* skip to blank */ ! 882: ; ! 883: *cp = '\0'; /* terminate name */ ! 884: stringspace(); ! 885: stringprt("Mail from %s ", lbuf + 5); ! 886: /* ! 887: * Print subject, and skip over header. ! 888: */ ! 889: while ((n = readline(mfd, lbuf, sizeof lbuf)) > 0) ! 890: if (strncmp(lbuf, "Subject:", 8) == 0) ! 891: stringprt("on %s ", lbuf + 9); ! 892: if (!emacs) ! 893: stringcat(arrows, 2); ! 894: else ! 895: stringcat(": ", 2); ! 896: if (n < 0) /* already at eof */ ! 897: goto out; ! 898: /* ! 899: * Print as much of the letter as we can. ! 900: */ ! 901: cp = sendbuf; ! 902: if ((n = columns - chars) > sizeof sendbuf - 1) ! 903: n = sizeof sendbuf - 1; ! 904: bufend = cp + n; ! 905: seenspace = 0; ! 906: while ((n = readline(mfd, lbuf, sizeof lbuf)) >= 0) { ! 907: register char *rp; ! 908: ! 909: if (strncmp(lbuf, "From ", 5) == 0) ! 910: break; ! 911: if (cp >= bufend) ! 912: continue; ! 913: if (!seenspace) { ! 914: *cp++ = ' '; /* space before lines */ ! 915: seenspace = 1; ! 916: } ! 917: rp = lbuf; ! 918: while (*rp && cp < bufend) ! 919: if (isspace(*rp)) { ! 920: if (!seenspace) { ! 921: *cp++ = ' '; ! 922: seenspace = 1; ! 923: } ! 924: rp++; ! 925: } else { ! 926: *cp++ = *rp++; ! 927: seenspace = 0; ! 928: } ! 929: } ! 930: *cp = 0; ! 931: stringcat(sendbuf, -1); ! 932: /* ! 933: * Want to update write time so a star will ! 934: * appear after the number of users until the ! 935: * user reads his mail. ! 936: */ ! 937: out: ! 938: mailsize = linebeg; ! 939: fclose(mfd); ! 940: touch(username); ! 941: return retval; ! 942: } ! 943: ! 944: /* ! 945: * readline -- read a line from fp and store it in buf. ! 946: * return the number of characters read. ! 947: */ ! 948: readline(fp, buf, n) ! 949: register FILE *fp; ! 950: char *buf; ! 951: register n; ! 952: { ! 953: register c; ! 954: register char *cp = buf; ! 955: ! 956: linebeg = ftell(fp); /* remember loc where line begins */ ! 957: cp = buf; ! 958: while (--n > 0 && (c = getc(fp)) != EOF && c != '\n') ! 959: *cp++ = c; ! 960: *cp = 0; ! 961: if (c == EOF && cp - buf == 0) ! 962: return -1; ! 963: return cp - buf; ! 964: } ! 965: ! 966: ! 967: /* ! 968: * string hacking functions ! 969: */ ! 970: ! 971: stringinit() ! 972: { ! 973: sp = strarr; ! 974: chars = 0; ! 975: } ! 976: ! 977: /*VARARGS1*/ ! 978: stringprt(format, a, b, c) ! 979: char *format; ! 980: { ! 981: char tempbuf[150]; ! 982: ! 983: (void)sprintf(tempbuf, format, a, b, c); ! 984: stringcat(tempbuf, -1); ! 985: } ! 986: ! 987: stringdump() ! 988: { ! 989: char bigbuf[sizeof strarr + 200]; ! 990: register char *bp = bigbuf; ! 991: register int i; ! 992: ! 993: if (!emacs) { ! 994: if (sawmail) ! 995: bp = strcpy1(bp, bell); ! 996: if (eslok) ! 997: bp = strcpy1(bp, tparm(to_status_line, ! 998: leftline ? 0 : columns - chars)); ! 999: else { ! 1000: bp = strcpy1(bp, to_status_line); ! 1001: if (!shortline && !leftline) ! 1002: for (i = columns - chars; --i >= 0;) ! 1003: *bp++ = ' '; ! 1004: } ! 1005: if (reverse && revtime != 0) ! 1006: bp = strcpy1(bp, rev_out); ! 1007: } ! 1008: *sp = 0; ! 1009: bp = strcpy1(bp, strarr); ! 1010: if (!emacs) { ! 1011: if (reverse) ! 1012: bp = strcpy1(bp, rev_end); ! 1013: bp = strcpy1(bp, from_status_line); ! 1014: if (sawmail) ! 1015: bp = strcpy1(strcpy1(bp, bell), bell); ! 1016: *bp = 0; ! 1017: tputs(bigbuf, 1, outc); ! 1018: if (mustclear) { ! 1019: mustclear = 0; ! 1020: tputs(clr_eol, 1, outc); ! 1021: } ! 1022: if (dbug) ! 1023: putchar('\n'); ! 1024: fflush(stdout); ! 1025: } else ! 1026: write(2, bigbuf, bp - bigbuf); ! 1027: } ! 1028: ! 1029: stringspace() ! 1030: { ! 1031: if (reverse && revtime != 0) { ! 1032: #ifdef TERMINFO ! 1033: stringcat(rev_end, ! 1034: magic_cookie_glitch <= 0 ? 0 : magic_cookie_glitch); ! 1035: stringcat(" ", 1); ! 1036: stringcat(rev_out, ! 1037: magic_cookie_glitch <= 0 ? 0 : magic_cookie_glitch); ! 1038: #else ! 1039: stringcat(rev_end, 0); ! 1040: stringcat(" ", 1); ! 1041: stringcat(rev_out, 0); ! 1042: #endif TERMINFO ! 1043: } else ! 1044: stringcat(" ", 1); ! 1045: } ! 1046: ! 1047: /* ! 1048: * stringcat :: concatenate the characters in string str to the list we are ! 1049: * building to send out. ! 1050: * str - the string to print. may contain funny (terminal control) chars. ! 1051: * n - the number of printable characters in the string ! 1052: * or if -1 then str is all printable so we can truncate it, ! 1053: * otherwise don't print only half a string. ! 1054: */ ! 1055: stringcat(str, n) ! 1056: register char *str; ! 1057: register n; ! 1058: { ! 1059: register char *p = sp; ! 1060: ! 1061: if (n < 0) { /* truncate */ ! 1062: n = columns - chars; ! 1063: while ((*p++ = *str++) && --n >= 0) ! 1064: ; ! 1065: p--; ! 1066: chars += p - sp; ! 1067: sp = p; ! 1068: } else if (chars + n <= columns) { /* don't truncate */ ! 1069: while (*p++ = *str++) ! 1070: ; ! 1071: chars += n; ! 1072: sp = p - 1; ! 1073: } ! 1074: } ! 1075: ! 1076: /* ! 1077: * touch :: update the modify time of a file. ! 1078: */ ! 1079: touch(name) ! 1080: char *name; /* name of file */ ! 1081: { ! 1082: register fd; ! 1083: char buf; ! 1084: ! 1085: if ((fd = open(name, 2)) >= 0) { ! 1086: read(fd, &buf, 1); /* get first byte */ ! 1087: lseek(fd, 0L, 0); /* go to beginning */ ! 1088: write(fd, &buf, 1); /* and rewrite first byte */ ! 1089: close(fd); ! 1090: } ! 1091: } ! 1092: ! 1093: ! 1094: /* ! 1095: * clearbotl :: clear bottom line. ! 1096: * called when process quits or is killed. ! 1097: * it clears the bottom line of the terminal. ! 1098: */ ! 1099: clearbotl() ! 1100: { ! 1101: register int fd; ! 1102: int exit(); ! 1103: ! 1104: signal(SIGALRM, exit); ! 1105: alarm(30); /* if can't open in 30 secs, just die */ ! 1106: if (!emacs && (fd = open(ourtty, 1)) >= 0) { ! 1107: write(fd, dis_status_line, strlen(dis_status_line)); ! 1108: close(fd); ! 1109: } ! 1110: #ifdef PROF ! 1111: if (chdir("/usr/src/ucb/sysline") < 0) ! 1112: (void) chdir("/tmp"); ! 1113: #endif ! 1114: exit(0); ! 1115: } ! 1116: ! 1117: #ifdef TERMINFO ! 1118: initterm() ! 1119: { ! 1120: static char standbuf[40]; ! 1121: ! 1122: setupterm(0, 1, 0); ! 1123: if (!window && !has_status_line) { ! 1124: /* not an appropriate terminal */ ! 1125: if (!quiet) ! 1126: fprintf(stderr, "sysline: no status capability for %s\n", ! 1127: getenv("TERM")); ! 1128: exit(1); ! 1129: } ! 1130: if (window || status_line_esc_ok) { ! 1131: if (set_attributes) { ! 1132: /* reverse video mode */ ! 1133: strcpy(standbuf, ! 1134: tparm(set_attributes,0,0,1,0,0,0,0,0,0)); ! 1135: rev_out = standbuf; ! 1136: rev_end = exit_attribute_mode; ! 1137: } else if (enter_standout_mode && exit_standout_mode) { ! 1138: rev_out = enter_standout_mode; ! 1139: rev_end = exit_standout_mode; ! 1140: } else ! 1141: rev_out = rev_end = ""; ! 1142: } else ! 1143: rev_out = rev_end = ""; ! 1144: columns--; /* avoid cursor wraparound */ ! 1145: } ! 1146: ! 1147: #else /* TERMCAP */ ! 1148: ! 1149: initterm() ! 1150: { ! 1151: char *term, *cp; ! 1152: static char tbuf[1024]; ! 1153: char is2[40]; ! 1154: extern char *UP; ! 1155: ! 1156: if ((term = getenv("TERM")) == NULL) { ! 1157: if (!quiet) ! 1158: fprintf(stderr, ! 1159: "sysline: No TERM variable in enviroment\n"); ! 1160: exit(1); ! 1161: } ! 1162: if (tgetent(tbuf, term) <= 0) { ! 1163: if (!quiet) ! 1164: fprintf(stderr, ! 1165: "sysline: Unknown terminal type: %s\n", term); ! 1166: exit(1); ! 1167: } ! 1168: if (!window && tgetflag("hs") <= 0) { ! 1169: if (!strncmp(term, "h19", 3)) { ! 1170: /* for upward compatability with h19sys */ ! 1171: strcpy(to_status_line, ! 1172: "\033j\033x5\033x1\033Y8%+ \033o"); ! 1173: strcpy(from_status_line, "\033k\033y5"); ! 1174: strcpy(dis_status_line, "\033y1"); ! 1175: strcpy(rev_out, "\033p"); ! 1176: strcpy(rev_end, "\033q"); ! 1177: arrows = "\033Fhh\033G"; ! 1178: columns = 80; ! 1179: UP = "\b"; ! 1180: return; ! 1181: } ! 1182: if (!quiet) ! 1183: fprintf(stderr, ! 1184: "sysline: No status capability for %s\n", term); ! 1185: exit(1); ! 1186: } ! 1187: cp = is2; ! 1188: if (tgetstr("i2", &cp) != NULL) { ! 1189: /* someday tset will do this */ ! 1190: tputs(is2, 1, erroutc); ! 1191: fflush(stdout); ! 1192: } ! 1193: ! 1194: /* the "-1" below is to avoid cursor wraparound problems */ ! 1195: columns = tgetnum("ws"); ! 1196: hasws = columns >= 0; ! 1197: if (!hasws) ! 1198: columns = tgetnum("co"); ! 1199: columns -= 1; ! 1200: if (window) { ! 1201: strcpy(to_status_line, "\r"); ! 1202: cp = dis_status_line; /* use the clear line sequence */ ! 1203: *cp++ = '\r'; ! 1204: tgetstr("ce", &cp); ! 1205: if (leftline) ! 1206: strcpy(from_status_line, dis_status_line + 1); ! 1207: else ! 1208: strcpy(from_status_line, ""); ! 1209: } else { ! 1210: cp = to_status_line; ! 1211: tgetstr("ts", &cp); ! 1212: cp = from_status_line; ! 1213: tgetstr("fs", &cp); ! 1214: cp = dis_status_line; ! 1215: tgetstr("ds", &cp); ! 1216: eslok = tgetflag("es"); ! 1217: } ! 1218: if (eslok || window) { ! 1219: cp = rev_out; ! 1220: tgetstr("so", &cp); ! 1221: cp = rev_end; ! 1222: tgetstr("se", &cp); ! 1223: cp = clr_eol; ! 1224: tgetstr("ce", &cp); ! 1225: } else ! 1226: reverse = 0; /* turn off reverse video */ ! 1227: UP = "\b"; ! 1228: if (!strncmp(term, "h19", 3)) ! 1229: arrows = "\033Fhh\033G"; /* "two tiny graphic arrows" */ ! 1230: else ! 1231: arrows = "->"; ! 1232: } ! 1233: #endif TERMINFO ! 1234: ! 1235: #ifdef pdp11 ! 1236: loadav(ap) ! 1237: double ap[]; ! 1238: { ! 1239: register int i; ! 1240: short s_avenrun[3]; ! 1241: ! 1242: lseek(kmem, (long)nl[NL_AVEN].n_value, 0); ! 1243: read(kmem, s_avenrun, sizeof(s_avenrun)); ! 1244: for (i=0; i < (sizeof(s_avenrun)/sizeof(s_avenrun[0])); i++) ! 1245: ap[i] = s_avenrun[i] / 256.0; ! 1246: } ! 1247: #endif ! 1248: ! 1249: #ifdef RWHO ! 1250: char * ! 1251: sysrup(hp) ! 1252: register struct remotehost *hp; ! 1253: { ! 1254: char filename[100]; ! 1255: struct whod wd; ! 1256: #define WHOD_HDR_SIZE (sizeof (wd) - sizeof (wd.wd_we)) ! 1257: static char buffer[50]; ! 1258: time_t now; ! 1259: ! 1260: /* ! 1261: * rh_file is initially 0. ! 1262: * This is ok since standard input is assumed to exist. ! 1263: */ ! 1264: if (hp->rh_file == 0) { ! 1265: /* ! 1266: * Try rwho hostname file, and if that fails try ucbhostname. ! 1267: */ ! 1268: (void) strcpy1(strcpy1(filename, RWHOLEADER), hp->rh_host); ! 1269: if ((hp->rh_file = open(filename, 0)) < 0) { ! 1270: (void) strcpy1(strcpy1(strcpy1(filename, RWHOLEADER), ! 1271: NETPREFIX), hp->rh_host); ! 1272: hp->rh_file = open(filename, 0); ! 1273: } ! 1274: } ! 1275: if (hp->rh_file < 0) { ! 1276: (void) sprintf(buffer, "%s?", hp->rh_host); ! 1277: return(buffer); ! 1278: } ! 1279: (void) lseek(hp->rh_file, (off_t)0, 0); ! 1280: if (read(hp->rh_file, (char *)&wd, WHOD_HDR_SIZE) != WHOD_HDR_SIZE) { ! 1281: (void) sprintf(buffer, "%s ?", hp->rh_host); ! 1282: return(buffer); ! 1283: } ! 1284: (void) time(&now); ! 1285: if (now - wd.wd_recvtime > DOWN_THRESHOLD) { ! 1286: long interval; ! 1287: long days, hours, minutes; ! 1288: ! 1289: interval = now - wd.wd_recvtime; ! 1290: minutes = (interval + 59) / 60; /* round to minutes */ ! 1291: hours = minutes / 60; /* extract hours from minutes */ ! 1292: minutes %= 60; /* remove hours from minutes */ ! 1293: days = hours / 24; /* extract days from hours */ ! 1294: hours %= 24; /* remove days from hours */ ! 1295: if (days > 7 || days < 0) ! 1296: (void) sprintf(buffer, "%s down", hp->rh_host); ! 1297: else if (days > 0) ! 1298: (void) sprintf(buffer, "%s %d+%d:%02d", ! 1299: hp->rh_host, days, hours, minutes); ! 1300: else ! 1301: (void) sprintf(buffer, "%s %d:%02d", ! 1302: hp->rh_host, hours, minutes); ! 1303: } else ! 1304: (void) sprintf(buffer, "%s %.1f", ! 1305: hp->rh_host, wd.wd_loadav[0]/100.0); ! 1306: return buffer; ! 1307: } ! 1308: #endif RWHO ! 1309: ! 1310: getwinsize() ! 1311: { ! 1312: #ifdef TIOCGWINSZ ! 1313: struct winsize winsize; ! 1314: ! 1315: /* the "-1" below is to avoid cursor wraparound problems */ ! 1316: if (!hasws && ioctl(2, TIOCGWINSZ, (char *)&winsize) >= 0 && ! 1317: winsize.ws_col != 0) ! 1318: columns = winsize.ws_col - 1; ! 1319: #endif ! 1320: } ! 1321: ! 1322: #ifdef SIGWINCH ! 1323: sigwinch() ! 1324: { ! 1325: winchanged++; ! 1326: } ! 1327: #endif ! 1328: ! 1329: char * ! 1330: strcpy1(p, q) ! 1331: register char *p, *q; ! 1332: { ! 1333: ! 1334: while (*p++ = *q++) ! 1335: ; ! 1336: return p - 1; ! 1337: } ! 1338: ! 1339: outc(c) ! 1340: char c; ! 1341: { ! 1342: if (dbug) ! 1343: printf("%s", unctrl(c)); ! 1344: else ! 1345: putchar(c); ! 1346: } ! 1347: ! 1348: erroutc(c) ! 1349: char c; ! 1350: { ! 1351: if (dbug) ! 1352: fprintf(stderr, "%s", unctrl(c)); ! 1353: else ! 1354: putc(c, stderr); ! 1355: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.