pty session manager

John F Haugh II jfh at rpp386.cactus.org
Sat Dec 22 06:48:38 AEST 1990


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 at 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."



More information about the Alt.sources mailing list