home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / unix / volume3 / idledaemon < prev    next >
Internet Message Format  |  1986-11-30  |  32KB

  1. From: Stephen Crawley <genrad!panda!talcott!ucl-cs.arpa:scc@computer-lab.cambridge.ac.uk>
  2. Subject: Yet another idledaemon
  3. Newsgroups: mod.sources
  4. Approved: jpn@panda.UUCP
  5.  
  6. Mod.sources:  Volume 3, Issue 17
  7. Submitted by: Stephen Crawley <ucl-cs.arpa:scc@computer-lab.cambridge.ac.uk>
  8.  
  9.  
  10. This posting contains the source and manual entry for a daemon for
  11. managing a pool of terminal ports to try to make sure that there
  12. will be free one when it is needed.  It does this by checking for
  13. sessions that have had no input, and that have no active processes.
  14. When there is a shortage of ports, some or all of these sessions are
  15. sent warning messages, and if necessary killed off.  
  16.  
  17. The program is 4.2 specific, since (for example) it grubs around in 
  18. the process table to determine whether a session has active processes.
  19.  
  20. To compile the program, feed the stuff below the dotted line into /bin/sh,
  21. then type
  22.         cc idledaemon
  23.  
  24. If you want to see what the program is up to, compile it with the DEBUG
  25. flag set, and it will tell all on stderr.
  26.  
  27. Read the manual entry, to find out about the various parameters you can
  28. tweak, then run the daemon.  I recommend that before you install it for
  29. real, you run the program with the -d option for a few days to get your 
  30. users used to the idea.  
  31.  
  32. We have been running the daemon for a few weeks now, but bugs are still
  33. jumping out occasionally.  Please send any bug reports, suggestions for 
  34. changes and so on to me at the address below.
  35.  
  36.             Stephen C. Crawley
  37.  
  38. ARPA:    scc%uk.ac.cam.cl@ucl-cs.ARPA  SMail: Cambridge Univ. Computer Lab.,
  39. JANET:    scc@uk.ac.cam.cl                     Corn Exchange Street,
  40. UUCP:    {ukc,kcl-cs}!cl-jenny!scc         Cambridge,
  41.     scc@cl-jenny.UUCP             CB2 3QG, 
  42. PHONE:    +44 223 352 435                      England.
  43.  
  44.  
  45. ------------------Cut-Here-And-Feed-Into-/bin/sh---------------------------
  46. #!/bin/sh
  47. echo 'Start of pack.out, part 01 of 01:'
  48. echo 'x - idledaemon.c'
  49. sed 's/^X//' > idledaemon.c << '/'
  50. X#ifndef lint
  51. Xstatic char rcsid[] = "CUCL:    $Header: idledaemon.c,v 1.5 85/09/23 01:40:32 scc Exp $";
  52. X#endif lint
  53. X
  54. X/*********************************************************************\
  55. X*                                       *
  56. X*                                        *
  57. X*                 Copyright Notice:                   *
  58. X*                                       *
  59. X*         (C) Cambridge University Computer Laboratory 1985          *
  60. X*                                       *
  61. X*     This program is Public Domain.  Permission is hereby given    *
  62. X*     to anyone to redistributed it to anyone they want to, with    *
  63. X*     the proviso that this notice is included unaltered.          *
  64. X*                                       *
  65. X*    Neither the author or C.U.C.L accepts legal responsibility    *
  66. X*    for bugs in this program, or for any trouble it may cause.    *
  67. X*                                      *
  68. X*     Please send any comments, suggestions for improvements and    *
  69. X*     bug reports/fixes to the author :-                  *
  70. X*                                       *
  71. X*                 Stephen Crawley                  *
  72. X*                                       *
  73. X*             USENET: scc@cl-jenny.UUCP                  *
  74. X*             ARPA:   scc%cl.cam.ac.uk@ucl-cs.ARPA          *
  75. X*             JANET:  scc@uk.ac.cam.cl                  *
  76. X*                                       *
  77. X\*********************************************************************/
  78. X
  79. X
  80. X/*
  81. X * idledaemon [-a] [-d] [-f<number>] [-{s,i,g}<minutes>] tty ...
  82. X *
  83. X * Kick idle users off the system if there is a shortage of terminals.
  84. X *     -a    kick them off anyway (even if there are free lines)
  85. X *    -f    try to keep this many lines free
  86. X *    -s     gives the time idledaemon sleeps between runs
  87. X *    -i    gives the idle threshold
  88. X *    -g    gives the grace period
  89. X *    -d    debug mode ... send messages to users, but don't
  90. X *        actually kill anything.  I used this to get people
  91. X *        used to the idea before installing the daemon for real.
  92. X *
  93. X * Idledaemon first checks /etc/utmp to see how many of the named terminals
  94. X * are free.  If enough terminals are free nothing more is done.  Otherwise,
  95. X * idle terminals for which a warning has been sent and for which the grace
  96. X * period has expired are SIGHUPed then SIGKILLed.  If the number of free
  97. X * terminals is still less than the threshold, warnings are sent to any 
  98. X * other idle terminals.
  99. X *
  100. X * Idledaemon's idea of an idle terminal is one for which there has been no
  101. X * terminal input processed in the time period, and for which there are no
  102. X * background jobs running or "sleeping".  Detached processes such as 
  103. X * sysline are excluded from the latter category.
  104. X *
  105. X * If you send a SIGINT to the daemon, it will print a summary of the
  106. X * the state of the world to standard output.
  107. X */
  108. X
  109. X
  110. X/* #define DEBUG    /* print debugging info on stderr */
  111. X
  112. X
  113. X#define SECSPERMIN 60
  114. X
  115. X/*
  116. X * Tuning parameters and argument defaults.
  117. X */
  118. X#define PROC_SLEEPTIME         10
  119. X#define WARNING_TIMEOUT        (20 * SECSPERMIN)
  120. X#define DFLT_IDLETIME         (30 * SECSPERMIN)
  121. X#define DFLT_GRACETIME         (5 * SECSPERMIN)
  122. X#define DFLT_LOOPTIME         (5 * SECSPERMIN)
  123. X#define DFLT_MINTTYSFREE     1
  124. X
  125. X
  126. X
  127. X#include <stdio.h>
  128. X#include <signal.h>
  129. X#include <ctype.h>
  130. X#include <nlist.h>
  131. X#include <setjmp.h>
  132. X#include <utmp.h>
  133. X#include <sys/param.h>
  134. X#include <sys/time.h>
  135. X#include <sys/ioctl.h>
  136. X#include <sys/wait.h>
  137. X#include <sys/tty.h>
  138. X#include <sys/file.h>
  139. X#include <sys/proc.h>
  140. X#include <sys/stat.h>
  141. X
  142. Xstruct nlist nl[] = {
  143. X    { "_proc" },
  144. X#define    X_PROC        0
  145. X    { "_nproc" },
  146. X#define    X_NPROC        1
  147. X    { "" },
  148. X};
  149. X
  150. Xextern errno;
  151. Xlong lseek();
  152. Xchar *malloc();
  153. Xchar *strncpy();
  154. X
  155. Xint gracetime = DFLT_GRACETIME;    /* Minimum time between message and zapping */
  156. Xint idletime = DFLT_IDLETIME;    /* Time allowed before a terminal is idle */
  157. Xint looptime = DFLT_LOOPTIME;    /* Sleep this long between looking */
  158. X
  159. Xint minttysfree = DFLT_MINTTYSFREE; 
  160. X                /* If there are less than this many terminals
  161. X                   free, start invoking sanctions ... */
  162. X                    
  163. Xint killanyway = 0;        /* If non-zero, kill 'em irrespective of the
  164. X                   number of free terminals. */
  165. X
  166. Xint debug = 0;            /* Disable killing, and modify messages
  167. X                   sent to user's terminals */
  168. X
  169. Xstruct ttyinfo
  170. X{
  171. X    char    tty_line[8];    /* Terminal name */
  172. X    int        tty_state;
  173. X    char    tty_name[8];    /* Logged in user's name */
  174. X    time_t    tty_logintime;
  175. X    time_t    tty_accesstime;
  176. X    time_t    tty_warningtime;
  177. X    struct proc *tty_shellproc;    /* Process table entry for login shell */
  178. X    short    tty_shellpid;    /* Process id of login shell */
  179. X    short    tty_notifier;    /* Process which is writing to terminal */
  180. X};
  181. X
  182. X/*
  183. X * State of terminal.
  184. X */
  185. X#define TTY_UNKNOWN    0    /* tty not in utmp (yet) */
  186. X#define TTY_FREE    1    /* not logged in */
  187. X#define TTY_ACTIVE    2    /* logged in */
  188. X#define TTY_NOINPUT    3    /* idle session (last time we checked) */
  189. X#define TTY_WARNED    4    /* a warning has been sent */
  190. X#define TTY_KILLED    5    /* session has been killed (transient) */
  191. X#define TTY_STUCK    6    /* session is unkillable ! */
  192. X#define NOS_STATES    7
  193. X
  194. Xchar *statestrings[NOS_STATES] = {
  195. X    "UNKNOWN", "FREE", "ACTIVE", "NOINPUT", 
  196. X    "WARNED", "KILLED", "STUCK"};
  197. X
  198. Xint nttys, nttysfree;    /* number of terminals and terminals currently free */
  199. Xstruct ttyinfo *ttytable;
  200. X
  201. Xint utmp = -1;        /* fd for /etc/utmp */
  202. Xint nutmp = 0;        /* number of utmp entries */
  203. Xstruct utmp *utmptable = 0;
  204. X
  205. X/*
  206. X * Process table globals.
  207. X */
  208. Xint kmem;        /* fd for /dev/kmem */
  209. X
  210. Xunsigned long procp;    /* base of process table */
  211. Xunsigned long nproc;    /* entries in process table */
  212. Xstruct proc *proctable;
  213. X
  214. Xunsigned long kreadvar();
  215. X
  216. Xtime_t timenow;
  217. Xint notifiers;        /* number of outstanding notification processes */
  218. Xint notifierskilled;    /* the notifiers have been SIGKILLED */
  219. Xjmp_buf timeout;    /* ... for abandoning the wait when a notifier 
  220. X               gets stuck ... */
  221. X
  222. X#ifdef DEBUG
  223. X#define Bomb abort()
  224. X#define dprintf0(msg) fprintf(stderr,msg)
  225. X#define dprintf1(msg,a1) fprintf(stderr,msg,a1)
  226. X#define dprintf2(msg,a1,a2) fprintf(stderr,msg,a1,a2)
  227. X#else DEBUG
  228. X#define Bomb exit(1)
  229. X#define dprintf0(msg) 
  230. X#define dprintf1(msg,a1) 
  231. X#define dprintf2(msg,a1,a2) 
  232. X#endif DEBUG
  233. X
  234. X
  235. Xmain (argc, argv)
  236. Xint argc;
  237. Xchar **argv;
  238. X{
  239. X    int     argerror = 0;
  240. X    setlinebuf (stderr);
  241. X
  242. X    for (argc--, argv++; *argv && **argv == '-'; argv++, argc--)
  243. X    {
  244. X    switch ((*argv)[1])
  245. X    {
  246. X        case 'a': 
  247. X        killanyway++;
  248. X        break;
  249. X        case 'd':
  250. X        debug++;
  251. X        break;
  252. X        case 'f': 
  253. X        minttysfree = atoi (&(*argv)[2]);
  254. X        break;
  255. X        case 'g': 
  256. X        gracetime = atoi (&(*argv)[2]) * SECSPERMIN;
  257. X        break;
  258. X        case 'i': 
  259. X        idletime = atoi (&(*argv)[2]) * SECSPERMIN;
  260. X        break;
  261. X        case 'l': 
  262. X        looptime = atoi (&(*argv)[2]) * SECSPERMIN;
  263. X        break;
  264. X        default: 
  265. X        argerror = 1;
  266. X    }
  267. X    }
  268. X
  269. X    if (argerror || argc <= 0)
  270. X    {
  271. X    fprintf (stderr,
  272. X        "usage: idledaemon [-a] [-d] [-f<nos>] [-{s,i,g}<mins>] tty ..\n");
  273. X    Bomb;
  274. X    }
  275. X
  276. X    if (idletime <= looptime || gracetime <= 0 || looptime <= 0)
  277. X    {
  278. X    fprintf (stderr,
  279. X        "idledaemon: inconsistent idle, grace and loop times\n");
  280. X    Bomb;
  281. X    }
  282. X
  283. X    if (chdir ("/dev") < 0)
  284. X    {
  285. X    perror ("idledaemon: can't cd to \"/dev\": ");
  286. X    Bomb;
  287. X    }
  288. X
  289. X    init_signals ();
  290. X    init_ttytable (argc, argv);
  291. X    init_kmem ();
  292. X
  293. X    /*
  294. X     * All OK ... orphan ourselves.
  295. X     */
  296. X    if (fork() != 0)
  297. X    exit (0);
  298. X
  299. X    for (;;)
  300. X    {
  301. X    timenow = time ((time_t *) 0);
  302. X    dprintf1 ("\ntime is %s", ctime(&timenow));
  303. X    /*
  304. X         * Count the terminals in use, dealing with changes in user.
  305. X     */
  306. X    scanutmp ();
  307. X        dprintf1 ("there are %d terminals free\n", nttysfree);
  308. X
  309. X    if (nttysfree < minttysfree || killanyway)
  310. X    {
  311. X        /*
  312. X         * For each tty in use, see if there has been any input, and
  313. X         * clear the WARNED state if there has been.  The value
  314. X         * returned is the number of possibly idle terminals.
  315. X         */
  316. X        if (scanttys () > 0)
  317. X        {
  318. X        struct ttyinfo *tp;
  319. X
  320. X        /*
  321. X         * Knock sessions off one at a time in order of
  322. X         * decreasing idleness.
  323. X         */
  324. X        while (nttysfree < minttysfree || killanyway)
  325. X        {
  326. X            if (!killmostidle())
  327. X            break;
  328. X        }
  329. X        /*
  330. X         * Satisfied?
  331. X         */
  332. X        if (nttysfree < minttysfree || killanyway)
  333. X        {
  334. X            /*
  335. X             * Nope!  Send out warnings to the next batch.
  336. X             */
  337. X            read_proctable ();
  338. X            for (tp = ttytable; tp < &ttytable[nttys]; tp++)
  339. X            {
  340. X            if (tp -> tty_state == TTY_NOINPUT && ttyidle (tp))
  341. X                ttywarning (tp);
  342. X            }
  343. X        }
  344. X        collectnotifiers ();
  345. X        }    
  346. X    }
  347. X    sleep ((unsigned) looptime);
  348. X    }
  349. X}
  350. X
  351. X
  352. X/*
  353. X * Initialise tty table from (the rest of) the argument vector.
  354. X */
  355. Xinit_ttytable(argc, argv)
  356. Xint argc;
  357. Xchar **argv;
  358. X{
  359. X    struct ttyinfo *tp;
  360. X
  361. X    ttytable = (struct ttyinfo *) malloc((unsigned) argc * sizeof(*ttytable));
  362. X    if (ttytable == 0)
  363. X    {
  364. X    fprintf (stderr, "idledaemon: can't calloc ttytable!");
  365. X    Bomb;
  366. X    }
  367. X    nttys = argc;
  368. X
  369. X    for (tp = ttytable; argc > 0; argc--, argv++, tp++)
  370. X    {
  371. X    struct stat stbuf;
  372. X
  373. X    /*
  374. X     * Check we can stat each terminal.
  375. X     */
  376. X    if (stat(*argv, &stbuf) < 0)
  377. X    {
  378. X        fprintf (stderr, "idledaemon: can't stat %s: ", *argv);
  379. X        perror ("");
  380. X        Bomb;
  381. X    }
  382. X    (void) strncpy (tp -> tty_line, *argv, sizeof (tp -> tty_line));
  383. X    tp -> tty_name[0] = '\000';
  384. X    tp -> tty_logintime = 0;
  385. X    tp -> tty_warningtime = 0;
  386. X    tp -> tty_accesstime = 0;
  387. X    tp -> tty_shellpid = 0;
  388. X    tp -> tty_notifier = 0;
  389. X    tp -> tty_state = TTY_UNKNOWN;
  390. X    }
  391. X}
  392. X
  393. X
  394. X/*
  395. X * Initialise bits and pieces needed to read the proc table
  396. X */
  397. Xinit_kmem ()
  398. X{
  399. X    if ((kmem = open ("kmem", O_RDONLY, 0)) < 0)
  400. X    {
  401. X    perror ("idledaemon: can't open /dev/kmem");
  402. X    Bomb;
  403. X    }
  404. X    nlist ("/vmunix", nl);
  405. X    if (nl[0].n_type == 0)
  406. X    {
  407. X    fprintf (stderr, "idledaemon: can't get namelist for /vmunix\n");
  408. X    Bomb;
  409. X    }
  410. X    procp = kreadvar (X_PROC);
  411. X    nproc = kreadvar (X_NPROC);
  412. X    proctable = (struct proc *) malloc((unsigned) nproc * sizeof(*proctable));
  413. X    if (proctable == 0)
  414. X    {
  415. X    fprintf (stderr, "idledaemon: can't calloc proc table buffer!\n");
  416. X    Bomb;
  417. X    }
  418. X}
  419. X
  420. X
  421. X/*
  422. X * Read complete proc table into the proctable buffer.
  423. X */
  424. Xread_proctable ()
  425. X{
  426. X    if (lseek (kmem, (long) procp, L_SET) != procp)
  427. X    {
  428. X    perror ("idledaemon: lseek to proc table failed: ");
  429. X    Bomb;
  430. X    }
  431. X#define proctabsize (nproc * sizeof (struct proc))
  432. X    if (read (kmem, (char *) proctable, (int) proctabsize) != proctabsize)
  433. X    {
  434. X    perror ("idledaemon: proc table read failed: ");
  435. X    Bomb;
  436. X    }
  437. X}
  438. X
  439. X
  440. X/*
  441. X * Read a long from the kernel.
  442. X */
  443. Xunsigned long kreadvar (name)
  444. Xint name;
  445. X{
  446. X    unsigned long res;
  447. X    unsigned long addr = nl[name].n_value;
  448. X
  449. X    if (lseek (kmem, (long) addr, L_SET) != addr)
  450. X    {
  451. X    perror ("idledaemon: lseek on kmem failed: ");
  452. X    Bomb;
  453. X    }
  454. X    if (read (kmem, (char *) &res, sizeof(res)) != sizeof (res))
  455. X    {
  456. X    perror ("idledaemon: read on kmem failed: ");
  457. X    Bomb;
  458. X    }
  459. X    dprintf2 ("kreadvar: &%x -> %x\n", addr, res);
  460. X    return (res);
  461. X}
  462. X
  463. X
  464. X/*
  465. X * Relocate a proc table pointer, relative to proctable.
  466. X */
  467. Xstruct proc *reloc (pp)
  468. Xstruct proc *pp;
  469. X{
  470. X    return (struct proc *) (
  471. X        (unsigned) pp - (unsigned) procp + (unsigned) proctable);
  472. X}
  473. X
  474. X
  475. X/*
  476. X * Read utmp file and update the tty table for sessions that have
  477. X * logged in or out.  Then count free terminals..
  478. X */
  479. Xscanutmp ()
  480. X{
  481. X    struct stat statbuf;
  482. X    int size;
  483. X    struct utmp *up;
  484. X    struct ttyinfo *tp;
  485. X
  486. X    /*  Open utmp file if we haven't already */
  487. X    if (utmp == -1)
  488. X    {
  489. X    utmp = open ("/etc/utmp", O_RDONLY, 0);
  490. X    if (utmp < 0)
  491. X    {
  492. X        perror ("idledaemon: can't open /etc/utmp: ");
  493. X        Bomb;
  494. X    }
  495. X    }
  496. X    else
  497. X    {
  498. X    if (lseek (utmp, (long) 0, L_SET) != 0)
  499. X    {
  500. X        perror ("idledaemon: lseek on utmp failed: ");
  501. X        Bomb;
  502. X    }
  503. X    }
  504. X
  505. X    /*
  506. X     * Find out how big the file is.  If it is bigger than it was before,
  507. X     * allocate a bigger buffer.
  508. X     */
  509. X    if (fstat (utmp, &statbuf) < 0)
  510. X    {
  511. X    perror ("idledaemon: utmp stat failed: ");
  512. X    Bomb;
  513. X    }
  514. X    size = statbuf.st_size;
  515. X    if ((size / sizeof (struct utmp)) != nutmp)
  516. X    {
  517. X    nutmp = size / sizeof (struct utmp);
  518. X    if (utmptable != 0)
  519. X        free ((char *) utmptable);
  520. X    utmptable = (struct utmp *) malloc ((unsigned) size);
  521. X    }
  522. X
  523. X    /*
  524. X     * Read utmp and update the tty table from it.
  525. X     */
  526. X    if (read (utmp, (char *) utmptable, size) != size)
  527. X    {
  528. X    fprintf (stderr, "idledaemon: utmp read failed\n");
  529. X    Bomb;
  530. X    }
  531. X    for (up = utmptable; up < &utmptable[nutmp]; up++)
  532. X    {
  533. X    for (tp = ttytable; tp < &ttytable[nttys]; tp++)
  534. X    {
  535. X        if (strcmp (tp -> tty_line, up -> ut_line) == 0)
  536. X        {
  537. X            if (up -> ut_name[0] == '\000')
  538. X            {
  539. X            tp -> tty_name[0] = '\000';
  540. X            if (tp -> tty_state != TTY_FREE)
  541. X                ttystate (tp, TTY_FREE);
  542. X        }
  543. X        else
  544. X        {
  545. X            if (up -> ut_time != tp -> tty_logintime ||
  546. X            strcmp (tp -> tty_name, up -> ut_name) != 0)
  547. X            {
  548. X            (void) strncpy (tp -> tty_name, up -> ut_name, 
  549. X                 sizeof (tp -> tty_name));
  550. X            tp -> tty_logintime = up -> ut_time;
  551. X            tp -> tty_accesstime = 0;
  552. X            tp -> tty_shellpid = 0;
  553. X            ttystate (tp, TTY_ACTIVE);
  554. X            }
  555. X        }
  556. X        }
  557. X    }
  558. X    }
  559. X    /*
  560. X     * Count the free terminals.
  561. X     */
  562. X    nttysfree = 0;
  563. X    for (tp = ttytable; tp < &ttytable[nttys]; tp++)
  564. X    if (tp -> tty_state == TTY_FREE || tp -> tty_state == TTY_UNKNOWN)
  565. X        nttysfree++;
  566. X}
  567. X
  568. X
  569. X/*
  570. X * Look at the atimes for all ttys in the table, and adjust state.
  571. X * The number of terminals that have had no input is returned.
  572. X */
  573. Xscanttys ()
  574. X{
  575. X    struct stat statbuf;
  576. X    struct ttyinfo *tp;
  577. X    int candidates = 0;
  578. X
  579. X    for (tp = ttytable; tp < &ttytable[nttys]; tp++)
  580. X    {
  581. X    if (tp -> tty_state != TTY_FREE)
  582. X    {
  583. X        if (stat (tp -> tty_line, &statbuf) < 0)
  584. X        {
  585. X        fprintf (stderr, "stat(%s) failed: ", tp -> tty_line);
  586. X        perror ("");
  587. X        continue;
  588. X        }
  589. X
  590. X        if (statbuf.st_atime != tp -> tty_accesstime)
  591. X        {
  592. X            tp -> tty_accesstime = statbuf.st_atime;
  593. X            if (tp -> tty_state != TTY_ACTIVE)
  594. X        {
  595. X            /*
  596. X             * At some point since the last scan, the terminal
  597. X             * has reactivated.  It may not still be active,
  598. X             * but that is dealt with below.
  599. X             */
  600. X            ttystate (tp, TTY_ACTIVE);
  601. X        }
  602. X        }
  603. X        
  604. X        /*
  605. X         * Spot idle terminals, and timeout old warnings.
  606. X         */
  607. X        if (timenow > tp -> tty_accesstime + idletime)
  608. X        {
  609. X        if (tp -> tty_state == TTY_ACTIVE ||
  610. X            (tp -> tty_state == TTY_WARNED &&
  611. X             timenow > tp -> tty_warningtime + WARNING_TIMEOUT))
  612. X        {
  613. X            ttystate (tp, TTY_NOINPUT);
  614. X        }
  615. X        candidates++;
  616. X        }
  617. X    }
  618. X    }
  619. X    dprintf1 ("scantty: %d candidates\n", candidates);
  620. X    return (candidates);
  621. X}
  622. X
  623. X
  624. X/*
  625. X * Decide if a terminal has no active processes.
  626. X */
  627. Xttyidle (tp)
  628. Xstruct ttyinfo *tp;
  629. X{
  630. X    if (tp -> tty_shellpid == 0)
  631. X    {    
  632. X    /*
  633. X     * Cache information about process shell.
  634. X     */
  635. X    if (!findshell (tp))
  636. X        return (0);
  637. X    }
  638. X    else
  639. X    {
  640. X    if (tp -> tty_shellproc -> p_pid != tp -> tty_shellpid)
  641. X    {
  642. X        dprintf0 ("err ... this process table is like wierd man!\n");
  643. X        if (!findshell (tp))
  644. X        return (0);
  645. X    }
  646. X    }
  647. X    return (idleprocs (tp -> tty_shellproc, 0));
  648. X}
  649. X
  650. X
  651. X/*
  652. X * Change terminal state and dprint ... it happens a lot
  653. X */
  654. Xttystate (tp, state)
  655. Xstruct ttyinfo *tp;
  656. Xint state;
  657. X{
  658. X    tp -> tty_state = state;
  659. X    dprintf2 ("%s state -> %s\n", tp -> tty_line, statestrings[state]);
  660. X}
  661. X
  662. X
  663. X/*
  664. X * Starting at the shell process, traverse the tree of processes looking for
  665. X * an active one.  An active process is defined to be any process in states
  666. X * SWAIT, SRUN or SIDL, or in state SSLEEP with slptime < MAXSLP.
  667. X */
  668. Xidleprocs (pp, depth)
  669. Xstruct proc *pp;
  670. Xint depth;
  671. X{
  672. X    register struct proc *cpp;
  673. X    register short width = 0;
  674. X
  675. X    if (depth > nproc)
  676. X    {
  677. X    dprintf0 ("eek!! idleprocs looping vertically!!\n");
  678. X    return (0);
  679. X    }
  680. X
  681. X    switch (pp -> p_stat)
  682. X    {
  683. X        case SSLEEP:
  684. X        if (pp -> p_slptime >= PROC_SLEEPTIME)
  685. X        break;
  686. X    case SWAIT:
  687. X    case SRUN:
  688. X    case SIDL:
  689. X        dprintf1 ("process %d is active\n", pp -> p_pid);
  690. X        return (0);        /* not idle */
  691. X    }
  692. X    dprintf1 ("process %d is idle\n", pp -> p_pid);
  693. X
  694. X    /*
  695. X     * Recursively check all children starting at youngest.
  696. X     */
  697. X    cpp = pp -> p_cptr;
  698. X    while (cpp != NULL)
  699. X    {
  700. X    if (width++ > nproc)
  701. X    {
  702. X        dprintf0 ("eek!! idleproc looping horizontally!!\n");
  703. X        return (0);
  704. X    }
  705. X    cpp = reloc (cpp);
  706. X    if (!idleprocs(cpp, depth + 1))
  707. X        return (0);
  708. X    cpp = cpp -> p_osptr;
  709. X    }
  710. X    dprintf2 ("checked %d children for %d ...\n", width, pp -> p_pid);
  711. X    return (1);
  712. X}
  713. X
  714. X
  715. X/*
  716. X * Find the "shell" process associated with the terminal.  
  717. X * The algorithm is ...
  718. X *    Look up the controlling process group for the tty. If the
  719. X *        associated process DNE, search for one with the same pgrp.
  720. X *    Starting with the process, chain back through the parents until a
  721. X *          process with ppid == 1 is found.  That's the shell.
  722. X */
  723. Xfindshell (tp)
  724. Xstruct ttyinfo *tp;
  725. X{
  726. X    int tty = open (tp -> tty_line, O_WRONLY, 0);
  727. X    short pgrp;
  728. X    register paranoia = 0;
  729. X    register struct proc *pp, *altpp;
  730. X
  731. X    if (tty < 0)
  732. X    {
  733. X    fprintf (stderr, "idledaemon: open /dev/%s failed in findshell: ",
  734. X        tp -> tty_line);
  735. X    perror ("");
  736. X    return (0);
  737. X    }
  738. X    /* 4.2 lint library (?) bug ... */
  739. X    if (ioctl (tty, TIOCGPGRP, (char *) &pgrp) < 0)
  740. X    {
  741. X    fprintf (stderr, "idledaemon: ioctl on /dev/%s to get pgrp failed: ",
  742. X        tp -> tty_line);
  743. X    perror ("");
  744. X    close (tty);
  745. X    return (0);
  746. X    }
  747. X    close (tty);
  748. X    for (pp = proctable; pp < &proctable[nproc] && pp -> p_pid != pgrp; pp++)
  749. X    {
  750. X    if (pp -> p_pgrp == pgrp)
  751. X        altpp = pp;
  752. X    }
  753. X    if (pp -> p_pid != pgrp)
  754. X    if (altpp -> p_pgrp == pgrp)
  755. X    {
  756. X        pp = altpp;
  757. X        dprintf2 ("findshell starting with proc %d in pgrp %d\n", 
  758. X              altpp -> p_pid, pgrp);
  759. X    }
  760. X    else
  761. X    {
  762. X        fprintf (stderr, 
  763. X            "idledaemon: no processes for pgrp %d (/dev/%s)\n",
  764. X            pgrp, tp -> tty_line);
  765. X        return (0);
  766. X    }
  767. X    else
  768. X    dprintf1 ("findshell starting with pgrp leader %d\n", pgrp);
  769. X    while (pp -> p_ppid > 1)
  770. X    {
  771. X    pp = reloc (pp -> p_pptr);
  772. X    dprintf1 ("parent is %d\n", pp -> p_pid);
  773. X    if (paranoia++ > nproc)
  774. X    {
  775. X        dprintf0 ("eek!! findshell looping!!\n");
  776. X        return (0);
  777. X    }
  778. X    }
  779. X    dprintf1 ("findshell found process %d\n", pp -> p_pid);
  780. X    tp -> tty_shellpid = pp -> p_pid;
  781. X    tp -> tty_shellproc = pp;
  782. X    return (1);
  783. X}
  784. X
  785. X
  786. X/*
  787. X * Send a HUP to all the decendents of the process ... depth first
  788. X */
  789. Xhupprocs (pp, depth)
  790. Xstruct proc *pp;
  791. Xint depth;
  792. X{
  793. X    register struct proc *cpp;
  794. X    register width = 0;
  795. X    register i;
  796. X
  797. X    if (depth > nproc)
  798. X    {
  799. X    dprintf0 ("eek!! killprocs looping vertically!!\n");
  800. X    return;
  801. X    }
  802. X
  803. X    /*
  804. X     * See if the process has gone already ...
  805. X     */
  806. X    if (kill (pp -> p_pid, 0) < 0)
  807. X    {
  808. X    dprintf2 ("hupprocs: process %d has already gone away: errno %d\n", 
  809. X        pp -> p_pid, errno);
  810. X    return;
  811. X    }
  812. X
  813. X    dprintf1 ("hupprocs: doing children of process %d\n", pp -> p_pid);
  814. X
  815. X    /*
  816. X     * Recursively HUP all children starting at youngest.
  817. X     */
  818. X    cpp = pp -> p_cptr;
  819. X    while (cpp != NULL)
  820. X    {
  821. X    if (width++ > nproc)
  822. X    {
  823. X        dprintf0 ("eek!! killproc looping horizontally!!\n");
  824. X        return;
  825. X    }
  826. X    cpp = reloc (cpp);
  827. X    hupprocs(cpp, depth + 1);
  828. X    cpp = cpp -> p_osptr;
  829. X    }
  830. X    dprintf2 ("HUPed %d children for %d ...\n", width, pp -> p_pid);
  831. X    
  832. X    if (kill (pp -> p_pid, SIGHUP) < 0)
  833. X    {
  834. X    dprintf2 ("HUP to %d failed ... errno %d\n", pp -> p_pid, errno);
  835. X    return;
  836. X    }
  837. X    else
  838. X    dprintf1 ("HUP sent to %d\n", pp -> p_pid);
  839. X    if (pp -> p_stat == SSTOP)
  840. X    {
  841. X    (void) kill (pp -> p_pid, SIGCONT);
  842. X    dprintf1 ("CONT sent to %d\n", pp -> p_pid);
  843. X    }
  844. X
  845. X    for (i = 0; i < 5; i++)
  846. X    {
  847. X        if (kill (pp -> p_pid, 0) < 0)
  848. X    {
  849. X        dprintf1 ("HUPed process %d has gone\n", pp -> p_pid);
  850. X        return;
  851. X    }
  852. X    dprintf0 (".");        
  853. X    sleep (1);
  854. X    }
  855. X    dprintf1 ("HUPed process %d didn't go away\n", pp -> p_pid);
  856. X}
  857. X
  858. X
  859. X/*
  860. X * Send an idle warning message.
  861. X */
  862. Xttywarning (tp)
  863. Xstruct ttyinfo *tp;
  864. X{
  865. X    ttymsg (tp, "WARNING: you may be auto-logged out in %d mins.\r\n", 
  866. X        gracetime / SECSPERMIN);
  867. X    tp -> tty_warningtime = timenow;    /* (or there abouts) */
  868. X    ttystate (tp, TTY_WARNED);
  869. X}
  870. X
  871. X
  872. X/*
  873. X * Search and destroy the terminal with the most idle time.
  874. X */
  875. Xkillmostidle()
  876. X{
  877. X    register struct ttyinfo *tp,
  878. X                           *itp;
  879. X
  880. X    read_proctable ();
  881. X /* Find worst offender.  */
  882. X    for (tp = ttytable, itp = (struct ttyinfo  *) 0;
  883. X        tp < &ttytable[nttys]; tp++)
  884. X    {
  885. X    if (tp -> tty_state == TTY_WARNED &&
  886. X        tp -> tty_accesstime + idletime + gracetime < timenow &&
  887. X        (!itp || (tp -> tty_accesstime < itp -> tty_accesstime)))
  888. X    {
  889. X        if (!ttyidle (tp))
  890. X        {
  891. X        /* 
  892. X         * Zombified session has revived itself without
  893. X         * any input from terminal.  Probably some clock
  894. X         * driven process.
  895. X         */
  896. X        dprintf1 ("Urghhh! terminal %s has revived itself\n",
  897. X            tp -> tty_line);
  898. X        ttystate (tp, TTY_NOINPUT);
  899. X        }
  900. X        else
  901. X        itp = tp;
  902. X    }
  903. X    }
  904. X    if (!itp)
  905. X    return (0);        /* Ah well ... nothing left to kill */
  906. X
  907. X/*
  908. X * Zap all processes attached to a terminal.  If the shell process goes
  909. X * away, nttysfree is incremented to stop the mainline killing more
  910. X * sessions or sending out warnings unnecessarily (unneces-celery).
  911. X */
  912. X    ttymsg (itp, "Your session is being auto-logged out\r\n", 0);
  913. X    sleep (1);
  914. X
  915. X    read_proctable ();        /* Hmm ... a little bit of paranoia here */
  916. X    if (debug == 0 && (itp -> tty_shellproc -> p_pid == itp -> tty_shellpid))
  917. X    {
  918. X    /* 
  919. X     * Send HUPs to all processes attached to terminal.
  920. X     */
  921. X    hupprocs (itp -> tty_shellproc, 0);
  922. X
  923. X    /* 
  924. X     * If the shell is still hanging around, stick the knife in.
  925. X     */
  926. X    if (kill (itp -> tty_shellpid, 0) == 0)
  927. X    {
  928. X        dprintf1 ("KILLing shell process %d\n", itp -> tty_shellpid);
  929. X        (void) kill (itp -> tty_shellpid, SIGKILL);
  930. X        sleep (5);
  931. X        if (kill (itp -> tty_shellpid, 0) < 0)
  932. X        nttysfree++;
  933. X        else
  934. X        {
  935. X        dprintf1 ("Shell process %d cannot be killed!!\n", 
  936. X              itp -> tty_shellpid);
  937. X        ttystate (itp, TTY_STUCK);
  938. X        return (1);
  939. X        }
  940. X    }
  941. X    else
  942. X        nttysfree++;
  943. X    }
  944. X    else
  945. X    {
  946. X    dprintf1 ("skipped kills for process %d\n", itp -> tty_shellpid);
  947. X    nttysfree++;
  948. X    }
  949. X    ttystate (itp, TTY_KILLED);
  950. X    return (1);
  951. X}
  952. X
  953. X
  954. X/*
  955. X * Send a message to a user's terminal from a sub-process
  956. X */
  957. Xttymsg (tp, message, arg)
  958. Xstruct ttyinfo *tp;
  959. Xchar *message;
  960. Xint arg;
  961. X{
  962. X    tp -> tty_notifier = fork();
  963. X    if (tp -> tty_notifier < 0)
  964. X    {
  965. X    perror ("idledaemon: fork failed: ");
  966. X    return;
  967. X    }
  968. X    /*
  969. X     * Child process sends message.  It will be collected later.
  970. X     */
  971. X    if (tp -> tty_notifier == 0)
  972. X    notifier (tp -> tty_line, message, arg);    /* does not return */
  973. X    notifiers++;
  974. X    (void) alarm (2);
  975. X}
  976. X
  977. X
  978. X/*
  979. X * ALARM handler for collectnotifiers() ... give the notifiers a helping hand
  980. X */
  981. Xkillnotifiers ()
  982. X{
  983. X    struct ttyinfo *tp;
  984. X
  985. X    /*
  986. X     * If we have already sent the kills, something screwy is going on!
  987. X     */
  988. X    if (notifierskilled)
  989. X    {
  990. X    dprintf0 ("Timeout for collectnotifiers() after sending kills!\n");
  991. X    /* 4.2 lint library bug ... */
  992. X    longjmp(timeout, 1);
  993. X    }
  994. X
  995. X    for (tp = ttytable; tp < &ttytable[nttys]; tp++)
  996. X    {
  997. X        if (tp -> tty_notifier > 0)
  998. X    {
  999. X        (void) kill (tp -> tty_notifier, SIGKILL);
  1000. X        dprintf1 ("killing notifier %d\n", tp -> tty_notifier);
  1001. X    }
  1002. X    }
  1003. X    notifierskilled++;
  1004. X    (void) alarm (5);    
  1005. X}
  1006. X
  1007. X
  1008. X/*
  1009. X * Collect all terminal notification processes, sending a kill if
  1010. X * necessary in case someone has left their terminal with output blocked.
  1011. X */
  1012. Xcollectnotifiers ()
  1013. X{
  1014. X    union wait status;
  1015. X    int     child;
  1016. X    struct ttyinfo *tp;
  1017. X
  1018. X    if (notifiers == 0)
  1019. X    return;
  1020. X
  1021. X    dprintf0 ("Collecting notifiers\n");
  1022. X    notifierskilled = 0;
  1023. X
  1024. X    (void) signal (SIGALRM, killnotifiers);
  1025. X    (void) alarm (5);
  1026. X
  1027. X    if (setjmp (timeout) == 0)
  1028. X    {
  1029. X    while (notifiers > 0)
  1030. X    {
  1031. X        child = wait (&status);
  1032. X        dprintf2 ("notifier %d status %x\n", child, status);
  1033. X        for (tp = ttytable; tp < &ttytable[nttys]; tp++)
  1034. X        {
  1035. X        if (child == tp -> tty_notifier)
  1036. X        {
  1037. X            tp -> tty_notifier = 0;
  1038. X            notifiers--;
  1039. X        }
  1040. X        }
  1041. X    }
  1042. X    }
  1043. X    else
  1044. X    dprintf1 ("Abandoned wait for %d notifiers\n", notifiers);
  1045. X
  1046. X    notifiers = 0;
  1047. X    (void) alarm (0);
  1048. X    (void) signal (SIGALRM, SIG_IGN);
  1049. X}
  1050. X
  1051. X
  1052. X/*
  1053. X * (In a child process ...) Send a message to a terminal, then exit.
  1054. X */
  1055. Xnotifier (line, message, arg)
  1056. Xchar *line, *message;
  1057. Xint arg;
  1058. X{
  1059. X    FILE * tty;
  1060. X    char    hostname[20];
  1061. X
  1062. X    /* 4.2 lint library bug ... */
  1063. X    (void) gethostname (hostname, sizeof (hostname));
  1064. X    tty = fopen (line, "w");
  1065. X
  1066. X    if (tty != NULL)
  1067. X    {
  1068. X    if (debug)
  1069. X        fprintf (tty, "**** Testing idledaemon: PLEASE IGNORE!\r\n\r\n");
  1070. X    fprintf (tty, "**** \007\007Message from %s.idledaemon at %8.8s\r\n\r\n**** ",
  1071. X        hostname, ctime (&timenow) + 11);
  1072. X    fprintf (tty, message, arg);
  1073. X    fputs ("\r\n", tty);
  1074. X    (void) fclose (tty);
  1075. X    }
  1076. X    else
  1077. X    dprintf2 ("notifier failed to open %s ... errno %d\n", line, errno);
  1078. X    (void) fflush (stderr);
  1079. X    exit (0);
  1080. X}
  1081. X
  1082. X
  1083. Xdisplaysig (sig, x, y)
  1084. X{
  1085. X    time_t sigtime;
  1086. X
  1087. X    time (&sigtime);
  1088. X    dprintf2 ("Signal %d caught at %s", sig, ctime(&sigtime));
  1089. X    fflush (stderr);
  1090. X    exit (1);
  1091. X}
  1092. X
  1093. X
  1094. X/*
  1095. X *  Signal routine to display tty state tables on standard output.
  1096. X */
  1097. Xprintstate (sig, x, y)
  1098. X{
  1099. X    time_t sigtime;
  1100. X    register i;
  1101. X    register struct ttyinfo *tp;
  1102. X
  1103. X    sleep (1);            /* time for the next shell prompt ... */
  1104. X    time (&sigtime);
  1105. X    printf ("\nIdledaemon terminal info: time %25.25s", ctime(&sigtime));
  1106. X    printf ("%d terminals out of %d are free\n", nttysfree, nttys);
  1107. X    printf (
  1108. X"tty     state   user    login time        last access       warning sent\n");
  1109. X    for (i = 0, tp = ttytable; i < nttys; i++, tp++)
  1110. X    {
  1111. X    printf ("%s\t%s", tp -> tty_line, statestrings[tp -> tty_state]);
  1112. X    if (tp -> tty_state == TTY_FREE || tp -> tty_state == TTY_UNKNOWN)
  1113. X    {
  1114. X        putchar ('\n');
  1115. X        continue;
  1116. X    }
  1117. X        printf ("\t%s", tp -> tty_name);
  1118. X    printf ("\t%15.15s", ctime (&(tp -> tty_logintime)) + 4);
  1119. X    if (tp -> tty_state != TTY_ACTIVE)
  1120. X    {
  1121. X        printf ("   %15.15s", ctime (&(tp -> tty_accesstime)) + 4);
  1122. X        if (tp -> tty_state == TTY_WARNED)
  1123. X            printf ("   %15.15s", ctime (&(tp -> tty_warningtime)) + 4);
  1124. X    }
  1125. X    putchar ('\n');
  1126. X    }
  1127. X    fflush (stdout);
  1128. X}
  1129. X
  1130. X
  1131. X/*
  1132. X *  Set up signal handlers.
  1133. X */
  1134. Xinit_signals ()
  1135. X{
  1136. X    register i;
  1137. X    for (i = 1; i <= SIGPROF; i++)
  1138. X    {
  1139. X    if (i != SIGCONT && i != SIGCHLD && i != SIGHUP && i != SIGINT)
  1140. X        (void) signal (i, displaysig);
  1141. X    }
  1142. X    signal (SIGHUP, SIG_IGN);
  1143. X    signal (SIGINT, printstate);
  1144. X}
  1145. /
  1146. echo 'x - idledaemon.8l'
  1147. sed 's/^X//' > idledaemon.8l << '/'
  1148. X.TH IDLEDAEMON 8L "6 September 1985"
  1149. X.UC 4
  1150. X.SH NAME
  1151. Xidledaemon \- auto-logout idle terminals
  1152. X.SH SYNOPSIS
  1153. X.B /etc/idledaemon 
  1154. X[
  1155. X.B \-a
  1156. X] [
  1157. X.BI \-f nosfree
  1158. X] [
  1159. X.BI \-gil minutes
  1160. X]
  1161. X.B tty ...
  1162. X.SH DESCRIPTION
  1163. X.I Idledaemon 
  1164. Xmonitors the use of a group of terminal lines.  When the number
  1165. Xof free terminals in the group falls below a given threshold, it finds those
  1166. Xwhich are idle, sends a warning message, and if necessary logs them out.
  1167. XThe options to 
  1168. X.I idledaemon
  1169. Xare as follows:
  1170. X.TP
  1171. X.B \-a
  1172. Xcauses the daemon to kill idle terminals irrespective of the number that are 
  1173. Xfree.
  1174. X.TP
  1175. X.B \-d
  1176. Xruns the daemon in 
  1177. X.I debug
  1178. Xmode.  The daemon goes through the motions of checking terminals and sending
  1179. Xmessages, but does not kill off sessions.  You might want to use this to get
  1180. Xyour users used to the idea of an idle daemon.
  1181. X.TP
  1182. X.BI \-f nosfree
  1183. Xsets the size of the free terminal pool that the daemon tries to keep.  This
  1184. Xparameter defaults to 1.
  1185. X.TP
  1186. X.BI \-i minutes
  1187. Xchanges the terminal
  1188. X.B idle time
  1189. Xfrom the default of 30 minutes.  If there is no terminal input processed for
  1190. Xthis many minutes, the session may be deemed to be idle (see below).
  1191. X.TP
  1192. X.BI \-g minutes
  1193. Xchanges the minimum
  1194. X.B grace time
  1195. Xbetween the warning message and auto-logout from the default of 5 minutes.
  1196. XNotifications which have not been followed by kills time out after 30 minutes.
  1197. X.TP
  1198. X.BI \-l minutes
  1199. Xchanges the time between idle daemon scans from the default of 5 minutes.
  1200. X.PP
  1201. XThe algorithm for finding and killing idle sessions is complicated and not
  1202. Xfoolproof.  When a terminal has had no input (strictly, no program has
  1203. Xread input from the terminal) for the prescribed number of minutes, the
  1204. Xidledaemon scans the processes 
  1205. X.B attached
  1206. Xto the session to see if any are active.  Attached processes include
  1207. Xstopped or background processes started by the session but exclude
  1208. Xbackground processes started by previous sessions and daemon processes 
  1209. Xlike 
  1210. X.IR sysline (1).  
  1211. XAn 
  1212. X.B active
  1213. Xprocess is any process that is ready to execute or has been in the past
  1214. Xfew seconds; basically anything that
  1215. X.IR ps (1) 
  1216. Xshows with a STAT of R, P, D or S.
  1217. XWhen an idle session has been found, all processes attached to the session
  1218. Xare sent HUP signals to allow them to tidy up, then the shell process is
  1219. Xsent a KILL.
  1220. X.PP
  1221. XWhen the idledaemon receives a SIGINT signal, it prints a summary of the
  1222. Xcurrent state to its output stream.  This could be directed to the console.
  1223. X.SH AUTHOR
  1224. XStephen Crawley
  1225. X.SH FILES
  1226. X/dev/tty*,
  1227. X/etc/utmp
  1228. X.SH "SEE ALSO"
  1229. Xps(1)
  1230. X.SH BUGS
  1231. XThe scheme the daemon uses to find idle sessions is trivially easy to fool.
  1232. X.PP
  1233. XThe daemon doesn't do anything about objectionable people who hog lots of
  1234. Xterminals ports at the same time.
  1235. X.PP
  1236. XProbably lots of bugs in some of the unusual cases (like stuck ttys).
  1237. /
  1238. echo 'Part 01 of pack.out complete.'
  1239. exit
  1240. ----------------------------That's-All-Folks-------------------------------
  1241.  
  1242.  
  1243.