home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1992 March / Source_Code_CD-ROM_Walnut_Creek_March_1992.iso / usenet / altsrcs / 2 / 2421 / sm.c
Encoding:
C/C++ Source or Header  |  1990-12-28  |  14.4 KB  |  784 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 (pat@rwing.UUCP)
  10.  * and Dan Bernstein.
  11.  */
  12.  
  13. #include <sys/types.h>
  14. #include <sys/termio.h>
  15. #include <sys/stat.h>
  16. #include <string.h>
  17. #include <stdio.h>
  18. #include <signal.h>
  19. #include <time.h>
  20. #include <fcntl.h>
  21. #include <errno.h>
  22.  
  23. /*
  24.  * MAXSESSIONS is the number of sessions which a single user can
  25.  * manage with this program at a single time.  16 is plenty.  4 or
  26.  * 5 might be a better idea if pty's are a scarce resource.
  27.  */
  28.  
  29. #define    MAXSESSIONS    16
  30.  
  31. int    childpids[MAXSESSIONS];        /* Process ID of each session leader */
  32. int    writepid;            /* Process ID of PTY writing process */
  33. int    pspid;                /* Obfuscation is my life            */
  34. int    masters[MAXSESSIONS];        /* File descriptor for PTY master    */
  35. int    nsessions;            /* High-water mark for session count */
  36. int    current = -1;            /* Currently active session          */
  37. int    last = -1;            /* Previously active session         */
  38. int    caught = 0;            /* Some signal was caught            */
  39.  
  40. struct    termio    sanetty;        /* Initial TTY modes on entry        */
  41. struct    termio    rawtty;            /* Modes used when session is active */
  42.  
  43. void    exit ();
  44. void    _exit ();
  45. char    *getlogin ();
  46. char    *getenv ();
  47. struct    passwd    *getpwnam ();
  48. extern    char    **environ;
  49.  
  50. /*
  51.  * parse - see if "s" and "pat" smell alike
  52.  */
  53.  
  54. char *
  55. parse (s, pat)
  56. char    *s;
  57. char    *pat;
  58. {
  59.     int    match = 0;
  60.     int    star = 0;
  61.  
  62.     /*
  63.      * Match all of the characters which are identical.  The '*'
  64.      * character is used to denote the end of the unique suffix
  65.      * for a pattern.  Everything after that is optional, but
  66.      * must be matched exactly if given.
  67.      */
  68.  
  69.     while (*s && *pat) {
  70.         if (*s == *pat && *s) {
  71.             s++, pat++;
  72.             continue;
  73.         }
  74.         if (*pat == '*') {
  75.             star++;
  76.             pat++;
  77.             continue;
  78.         }
  79.         if ((*s == ' ' || *s == '\t') && star)
  80.             return s;
  81.         else
  82.             return 0;
  83.     }
  84.  
  85.     /*
  86.      * The pattern has been used up - see if whitespace
  87.      * follows, or if the input string is also finished.
  88.      */
  89.  
  90.     if (! *pat && (*s == '\0' || *s == ' ' || *s == '\t'))
  91.         return s;
  92.  
  93.     /*
  94.      * The input string has been used up.  The unique
  95.      * prefix must have been matched.
  96.      */
  97.  
  98.     if (! *s && (star || *pat == '*'))
  99.         return s;
  100.  
  101.     return 0;
  102. }
  103.  
  104. /*
  105.  * murder - reap a single child process
  106.  */
  107.  
  108. void
  109. murder (sig)
  110. int    sig;
  111. {
  112.     int    pid;
  113.     int    i;
  114.  
  115.     pid = wait ((int *) 0);
  116.  
  117.     /*
  118.      * See what children have died recently.
  119.      */
  120.  
  121.     for (i = 0;pid != -1 && i < nsessions;i++) {
  122.  
  123.         /*
  124.          * Close their master sides and mark the
  125.          * session as "available".
  126.          */
  127.  
  128.         if (pid == childpids[i]) {
  129.             childpids[i] = -1;
  130.             close (masters[i]);
  131.             masters[i] = -1;
  132.             break;
  133.         }
  134.     }
  135.     if (writepid != -1 && pid == writepid)
  136.         writepid = -1;
  137.  
  138.     if (pspid != -1 && pid == pspid)
  139.         pspid = -1;
  140.  
  141.     signal (sig, murder);
  142. }
  143.  
  144. /*
  145.  * catch - catch a signal and set a flag
  146.  */
  147.  
  148. void
  149. catch (sig)
  150. int    sig;
  151. {
  152.     caught = 1;
  153.     signal (sig, catch);
  154. }
  155.  
  156. /*
  157.  * reader - read characters from the pty and write to the screen
  158.  */
  159.  
  160. int
  161. reader (fd)
  162. int    fd;
  163. {
  164.     char    c;
  165.     int    cnt;
  166.  
  167.     /*
  168.      * Ignore the SIGINT and SIGQUIT signals.
  169.      */
  170.  
  171.     signal (SIGINT, SIG_IGN);
  172.     signal (SIGQUIT, SIG_IGN);
  173.  
  174.     while (1) {
  175.         if ((cnt = read (fd, &c, 1)) == -1) {
  176.             if (errno != EINTR)
  177.                 return -1;
  178.  
  179.             if (caught)
  180.                 return 0;
  181.             else
  182.                 continue;
  183.         }
  184.         if (cnt == 0)
  185.             return -1;
  186.  
  187.         write (1, &c, 1);
  188.     }
  189. }
  190.  
  191. /*
  192.  * writer - write characters read from the keyboard down the pty
  193.  */
  194.  
  195. writer (fd)
  196. int    fd;
  197. {
  198.     char    c;
  199.     int    cnt;
  200.     int    zflg = 0;
  201.  
  202.     signal (SIGINT, SIG_IGN);
  203.     signal (SIGQUIT, SIG_IGN);
  204.     signal (SIGHUP, _exit);
  205.  
  206.     /*
  207.      * Read characters until an error is returned or ^Z is seen
  208.      * followed by a non-^Z character.
  209.      */
  210.  
  211.     while (1) {
  212.         errno = 0;
  213.         if ((cnt = read (0, &c, 1)) == 0)
  214.             continue;
  215.  
  216.         /*
  217.          * Some signal may have occured, so retry
  218.          * the read.
  219.          */
  220.  
  221.         if (cnt == -1) {
  222.             if (errno == EINTR && caught)
  223.                 continue;
  224.             else
  225.                 exit (0);
  226.         }
  227.  
  228.         /*
  229.          * Process a ^Z.  If one was not seen earlier, 
  230.          * set a flag and go read another character.
  231.          */
  232.  
  233.         if (c == ('z' & 037)) {
  234.             if (! zflg++)
  235.                 continue;
  236.         }
  237.         
  238.         /*
  239.          * See if a ^Z was seen before.  If so, signal
  240.          * the master and exit.
  241.          */
  242.  
  243.         else if (zflg) {
  244.             kill (getppid (), SIGUSR1);
  245.             exit (0);
  246.         }
  247.  
  248.         /*
  249.          * Just output the character as is.
  250.          */
  251.  
  252.         zflg = 0;
  253.         if (write (fd, &c, 1) != 1)
  254.             break;
  255.     }
  256.     exit (0);
  257. }
  258.  
  259. /*
  260.  * usage - command line syntax
  261.  */
  262.  
  263. usage ()
  264. {
  265.     fprintf (stderr, "usage: sm\n");
  266.     exit (1);
  267. }
  268.  
  269. /*
  270.  * help - built-in command syntax
  271.  */
  272.  
  273. help ()
  274. {
  275.     fprintf (stderr, "Valid commands are:\n");
  276.     fprintf (stderr, "\tconnect [ # ]\t(connects to session)\n");
  277.     fprintf (stderr, "\tcreate\t\t(sets up a new pty session)\n");
  278.     fprintf (stderr, "\tcurrent\t\t(shows current session number)\n");
  279.     fprintf (stderr, "\tdelete #\t(deletes session)\n");
  280.     fprintf (stderr, "\thelp\t\tdisplay this message\n");
  281.     fprintf (stderr, "\tjobs\t\t(shows a ps listing of current session)\n");
  282.     fprintf (stderr, "\tquit\tor exit (terminate session manager)\n");
  283.     fprintf (stderr, "\tset #\t\t(# is 0-15 - selets current session)\n");
  284.     fprintf (stderr, "\ttoggle\t\t(switch to previous session)\n\n");
  285.     fprintf (stderr, "Commands may be abbreviated to a unique prefix\n\n");
  286.     fprintf (stderr, "Note - to exit a session back into sm so one can\n");
  287.     fprintf (stderr, " select another session, type a ^Z and a return\n");
  288.     fprintf (stderr, " To send ^Z to the shell, type two ^Z chars\n\n");
  289. }
  290.  
  291. /*
  292.  * session - create a new session on a pty
  293.  */
  294.  
  295. void
  296. session ()
  297. {
  298.     char    mastername[BUFSIZ];
  299.     char    slavename[BUFSIZ];
  300.     char    *digits = "0123456789abcdef";
  301.     char    *letters = "pqrs";
  302.     char    *shell;
  303.     char    *arg;
  304.     int    oumask;
  305.     int    i;
  306.     int    pty;
  307.     int    ptys = 64;
  308.     struct    stat    sb;
  309.  
  310.     /*
  311.      * Find the number of the new session.  An error will be
  312.      * given if no sessions are available.
  313.      */
  314.  
  315.     for (i = 0;i < nsessions && masters[i] != -1;i++)
  316.         ;
  317.  
  318.     if (i == MAXSESSIONS) {
  319.         printf ("out of sessions\n");
  320.         return;
  321.     }
  322.     if (i == nsessions)
  323.         nsessions++;
  324.  
  325.     /*
  326.      * Save the previous sesssion number.  This is so the
  327.      * "toggle" command will work after a "create".
  328.      */
  329.  
  330.     if (current != -1)
  331.         last = current;
  332.  
  333.     current = i;
  334.  
  335.     /*
  336.      * Go find the master side of a PTY to use.  Masters are
  337.      * found by trying to open them.  Each PTY master is an
  338.      * exclusive access device.  If every pty is tried but no
  339.      * available ones are found, scream.
  340.      */
  341.  
  342.     for (pty = 0;pty < ptys;pty++) {
  343.         sprintf (mastername, "/dev/pty%c%c",
  344.             letters[pty >> 4], digits[pty & 0xf]);
  345.         if ((masters[i] = open (mastername, O_RDWR)) != -1)
  346.             break;
  347.     }
  348.     if (masters[i] == -1) {
  349.         printf ("out of ptys\n");
  350.         return;
  351.     }
  352.  
  353.     /*
  354.      * Let's make a child process.
  355.      */
  356.  
  357.     switch (childpids[i] = fork ()) {
  358.         case -1:
  359.             printf ("out of processes\n");
  360.             exit (1);
  361.         case 0:
  362.  
  363.             /*
  364.              * Disassociate from the parent process group
  365.              * and tty's.
  366.              */
  367.  
  368.             close (0);
  369.             close (1);
  370.             for (i = 0;i < nsessions;i++)
  371.                 close (masters[i]);
  372.  
  373.             setpgrp ();
  374.  
  375.             /*
  376.              * Reset any signals that have been upset.
  377.              */
  378.  
  379.             signal (SIGINT, SIG_DFL);
  380.             signal (SIGQUIT, SIG_DFL);
  381.             signal (SIGCLD, SIG_DFL);
  382.             signal (SIGHUP, SIG_DFL);
  383.             signal (SIGUSR1, SIG_DFL);
  384.  
  385.             /*
  386.              * Make up the name of the slave side of the
  387.              * PTY and open it.  It will be opened as stdin.
  388.              */
  389.  
  390.             sprintf (slavename, "/dev/tty%c%c",
  391.                 letters[pty >> 4], digits[pty & 0xf]);
  392.  
  393.             if (open (slavename, O_RDWR) == -1) {
  394.                 fprintf (stderr, "can't open %s\n", slavename);
  395.                 _exit (-1);
  396.             }
  397.  
  398.             /*
  399.              * Try to change the owner of the master and slave
  400.              * side of the PTY.  This will only work if the
  401.              * invoker has an effective UID of 0.  Change the
  402.              * mode of the slave to be the same as the parent
  403.              * tty.
  404.              */
  405.  
  406.             (void) chown (mastername, getuid (), getgid ());
  407.             (void) chown (slavename, getuid (), getgid ());
  408.             if (fstat (2, &sb) == 0)
  409.                 (void) chmod (slavename, sb.st_mode & 0777);
  410.  
  411.             /*
  412.              * Close the last open file descriptor and make
  413.              * the new stdout and stderr descriptors.  Copy
  414.              * the tty modes from the parent tty to the slave
  415.              * pty.
  416.              */
  417.  
  418.             close (2);
  419.             dup (0);
  420.             dup (0);
  421.             ioctl (0, TCSETAF, &sanetty);
  422.  
  423.             /*
  424.              * See if the invoker has a shell in their
  425.              * environment and use the default value if
  426.              * not.
  427.              */
  428.  
  429.             if (! (shell = getenv ("SHELL")))
  430.                 shell = "/bin/sh";
  431.  
  432.             /*
  433.              * Undo any set-UID or set-GID bits on the
  434.              * executable.
  435.              */
  436.  
  437.             setgid (getgid ());
  438.             setuid (getuid ());
  439.  
  440.             /*
  441.              * Start off the new session.
  442.              */
  443.  
  444.             if (arg = strrchr (shell, '/'))
  445.                 arg++;
  446.             else
  447.                 arg = shell;
  448.  
  449.             execl (shell, arg, 0);
  450.             _exit (-1);
  451.     }
  452. }
  453.  
  454. /*
  455.  * quit - kill all active sessions
  456.  */
  457.  
  458. quit (sig)
  459. int    sig;
  460. {
  461.     int    i;
  462.  
  463.     for (i = 0;i < nsessions;i++) {
  464.         if (masters[i] != -1) {
  465.             close (masters[i]);
  466.             masters[i] = -1;
  467.             kill (- childpids[i], SIGHUP);
  468.         }
  469.     }
  470.     exit (sig);
  471. }
  472.  
  473. /*
  474.  * sm - manage pty sessions
  475.  */
  476.  
  477. main (argc, argv)
  478. int    argc;
  479. char    **argv;
  480. {
  481.     char    buf[BUFSIZ];
  482.     char    *cp;
  483.     int    i;
  484.     int    pid;
  485.     FILE    *fp;
  486.  
  487.     /*
  488.      * No arguments are allowed on the command line
  489.      */
  490.  
  491.     if (argc > 1)
  492.         usage ();
  493.  
  494.     /*
  495.      * Set up all the file descriptors and process IDs
  496.      */
  497.  
  498.     for (i = 0;i < MAXSESSIONS;i++) {
  499.         childpids[i] = -1;
  500.         masters[i] = -1;
  501.     }
  502.  
  503.     /*
  504.      * Get the current tty settings, and make a copy that can
  505.      * be tinkered with.  The sane values are used while getting
  506.      * commands.  The raw values are used while sessions are
  507.      * active.  New sessions are set to have the same tty values
  508.      * as the sane values.
  509.      */
  510.  
  511.     ioctl (0, TCGETA, &sanetty);
  512.     rawtty = sanetty;
  513.  
  514.     rawtty.c_oflag &= ~OPOST;
  515.     rawtty.c_lflag = 0;
  516.     rawtty.c_cc[VMIN] = 1;
  517.     rawtty.c_cc[VTIME] = 1;
  518.  
  519.     /*
  520.      * SIGCLG is caught to detect when a session has died or when
  521.      * the writer for the session has exited.  SIGUSR1 is used to
  522.      * signal that a ^Z has been seen.
  523.      */
  524.  
  525.     signal (SIGCLD, murder);
  526.     signal (SIGUSR1, catch);
  527.  
  528.     /*
  529.      * The file $HOME/.smrc is read for initializing commands.
  530.      */
  531.  
  532.     if (cp = getenv ("HOME")) {
  533.         sprintf (buf, "%s/.smrc", cp);
  534.         if (access (buf, 04) != 0 || ! (fp = fopen (buf, "r")))
  535.             fp = stdin;
  536.     }
  537.  
  538.     /*
  539.      * This is the main loop.  A line is read and executed.  If
  540.      * EOF is read, the loop is exited (except if input is the
  541.      * .smrc file).
  542.      */
  543.  
  544.     while (1) {
  545.  
  546.         /*
  547.          * Keyboard signals cause an exit.
  548.          */
  549.  
  550.         signal (SIGINT, quit);
  551.         signal (SIGQUIT, quit);
  552.  
  553.         /*
  554.          * Prompt for input only when it is not coming from
  555.          * the .smrc file.  A single line will be read and
  556.          * executed.  The read is retried if an interrupt
  557.          * has been seen.
  558.          */
  559.  
  560.         if (fp == stdin) {
  561.             printf ("pty-> ");
  562.             fflush (stdout);
  563.         }
  564.         while (errno = 0, fgets (buf, sizeof buf, fp) == 0) {
  565.             if (errno == EINTR) {
  566.                 continue;
  567.             } else if (fp != stdin) {
  568.                 fclose (fp);
  569.                 fp = stdin;
  570.                 buf[0] = '\0';
  571.                 break;
  572.             } else {
  573.                 strcpy (buf, "quit");
  574.                 break;
  575.             }
  576.         }
  577.         if (cp = strchr (buf, '\n'))
  578.             *cp = '\0';
  579.  
  580.         if (! buf[0])
  581.             continue;
  582.  
  583.         /*
  584.          * Parse the command.  Each command consists of a
  585.          * verb, with some commands accepting a session ID
  586.          * to act on.  The command will be accepted if a
  587.          * unique prefix of the command is entered.
  588.          */
  589.  
  590.         if (parse (buf, "q*uit") || parse (buf, "e*xit")) {
  591.  
  592.             /*
  593.              * Just give up.
  594.              */
  595.  
  596.             quit (0);
  597.         } else if (parse (buf, "cr*eate")) {
  598.  
  599.             /*
  600.              * Create a new session and make it current
  601.              */
  602.  
  603.             session ();
  604.             continue;
  605.         } else if (parse (buf, "cu*rrent")) {
  606.  
  607.             /*
  608.              * Give the session ID of the current session.
  609.              */
  610.  
  611.             if (current != -1)
  612.                 printf ("current session is %d\n", current);
  613.             else
  614.                 printf ("no current session\n");
  615.  
  616.             continue;
  617.         } else if (cp = parse (buf, "s*et")) {
  618.  
  619.             /*
  620.              * Set the current session ID to #
  621.              */
  622.  
  623.             if (*cp) {
  624.                 i = strtol (cp, &cp, 10);
  625.                 if (*cp == '\0') {
  626.                     last = current;
  627.                     current = i;
  628.                     continue;
  629.                 }
  630.             }
  631.             printf ("eh?\n");
  632.             continue;
  633.         } else if (parse (buf, "a*ctive")) {
  634.  
  635.             /*
  636.              * List the session IDs of all active sessions
  637.              */
  638.  
  639.             for (i = 0;i < nsessions;i++)
  640.                 if (masters[i] != -1)
  641.                     printf ("%d ", i);
  642.  
  643.             putchar ('\n');
  644.             continue;
  645.         } else if (parse (buf, "j*obs")) {
  646.             int    pids = 0;
  647.  
  648.             /*
  649.              * Give a "ps" listing of all active sessions
  650.              */
  651.  
  652.             buf[0] = '\0';
  653.             for (i = 0;i < nsessions;i++) {
  654.                 if (childpids[i] != -1) {
  655.                     if (pids++)
  656.                         strcat (buf, ",");
  657.  
  658.                     sprintf (buf + strlen (buf), "%d",
  659.                         childpids[i]);
  660.                 }
  661.             }
  662.             if (pids) {
  663.                 if (! (pspid = fork ())) {
  664.                     setgid (getgid ());
  665.                     setuid (getuid ());
  666.                     execl ("/bin/ps", "ps", "-fp", buf, 0);
  667.                     _exit (1);
  668.                 }
  669.                 while (pspid != -1)
  670.                     pause ();
  671.             } else {
  672.                 printf ("no jobs\n");
  673.             }
  674.             continue;
  675.         } else if (cp = parse (buf, "co*nnect")) {
  676.  
  677.             /*
  678.              * Connect to the current or named session.
  679.              */
  680.  
  681.             if (*cp) {
  682.                 i = strtol (cp, &cp, 10);
  683.                 if (*cp == '\0') {
  684.                     last = current;
  685.                     current = i;
  686.                     /* FALLTHROUGH */
  687.                 } else {
  688.                     printf ("eh?\n");
  689.                     continue;
  690.                 }
  691.             }
  692.         } else if (parse (buf, "t*oggle")) {
  693.  
  694.             /*
  695.              * Toggle between the previous and current session
  696.              */
  697.  
  698.             i = current;
  699.             current = last;
  700.             last = i;
  701.             /* FALLTHROUGH */
  702.         } else if (cp = parse (buf, "d*elete")) {
  703.  
  704.             /*
  705.              * Delete the named session
  706.              */
  707.  
  708.             if (*cp) {
  709.                 i = strtol (cp, &cp, 10);
  710.                 if (*cp == '\0' && i >= 0 && i < MAXSESSIONS) {
  711.                     if (masters[i] != -1) {
  712.                         close (masters[i]);
  713.                         masters[i] = -1;
  714.                         kill (- childpids[i], SIGHUP);
  715.                         continue;
  716.                     }
  717.                 }
  718.             }
  719.             printf ("eh?\n");
  720.             continue;
  721.         } else {
  722.  
  723.             /*
  724.              * The command was not recognized
  725.              */
  726.  
  727.             help ();
  728.             continue;
  729.         }
  730.  
  731.         /*
  732.          * Validate the session number.  It must be in the
  733.          * range 0 .. (MAXSESSIONS-1) to be valid.  The current
  734.          * session must also be associated with an open PTY.
  735.          */
  736.  
  737.         if (current < 0 || current >= MAXSESSIONS)
  738.             current = -1;
  739.  
  740.         if (current == -1 || masters[current] == -1) {
  741.             printf ("no current session\n");
  742.             current = -1;
  743.             continue;
  744.         }
  745.  
  746.         /*
  747.          * Let's make a process to read from the child ...
  748.          */
  749.  
  750.         switch (writepid = fork ()) {
  751.             case -1:
  752.                 kill (childpids[current], SIGKILL);
  753.                 perror ("fork");
  754.                 break;
  755.             case 0:
  756.                 writer (masters[current]);
  757.                 exit (1);
  758.         }
  759.  
  760.         /*
  761.          * Set up the raw TTY modes and start writing to
  762.          * the child.
  763.          */
  764.  
  765.         ioctl (0, TCSETAF, &rawtty);
  766.  
  767.         if (reader (masters[current]) == -1) {
  768.             close (masters[current]);
  769.             masters[current] = -1;
  770.             childpids[current] = -1;
  771.             current = -1;
  772.             if (writepid > 0)
  773.                 kill (writepid, SIGTERM);
  774.         }
  775.  
  776.         /*
  777.          * Reset the tty modes to resume the command loop.
  778.          */
  779.  
  780.         ioctl (0, TCSETA, &sanetty);
  781.     }
  782.     exit (0);
  783. }
  784.