[comp.unix.wizards] Help, please, with pseudo tty

cl@datlog.co.uk (Charles Lambert) (11/24/88)

I'm struggling with my first attempt to use a pseudo-tty on AIX 2.2.

What I want is a daemon that opens the controller side then waits forever,
processing data as and when something writes to the server side. Processes
may come and go on the server side but the daemon goes on forever.

I've got the controller side open; now, how do I get the deamon to hang
pending a write to the server side?  I can't find any explanation of how
reads are satisfied on the controller side.

Charlie		cl@datlog.co.uk

lm@snafu.Sun.COM (Larry McVoy) (11/29/88)

In article <926@dlhpedg.co.uk> cl@datlog.co.uk (Charles Lambert) writes:
>
>I'm struggling with my first attempt to use a pseudo-tty on AIX 2.2.

[ Someone else wanted this as well, enjoy ]

This is some code that basically gives you a shell (or an su, I don't 
remember) and logs all the commands that you type.  Last I remember, it
more or less worked - no promises.  At any rate, it certainly gives all
those pty hackers a running start.  This source was generated on and for 
Sun machines.

# This is a shell archive.  Remove anything before this line, then
# unpack it by saving it in a file and typing "sh file".  (Files
# unpacked will be owned by you and have default permissions.)
#
# This archive contains:
# Makefile log.awk log.c mode.c pty.c ptypair.c

echo x - Makefile
cat > "Makefile" << '//E*O*F Makefile//'
CFLAGS = -DLOGFILE=\"./log\"
O = pty.o ptypair.o log.o

pty: $O
	cc $(CFLAGS) $O -o pty

clobber:
	/bin/rm -f $O pty log
//E*O*F Makefile//

echo x - log.awk
cat > "log.awk" << '//E*O*F log.awk//'
BEGIN { FS=":" }

NF == 3  && $2 == "END" { IN=0; }

NF == 6 && $1 == "START" { IN = 1; printf("user=%s time=%s\n", $3, $2); getline; }

IN == 1 { print; }
//E*O*F log.awk//

echo x - log.c
cat > "log.c" << '//E*O*F log.c//'
# include	<stdio.h>
# include	<sys/file.h>
# include	<sys/types.h>
# include	<sys/stat.h>
# include	<pwd.h>
# ifndef	LOGFILE
# define	LOGFILE		"/usr/adm/sulog"
# endif
static          logfd;
static          opened;
static char    *tmp = "/tmp/ReXXXXXX";

logstartup()
{
    struct stat     st;
    struct passwd  *p;
    struct passwd  *getpwuid();
    struct passwd  *getpwnam();
    char           *date();
    char           *getwd();
    char           *getlogin();
    char            buf[200];
    char            wd[100];

    if (stat(LOGFILE, &st))
	close(creat(LOGFILE, 0600));
    logfd = open(mktemp(tmp), O_RDWR | O_CREAT, 0600);
    opened = 1;
    setpwent();
    if (!(p = getpwnam(getlogin())) && !(p = getpwuid(getuid()))) {
	printf("Who are you?\n");
	exit(1);
    }
    endpwent();
    sprintf(buf, "START:%s:%s:%d:%d:%s\n",
	    date(), p->pw_name, getuid(), getgid(), getwd(wd));
    logadd(buf, strlen(buf));
}

char           *
date()
{
    long            t;
    register char  *s;
    char           *ctime();

    time(&t);
    s = ctime(&t);
    s[13] = '.';
    s[16] = 0;
    return s;
}

logadd(buf, n)
    char           *buf;
{
    register        i;

    if (!opened) {
	logstartup();
	opened = 1;
    }
    for (i = 0; i < n; i++)
	if (buf[i] == '\r')
	    buf[i] = '\n';
    write(logfd, buf, n);
}

