home *** CD-ROM | disk | FTP | other *** search
- /*
- * This code is in the public domain. THIS CODE IS PROVIDED ON AN AS-IS
- * BASIS. THE USER ACCEPTS ALL RISKS ASSOCIATED WITH USING THIS CODE AND
- * IS SOLELY RESPONSIBLE FOR CORRECTING ANY SOFTWARE ERRORS OR DAMAGE
- * CAUSED BY SOFTWARE ERRORS OR MISUSE.
- *
- * Written By: John F Haugh II, 12/21/90
- *
- * Modified to include suggestions made by Pat Myrto (pat@rwing.UUCP)
- * and Dan Bernstein.
- */
-
- #include <sys/types.h>
- #include <sys/termio.h>
- #include <sys/stat.h>
- #include <string.h>
- #include <stdio.h>
- #include <signal.h>
- #include <time.h>
- #include <fcntl.h>
- #include <errno.h>
-
- /*
- * MAXSESSIONS is the number of sessions which a single user can
- * manage with this program at a single time. 16 is plenty. 4 or
- * 5 might be a better idea if pty's are a scarce resource.
- */
-
- #define MAXSESSIONS 16
-
- int childpids[MAXSESSIONS]; /* Process ID of each session leader */
- int writepid; /* Process ID of PTY writing process */
- int pspid; /* Obfuscation is my life */
- int masters[MAXSESSIONS]; /* File descriptor for PTY master */
- int nsessions; /* High-water mark for session count */
- int current = -1; /* Currently active session */
- int last = -1; /* Previously active session */
- int caught = 0; /* Some signal was caught */
-
- struct termio sanetty; /* Initial TTY modes on entry */
- struct termio rawtty; /* Modes used when session is active */
-
- void exit ();
- void _exit ();
- char *getlogin ();
- char *getenv ();
- struct passwd *getpwnam ();
- extern char **environ;
-
- /*
- * parse - see if "s" and "pat" smell alike
- */
-
- char *
- parse (s, pat)
- char *s;
- char *pat;
- {
- int match = 0;
- int star = 0;
-
- /*
- * Match all of the characters which are identical. The '*'
- * character is used to denote the end of the unique suffix
- * for a pattern. Everything after that is optional, but
- * must be matched exactly if given.
- */
-
- while (*s && *pat) {
- if (*s == *pat && *s) {
- s++, pat++;
- continue;
- }
- if (*pat == '*') {
- star++;
- pat++;
- continue;
- }
- if ((*s == ' ' || *s == '\t') && star)
- return s;
- else
- return 0;
- }
-
- /*
- * The pattern has been used up - see if whitespace
- * follows, or if the input string is also finished.
- */
-
- if (! *pat && (*s == '\0' || *s == ' ' || *s == '\t'))
- return s;
-
- /*
- * The input string has been used up. The unique
- * prefix must have been matched.
- */
-
- if (! *s && (star || *pat == '*'))
- return s;
-
- return 0;
- }
-
- /*
- * murder - reap a single child process
- */
-
- void
- murder (sig)
- int sig;
- {
- int pid;
- int i;
-
- pid = wait ((int *) 0);
-
- /*
- * See what children have died recently.
- */
-
- for (i = 0;pid != -1 && i < nsessions;i++) {
-
- /*
- * Close their master sides and mark the
- * session as "available".
- */
-
- if (pid == childpids[i]) {
- childpids[i] = -1;
- close (masters[i]);
- masters[i] = -1;
- break;
- }
- }
- if (writepid != -1 && pid == writepid)
- writepid = -1;
-
- if (pspid != -1 && pid == pspid)
- pspid = -1;
-
- signal (sig, murder);
- }
-
- /*
- * catch - catch a signal and set a flag
- */
-
- void
- catch (sig)
- int sig;
- {
- caught = 1;
- signal (sig, catch);
- }
-
- /*
- * reader - read characters from the pty and write to the screen
- */
-
- int
- reader (fd)
- int fd;
- {
- char c;
- int cnt;
-
- /*
- * Ignore the SIGINT and SIGQUIT signals.
- */
-
- signal (SIGINT, SIG_IGN);
- signal (SIGQUIT, SIG_IGN);
-
- while (1) {
- if ((cnt = read (fd, &c, 1)) == -1) {
- if (errno != EINTR)
- return -1;
-
- if (caught)
- return 0;
- else
- continue;
- }
- if (cnt == 0)
- return -1;
-
- write (1, &c, 1);
- }
- }
-
- /*
- * writer - write characters read from the keyboard down the pty
- */
-
- writer (fd)
- int fd;
- {
- char c;
- int cnt;
- int zflg = 0;
-
- signal (SIGINT, SIG_IGN);
- signal (SIGQUIT, SIG_IGN);
- signal (SIGHUP, _exit);
-
- /*
- * Read characters until an error is returned or ^Z is seen
- * followed by a non-^Z character.
- */
-
- while (1) {
- errno = 0;
- if ((cnt = read (0, &c, 1)) == 0)
- continue;
-
- /*
- * Some signal may have occured, so retry
- * the read.
- */
-
- if (cnt == -1) {
- if (errno == EINTR && caught)
- continue;
- else
- exit (0);
- }
-
- /*
- * Process a ^Z. If one was not seen earlier,
- * set a flag and go read another character.
- */
-
- if (c == ('z' & 037)) {
- if (! zflg++)
- continue;
- }
-
- /*
- * See if a ^Z was seen before. If so, signal
- * the master and exit.
- */
-
- else if (zflg) {
- kill (getppid (), SIGUSR1);
- exit (0);
- }
-
- /*
- * Just output the character as is.
- */
-
- zflg = 0;
- if (write (fd, &c, 1) != 1)
- break;
- }
- exit (0);
- }
-
- /*
- * usage - command line syntax
- */
-
- usage ()
- {
- fprintf (stderr, "usage: sm\n");
- exit (1);
- }
-
- /*
- * help - built-in command syntax
- */
-
- help ()
- {
- fprintf (stderr, "Valid commands are:\n");
- fprintf (stderr, "\tconnect [ # ]\t(connects to session)\n");
- fprintf (stderr, "\tcreate\t\t(sets up a new pty session)\n");
- fprintf (stderr, "\tcurrent\t\t(shows current session number)\n");
- fprintf (stderr, "\tdelete #\t(deletes session)\n");
- fprintf (stderr, "\thelp\t\tdisplay this message\n");
- fprintf (stderr, "\tjobs\t\t(shows a ps listing of current session)\n");
- fprintf (stderr, "\tquit\tor exit (terminate session manager)\n");
- fprintf (stderr, "\tset #\t\t(# is 0-15 - selets current session)\n");
- fprintf (stderr, "\ttoggle\t\t(switch to previous session)\n\n");
- fprintf (stderr, "Commands may be abbreviated to a unique prefix\n\n");
- fprintf (stderr, "Note - to exit a session back into sm so one can\n");
- fprintf (stderr, " select another session, type a ^Z and a return\n");
- fprintf (stderr, " To send ^Z to the shell, type two ^Z chars\n\n");
- }
-
- /*
- * session - create a new session on a pty
- */
-
- void
- session ()
- {
- char mastername[BUFSIZ];
- char slavename[BUFSIZ];
- char *digits = "0123456789abcdef";
- char *letters = "pqrs";
- char *shell;
- char *arg;
- int oumask;
- int i;
- int pty;
- int ptys = 64;
- struct stat sb;
-
- /*
- * Find the number of the new session. An error will be
- * given if no sessions are available.
- */
-
- for (i = 0;i < nsessions && masters[i] != -1;i++)
- ;
-
- if (i == MAXSESSIONS) {
- printf ("out of sessions\n");
- return;
- }
- if (i == nsessions)
- nsessions++;
-
- /*
- * Save the previous sesssion number. This is so the
- * "toggle" command will work after a "create".
- */
-
- if (current != -1)
- last = current;
-
- current = i;
-
- /*
- * Go find the master side of a PTY to use. Masters are
- * found by trying to open them. Each PTY master is an
- * exclusive access device. If every pty is tried but no
- * available ones are found, scream.
- */
-
- for (pty = 0;pty < ptys;pty++) {
- sprintf (mastername, "/dev/pty%c%c",
- letters[pty >> 4], digits[pty & 0xf]);
- if ((masters[i] = open (mastername, O_RDWR)) != -1)
- break;
- }
- if (masters[i] == -1) {
- printf ("out of ptys\n");
- return;
- }
-
- /*
- * Let's make a child process.
- */
-
- switch (childpids[i] = fork ()) {
- case -1:
- printf ("out of processes\n");
- exit (1);
- case 0:
-
- /*
- * Disassociate from the parent process group
- * and tty's.
- */
-
- close (0);
- close (1);
- for (i = 0;i < nsessions;i++)
- close (masters[i]);
-
- setpgrp ();
-
- /*
- * Reset any signals that have been upset.
- */
-
- signal (SIGINT, SIG_DFL);
- signal (SIGQUIT, SIG_DFL);
- signal (SIGCLD, SIG_DFL);
- signal (SIGHUP, SIG_DFL);
- signal (SIGUSR1, SIG_DFL);
-
- /*
- * Make up the name of the slave side of the
- * PTY and open it. It will be opened as stdin.
- */
-
- sprintf (slavename, "/dev/tty%c%c",
- letters[pty >> 4], digits[pty & 0xf]);
-
- if (open (slavename, O_RDWR) == -1) {
- fprintf (stderr, "can't open %s\n", slavename);
- _exit (-1);
- }
-
- /*
- * Try to change the owner of the master and slave
- * side of the PTY. This will only work if the
- * invoker has an effective UID of 0. Change the
- * mode of the slave to be the same as the parent
- * tty.
- */
-
- (void) chown (mastername, getuid (), getgid ());
- (void) chown (slavename, getuid (), getgid ());
- if (fstat (2, &sb) == 0)
- (void) chmod (slavename, sb.st_mode & 0777);
-
- /*
- * Close the last open file descriptor and make
- * the new stdout and stderr descriptors. Copy
- * the tty modes from the parent tty to the slave
- * pty.
- */
-
- close (2);
- dup (0);
- dup (0);
- ioctl (0, TCSETAF, &sanetty);
-
- /*
- * See if the invoker has a shell in their
- * environment and use the default value if
- * not.
- */
-
- if (! (shell = getenv ("SHELL")))
- shell = "/bin/sh";
-
- /*
- * Undo any set-UID or set-GID bits on the
- * executable.
- */
-
- setgid (getgid ());
- setuid (getuid ());
-
- /*
- * Start off the new session.
- */
-
- if (arg = strrchr (shell, '/'))
- arg++;
- else
- arg = shell;
-
- execl (shell, arg, 0);
- _exit (-1);
- }
- }
-
- /*
- * quit - kill all active sessions
- */
-
- quit (sig)
- int sig;
- {
- int i;
-
- for (i = 0;i < nsessions;i++) {
- if (masters[i] != -1) {
- close (masters[i]);
- masters[i] = -1;
- kill (- childpids[i], SIGHUP);
- }
- }
- exit (sig);
- }
-
- /*
- * sm - manage pty sessions
- */
-
- main (argc, argv)
- int argc;
- char **argv;
- {
- char buf[BUFSIZ];
- char *cp;
- int i;
- int pid;
- FILE *fp;
-
- /*
- * No arguments are allowed on the command line
- */
-
- if (argc > 1)
- usage ();
-
- /*
- * Set up all the file descriptors and process IDs
- */
-
- for (i = 0;i < MAXSESSIONS;i++) {
- childpids[i] = -1;
- masters[i] = -1;
- }
-
- /*
- * Get the current tty settings, and make a copy that can
- * be tinkered with. The sane values are used while getting
- * commands. The raw values are used while sessions are
- * active. New sessions are set to have the same tty values
- * as the sane values.
- */
-
- ioctl (0, TCGETA, &sanetty);
- rawtty = sanetty;
-
- rawtty.c_oflag &= ~OPOST;
- rawtty.c_lflag = 0;
- rawtty.c_cc[VMIN] = 1;
- rawtty.c_cc[VTIME] = 1;
-
- /*
- * SIGCLG is caught to detect when a session has died or when
- * the writer for the session has exited. SIGUSR1 is used to
- * signal that a ^Z has been seen.
- */
-
- signal (SIGCLD, murder);
- signal (SIGUSR1, catch);
-
- /*
- * The file $HOME/.smrc is read for initializing commands.
- */
-
- if (cp = getenv ("HOME")) {
- sprintf (buf, "%s/.smrc", cp);
- if (access (buf, 04) != 0 || ! (fp = fopen (buf, "r")))
- fp = stdin;
- }
-
- /*
- * This is the main loop. A line is read and executed. If
- * EOF is read, the loop is exited (except if input is the
- * .smrc file).
- */
-
- while (1) {
-
- /*
- * Keyboard signals cause an exit.
- */
-
- signal (SIGINT, quit);
- signal (SIGQUIT, quit);
-
- /*
- * Prompt for input only when it is not coming from
- * the .smrc file. A single line will be read and
- * executed. The read is retried if an interrupt
- * has been seen.
- */
-
- if (fp == stdin) {
- printf ("pty-> ");
- fflush (stdout);
- }
- while (errno = 0, fgets (buf, sizeof buf, fp) == 0) {
- if (errno == EINTR) {
- continue;
- } else if (fp != stdin) {
- fclose (fp);
- fp = stdin;
- buf[0] = '\0';
- break;
- } else {
- strcpy (buf, "quit");
- break;
- }
- }
- if (cp = strchr (buf, '\n'))
- *cp = '\0';
-
- if (! buf[0])
- continue;
-
- /*
- * Parse the command. Each command consists of a
- * verb, with some commands accepting a session ID
- * to act on. The command will be accepted if a
- * unique prefix of the command is entered.
- */
-
- if (parse (buf, "q*uit") || parse (buf, "e*xit")) {
-
- /*
- * Just give up.
- */
-
- quit (0);
- } else if (parse (buf, "cr*eate")) {
-
- /*
- * Create a new session and make it current
- */
-
- session ();
- continue;
- } else if (parse (buf, "cu*rrent")) {
-
- /*
- * Give the session ID of the current session.
- */
-
- if (current != -1)
- printf ("current session is %d\n", current);
- else
- printf ("no current session\n");
-
- continue;
- } else if (cp = parse (buf, "s*et")) {
-
- /*
- * Set the current session ID to #
- */
-
- if (*cp) {
- i = strtol (cp, &cp, 10);
- if (*cp == '\0') {
- last = current;
- current = i;
- continue;
- }
- }
- printf ("eh?\n");
- continue;
- } else if (parse (buf, "a*ctive")) {
-
- /*
- * List the session IDs of all active sessions
- */
-
- for (i = 0;i < nsessions;i++)
- if (masters[i] != -1)
- printf ("%d ", i);
-
- putchar ('\n');
- continue;
- } else if (parse (buf, "j*obs")) {
- int pids = 0;
-
- /*
- * Give a "ps" listing of all active sessions
- */
-
- buf[0] = '\0';
- for (i = 0;i < nsessions;i++) {
- if (childpids[i] != -1) {
- if (pids++)
- strcat (buf, ",");
-
- sprintf (buf + strlen (buf), "%d",
- childpids[i]);
- }
- }
- if (pids) {
- if (! (pspid = fork ())) {
- setgid (getgid ());
- setuid (getuid ());
- execl ("/bin/ps", "ps", "-fp", buf, 0);
- _exit (1);
- }
- while (pspid != -1)
- pause ();
- } else {
- printf ("no jobs\n");
- }
- continue;
- } else if (cp = parse (buf, "co*nnect")) {
-
- /*
- * Connect to the current or named session.
- */
-
- if (*cp) {
- i = strtol (cp, &cp, 10);
- if (*cp == '\0') {
- last = current;
- current = i;
- /* FALLTHROUGH */
- } else {
- printf ("eh?\n");
- continue;
- }
- }
- } else if (parse (buf, "t*oggle")) {
-
- /*
- * Toggle between the previous and current session
- */
-
- i = current;
- current = last;
- last = i;
- /* FALLTHROUGH */
- } else if (cp = parse (buf, "d*elete")) {
-
- /*
- * Delete the named session
- */
-
- if (*cp) {
- i = strtol (cp, &cp, 10);
- if (*cp == '\0' && i >= 0 && i < MAXSESSIONS) {
- if (masters[i] != -1) {
- close (masters[i]);
- masters[i] = -1;
- kill (- childpids[i], SIGHUP);
- continue;
- }
- }
- }
- printf ("eh?\n");
- continue;
- } else {
-
- /*
- * The command was not recognized
- */
-
- help ();
- continue;
- }
-
- /*
- * Validate the session number. It must be in the
- * range 0 .. (MAXSESSIONS-1) to be valid. The current
- * session must also be associated with an open PTY.
- */
-
- if (current < 0 || current >= MAXSESSIONS)
- current = -1;
-
- if (current == -1 || masters[current] == -1) {
- printf ("no current session\n");
- current = -1;
- continue;
- }
-
- /*
- * Let's make a process to read from the child ...
- */
-
- switch (writepid = fork ()) {
- case -1:
- kill (childpids[current], SIGKILL);
- perror ("fork");
- break;
- case 0:
- writer (masters[current]);
- exit (1);
- }
-
- /*
- * Set up the raw TTY modes and start writing to
- * the child.
- */
-
- ioctl (0, TCSETAF, &rawtty);
-
- if (reader (masters[current]) == -1) {
- close (masters[current]);
- masters[current] = -1;
- childpids[current] = -1;
- current = -1;
- if (writepid > 0)
- kill (writepid, SIGTERM);
- }
-
- /*
- * Reset the tty modes to resume the command loop.
- */
-
- ioctl (0, TCSETA, &sanetty);
- }
- exit (0);
- }
-