home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1992 March / Source_Code_CD-ROM_Walnut_Creek_March_1992.iso / usenet / altsrcs / 2 / 2618 / sm.c < prev   
Encoding:
C/C++ Source or Header  |  1991-01-29  |  20.7 KB  |  1,150 lines

  1. /*
  2.  * This code is in the public domain.  THIS CODE IS PROVIDED ON AN AS-IS
  3.  * BASIS.  THE USER ACCEPTS ALL RISKS ASSOCIATED WITH USING THIS CODE AND
  4.  * IS SOLELY RESPONSIBLE FOR CORRECTING ANY SOFTWARE ERRORS OR DAMAGE
  5.  * CAUSED BY SOFTWARE ERRORS OR MISUSE.
  6.  *
  7.  * Written By: John F Haugh II, 12/21/90
  8.  *
  9.  * Modified to include suggestions made by Pat Myrto, Dan Bernstein,
  10.  * and Axel Fischer.
  11.  */
  12.  
  13. #include <sys/types.h>
  14. #ifdef    SYSV_IOCTL
  15. #include <sys/termio.h>
  16. #endif
  17. #include <sys/stat.h>
  18. #include <utmp.h>
  19. #include <string.h>
  20. #include <stdio.h>
  21. #include <signal.h>
  22. #include <time.h>
  23. #include <fcntl.h>
  24. #include <errno.h>
  25. #include <ctype.h>
  26. #include <pwd.h>
  27.  
  28. /*
  29.  * BLOCKING_PTY is defined if you PTY device driver is incapable
  30.  * of correctly handling non-blocking read requests.
  31.  */
  32.  
  33. #undef    BLOCKING_PTY
  34.  
  35. /*
  36.  * MAXSESSIONS is the number of sessions which a single user can
  37.  * manage with this program at a single time.  16 is plenty.  4 or
  38.  * 5 might be a better idea if pty's are a scarce resource.  8
  39.  * should be a nice compromise.
  40.  */
  41.  
  42. #define    MAXSESSIONS    8
  43.  
  44. int    childpids[MAXSESSIONS];        /* Process ID of each session leader */
  45. char    *labels[MAXSESSIONS];        /* Names of sessions                 */
  46. char    *ports[MAXSESSIONS];        /* Names of tty's                    */
  47. int    writepid;            /* Process ID of PTY writing process */
  48. int    pspid;                /* Obfuscation is my life            */
  49. int    masters[MAXSESSIONS];        /* File descriptor for PTY master    */
  50. int    nsessions;            /* High-water mark for session count */
  51. int    current = -1;            /* Currently active session          */
  52. int    last = -1;            /* Previously active session         */
  53. int    caught = 0;            /* Some signal was caught            */
  54.  
  55. #ifdef    SYSV_IOCTL
  56. struct    termio    sanetty;        /* Initial TTY modes on entry        */
  57. struct    termio    rawtty;            /* Modes used when session is active */
  58. #endif
  59.  
  60. void    exit ();
  61. void    _exit ();
  62. char    *getlogin ();
  63. char    *getenv ();
  64. char    *malloc ();
  65. char    *whoami;
  66. struct    passwd    *getpwuid ();
  67. extern    char    **environ;
  68.  
  69. /*
  70.  * fix_utmp - add or remove utmp file entries
  71.  *
  72.  *    fix_utmp creates entries for new sessions and removes entries
  73.  *    for sessions which have died.
  74.  */
  75.  
  76. void
  77. fix_utmp (port, pid)
  78. char    *port;
  79. int    pid;
  80. {
  81.     char    *cp;
  82.     int    fd;
  83.     int    found = 0;
  84.     struct    utmp    utmp;
  85.  
  86.     if (strncmp (port, "/dev/", 5) == 0)
  87.         port += 5;
  88.  
  89.     if ((fd = open ("/etc/utmp", O_RDWR)) == -1)
  90.         return;
  91.  
  92.     while (read (fd, &utmp, sizeof utmp) == sizeof utmp) {
  93.         if (strncmp (port, utmp.ut_line, sizeof utmp.ut_line) == 0) {
  94.             found++;
  95.             break;
  96.         }
  97.     }
  98.     if (found)
  99.         lseek (fd, (long) - sizeof utmp, 1);
  100.     else if (pid == 0)
  101.         return;
  102.  
  103.     if (pid) {            /* Add new utmp entry */
  104.         memset (&utmp, 0, sizeof utmp);
  105.  
  106.         strncpy (utmp.ut_user, whoami, sizeof utmp.ut_user);
  107.         strncpy (utmp.ut_line, port, sizeof utmp.ut_line);
  108.  
  109.         if (cp = strrchr (port, '/'))
  110.             cp++;
  111.         else
  112.             cp = port;
  113.  
  114.         if (strncmp (cp, "tty", 3) == 0)
  115.             cp += 3;
  116.  
  117.         strncpy (utmp.ut_id, cp, sizeof utmp.ut_id);
  118.  
  119.         utmp.ut_pid = pid;
  120.         utmp.ut_type = USER_PROCESS;
  121.         time (&utmp.ut_time);
  122.     } else {            /* Remove utmp entry */
  123.         utmp.ut_type = DEAD_PROCESS;
  124.     }
  125.     write (fd, &utmp, sizeof utmp);
  126.     close (fd);
  127. }
  128.  
  129. /*
  130.  * parse - see if "s" and "pat" smell alike
  131.  */
  132.  
  133. char *
  134. parse (s, pat)
  135. char    *s;
  136. char    *pat;
  137. {
  138.     int    match = 0;
  139.     int    star = 0;
  140.  
  141.     /*
  142.      * Match all of the characters which are identical.  The '*'
  143.      * character is used to denote the end of the unique suffix
  144.      * for a pattern.  Everything after that is optional, but
  145.      * must be matched exactly if given.
  146.      */
  147.  
  148.     while (*s && *pat) {
  149.         if (*s == *pat && *s) {
  150.             s++, pat++;
  151.             continue;
  152.         }
  153.         if (*pat == '*') {
  154.             star++;
  155.             pat++;
  156.             continue;
  157.         }
  158.         if ((*s == ' ' || *s == '\t') && star)
  159.             return s;
  160.         else
  161.             return 0;
  162.     }
  163.  
  164.     /*
  165.      * The pattern has been used up - see if whitespace
  166.      * follows, or if the input string is also finished.
  167.      */
  168.  
  169.     if (! *pat && (*s == '\0' || *s == ' ' || *s == '\t'))
  170.         return s;
  171.  
  172.     /*
  173.      * The input string has been used up.  The unique
  174.      * prefix must have been matched.
  175.      */
  176.  
  177.     if (! *s && (star || *pat == '*'))
  178.         return s;
  179.  
  180.     return 0;
  181. }
  182.  
  183. /*
  184.  * murder - reap a single child process
  185.  */
  186.  
  187. void
  188. murder (sig)
  189. int    sig;
  190. {
  191.     int    pid;
  192.     int    i;
  193.  
  194.     pid = wait ((int *) 0);
  195.  
  196.     /*
  197.      * See what children have died recently.
  198.      */
  199.  
  200.     for (i = 0;pid != -1 && i < nsessions;i++) {
  201.  
  202.         /*
  203.          * Close their master sides and mark the
  204.          * session as "available".
  205.          */
  206.  
  207.         if (pid == childpids[i]) {
  208.             childpids[i] = -1;
  209.             if (masters[i] != -1) {
  210.                 close (masters[i]);
  211.                 if (writepid != -1)
  212.                     kill (writepid, SIGTERM);
  213.  
  214.                 masters[i] = -1;
  215.             }
  216.             if (labels[i]) {
  217.                 free (labels[i]);
  218.                 labels[i] = 0;
  219.             }
  220.             if (ports[i]) {
  221.                 fix_utmp (ports[i], 0);
  222.                 free (ports[i]);
  223.                 ports[i] = 0;
  224.             }
  225.             break;
  226.         }
  227.     }
  228.     if (writepid != -1 && pid == writepid)
  229.         writepid = -1;
  230.  
  231.     if (pspid != -1 && pid == pspid)
  232.         pspid = -1;
  233.  
  234.     signal (sig, murder);
  235. }
  236.  
  237. /*
  238.  * catch - catch a signal and set a flag
  239.  */
  240.  
  241. void
  242. catch (sig)
  243. int    sig;
  244. {
  245.     caught = 1;
  246.     signal (sig, catch);
  247. }
  248.  
  249. /*
  250.  * reader - read characters from the pty and write to the screen
  251.  */
  252.  
  253. int
  254. reader (fd)
  255. int    fd;
  256. #ifdef    BLOCKING_PTY
  257. {
  258.     char    c;        /* do reads a byte at a time */
  259.     int    cnt;        /* how many bytes were read */
  260.  
  261.     /*
  262.      * Ignore the SIGINT and SIGQUIT signals.
  263.      */
  264.  
  265.     signal (SIGINT, SIG_IGN);
  266.     signal (SIGQUIT, SIG_IGN);
  267.  
  268.     while (1) {
  269.         if ((cnt = read (fd, &c, 1)) == -1) {
  270.             if (errno != EINTR)
  271.                 return -1;
  272.  
  273.             if (caught)
  274.                 return 0;
  275.             else
  276.                 continue;
  277.         }
  278.         if (cnt == 0)
  279.             return -1;
  280.  
  281.         write (1, &c, 1);
  282.     }
  283. }
  284. #else
  285. {
  286.     char    buf[16];    /* do reads in 16 byte bursts */
  287.     int    cnt;        /* how many bytes were read */
  288.     int    wanted = sizeof buf; /* how may bytes to try reading */
  289.     int    flags;        /* fcntl flags */
  290.  
  291.     /*
  292.      * Ignore the SIGINT and SIGQUIT signals.
  293.      */
  294.  
  295.     signal (SIGINT, SIG_IGN);
  296.     signal (SIGQUIT, SIG_IGN);
  297.  
  298.     flags = fcntl (fd, F_GETFL, 0);
  299.     fcntl (fd, F_SETFL, flags|O_NDELAY);
  300.  
  301.     while (1) {
  302.         if ((cnt = read (fd, buf, wanted)) == -1) {
  303.             if (errno != EINTR)
  304.                 return -1;
  305.  
  306.             if (caught)
  307.                 return 0;
  308.             else
  309.                 continue;
  310.         }
  311.         if (cnt == 0 && wanted != 1) {
  312.             wanted = 1;
  313.             fcntl (fd, F_SETFL, flags & ~O_NDELAY);
  314.             continue;
  315.         }
  316.         if (cnt == 0 && wanted == 1)
  317.             return -1;
  318.  
  319.         if (wanted == 1)
  320.             flags = fcntl (fd, F_GETFL, flags|O_NDELAY);
  321.  
  322.         wanted = sizeof buf;
  323.         write (1, buf, cnt);
  324.     }
  325. }
  326. #endif
  327.  
  328. /*
  329.  * writer - write characters read from the keyboard down the pty
  330.  */
  331.  
  332. writer (fd)
  333. int    fd;
  334. {
  335.     char    c;
  336.     int    cnt;
  337.     int    zflg = 0;
  338.  
  339.     signal (SIGINT, SIG_IGN);
  340.     signal (SIGQUIT, SIG_IGN);
  341.     signal (SIGHUP, _exit);
  342.  
  343.     /*
  344.      * Read characters until an error is returned or ^Z is seen
  345.      * followed by a non-^Z character.
  346.      */
  347.  
  348.     while (1) {
  349.         errno = 0;
  350.         if ((cnt = read (0, &c, 1)) == 0)
  351.             continue;
  352.  
  353.         /*
  354.          * Some signal may have occured, so retry
  355.          * the read.
  356.          */
  357.  
  358.         if (cnt == -1) {
  359.             if (errno == EINTR && caught)
  360.                 continue;
  361.             else
  362.                 exit (0);
  363.         }
  364.  
  365.         /*
  366.          * Process a ^Z.  If one was not seen earlier, 
  367.          * set a flag and go read another character.
  368.          */
  369.  
  370.         if (c == ('z' & 037)) {
  371.             if (! zflg++)
  372.                 continue;
  373.         }
  374.         
  375.         /*
  376.          * See if a ^Z was seen before.  If so, signal
  377.          * the master and exit.
  378.          */
  379.  
  380.         else if (zflg) {
  381.             kill (getppid (), SIGUSR1);
  382.             exit (0);
  383.         }
  384.  
  385.         /*
  386.          * Just output the character as is.
  387.          */
  388.  
  389.         zflg = 0;
  390.         if (write (fd, &c, 1) != 1)
  391.             break;
  392.     }
  393.     exit (0);
  394. }
  395.  
  396. /*
  397.  * usage - command line syntax
  398.  */
  399.  
  400. usage ()
  401. {
  402.     fprintf (stderr, "usage: sm\n");
  403.     exit (1);
  404. }
  405.  
  406. /*
  407.  * help - built-in command syntax
  408.  */
  409.  
  410. help ()
  411. {
  412.     fprintf (stderr, "Valid commands are:\n");
  413.     fprintf (stderr, "\tconnect [ # ]\t(connects to session)\n");
  414.     fprintf (stderr, "\tcreate\t\t(sets up a new pty session)\n");
  415.     fprintf (stderr, "\tcurrent\t\t(shows current session number)\n");
  416.     fprintf (stderr, "\tdelete #\t(deletes session)\n");
  417.     fprintf (stderr, "\thelp\t\tdisplay this message\n");
  418.     fprintf (stderr, "\tjobs\t\t(shows a ps listing of current session)\n");
  419.     fprintf (stderr, "\tquit\tor exit (terminate session manager)\n");
  420.     fprintf (stderr, "\tset #\t\t(# is 0-15 - selets current session)\n");
  421.     fprintf (stderr, "\ttoggle\t\t(switch to previous session)\n\n");
  422.     fprintf (stderr, "Commands may be abbreviated to a unique prefix\n\n");
  423.     fprintf (stderr, "Note - to exit a session back into sm so one can\n");
  424.     fprintf (stderr, " select another session, type a ^Z and a return\n");
  425.     fprintf (stderr, " To send ^Z to the shell, type two ^Z chars\n\n");
  426. }
  427.  
  428. /*
  429.  * session - create a new session on a pty
  430.  */
  431.  
  432. int
  433. session (prompt, init)
  434. char    *prompt;
  435. int    init;
  436. {
  437.     char    mastername[BUFSIZ];
  438.     char    slavename[BUFSIZ];
  439.     char    newprompt[32];
  440.     char    newshell[32];
  441.     char    *digits = "0123456789abcdef";
  442.     char    *letters = "pqrs";
  443.     char    *shell;
  444.     char    *arg;
  445.     int    oumask;
  446.     int    i;
  447.     int    pty;
  448.     int    ptys = 64;
  449.     struct    stat    sb;
  450.     struct    passwd    *pwd;
  451.  
  452.     /*
  453.      * Find the number of the new session.  An error will be
  454.      * given if no sessions are available.
  455.      */
  456.  
  457.     for (i = 0;i < nsessions && masters[i] != -1;i++)
  458.         ;
  459.  
  460.     if (i == MAXSESSIONS) {
  461.         printf ("out of sessions\n");
  462.         return 0;
  463.     }
  464.     if (i == nsessions)
  465.         nsessions++;
  466.  
  467.     /*
  468.      * Save the previous sesssion number.  This is so the
  469.      * "toggle" command will work after a "create".
  470.      */
  471.  
  472.     if (current != -1)
  473.         last = current;
  474.  
  475.     current = i;
  476.  
  477.     /*
  478.      * Go find the master side of a PTY to use.  Masters are
  479.      * found by trying to open them.  Each PTY master is an
  480.      * exclusive access device.  If every pty is tried but no
  481.      * available ones are found, scream.
  482.      */
  483.  
  484.     for (pty = 0;pty < ptys;pty++) {
  485.         sprintf (mastername, "/dev/pty%c%c",
  486.             letters[pty >> 4], digits[pty & 0xf]);
  487.         if ((masters[i] = open (mastername, O_RDWR)) != -1)
  488.             break;
  489.     }
  490.     if (masters[i] == -1) {
  491.         printf ("out of ptys\n");
  492.         return 0;
  493.     }
  494.  
  495.     /*
  496.      * Determine what the name of the slave will be.  Save it
  497.      * for later use.
  498.      */
  499.  
  500.     sprintf (slavename, "/dev/tty%c%c",
  501.             letters[pty >> 4], digits[pty & 0xf]);
  502.  
  503.     if (ports[i])
  504.         free (ports[i]);
  505.  
  506.     ports[i] = strdup (slavename);
  507.  
  508.     /*
  509.      * Let's make a child process.
  510.      */
  511.  
  512.     switch (childpids[i] = fork ()) {
  513.         case -1:
  514.             printf ("out of processes\n");
  515.             return 0;
  516.         case 0:
  517.  
  518.             /*
  519.              * Disassociate from the parent process group
  520.              * and tty's.
  521.              */
  522.  
  523.             close (0);
  524.             close (1);
  525.             for (i = 0;i < nsessions;i++)
  526.                 close (masters[i]);
  527.  
  528.             setpgrp ();
  529.  
  530.             /*
  531.              * Reset any signals that have been upset.
  532.              */
  533.  
  534.             signal (SIGINT, SIG_DFL);
  535.             signal (SIGQUIT, SIG_DFL);
  536.             signal (SIGCLD, SIG_DFL);
  537.             signal (SIGHUP, SIG_DFL);
  538.             signal (SIGUSR1, SIG_DFL);
  539.  
  540.             /*
  541.              * Open the slave PTY.  It will be opened as stdin.
  542.              */
  543.  
  544.             if (open (slavename, O_RDWR) == -1) {
  545.                 fprintf (stderr, "can't open %s\n", slavename);
  546.                 _exit (-1);
  547.             }
  548.  
  549.             /*
  550.              * Try to change the owner of the master and slave
  551.              * side of the PTY.  This will only work if the
  552.              * invoker has an effective UID of 0.  Change the
  553.              * mode of the slave to be the same as the parent
  554.              * tty.
  555.              */
  556.  
  557.             (void) chown (mastername, getuid (), getgid ());
  558.             (void) chown (slavename, getuid (), getgid ());
  559.             if (fstat (2, &sb) == 0)
  560.                 (void) chmod (slavename, sb.st_mode & 0777);
  561.  
  562.             /*
  563.              * Close the last open file descriptor and make
  564.              * the new stdout and stderr descriptors.  Copy
  565.              * the tty modes from the parent tty to the slave
  566.              * pty.
  567.              */
  568.  
  569.             close (2);
  570.             dup (0);
  571.             dup (0);
  572. #ifdef    SYSV_IOCTL
  573.             ioctl (0, TCSETAF, &sanetty);
  574. #endif
  575.  
  576.             /*
  577.              * See if the invoker has a shell in their
  578.              * environment and use the default value if
  579.              * not.
  580.              */
  581.  
  582.             if (! (shell = getenv ("SHELL")))
  583.                 shell = "/bin/sh";
  584.  
  585.             /*
  586.              * Set the PS1 variable to "prompt " if
  587.              * "prompt" looks reasonable.
  588.              */
  589.  
  590.             if (! isalpha (*prompt))
  591.                 prompt = strrchr (slavename, '/') + 1;
  592.  
  593.             sprintf (newprompt, "PS1=%.16s%c ",
  594.                     prompt, getuid () ? '$':'#');
  595.                 
  596.             for (i = 0;environ[i];i++)
  597.                 if (! strncmp (environ[i], "PS1=", 4))
  598.                     break;
  599.  
  600.             if (environ[i])
  601.                 environ[i] = newprompt;
  602.  
  603.             /*
  604.              * See about changing current directory
  605.              */
  606.  
  607.             if (init && (pwd = getpwuid (getuid ())))
  608.                 chdir (pwd->pw_dir);
  609.  
  610.             endpwent ();
  611.  
  612.             /*
  613.              * Update the utmp file if this is a "login"
  614.              * session.
  615.              */
  616.  
  617.             if (init)
  618.                 fix_utmp (slavename, getpid ());
  619.  
  620.             /*
  621.              * Undo any set-UID or set-GID bits on the
  622.              * executable.
  623.              */
  624.  
  625.             setgid (getgid ());
  626.             setuid (getuid ());
  627.  
  628.             /*
  629.              * Start off the new session.
  630.              */
  631.  
  632.             if (arg = strrchr (shell, '/'))
  633.                 arg++;
  634.             else
  635.                 arg = shell;
  636.  
  637.             if (init)
  638.                 sprintf (newshell, "-%s", arg), arg = newshell;
  639.  
  640.             execl (shell, arg, 0);
  641.             _exit (-1);
  642.     }
  643.     return 1;
  644. }
  645.  
  646. /*
  647.  * quit - kill all active sessions
  648.  */
  649.  
  650. quit (sig)
  651. int    sig;
  652. {
  653.     int    i;
  654.  
  655.     for (i = 0;i < nsessions;i++) {
  656.         if (masters[i] != -1) {
  657.             close (masters[i]);
  658.             masters[i] = -1;
  659.  
  660.             if (childpids[i] != -1)
  661.                 kill (- childpids[i], SIGHUP);
  662.         }
  663.     }
  664.     exit (sig);
  665. }
  666.  
  667. /*
  668.  * label - manipulate session labels
  669.  *
  670.  *    label() will either create a new label (if 'sess' != -1) or
  671.  *    return the session number which matches 'name'.
  672.  */
  673.  
  674. int
  675. label (sess, name)
  676. int    sess;
  677. char    *name;
  678. {
  679.     char    buf[16];
  680.     int    i;
  681.     char    *cp;
  682.  
  683.     /*
  684.      * Make "name" into something with no leading
  685.      * whitespace that consists of characters in
  686.      * [a-zA-Z0-9].
  687.      */
  688.  
  689.     for (cp = name;isspace (*cp);cp++)
  690.         ;
  691.  
  692.     for (i = 0;cp[i] && i < (sizeof buf - 1);i++) {
  693.         if (isalnum (cp[i]))
  694.             buf[i] = cp[i];
  695.         else
  696.             return -1;
  697.     }
  698.     buf[i] = '\0';
  699.     cp = buf;
  700.  
  701.     /*
  702.      * Look-up the session named "name"
  703.      */
  704.  
  705.     if (sess == -1) {
  706.  
  707.         /*
  708.          * See if 'name' is a label
  709.          */
  710.  
  711.         for (i = 0;i < nsessions;i++)
  712.             if (strcmp (labels[i], cp) == 0)
  713.                 return i;
  714.  
  715.         /*
  716.          * Perhaps 'name' is a number?!?
  717.          */
  718.  
  719.         i = strtol (name, &cp, 10);
  720.         if (*cp)
  721.             return -1;
  722.         else
  723.             return i;
  724.     }
  725.     
  726.     /*
  727.      * Add a new name, can't be a number.
  728.      */
  729.  
  730.     if (*cp == '\0' || isdigit (*cp)) {
  731.         labels[sess] = 0;
  732.         return -1;
  733.     }
  734.  
  735.     /*
  736.      * Add a new session named "name"
  737.      */
  738.  
  739.     if (labels[sess])
  740.         free (labels[sess]);
  741.  
  742.     if (! (labels[sess] = malloc (strlen (cp) + 1)))
  743.         return -1;
  744.  
  745.     strcpy (labels[sess], cp);
  746.     return sess;
  747. }
  748.  
  749. /*
  750.  * sm - manage pty sessions
  751.  */
  752.  
  753. main (argc, argv)
  754. int    argc;
  755. char    **argv;
  756. {
  757.     char    buf[BUFSIZ];
  758.     char    *cp;
  759.     int    i;
  760.     int    pid;
  761.     FILE    *fp;
  762.     struct    passwd    *pwd;
  763.  
  764.     /*
  765.      * No arguments are allowed on the command line
  766.      */
  767.  
  768.     if (argc > 1)
  769.         usage ();
  770.  
  771.     /*
  772.      * Set up all the file descriptors and process IDs
  773.      */
  774.  
  775.     for (i = 0;i < MAXSESSIONS;i++) {
  776.         childpids[i] = -1;
  777.         masters[i] = -1;
  778.     }
  779.  
  780.     /*
  781.      * Get my login name
  782.      */
  783.  
  784.     if (! (whoami = getlogin ())) {
  785.         setpwent ();
  786.         if (pwd = getpwuid (getuid ())) {
  787.             whoami = strdup (pwd->pw_name);
  788.         } else {
  789.             printf ("who are you?\n");
  790.             exit (1);
  791.         }
  792.         endpwent ();
  793.     }
  794.  
  795.     /*
  796.      * Get the current tty settings, and make a copy that can
  797.      * be tinkered with.  The sane values are used while getting
  798.      * commands.  The raw values are used while sessions are
  799.      * active.  New sessions are set to have the same tty values
  800.      * as the sane values.
  801.      */
  802.  
  803. #ifdef    SYSV_IOCTL
  804.     ioctl (0, TCGETA, &sanetty);
  805.     rawtty = sanetty;
  806.  
  807.     rawtty.c_oflag &= ~OPOST;
  808.     rawtty.c_iflag &= ~(ICRNL|INLCR);
  809.     rawtty.c_lflag = 0;
  810.     rawtty.c_cc[VMIN] = 1;
  811.     rawtty.c_cc[VTIME] = 1;
  812. #endif
  813.  
  814.     /*
  815.      * SIGCLG is caught to detect when a session has died or when
  816.      * the writer for the session has exited.  SIGUSR1 is used to
  817.      * signal that a ^Z has been seen.
  818.      */
  819.  
  820.     signal (SIGCLD, murder);
  821.     signal (SIGUSR1, catch);
  822.  
  823.     /*
  824.      * The file $HOME/.smrc is read for initializing commands.
  825.      */
  826.  
  827.     if (cp = getenv ("HOME")) {
  828.         sprintf (buf, "%s/.smrc", cp);
  829.         if (access (buf, 04) != 0 || ! (fp = fopen (buf, "r")))
  830.             fp = stdin;
  831.     }
  832.  
  833.     /*
  834.      * This is the main loop.  A line is read and executed.  If
  835.      * EOF is read, the loop is exited (except if input is the
  836.      * .smrc file).
  837.      */
  838.  
  839.     while (1) {
  840.  
  841.         /*
  842.          * Keyboard signals cause an exit.
  843.          */
  844.  
  845.         signal (SIGINT, quit);
  846.         signal (SIGQUIT, quit);
  847.  
  848.         /*
  849.          * Prompt for input only when it is not coming from
  850.          * the .smrc file.  A single line will be read and
  851.          * executed.  The read is retried if an interrupt
  852.          * has been seen.
  853.          */
  854.  
  855.         if (fp == stdin) {
  856.             printf ("sm-> ");
  857.             fflush (stdout);
  858.         }
  859.         while (errno = 0, fgets (buf, sizeof buf, fp) == 0) {
  860.             if (errno == EINTR) {
  861.                 continue;
  862.             } else if (fp != stdin) {
  863.                 fclose (fp);
  864.                 fp = stdin;
  865.                 buf[0] = '\0';
  866.                 break;
  867.             } else {
  868.                 strcpy (buf, "quit");
  869.                 break;
  870.             }
  871.         }
  872.         if (cp = strchr (buf, '\n'))
  873.             *cp = '\0';
  874.  
  875.         if (! buf[0])
  876.             continue;
  877.  
  878.         /*
  879.          * Parse the command.  Each command consists of a
  880.          * verb, with some commands accepting a session ID
  881.          * to act on.  The command will be accepted if a
  882.          * unique prefix of the command is entered.
  883.          */
  884.  
  885.         if (parse (buf, "q*uit") || parse (buf, "e*xit")) {
  886.  
  887.             /*
  888.              * Just give up.
  889.              */
  890.  
  891.             quit (0);
  892.         } else if (cp = parse (buf, "cr*eate")) {
  893.             int    init = 0;
  894.  
  895.             /*
  896.              * Create a new session and make it current
  897.              */
  898.  
  899.             while (*cp == ' ' || *cp == '\t')
  900.                 cp++;
  901.  
  902.             if (*cp == '-')
  903.                 init++, cp++;
  904.  
  905.             last = current;
  906.  
  907.             if (session (cp, init))
  908.                 label (current, cp);
  909.  
  910.             continue;
  911.         } else if (parse (buf, "cu*rrent")) {
  912.  
  913.             /*
  914.              * Give the session ID of the current session.
  915.              */
  916.  
  917.             if (current != -1) {
  918.                 if (labels[current])
  919.                     printf ("current session is \"%s\"\n",
  920.                         labels[current]);
  921.                 else
  922.                     printf ("current session is %d\n",
  923.                         current);
  924.             } else
  925.                 printf ("no current session\n");
  926.  
  927.             continue;
  928.         } else if (cp = parse (buf, "s*et")) {
  929.  
  930.             /*
  931.              * Set the current session ID to #
  932.              */
  933.  
  934.             if (*cp) {
  935.                 if ((i = label (-1, cp)) != -1) {
  936.                     last = current;
  937.                     current = i;
  938.                     continue;
  939.                 }
  940.                 /* FALLTHROUGH */
  941.             }
  942.             printf ("eh?\n");
  943.             continue;
  944.         } else if (parse (buf, "a*ctive")) {
  945.  
  946.             /*
  947.              * List the session IDs of all active sessions
  948.              */
  949.  
  950.             for (i = 0;i < nsessions;i++) {
  951.                 if (masters[i] != -1) {
  952.                     if (labels[i])
  953.                         printf ("%s ", labels[i]);
  954.                     else
  955.                         printf ("%d ", i);
  956.  
  957.                 }
  958.             }
  959.             putchar ('\n');
  960.             continue;
  961.         } else if (cp = parse (buf, "j*obs")) {
  962.             int    pids = 0;
  963.  
  964.             /*
  965.              * Look for a "-" flag
  966.              */
  967.  
  968.             while (isspace (*cp))
  969.                 cp++;
  970.  
  971.             if (*cp != '-') {
  972.                 for (pids = i = 0;i < nsessions;i++) {
  973.                     if (childpids[i] == -1)
  974.                         continue;
  975.                     else
  976.                         pids++;
  977.  
  978.                     if (labels[i])
  979.                         printf ("%s (%d)\n", labels[i],
  980.                             childpids[i]);
  981.                     else
  982.                         printf ("#%d (%d)\n", i,
  983.                             childpids[i]);
  984.                 }
  985.                 if (! pids)
  986.                     printf ("no jobs\n");
  987.  
  988.                 continue;
  989.             }
  990.  
  991.             /*
  992.              * Give a "ps" listing of all active sessions
  993.              */
  994.  
  995.             buf[0] = '\0';
  996.             for (i = 0;i < nsessions;i++) {
  997.                 if (childpids[i] != -1) {
  998.                     if (pids++)
  999.                         strcat (buf, ",");
  1000.  
  1001.                     sprintf (buf + strlen (buf), "%d",
  1002.                         childpids[i]);
  1003.                 }
  1004.             }
  1005.             if (pids) {
  1006.                 if (! (pspid = fork ())) {
  1007.                     setgid (getgid ());
  1008.                     setuid (getuid ());
  1009.                     execl ("/bin/ps", "ps", "-fp", buf, 0);
  1010.                     _exit (1);
  1011.                 }
  1012.                 while (pspid != -1)
  1013.                     pause ();
  1014.             } else {
  1015.                 printf ("no jobs\n");
  1016.             }
  1017.             continue;
  1018.         } else if (cp = parse (buf, "co*nnect")) {
  1019.  
  1020.             /*
  1021.              * Connect to the current or named session.
  1022.              */
  1023.  
  1024.             if (*cp) {
  1025.                 if ((i = label (-1, cp)) != -1) {
  1026.                     last = current;
  1027.                     current = i;
  1028.                     /* FALLTHROUGH */
  1029.                 } else {
  1030.                     printf ("eh?\n");
  1031.                     continue;
  1032.                 }
  1033.             }
  1034.         } else if (parse (buf, "t*oggle")) {
  1035.  
  1036.             /*
  1037.              * Toggle between the previous and current session
  1038.              */
  1039.  
  1040.             i = current;
  1041.             current = last;
  1042.             last = i;
  1043.             /* FALLTHROUGH */
  1044.         } else if (cp = parse (buf, "d*elete")) {
  1045.  
  1046.             /*
  1047.              * Delete the named session
  1048.              */
  1049.  
  1050.             if (*cp) {
  1051.                 i = label (-1, cp);
  1052.                 if (i >= 0 && i < MAXSESSIONS) {
  1053.                     if (masters[i] != -1) {
  1054.                         close (masters[i]);
  1055.                         masters[i] = -1;
  1056.                         if (childpids[i] != -1)
  1057.                             kill (- childpids[i], SIGHUP);
  1058.  
  1059.                         if (i == last)
  1060.                             last == -1;
  1061.  
  1062.                         if (i == current)
  1063.                             current == -1;
  1064.  
  1065.                         continue;
  1066.                     }
  1067.                 }
  1068.                 /* FALLTHROUGH */
  1069.             }
  1070.             printf ("eh?\n");
  1071.             continue;
  1072.         } else if ((i = label (-1, buf)) != -1) {
  1073.             if (i >= 0 && i < MAXSESSIONS && masters[i]) {
  1074.                 last = current;
  1075.                 current = i;
  1076.             } else {
  1077.                 printf ("eh?\n");
  1078.                 continue;
  1079.             }
  1080.         } else {
  1081.  
  1082.             /*
  1083.              * The command was not recognized
  1084.              */
  1085.  
  1086.             help ();
  1087.             continue;
  1088.         }
  1089.  
  1090.         /*
  1091.          * Validate the session number.  It must be in the
  1092.          * range 0 .. (MAXSESSIONS-1) to be valid.  The current
  1093.          * session must also be associated with an open PTY.
  1094.          */
  1095.  
  1096.         if (current < 0 || current >= MAXSESSIONS)
  1097.             current = -1;
  1098.  
  1099.         if (current == -1 || masters[current] == -1) {
  1100.             printf ("no current session\n");
  1101.             current = -1;
  1102.             continue;
  1103.         }
  1104.  
  1105.         /*
  1106.          * Let's make a process to read from TTY and write to the
  1107.          * PTY.
  1108.          */
  1109.  
  1110.         switch (writepid = fork ()) {
  1111.             case -1:
  1112.                 if (childpids[current] != -1)
  1113.                     kill (childpids[current], SIGKILL);
  1114.  
  1115.                 perror ("fork");
  1116.                 break;
  1117.             case 0:
  1118.                 writer (masters[current]);
  1119.                 exit (1);
  1120.         }
  1121.  
  1122.         /*
  1123.          * Set up the raw TTY modes and start writing from the PTY to
  1124.          * the TTY.
  1125.          */
  1126.  
  1127. #ifdef    SYSV_IOCTL
  1128.         ioctl (0, TCSETAF, &rawtty);
  1129. #endif
  1130.  
  1131.         if (reader (masters[current]) == -1) {
  1132.             close (masters[current]);
  1133.             masters[current] = -1;
  1134.             childpids[current] = -1;
  1135.             current = -1;
  1136.             if (writepid > 0)
  1137.                 kill (writepid, SIGTERM);
  1138.         }
  1139.  
  1140.         /*
  1141.          * Reset the tty modes to resume the command loop.
  1142.          */
  1143.  
  1144. #ifdef    SYSV_IOCTL
  1145.         ioctl (0, TCSETA, &sanetty);
  1146. #endif
  1147.     }
  1148.     exit (0);
  1149. }
  1150.