logclose()
{
    register        trys = 0, nread, log;
    char            buf[BUFSIZ];

    while (trys++ < 4 && (log = open(LOGFILE, O_RDWR | O_EXCL | O_APPEND)) < 0)
	sleep(1);
    logadd(":END:\n", 8);
    close(logfd);
    logfd = open(tmp, O_RDONLY);
    sleep(1);
    while ((nread = read(logfd, buf, sizeof(buf))) > 0)
	write(log, buf, nread);
    close(logfd);
    close(log);
    unlink(tmp);
}
//E*O*F log.c//

echo x - mode.c
cat > "mode.c" << '//E*O*F mode.c//'
# include	<stdio.h>
# include	<sgtty.h>

static struct sgttyb old;
static struct sgttyb new;
static int      done;

m_init()
{
    ioctl(fileno(stdin), TIOCGETP, &old);
    new = old;
}

m_normal(fd)
{
    if (!done++)
	m_init();

    ioctl(fd, TIOCSETP, &old);
}

m_raw(fd)
{
    if (!done++)
	m_init();

    new.sg_flags = RAW;
    ioctl(fd, TIOCSETP, &new);
}
//E*O*F mode.c//

echo x - pty.c
cat > "pty.c" << '//E*O*F pty.c//'
# include       <stdio.h>
# include       <signal.h>
# include       <errno.h>
# include       <sgtty.h>
# include       <assert.h>
# include	<sys/types.h>

/*
 * pty filter - trap all keyboard input in a logfile
 */
# define	debug(x)	/* fprintf x	/* */
# define	BSIZ		256
# define	BAD_READ	1
# define	BAD_SELECT	2

int             cleanup();	/* called to quit */
int             kidid;		/* child pid & std{*} */
struct sgttyb   restore;	/* tty modes of stdin */

main(ac, av, ev)
    char          **av;
    char          **ev;
{
    int             fds[2];	/* master/slave */

    /* tidy cleanup, thank you. */
    signal(SIGHUP, cleanup);
    signal(SIGCLD, cleanup);

    /* save the modes for exit... */
    ioctl(0, TIOCGETP, &restore);
    if (ptypair(fds) == -1)
	perror("ptypair");
    if (!(kidid = fork())) {
	close(fds[0]);
	kid(fds[1], av, ev);
    }
    close(fds[1]);
    pass(fds[0]);
    /* NOTREACHED */
}

/* kid - exec a sh
 */
kid(fd, av, ev)
    char          **av;
    char          **ev;
{
    int             pgrp;

    /*
     * dup the slave side to std{in,out,err}, make a new process group, grab
     * the pty, set the pty modes to be "normal" for a shell, and exec.
     */
    dup2(fd, 0);
    dup2(fd, 1);
    dup2(fd, 2);
    ioctl(fd, TIOCSETP, &restore);
    setpgrp(0, pgrp = getpid());
    ioctl(fd, TIOCSPGRP, &pgrp);
    av[0] = "/bin/sh";
    execve(av[0], av, ev);
    syserr("exec");
    /* NOTREACHED */
}

/*
 * pass - pass through filter
 *
 * take any input from stdin and pass to fd
 * take any output from fd and pass to stdout
 */
# define	STDIN		1
# define	SLAVE		(1<<fd)
pass(fd)
{
    extern          errno;
    struct sgttyb   p;
    static u_char   buf[BSIZ];
    u_long          orgmask, mask;
    register        nread;

    /* input is raw (no signal processing & fast */
    ioctl(0, TIOCGETP, &p);
    p.sg_flags = RAW;
    ioctl(0, TIOCSETP, &p);
    orgmask = STDIN | SLAVE;

    logstartup();
    for (;;) {
	mask = orgmask;
	/*
	 * could check for EINTR except we're in RAW mode so no signals, and
	 * there are no itimers either...  pretty safe.
	 */
	if (select(32, &mask, 0, 0, 0) == -1)
	    cleanup(BAD_SELECT);
	debug((stderr, "mask=%#x\n", mask));
	/*
	 * input from the "keyboard"
	 */
	if (mask & STDIN) {
	    if ((nread = read(0, buf, sizeof(buf))) <= 0)
		cleanup(BAD_READ);
	    debug((stderr, "Line: %d bytes\n\r", nread));
	    write(fd, buf, nread);
	    logadd(buf, nread);
	}
	/*
	 * output from the process
	 */
	if (mask & SLAVE) {
	    if ((nread = read(fd, buf, sizeof(buf))) <= 0)
		cleanup(BAD_READ);
	    debug((stderr, "pty: %d bytes\n\r", nread));
	    write(1, buf, nread);
	}
    }
    /* NOTREACHED */
}

