From: jfh@rpp386.cactus.org (John F Haugh II)
Newsgroups: alt.sources
Subject: pty session manager
Message-ID: <18853@rpp386.cactus.org>
Date: 21 Dec 90 19:48:38 GMT


this is something i whipped up because i wanted to be able to
run multiple sessions on a single tube and i don't have job
control or any such stuff.

it requires ptys to run.  you may want to hack on it and make
it set-uid root so it can steal inactive pty's.  i didn't
bother to do that because i'm lazy this month, and besides,
i own all the pty's on this system anyhow ;-)  if you need a
pty device driver, and you have sco xenix, let me know as i
have one laying around here somewheres.  it was posted to
comp.sources.misc some time back.

it understands a few basic commands -

	create - start a shell on a pty.
	active - list of active (available) sessions.
	current - number of current session.
	jobs - ps output of active shells.
	connect [ # ] - connect keyboard to currently active
	    shell, or session # if # is given.
	quit, exit - quit (or exit ;-)

to get from "connected" state, you may press ^Z followed by
any character to get a "pty->" prompt back.  to send a ^Z
to your shell, press two.  i picked ^Z because that's what
i use for "suspend" on aix, and i'm too lazy to learn a new
keystroke.  someone should probably add a command to make it
setable.

it doesn't keep your utmp file up to date because it would
definitely have to be setuid then.  i had a version that
executed "login -f <your name>" and ran setuid root, but i
didn't think anyone would trust this program to run suid 0.

as always, unshar and enjoy.
----
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	sm.c
# This archive created: Fri Dec 21 13:45:53 1990
# By:	John F Haugh II (River Parishes Programming, Austin TX)
export PATH; PATH=/bin:/usr/bin:$PATH
if test -f 'sm.c'
then
	echo shar: "will not over-write existing file 'sm.c'"
else
cat << \SHAR_EOF > 'sm.c'
/*
 * This code is in the public domain.
 *
 * Written By: John F Haugh II, 12/21/90
 */

#include <sys/types.h>
#include <sys/termio.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>

#define	MAXSESSIONS	16

int	childpids[MAXSESSIONS];
int	writepid;
int	masters[MAXSESSIONS];
int	nsessions;
int	current = -1;
int	caught = 0;

struct	termio	sanetty;
struct	termio	rawtty;

void	exit ();
void	_exit ();
char	*getlogin ();
char	*getenv ();
struct	passwd	*getpwnam ();

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++) {
		if (pid == childpids[i]) {
			childpids[i] = -1;
			close (masters[i]);
			masters[i] = -1;
		}
	}
	signal (sig, murder);
}

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;

	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);

	while (1) {
		errno = 0;
		if ((cnt = read (0, &c, 1)) == 0)
			continue;

		if (cnt == -1) {
			if (errno == EINTR && caught)
				continue;
			else
				exit (0);
		}
		if (c == ('z' & 037)) {
			if (! zflg++)
				continue;
		} else if (zflg) {
			kill (getppid (), SIGUSR1);
			exit (0);
		}
		zflg = 0;
		if (write (fd, &c, 1) != 1)
			break;
	}
	exit (0);
}

usage ()
{
	fprintf (stderr, "usage: ptymgr\n");
	exit (1);
}

session ()
{
	char	mastername[BUFSIZ];
	char	slavename[BUFSIZ];
	char	*digits = "0123456789abcdef";
	char	*letters = "pqrs";
	char	*shell;
	int	i;
	int	pty;
	int	ptys = 64;

	for (i = 0;i < nsessions && masters[i] != -1;i++)
		;

	if (i == MAXSESSIONS)
		return -1;

	if (i == nsessions)
		nsessions++;

	current = i;

	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) {
		fprintf (stderr, "Can't find a pty\n");
		return -1;
	}

	/*
	 * Let's make a child process ...
	 */

	switch (childpids[i] = fork ()) {
		case -1:
			perror ("fork");
			exit (1);
		case 0:
			close (0);
			close (1);
			for (i = 0;i < nsessions;i++)
				close (masters[i]);

			setpgrp ();

			signal (SIGINT, SIG_DFL);
			signal (SIGQUIT, SIG_DFL);
			signal (SIGCLD, SIG_DFL);
			signal (SIGHUP, SIG_DFL);
			signal (SIGUSR1, SIG_DFL);

			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);
			}
			close (2);
			dup (0);
			dup (0);
			ioctl (0, TCSETAF, &sanetty);

			if (! (shell = getenv ("SHELL")))
				shell = "/bin/sh";

			execl (shell, strrchr (shell, '/') + 1, 0);
			_exit (-1);
	}
}

main (argc, argv)
int	argc;
char	**argv;
{
	char	buf[BUFSIZ];
	char	*cp;
	int	i;
	int	pid;

	for (i = 0;i < MAXSESSIONS;i++) {
		childpids[i] = -1;
		masters[i] = -1;
	}
	ioctl (0, TCGETA, &sanetty);
	rawtty = sanetty;

	/*
	 * Let's have our own little process group
	 */

	setpgrp ();

	rawtty.c_oflag &= ~OPOST;
	rawtty.c_lflag = 0;
	rawtty.c_cc[VMIN] = 1;
	rawtty.c_cc[VTIME] = 1;

	signal (SIGCLD, murder);
	signal (SIGUSR1, catch);

	while (1) {
		printf ("pty-> ");
		fflush (stdout);

		while (errno = 0, gets (buf) == 0) {
			if (errno == EINTR)
				continue;
			else
				exit (0);
		}
		if (! buf[0])
			continue;

		/*
		 * Get the command
		 */

		if (strcmp (buf, "quit") == 0 || strcmp (buf, "exit") == 0) {
			exit (0);
		} else if (strcmp (buf, "create") == 0) {
			session ();
			continue;
		} else if (strcmp (buf, "current") == 0) {
			printf ("current session is %d\n", current);
			continue;
		} else if (strncmp (buf, "set", 3) == 0) {
			i = strtol (buf + 3, &cp, 10);
			if (buf[3] != '\0' && *cp == '\0')
				current = i;
			else
				printf ("eh?\n");
			continue;
		} else if (strcmp (buf, "active") == 0) {
			for (i = 0;i < nsessions;i++)
				if (masters[i] != -1)
					printf ("%d ", i);

			putchar ('\n');
			continue;
		} else if (strcmp (buf, "jobs") == 0) {
			int	pids = 0;

			strcpy (buf, "ps -fp ");
			for (i = 0;i < nsessions;i++) {
				if (childpids[i] != -1) {
					if (pids++)
						strcat (buf, ",");

					sprintf (buf + strlen (buf), "%d",
						childpids[i]);
				}
			}
			if (pids)
				system (buf);
			continue;
		} else if (strncmp (buf, "connect", 7) != 0) {
			printf ("eh?\n");
			continue;
		}
		i = strtol (buf + 2, &cp, 10);
		if (*cp == '\0' && buf[2]) {
			if (masters[i] != -1)
				current = i;
			else
				current = -1;
		}
		if (current == -1) {
			printf ("no current session\n");
			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);
		}
		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);
		}
		ioctl (0, TCSETA, &sanetty);
	}
	exit (0);
}
SHAR_EOF
fi
exit 0
#	End of shell archive
-- 
John F. Haugh II                             UUCP: ...!cs.utexas.edu!rpp386!jfh
Ma Bell: (512) 832-8832                           Domain: jfh@rpp386.cactus.org
"While you are here, your wives and girlfriends are dating handsome American
 movie and TV stars. Stars like Tom Selleck, Bruce Willis, and Bart Simpson."