syserr(msg)
    char           *msg;
{
    extern int      errno;

    psyserr(msg);
    exit(errno ? errno : 100);
}

psyserr(msg)
    register char  *msg;
{
    extern int      errno, sys_nerr;
    extern char    *sys_errlist[];

    fprintf(stderr, "ERROR: %s (%d", msg, errno);
    if (errno > 0 && errno < sys_nerr)
	fprintf(stderr, "; %s)\n", sys_errlist[errno]);
    else
	fprintf(stderr, ")\n");
}

cleanup(sig)
{
    kill(kid, SIGHUP);
    ioctl(0, TIOCSETP, &restore);
    logclose();
    exit(0);
}
//E*O*F pty.c//

echo x - ptypair.c
cat > "ptypair.c" << '//E*O*F ptypair.c//'
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/errno.h>
#include <sys/ioctl.h>

/*
 * ptypair
 *   works like pipe() or socketpair(), but returns a master/slave
 *   pty pair.  The master is fd[0], the slave is fd[1].  The slave
 *   end is the end that actually looks like a tty.  The pty is
 *   returned in RAW/NO-PARITY mode and both ends are open read/write.
 *
 * returns 0 if it found a pty, -1 if not.
 */
int
ptypair(fd)
    int             fd[2];

{
    register        i;
    register char  *c, *line;
    struct stat     stb;
    struct sgttyb   sb;
    extern int      errno;
    char            ptymask[10];

#define PTYMASK_LEN 10

    strcpy(ptymask, "/dev/ptyXX");
    for (c = "pqrs"; *c != 0; c++) {
	line = ptymask;
	line[PTYMASK_LEN - 2] = *c;
	line[PTYMASK_LEN - 1] = '0';

	if (stat(line, &stb) < 0)	/* see if the block of ptys exists */
	    break;

	for (i = 0; i < 16; i++) {
	    line[PTYMASK_LEN - 1] = "0123456789abcdef"[i];
	    fd[0] = open(line, O_RDWR);
	    if (fd[0] > 0) {
		line[PTYMASK_LEN - 5] = 't';
		fd[1] = open(line, O_RDWR);
		if (fd[1] < 0) {/* if tty open fails, try another */
		    (void) close(fd[0]);
		    continue;
		}

		/* now, make it sane */
		(void) ioctl(fd[1], (int) TIOCGETP, (char *) &sb);
		sb.sg_ispeed = EXTA;
		sb.sg_ospeed = EXTA;
		sb.sg_flags = RAW | ANYP;
		(void) ioctl(fd[1], (int) TIOCSETP, (char *) &sb);
		return 0;
	    }
	}
    }

    errno = ENOSPC;		/* no space left on device -- it's close */
    return -1;
}
//E*O*F ptypair.c//

echo Possible errors detected by \'wc\' [hopefully none]:
temp=/tmp/shar$$
trap "rm -f $temp; exit" 0 1 2 3 15
cat > $temp <<\!!!
       8      21     121 Makefile
       7      37     156 log.awk
      85     205    1741 log.c
      29      47     363 mode.c
     150     447    3210 pty.c
      63     238    1524 ptypair.c
     342     995    7115 total
!!!
wc  Makefile log.awk log.c mode.c pty.c ptypair.c | sed 's=[^ ]*/==' | diff -b $temp -
exit 0

Larry McVoy      (lm%snafu@sun.com)