v23i033: Run a program under a pty session, Part03/06

Rich Salz rsalz at bbn.com
Thu Oct 11 00:16:40 AEST 1990


Submitted-by: Dan Bernstein <brnstnd at kramden.acf.nyu.edu>
Posting-number: Volume 23, Issue 33
Archive-name: pty/part03

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then feed it
# into a shell via "sh file" or similar.  To overwrite existing files,
# type "sh file -c".
# The tool that generated this appeared in the comp.sources.unix newsgroup;
# send mail to comp-sources-unix at uunet.uu.net if you want that tool.
# Contents:  config.h logs.c pty.1 sigler.c sock.c tty.c util/Makefile
# Wrapped by rsalz at litchi.bbn.com on Wed Oct 10 10:11:38 1990
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
echo If this archive is complete, you will see the following message:
echo '          "shar: End of archive 3 (of 6)."'
if test -f 'config.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'config.h'\"
else
  echo shar: Extracting \"'config.h'\" \(9610 characters\)
  sed "s/^X//" >'config.h' <<'END_OF_FILE'
X/* Copyright 1990, Daniel J. Bernstein. All rights reserved. */
X
X#ifndef PTY_CONFIG_H
X#define PTY_CONFIG_H
X
X/* It is recommended that you make local changes in the Makefile, */
X/* except for feature control, which is best done here. */
X
X/* Section 1: Feature control. */
X/* Section 2: Pseudo-terminal pathnames. */
X/* Section 3: Logging. */
X/* Section 4: Protection. */
X/* Section 5: Other. */
X/* Section 6: Sanity checks. */
X
X/* Each section starts with the necessary defines and finishes with */
X/* an explanation. */
X
X
X/* Section 1: Feature control. */
X
X/*#define MUST_UTMP  /* force utmp logging */
X/*#define NO_UTMP  /* prohibit utmp logging */
X/*#define MUST_WTMP  /* force utmp logging */
X/*#define NO_WTMP  /* prohibit wtmp logging */
X/*#define MUST_SESSION  /* force disconnectable sessions (what for?) */
X/*#define NO_SESSION  /* prohibit disconnectable sessions */
X/*#define MUST_CHOWN  /* force changing pty owner */
X/*#define NO_CHOWN  /* prohibit changing pty owner */
X/*#define NO_FDPASSING  /* prohibit file descriptor passing */
X/*#define NO_UNIXSOCKS  /* prohibit use of UNIX-domain sockets */
X#define TTY_WINDOWS  /* have winsize, WINCH, etc. in tty (4.3) */
X/*#define TTY_AUXCHARS  /* have systat, e.g. with ^T, in tty */
X#define DESPERATE_ALARMS  /* if NDELAY may not be enough */
X#define DONT_NDELAY  /* might as well, with DESPERATE_ALARMS */
X#define SIGINTERRUPT  /* if you have siginterrupt() (4.3) */
X#define USLEEP  /* if you have usleep() */
X
X/* These are mostly self-explanatory. If your system doesn't support */
X/* some feature, you should prohibit it by defining NO_WHATEVER. */
X
X/* Any BSD 4.2 or 4.3 system should have UNIXSOCKS and FDPASSING. Look */
X/* for /sys/h/un.h to see if you have UNIX-domain sockets. Look for */
X/* msg_accrights in the man page for recvmsg() to see if you have */
X/* file desriptor passing. (Sometimes fd passing is available but buggy, */
X/* as it's a relatively new, powerful, and rarely exploited feature. In */
X/* this case you should define NO_FDPASSING.) In the current version, */
X/* NO_UNIXSOCKS implies NO_FDPASSING and NO_SESSION; see Section 6. */
X
X/* For TTY_WINDOWS, look for struct winsize in /usr/include/sgtty.h. */
X/* For TTY_AUXCHARS, look for struct auxchars in the same place. */
X/* All BSD 4.3 systems should have winsize; auxchars isn't so common. */
X
X/* Opening the slave side of a pty without a master should hang the */
X/* process. Normally, opening with NDELAY gets around this effect; */
X/* opening the slave side first is necessary for some security checks. */
X/* On some systems, however, NDELAY doesn't do its job; you must */
X/* define DESPERATE_ALARMS to forcibly interrupt each open(). There */
X/* shouldn't be any problem in leaving this (and DONT_NDELAY) defined */
X/* anyway. (On a few systems, you must have DONT_NDELAY for -xn.) */
X
X/* Define SIGINTERRUPT if you have siginterrupt() (i.e., if you're */
X/* running BSD 4.3). Without this call, there's no correct way to do */
X/* per-process nonblocking I/O, and pty may block when it shouldn't */
X/* (namely, when it has N bytes to write to the output, the output is */
X/* a blocking pipe with M bytes of space, and N > M > 0). If you define */
X/* SIGINTERRUPT, pty will take some extra effort to never, ever, ever */
X/* block when it doesn't have to. (See OUTBUFSIZE, below.) */
X
X/* Define USLEEP if you have usleep(). */
X
X
X/* Section 2: Pathnames. */
X
X#ifndef PTYDIR
X#define PTYDIR "/usr/etc/pty"
X#endif
X#ifndef DEVMTY
X#define DEVMTY "/dev/pty  "
X#endif
X#ifndef DEVSTY
X#define DEVSTY "/dev/tty  "
X#endif
X#ifndef PTY1
X#define PTY1 "pqrstuvwxyzabcdefghijklmno"
X#endif
X#ifndef PTY2
X#define PTY2 "0123456789abcdef"
X#endif
X#ifndef TTYNAMELEN
X#define TTYNAMELEN 30
X#endif
X
X/* PTYDIR is where pty sessions store their information. You should */
X/* create it mode 700, owner pty, group irrelevant. You must define */
X/* PTYDIR as something even if you don't allow sessions. If you don't */
X/* set up PTYDIR at all but you do allow sessions, pty will switch */
X/* out of setuid after grabbing a pty, then set up session information */
X/* in the user's home directory. This isn't totally secure but it's */
X/* at least partially workable. */
X
X/* DEVMTY and DEVSTY are the basenames of the master and slave ptys */
X/* respectively. They must each end with two spaces, filled in with */
X/* characters from PTY1 and PTY2 respectively. If your ptys aren't */
X/* labelled by this convention, I'm surprised you have anything working; */
X/* either set up a new pty bank with normal names or change the pty code */
X/* that allocates ptys. Note that convention allows you to only have */
X/* some of a pty bank, as long as one of them is there; the first letter */
X/* in PTY2 must indicate that one. */
X
X/* The order of PTY1 and PTY2 is the pty searching order. pty allows */
X/* (and, as a break from tradition, uses by default) random searches, */
X/* starting from random spots in PTY1 and PTY2 rather than the beginning. */
X/* This has several good effects, although you may think utmp will look */
X/* a bit ugly. */
X
X/* If you want to gradually convert your system to using this program, */
X/* PTY1 and PTY2 shouldn't reference all your ptys. Instead, pick a */
X/* large enough subset of your ptys to be usable, and try things out */
X/* with just those. If everything works and you like pty's capabilities, */
X/* remove your old ptys, or recompile pty to reference all of them. */
X
X/* TTYNAMELEN should be the maximum length of a tty (not just pty!) */
X/* name. TTYNAMELEN shouldn't have to be there; it's used to work */
X/* around some fundamental failures in the current tty/pty model. */
X
X
X/* Section 3: Logging. */
X
X#ifndef PTYUTMP_FILE
X#define PTYUTMP_FILE "/etc/utmp"
X#endif
X#ifndef PTYWTMP_FILE
X#define PTYWTMP_FILE "/usr/adm/wtmp"
X#endif
X#ifndef PTYUTMP_OFFSET
X#define PTYUTMP_OFFSET 5
X#endif
X#ifndef PTYWTMP_OFFSET
X#define PTYWTMP_OFFSET PTYUTMP_OFFSET
X#endif
X#ifndef PTYUTMP_HOST
X#define PTYUTMP_HOST "pty"
X#endif
X#ifndef PTYWTMP_HOST
X#define PTYWTMP_HOST PTYUTMP_HOST
X#endif
X#ifndef PTYUTMP_SWHOST
X#define PTYUTMP_SWHOST PTYUTMP_HOST
X#endif
X#ifndef PTYWTMP_SWHOST
X#define PTYWTMP_SWHOST "pty-sessuser"
X#endif
X
X/* These are straightforward. FILE is where you want the logs. utmp and */
X/* wtmp are about as sensible as /etc/passwd and unfortunately just as */
X/* popular; typically they're in /etc/utmp and /usr/adm/wtmp, and the */
X/* utility programs in this package support that convention. (wtmp logs */
X/* all ins and outs. utmp only has one entry per line, namely the last */
X/* wtmp entry for it.) OFFSET is the number of chars from a pty extension */
X/* to remove from a log entry; usually this is 5, to make /dev/ttyxx into */
X/* ttyxx. Finally, HOST is the name that you want in the ``remote'' field */
X/* of the log; that field is rather illogical, but it's there. */
X
X/* SWHOST is a variant of HOST, for what's logged upon a session uid */
X/* switch. */
X
X/* If you're using pty under telnet/login/whatever, which do their own */
X/* utmp and wtmp handling, then pty will usually have -xUW, so these */
X/* defines aren't too important. Except for SWHOST, which is necessary */
X/* for sessuser's action to make sense. */
X
X
X/* Section 4: Protection. */
X
X#ifndef USEDPTYMODE
X#define USEDPTYMODE 0600
X#endif
X#ifndef UNUSEDPTYMODE
X#define UNUSEDPTYMODE 0600
X#endif
X#ifndef PTYOWNER
X#define PTYOWNER euid
X#endif
X#ifndef PTYGROUP
X#define PTYGROUP 4
X#endif
X
X/* pty -xc will change the owner of the pty back to PTYOWNER after the */
X/* child has exited. This should be euid, for the effective uid of the */
X/* pty process. If a user runs pty as himself, he won't get to change */
X/* the owner of the pseudo-terminal in the first place. */
X
X/* PTYGROUP should be the ``tty'' group in /etc/group, normally 4. All */
X/* ptys and ttys should be in this group at all times. */
X
X/* USEDPTYMODE is the mode of ptys while they're being used. 600 is a */
X/* good choice; it means messages off. If you want msgs y by default, */
X/* try 620. Don't use the old 622: it's exceedingly insecure. */
X
X/* UNUSEDPTYMODE is the mode of ptys while they're unused. Traditionally */
X/* this would be 666, so that any process needing a pty can just grab */
X/* one. However, this leads to quite a few security holes. If you have */
X/* lots of programs like emacs that really, really, really want to use */
X/* ptys and don't support this interface, try 0660, and make those */
X/* programs setgid tty. Otherwise, stick to 0600. */
X
X
X/* Section 5: Other. */
X
X#ifndef SIGRET_TYPE
X#define SIGRET_TYPE int
X#endif
X
X#ifndef OUTBUFSIZE
X#define OUTBUFSIZE 16384
X#endif
X
X#ifndef GENERIC
X#define GENERIC char
X#endif
X
X#ifdef TTY_AUXCHARS
X#define USESTAT
X#endif
X
X/* SIGRET_TYPE is the type returned by signal handlers. Logically, it */
X/* should be void, but C and signals were around before void was. */
X
X/* OUTBUFSIZE is the size of the extra buffers provided by pty, both */
X/* for data coming in to the pseudo-terminal and for data going out. */
X
X/* It should be possible to cast any pointer to a GENERIC * pointer */
X/* and back. Again, void makes the most sense and is the best choice */
X/* under ANSI, but char is correct and much more portable. */
X
X/* Setting USESTAT is necessary for systems supporting auxchars. */
X
X
X/* Section 6: Sanity checks. */
X
X#ifdef NO_UNIXSOCKS
X#define NO_FDPASSING
X#define NO_SESSION
X#endif
X
X#ifdef NO_SESSION
X#ifdef MUST_SESSION
X#undef MUST_SESSION
X#endif
X#endif
X
X#ifdef NO_UTMP
X#ifdef MUST_UTMP
X#undef MUST_UTMP
X#endif
X#endif
X
X#ifdef NO_WTMP
X#ifdef MUST_WTMP
X#undef MUST_WTMP
X#endif
X#endif
X
X#ifdef NO_CHOWN
X#ifdef MUST_CHOWN
X#undef MUST_CHOWN
X#endif
X#endif
X
X#endif
END_OF_FILE
  if test 9610 -ne `wc -c <'config.h'`; then
    echo shar: \"'config.h'\" unpacked with wrong size!
  fi
  # end of 'config.h'
fi
if test -f 'logs.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'logs.c'\"
else
  echo shar: Extracting \"'logs.c'\" \(1853 characters\)
  sed "s/^X//" >'logs.c' <<'END_OF_FILE'
X/* Copyright 1990, Daniel J. Bernstein. All rights reserved. */
X
X#include "config.h"
X#include "logs.h"
X#include "file.h"
X#include <utmp.h>
X#include <strings.h>
X
X/* utmp and wtmp are about as sensible as /etc/passwd. */
X
X/* We never bother to shrink /etc/utmp. */
X
Xextern long time();
X
Xlong now()
X{
X return time((long *) 0);
X}
X
Xint utmp(line,name,host,date)
Xchar *line;
Xchar *name;
Xchar *host;
Xlong date;
X{
X struct utmp ut;
X struct utmp xt;
X int fd;
X int i;
X int j;
X
X /* only use of strncpy, ever */
X (void) strncpy(ut.ut_line,line,sizeof(ut.ut_line));
X (void) strncpy(ut.ut_name,name,sizeof(ut.ut_name));
X (void) strncpy(ut.ut_host,host,sizeof(ut.ut_host));
X ut.ut_time = date;
X
X fd = open(PTYUTMP_FILE,O_RDWR);
X if (fd == -1)
X   return -1;
X j = i = 0;
X while (read(fd,(char *) &xt,sizeof(xt)) == sizeof(xt))
X  {
X   i++;
X   if (strncmp(xt.ut_line,ut.ut_line,sizeof(ut.ut_line)) == 0)
X    {
X     j = i;
X     break;
X    }
X  }
X if (j)
X  {
X   if (lseek(fd,(long) ((j - 1) * sizeof(xt)),L_SET) == -1)
X    {
X     (void) close(fd);
X     return -1;
X    }
X  }
X else
X  {
X   /* We have to reopen to avoid a race with other end-of-utmp entries. */
X   (void) close(fd);
X   if ((fd = open(PTYUTMP_FILE,O_RDWR | O_APPEND)) == -1)
X     return -1;
X  }
X if (write(fd,(char *) &ut,sizeof(ut)) < sizeof(ut))
X  {
X   (void) close(fd);
X   return -1;
X  }
X (void) close(fd);
X return 0;
X}
X
Xint wtmp(line,name,host,date)
Xchar *line;
Xchar *name;
Xchar *host;
Xlong date;
X{
X struct utmp wt;
X int fd;
X
X (void) strncpy(wt.ut_line,line,sizeof(wt.ut_line));
X (void) strncpy(wt.ut_name,name,sizeof(wt.ut_name));
X (void) strncpy(wt.ut_host,host,sizeof(wt.ut_host));
X wt.ut_time = date;
X
X fd = open(PTYWTMP_FILE,O_WRONLY | O_APPEND);
X if (fd == -1)
X   return -1;
X if (write(fd,(char *) &wt,sizeof(wt)) < sizeof(wt))
X  {
X   (void) close(fd);
X   return -1;
X  }
X (void) close(fd);
X return 0;
X}
END_OF_FILE
  if test 1853 -ne `wc -c <'logs.c'`; then
    echo shar: \"'logs.c'\" unpacked with wrong size!
  fi
  # end of 'logs.c'
fi
if test -f 'pty.1' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'pty.1'\"
else
  echo shar: Extracting \"'pty.1'\" \(12049 characters\)
  sed "s/^X//" >'pty.1' <<'END_OF_FILE'
X.TH pty 1
X.SH NAME
Xpty \- run a program under a pseudo-terminal session
X.SH SYNTAX
Xpty
X[
X\fB\-qQvdDe3EjJsStT0\fI
X] [
X\fB\-F\fI
X] [
X\fB\-f\fIn
X] [
X\fB\-p\fI[cCdDeEnNrRsS0]
X] [
X\fB\-x\fI[cCeEnNoOrRsSuUwWxX]
X] [
X\fB\-ACHUVW\fI
X]
Xprogram
X[
Xarg ...
X]
X.SH DESCRIPTION
X.I pty
Xdetaches itself from its original
Xterminal,
Xallocates a pseudo-terminal,
Xand runs
X.I program
Xon that pseudo-terminal
Xwith any given arguments.
X.I pty
Xlets you easily disconnect from and reconnect to
Xsessions;
Xit has over fifty options for precise control,
Xand is meant to act as the sole interface
Xbetween pseudo-terminals and the rest of the system.
X.PP
XThere are a few very common invocations of
X.I pty.
XThe most common is just
X.I pty program,
Xwith no options;
Xthis has several effects as described below.
X.I pty \-s program
Xsets up a disconnectable session;
Xit is described further in
X.I sess(1).
X.I pty \-0 program
Xisolates the rest of the world from
X.I program
Xin several ways;
Xit is described further in
X.I condom(1).
X.PP
XThe two next most commonly used options
Xare 
X.I\-d,
Xif
X.I pty
Xis started without a controlling terminal;
Xand
X.I\-xu,
Xwhich makes an entry in
X/etc/utmp.
X.PP
XSome programs (such as
X.I vi)
Xdon't like taking input or output
Xfrom a pipe; under
X.I pty,
Xthey won't notice a thing.
XOther programs buffer as much output as possible
Xif they're in a pipe;
Xunder
X.I pty,
Xthey'll buffer at most a line of output.
X.I pty
Xis very careful to restore terminal modes upon
Xstopping or exiting;
Xa careless
X.I program
Xis shielded from your terminal.
XOtherwise,
X.I pty program
Xshould feel just like
X.I program.
X.PP
X.I pty
Xchanges the original terminal to character-at-a-time
X(raw, cbreak, and noecho) mode; it sets the pseudo-terminal to
Xthe original mode. When
X.I program
Xfinishes,
X.I pty
Xwill set the original terminal back to its original mode.
XAny mode changes on the pseudo-terminal will be lost.
X.PP
X.I pty
Xsets file descriptor 0 to input from the
Xpseudo-terminal, 1 and 2 to output.
X.I pty
Xalso supports the ``3 is /dev/tty'' convention:
Xit sets up file descriptor 3 for input from, output to,
Xand terminal commands for
X/dev/tty
X(not the original tty, but the pseudo tty).
X.PP
XOptions
X.B ACHUVW
Xprint the authorship notice,
Xcopyright notice,
Xhelp notice,
Xshort usage summary,
Xversion number,
Xand warranty information respectively.
X.PP
X.I pty
Xhas quite a few flags,
Xprocessed left to right:
X.TP 12
X\fB\-d\fI
X.I pty
Xwill assume it is already detached from the terminal.
XThis forces
X.B\-T;
Xit sets but doesn't force
X.B\-J.
XAlso, instead of copying pseudo-terminal modes from
Xthe original terminal,
X.I pty
Xwill set up a generic new-discipline line-at-a-time mode.
X.TP
X\fB\-D\fI
X.I pty
Xwill assume that it is attached to a terminal (default).
XThis sets
X.B\-jt.
X.TP
X\fB\-e\fI
XPreserve
Xstandard error (file descriptor 2)
Xand standard tty (file descriptor 3).
X.TP
X\fB\-3\fI
XPreserve fd 3, but point fd 2 at the pseudo-terminal.
X.TP
X\fB\-E\fI
XDirect both file descriptors to the pseudo-terminal (default).
X(Actually,
X.I pty
Xwill point standard error at the tty by name,
Xbut fd 3 at /dev/tty,
Xso that various
X.I ioctl()
Xcommands on fd 3 will work.)
XAlso close all higher-numbered file descriptors.
X.TP
X\fB\-j\fI
XJob control (default): When
X.I program
Xstops,
X.I pty
Xstops.
XWhen 
X.I pty
Xis restarted,
Xit restarts
X.I program.
X.TP
X\fB\-J\fI
XNo job control.
XIf
X.I program
Xstops,
X.I pty
Xwill ignore it
X(though it will always restart it upon a reconnect).
XThis behavior is much less
Xantisocial than
Xthe behavior of the previous
X.I pty
Xversion.
X.TP
X\fB\-t\fI
X.I pty
Xwill set the
Xoriginal terminal to
Xno-echo, character-at-a-time mode (default).
X.TP
X\fB\-T\fI
X.I pty
Xwill not touch the original terminal, if there is one.
XIt is always dangerous to put two programs in a pipe if both
Xchange tty modes;
X.I pty,
X.I vi,
Xand
X.I more
Xare examples of such programs. If you use
X.I pty
Xin a pipe
Xwith another tty-mode-changing program,
Xmake sure to specify
X.B\-0
X(which is an abbreviation for
X.B\-eSTp0)
Xso that
X.I pty
Xwill neither affect nor be affected by the other program.
XIf you use a pipe of ptys,
Xyou should probably specify
X.B\-0
Xin all but one.
X.TP
X\fB\-s\fI
XSession.
XWhen the connection is hung up or manually disconnected,
X.I pty
Xwill patiently wait for a
Xreconnection.
X.I program
Xwon't notice a thing.
X.B\-s
Xsets but does not force
X.B\-xu.
XIt forces
X.B\-E.
X.TP
X\fB\-S\fI
XNo session (default).
XWhen the connection is hung up,
X.I pty
Xwill send a HUP to
X.I program.
X.B\-S
Xsets but does not force
X.B\-xU.
X.TP
X\fB\-q\fI
XQuiet.
X.I pty
Xwill print absolutely nothing on standard error.
XIt will communicate strange events through its exit code.
X.TP
X\fB\-Q\fI
XNot quiet (default).
X.I pty
Xwill generate bits of chatter about interesting
Xevents.
X.TP
X\fB\-v\fI
XVerbose.
X.I pty
Xwill blabber on and on and on and on and on and on and on and on.
XIt keeps going,
Xand going,
Xand going,
Xand going ...
X.TP
X\fB\-f\fIfd
XPass the master and slave sides of the pseudo-terminal
Xup in file descriptor
X.I fd.
XThis is only available if your machine supports descriptor passing.
X
XThe protocol, from the point of view of the receiver (``controller''),
Xis to pty_getch a character off the other side
Xof the passing descriptor, perhaps check that it is a G,
Xand pty_putgetint a G for the process id of the signaller
X(the process to recieve a HUP if the connection drops);
Xpty_getch a character, perhaps check that it is m,
Xand pty_putgetfd an m for the master side of the pseudo-terminal;
Xand similarly for s and the slave side. (These functions are all
Xavailable in sock.c in the
X.I pty
Xsource code.)
X
XWhen the connection is dropped,
X.I pty
Xwill send up a period,
Xfollowed by one pid and two new descriptors as above
Xif it reconnects.
XIn the meantime, the controller has full responsibility for
Xperforming terminal I/O.
X.TP
X\fB\-F\fI
XDo not pass anything (default).
X.TP
X\fB\-p\fImmm
XSet the
Xpseudo-terminal to modes specified by
X.I m.
XUnder
X.B\-d,
Xdefaults are taken from the
Xcurrent terminal;
Xunder
X.B\-D,
Xdefaults are as below.
XPredefined modes:
X.RS
X.TP 5
X.I c
XSet cbreak (character-at-a-time) mode.
X.TP
X.I C
XDo not set cbreak mode (default). 
X.TP
X.I d
XUse the new discipline (default, breaking with tradition).
X.TP
X.I D
XUse the old discipline, without job control or other fancy tty features.
X.TP
X.I e
XEcho characters (default).
X.TP
X.I E
XDo not echo.
X.TP
X.I n
XChange return to newline (default).
X.TP
X.I N
XDo not change return to newline.
X.TP
X.I r
XSet raw mode: do not produce signals, and pass eight-bit characters.
X.TP
X.I R
XSet non-raw (``cooked'') mode (default).
X.TP
X.I s
XSet line editing modes appropriate for a screen (default).
X.TP
X.I S
XDo not set crt line editing modes.
X.TP
X.I 0
XAn abbreviation for pcEN.
X.RE
X.TP
X\fB\-x\fIsss
XUse security, experimental, or extended measures specified by
X.I s.
XSome of these may be required or disabled by your system administrator.
XPredefined values:
X.RS
X.TP 5
X.I c
XChange the ownership and protections of the pty appropriately.
XThis reflects several errors in the
X.I pty
Xmodel, but it's customary.
X.TP
X.I C
XDo not change pty ownership (default).
X.TP
X.I e
XOpen stderr write-only to the pseudo-terminal.
XThis should be default, but such programs as
X.I csh
Xand
X.I more
Xinsist on reading from stderr and dying horribly
Xif they fail.
X.B\-xe
Xis useless under
X.B\-e.
X.TP
X.I E
XOpen stderr for reading and writing (default).
X.TP
X.I u
XEnter login name into /etc/utmp.
XAs a rule of thumb,
Xyou should do this for interactive sessions.
X.TP
X.I n
XUse some heuristics to try to figure out if someone
Xhas the pty open (default).
X.TP
X.I N
XDon't worry about pre-opened ptys.
X.TP
X.I o
XSame as
X.B\-xn,
Xbut go on to the next pseudo-terminal
Xif this one is open.
X.TP
X.I O
XDon't skip pre-opened ptys.
X.TP
X.I r
XRandom pseudo-terminal searching (default).
XThis can provide a huge boost to speed and security.
XIt hasn't been used because programmers don't realize
Xthe virtues of modularity, are consequently too lazy to
Xwrite something like
X.I pty,
Xand don't want to take the effort for random pty searching
Xin every program that uses pseudo-terminals.
X.TP
X.I R
XStraight pty searching, from the bottom on up.
X.TP
X.I s
XSetuid (default).
X.I pty 
Xwill use a common directory
Xfor storing session information.
X.TP
X.I S
XNot setuid.
X.I pty
Xwill revoke all privileges and
Xuse a subdirectory of your HOME directory.
X.TP
X.I U
XDo not use utmp (default).
X.TP
X.I w
XMake an entry in /usr/adm/wtmp.
XThis probably isn't a good idea for general use,
Xas
Xconnection time recorded in
X.I wtmp
Xis often pressed into unfortunate service as
Xa senseless basis for charged computer time.
X.TP
X.I W
XDo not use wtmp (default).
X.TP
X.I x
XSet exclusive use on the pty.
XNo processes can open the pty after this;
X.I program
Xcan't even reopen
X/dev/tty!
X(It can use file descriptor 3 instead.)
XThis can be very important for security when
X.I pty
Xhas not been installed by the system administrator.
XIt should be set all the time, but
Xtoo many programs rely on a filename for the terminal.
X.TP
X.I X
XDo not set exclusive use (default).
X.RE
X.PP
X.SH DIAGNOSTICS
X.TP
Xvarious usage messages
XExit 1.
X.TP
X.I fatal: cannot find control terminal
X.I pty
Xis unable to find its current control terminal.
XExit 2.
X.TP
X.I fatal: cannot get current tty modes
XThis shouldn't happen.
XExit 3.
X.TP
X.I fatal: cannot set modes of original tty
XThis shouldn't happen.
XExit 4.
X.TP
X.I fatal: no ptys available
XSelf-explanatory.
XExit 5.
X.TP
X.I fatal: can't fcntl pty
X.TP
X.I fatal: slave unopenable
XThere's a serious problem with your pseudo-terminal setup.
XReport this error to your system administrator.
XExit 6.
X.TP
X.I fatal: cannot fork
X.I pty
Xhas run out of processes.
XExit 7.
X.TP
X.I fatal: cannot change to session directory
XSelf-explanatory.
XExit 8.
X.TP
X.I fatal: cannot open internal pipe.
XCannot happen.
XExit 10.
X.TP
X.I fatal: socket read error
XSelf-explanatory. Exit 11.
X.TP
X.I fatal: socket write error
XSelf-explanatory. Exit 12.
X.TP
X.I fatal: input read error
XSelf-explanatory. Exit 13.
X.TP
X.I fatal: output write error
XSelf-explanatory. Exit 14.
X.SH RESTRICTIONS
XThere are many security problems
Xand limitations associated with BSD-style ttys.
X.I pty
Xdoes its best to avoid them,
Xbut a Streams-based system would be much better.
XThe author plans to rewrite
X.I pty,
Xwith the same interface,
Xfor a Streams system.
X.PP
XThe current
X.B\-J
Xbehavior is a bit dull.
XI wish programs would use the job control
Xmechanisms more cleanly.
X.PP
XTo avoid a race condition,
X.I pty
Xchews up a tiny bit more CPU time than it should every
Xtime
X.I program
Xis stopped and then restarted.
X.PP
XIf
X.I program
Xcloses the pseudo-terminal but doesn't die,
X.I pty
Xwill wait for it, even though it will have no further interaction with
Xit.
X.PP
X.I pty
Xdoes not provide any way to loudly proclaim that
X.I program
Xdoesn't exist.
XIt simply dies quietly.
X.PP
XBecause of BSD's ridiculous controlling terminal mechanism,
Xa reconnecting
X.I pty
Xhas to
Xpass the name of its original terminal
Xto the session underneath.
XSince there is no portable way to find out that name,
X.I pty
Xrequires that some file descriptor be open, pointing
Xto the current terminal
X.I (not
X/dev/tty!).
X(More precisely,
Xthe highest-numbered file descriptor that
Xis a terminal file but not /dev/tty
Xmust be the real name of the original controlling terminal.
XThat's one good use for fd 3.
XIf that file descriptor is some other terminal,
Xthe reconnect will fail miserably.)
X.SH BUGS
XNone known, but they're probably hiding somewhere.
XIt is the author's opinion that
X.I pty
Xis the ``right'' way to handle pseudo-terminals;
Xif programmers use
X.I pty
Xinstead of writing equivalent code in each program,
Xthen everything becomes much more portable and bug-free.
XAs different systems provide different
Xpseudo-terminal mechanisms,
Xthe only program that need be changed is
X.I pty.
X(This is called ``modularity,''
X``interface design,''
Xor ``outside-in programming.'')
X.SH MACHINES
X.I pty
Xhas been tested thoroughly
Xon several BSD 4.3-based machines
Xand tested on
Xseveral BSD 4.2-based machines.
X.SH VERSION
Xpty version 3.001, dated August 21, 1990.
X.SH AUTHOR
XCopyright 1990, Daniel J. Bernstein.
X.SH "SEE ALSO"
Xpty(4),
Xtty(4)
END_OF_FILE
  if test 12049 -ne `wc -c <'pty.1'`; then
    echo shar: \"'pty.1'\" unpacked with wrong size!
  fi
  # end of 'pty.1'
fi
if test -f 'sigler.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'sigler.c'\"
else
  echo shar: Extracting \"'sigler.c'\" \(8120 characters\)
  sed "s/^X//" >'sigler.c' <<'END_OF_FILE'
X/* Copyright 1990, Daniel J. Bernstein. All rights reserved. */
X
X#include "config.h"
X#include <sys/types.h>
X#include <sys/time.h>
X#include <stdio.h>
X#include <strings.h>
X#include "pty.h"
X#include "sigler.h"
X#include "sig.h"
X#include "file.h"
X#include "sock.h"
X#include "err.h"
X#include "misc.h"
X
X/* General XXX: In failure cases, sig.* isn't always removed. */
X
Xstatic char *glfnsty;
X
Xstatic int masterpid;
X
Xstatic int flagmaster = 1;
Xstatic int flagcont = 0;
Xstatic int pi[2];
X
Xstatic void deatherrp(i,s)
Xint i;
Xchar *s;
X{
X int e = errno;
X
X (void) kill(masterpid,SIGTERM);
X /* XXX: should wait while flagmaster */
X (void) tty_modifymodes(fdtty,&tmotty,&tmochartty);
X fatalerrp(i,s,e);
X}
X
Xstatic void finish(i)
Xsig_num i;
X{
X if (i == SIGTERM)
X  {
X   flagmaster = 0;
X   (void) write(pi[1],".",1);
X  }
X else
X   (void) write(pi[1],",",1);
X}
X
X/*ARGSUSED*/
Xstatic void sig_usr2(i)
Xsig_num i;
X{
X if (flagsession)
X  {
X   char fnsess[20];
X   int fdsess;
X   int newuid = uid;
X   char newsuid[10];
X
X   /* XXX: We should have some error recovery here! */
X
X   /* We can make quite a few assumptions, because we only get USR2 */
X   /* forwarded from the master. */
X
X   (void) sprintf(fnsess,"sess.%s",glfnsty + sizeof(DEVSTY) - 3);
X   if ((fdsess = open(fnsess,O_RDONLY,0600)) != -1)
X    {
X     (void) read(fdsess,(char *) &newuid,sizeof(int));
X     (void) sprintf(newsuid,"%d",newuid);
X
X     (void) chdir("..");
X     (void) chdir(newsuid);
X  
X     uid = newuid;
X     (void) setreuid(uid,euid);
X     (void) close(fdsess);
X    }
X  }
X}
X
X/*ARGSUSED*/
Xstatic void sig_cont(i)
Xsig_num i;
X{
X flagcont = 1;
X (void) write(pi[1]," ",1);
X}
X
Xvoid sigler(fnsty,master)
Xchar *fnsty;
Xint master;
X{
X char ch;
X char path[100];
X int fd = -1;
X char *ttyn;
X char fntty[TTYNAMELEN];
X
X glfnsty = fnsty;
X masterpid = master;
X /* pid = getpid() is already true */
X (void) sprintf(path,"sig.%s",fnsty + sizeof(DEVSTY) - 3);
X (void) unlink(path);
X
X (void) close(fdmty);
X (void) close(fdsty);
X if (fdre != -1) (void) close(fdre);
X
X if (pipe(pi) == -1) /* clumsy, but stops several races */
X   /* This is absolutely impossible. fdmty and fdsty must have been open */
X   /* before this, and we just closed them, so there must be two fds */
X   /* available for the pipe. */
X   deatherrp(10,"pty: fatal: cannot open internal pipe");
X (void) fcntl(pi[1],F_SETFL,FNDELAY);
X
X sig_ignore(SIGCHLD);
X sig_ignore(SIGXCPU);
X sig_ignore(SIGXFSZ);
X sig_ignore(SIGPROF);
X sig_ignore(SIGVTALRM);
X
X sig_default(SIGEMT); /* XXX: really dump? */
X sig_default(SIGIOT);
X sig_default(SIGILL);
X sig_default(SIGSEGV);
X
X sig_default(SIGTTOU);
X sig_default(SIGTTIN);
X sig_default(SIGTSTP);
X sig_default(SIGSTOP);
X
X sig_sethandler(SIGTERM,finish); sig_handle(SIGTERM);
X sig_sethandler(SIGINT,finish); sig_handle(SIGINT);
X sig_sethandler(SIGQUIT,finish); sig_handle(SIGQUIT);
X sig_sethandler(SIGHUP,finish); sig_handle(SIGHUP);
X sig_sethandler(SIGUSR1,finish); sig_handle(SIGUSR1); /* disconnect */
X
X sig_sethandler(SIGCONT,sig_cont); sig_handle(SIGCONT);
X
X sig_sethandler(SIGUSR2,sig_usr2); sig_handle(SIGUSR2);
X
X for (;;)
X  {
X   ch = '0';
X   if (flagcont)
X    {
X     flagcont = 0;
X     if (tty_modifymodes(fdtty,&tmochartty,&tmotty) == -1)
X       ; /* XXX: impossible, but what if it happens? */
X     (void) kill(masterpid,SIGUSR1); /* not CONT---see master.c's sig_cont() */
X    }
X#ifdef NO_FDPASSING
X   if (flagmaster == 2)
X    {
X     static fd_set rfds;
X     static fd_set wfds;
X     static int r;
X     static int w;
X     static char foobuf[OUTBUFSIZE];
X     int fdnum;
X     static char *s;
X
X     fdnum = fd; if (fdin > fdnum) fdnum = fdin;
X     if (pi[0] > fdnum) fdnum = pi[0]; fdnum++;
X
X     FD_ZERO(&rfds);
X     FD_ZERO(&wfds);
X     FD_SET(fd,&rfds);
X     FD_SET(fdin,&rfds);
X     FD_SET(pi[0],&rfds);
X
X     r = select(fdnum,&rfds,&wfds,(fd_set *) 0,(struct timeval *) 0);
X     if (r > 0)
X      {
X       if (FD_ISSET(fd,&rfds))
X	{
X	 if ((r = read(fd,foobuf,OUTBUFSIZE)) == -1)
X	   deatherrp(11,"pty: fatal: socket read error");
X	 s = foobuf;
X	 /* XXX: r can't be zero, but what if it is? */
X	 while (r)
X	  {
X	   if ((w = write(fdout,s,r)) == -1)
X	     deatherrp(14,"pty: fatal: output write error");
X	   r -= w; s += w;
X	  }
X	}
X       if (FD_ISSET(fdin,&rfds))
X	{
X	 if ((r = read(fdin,foobuf,OUTBUFSIZE)) == -1)
X	   deatherrp(13,"pty: fatal: input read error");
X	 s = foobuf;
X	 /* XXX: What if r is zero? Can't pass EOF, grrrr */
X	 while (r)
X	  {
X	   if ((w = write(fd,s,r)) == -1)
X	     deatherrp(12,"pty: fatal: socket write error");
X	   r -= w; s += w;
X	  }
X	}
X       if (FD_ISSET(pi[0],&rfds))
X	 (void) read(pi[0],&ch,1);
X      }
X    }
X   else
X     (void) read(pi[0],&ch,1);
X#else
X   (void) read(pi[0],&ch,1);
X#endif
X   if ((ch == '.') || (ch == ','))
X    {
X     if (fd != -1)
X       (void) close(fd);
X     if (flagmaster)
X      {
X       if (kill(masterpid,SIGTERM) == -1)
X	 flagmaster = 0; /* XXX */
X
X       if (fdpass != -1)
X	 (void) write(fdpass,".",1);
X	
X       while (flagmaster)
X         ;
X       while (ch != '.')
X         (void) read(pi[0],&ch,1);
X      }
X
X     /* We don't test at this point whether the killing signal was a HUP. */
X     /* This means that hanging up on a reconnecting sigler won't stop */
X     /* the reconnect; instead, the new session will be instantly hung */
X     /* up. The USR1 used for a manual disconnect could be HUP for this */
X     /* reason. */
X     if (flagsession
X       &&((fd = open(path,O_RDONLY)) != -1)
X       &&(read(fd,fnsty,sizeof(DEVSTY) - 1) > 0))
X      {
X       warnerr2("pty: reconnecting to %s\r\n",fnsty);
X       (void) close(fd);
X       (void) unlink(path);
X       if ((fd = pty_writesock(fnsty)) == -1)
X	 warnerr2("pty: reconnect failed: cannot talk to %s\r\n",fnsty);
X       else
X	{
X	 if ((pty_getch(fd,&ch) != -1) && (ch == 'p'))
X	   if (pty_putgetint(fd,'p',&masterpid) != -1)
X	     do
X	      {
X#ifdef NO_FDPASSING
X               if (fdtty != -1)
X		{
X                 if (!(ttyn = real_ttyname(fdtty))) break;
X	         /* XXX: Should we NXCL here? */
X	         (void) strncpy(fntty,ttyn,TTYNAMELEN - 1);
X                 if (pty_sendstr(fd,'s',fntty) == -1) break;
X	         if (flagverbose)
X		   warnerr2("pty: sent parent tty %s\r\n",fntty);
X		}
X#else
X	       if (fdtty != -1)
X		{
X                 if (!(ttyn = real_ttyname(fdtty))) break;
X	         /* XXX: Should we NXCL here? */
X	         (void) strncpy(fntty,ttyn,TTYNAMELEN - 1);
X                 if (pty_sendstr(fd,'s',fntty) == -1) break;
X	         if (flagverbose)
X	  	   warnerr2("pty: sent parent tty %s\r\n",fntty);
X	         /* We shouldn't have to send the parent tty name, */
X	         /* but passing the fd alone doesn't set the control */
X	         /* terminal of the receiver (grrrr), and a detached */
X	         /* process effectively has no pgrp. Aargh. */
X		}
X
X	       if (fdpass == -1)
X		{
X	         if (pty_sendfd(fd,'0',&fdin) == -1) break;
X	         if (pty_sendfd(fd,'1',&fdout) == -1) break;
X		}
X	       else
X		 if (pty_sendfd(fd,'f',&fdpass) == -1) break;
X	       if (fdtty != -1)
X		 if (pty_sendfd(fd,'t',&fdtty) == -1) break;
X#endif
X	       if (pty_sendint(fd,'p',&pid) == -1) break;
X	       if (pty_sendint(fd,'g',&pgrp) == -1) break;
X	       if (pty_sendint(fd,'j',&flagjobctrl) == -1) break;
X	       if (fdtty != -1)
X		{
X		 if (pty_sendtty(fd,'c',&tmochartty) == -1) break;
X		 if (pty_sendtty(fd,'n',&tmotty) == -1) break;
X		}
X	       if (pty_putch(fd," ") == -1) break;
X#ifdef NO_FDPASSING
X	       flagmaster = 2; /* Success, but pain coming up. */
X#else
X	       flagmaster = 1; /* Successfully reconnected! */
X#endif
X	      }
X	     while(0);
X	 if (flagmaster < 2)
X	  {
X           (void) close(fd);
X	   fd = -1;
X	  }
X	}
X       if (flagmaster)
X	{
X	 /* remake path for new pty */
X         (void) sprintf(path,"sig.%s",fnsty + sizeof(DEVSTY) - 3);
X         (void) unlink(path);
X	 warnerr2("pty: successfully reconnected to %s\r\n",fnsty);
X	}
X       else
X	 warnerr2("pty: reconnect to %s failed\r\n",fnsty);
X      }
X     if (!flagmaster)
X      {
X       if (tty_modifymodes(fdtty,&tmotty,&tmochartty) == -1)
X         warnerr2("%s","pty: can't restore tty modes\n"); /*XXX*/
X       fatal(0);
X       /*NOTREACHED*/
X      }
X    }
X  }
X}
END_OF_FILE
  if test 8120 -ne `wc -c <'sigler.c'`; then
    echo shar: \"'sigler.c'\" unpacked with wrong size!
  fi
  # end of 'sigler.c'
fi
if test -f 'sock.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'sock.c'\"
else
  echo shar: Extracting \"'sock.c'\" \(5474 characters\)
  sed "s/^X//" >'sock.c' <<'END_OF_FILE'
X/* Copyright 1990, Daniel J. Bernstein. All rights reserved. */
X
X#include "config.h"
X#include <sys/types.h>
X#include <sys/time.h>
X#ifndef NO_UNIXSOCKS
X#include <sys/socket.h>
X#include <sys/un.h>
X#ifndef NO_FDPASSING
X#include <sys/uio.h>
X#endif
X#include <stdio.h>
X#include <strings.h>
X#endif
X#include "sock.h"
X#include "tty.h"
X#include "err.h"
X
Xstatic int bufwrite(fd,buf,num)
Xint fd;
Xchar *buf;
Xint num;
X{
X int r;
X
X do
X  {
X   r = write(fd,buf,num);
X   if (r > 0)
X    {
X     buf += r;
X     num -= r;
X    }
X  }
X while ((num > 0) && ((r != -1) || (errno == EINTR)));
X return (r >= 0) ? 0 : -1;
X}
X
Xstatic int bufread(fd,buf,num)
Xint fd;
Xchar *buf;
Xint num;
X{
X int r;
X
X do
X  {
X   r = read(fd,buf,num);
X   if (r > 0)
X    {
X     buf += r;
X     num -= r;
X    }
X  }
X while ((num > 0) && ((r != -1) || (errno == EINTR)));
X /* Note that we ignore EOF. */
X return (r >= 0) ? 0 : -1;
X}
X
Xint pty_readsock(line,path)
Xchar *line;
Xchar *path;
X{
X#ifdef NO_UNIXSOCKS
X return -1;
X#else
X int s;
X struct sockaddr_un sa;
X
X if ((s = socket(AF_UNIX,SOCK_STREAM,0)) == -1)
X   return -1;
X sa.sun_family = AF_UNIX;
X (void) sprintf(sa.sun_path,"re.%s",line + sizeof(DEVSTY) - 3);
X (void) strcpy(path,sa.sun_path);
X (void) unlink(sa.sun_path);
X if (bind(s,(struct sockaddr *) &sa,strlen(sa.sun_path) + 2) == -1)
X   return -1;
X if (listen(s,5) == -1)
X   return -1;
X return s;
X#endif
X}
X
Xint pty_writesock(line)
Xchar *line;
X{
X#ifdef NO_UNIXSOCKS
X return -1;
X#else
X int s;
X struct sockaddr_un sa;
X
X if ((s = socket(AF_UNIX,SOCK_STREAM,0)) == -1)
X   return -1;
X sa.sun_family = AF_UNIX;
X (void) sprintf(sa.sun_path,"re.%s",line + sizeof(DEVSTY) - 3);
X if (connect(s,(struct sockaddr *) &sa,strlen(sa.sun_path) + 2) == -1)
X   return -1;
X (void) unlink(sa.sun_path);
X return s;
X#endif
X}
X
Xint pty_acceptsock(fd)
Xint fd;
X{
X#ifdef NO_UNIXSOCKS
X return -1;
X#else
X struct sockaddr_un sa;
X int salen = sizeof(sa);
X
X return accept(fd,(struct sockaddr *) &sa,&salen);
X#endif
X}
X
Xint pty_putgetonefd(fd,fp)
Xint fd;
Xint *fp;
X{
X#ifdef NO_FDPASSING
X return -1;
X#else
X struct msghdr msg[2];
X int acc[5];
X struct iovec i[2];
X
X msg[0].msg_name = 0;
X msg[0].msg_namelen = 0;
X msg[0].msg_iov = &i[0]; /* grrrr */
X msg[0].msg_iovlen = 0;
X msg[0].msg_accrights = (caddr_t) acc;
X msg[0].msg_accrightslen = 5 * sizeof(int);
X#ifdef USLEEP
X (void) usleep((unsigned) 100000);
X#else
X (void) sleep(1); /* XXX: work around fd passing bug */
X#endif
X if (recvmsg(fd,msg,0) == -1)
X   return -1;
X if (msg[0].msg_accrightslen != sizeof(int))
X   return -1;
X if (*fp != -1)
X   (void) close(*fp);
X *fp = acc[0]; /* yay! we've passed a file descriptor! */
X return 0;
X#endif
X}
X
Xint pty_putgetfd(fd,ch,fp)
Xint fd;
Xchar ch;
Xint *fp;
X{
X#ifdef NO_FDPASSING
X return -1;
X#else
X if (bufwrite(fd,&ch,1) < 0)
X   return -1;
X if (pty_putgetonefd(fd,fp) < 0)
X   return -1;
X if (bufwrite(fd,".",1) < 0)
X   return -1;
X return 0;
X#endif
X}
X
Xint pty_putgetint(fd,ch,ip)
Xint fd;
Xchar ch;
Xint *ip;
X{
X if (bufwrite(fd,&ch,1) < 0)
X   return -1;
X if (bufread(fd,(char *) ip,sizeof(int)) < 0)
X   return -1;
X if (bufwrite(fd,".",1) < 0)
X   return -1;
X return 0;
X}
X
Xint pty_putgetstr(fd,ch,str)
Xint fd;
Xchar ch;
Xchar str[TTYNAMELEN];
X{
X if (bufwrite(fd,&ch,1) < 0)
X   return -1;
X if (bufread(fd,str,TTYNAMELEN) < 0)
X   return -1;
X if (bufwrite(fd,".",1) < 0)
X   return -1;
X return 0;
X}
X
Xint pty_putgettty(fd,ch,tmo)
Xint fd;
Xchar ch;
Xstruct ttymodes *tmo;
X{
X if (bufwrite(fd,&ch,1) < 0)
X   return -1;
X if (bufread(fd,(char *) tmo,sizeof(struct ttymodes)) < 0)
X   return -1;
X if (bufwrite(fd,".",1) < 0)
X   return -1;
X return 0;
X}
X
Xint pty_sendonefd(fd,fp)
Xint fd;
Xint *fp;
X{
X#ifdef NO_FDPASSING
X return -1;
X#else
X struct msghdr msg[2];
X int acc[5]; /* or just 5? or just 1? who cares */
X struct iovec i[2];
X
X msg[0].msg_name = 0;
X msg[0].msg_namelen = 0;
X msg[0].msg_iov = i; /* grrrr */
X msg[0].msg_iovlen = 0;
X msg[0].msg_accrights = (caddr_t) acc;
X msg[0].msg_accrightslen = sizeof(int);
X acc[0] = *fp;
X if (sendmsg(fd,&msg[0],0) == -1)
X   return -1;
X /* yay! we've passed a file descriptor! */
X return 0;
X#endif
X}
X
Xint pty_sendfd(fd,ch,fp)
Xint fd;
Xchar ch;
Xint *fp;
X{
X#ifdef NO_FDPASSING
X return -1;
X#else
X if (bufwrite(fd,&ch,1) < 0)
X   return -1;
X if (bufread(fd,&ch,1) < 0)
X   return -1;
X if (ch == ' ')
X   return 1;
X if (pty_sendonefd(fd,fp) < 0)
X   return -1;
X if (bufread(fd,&ch,1) < 0)
X   return -1;
X if (ch != '.')
X   return 1;
X return 0;
X#endif
X}
X
Xint pty_sendint(fd,ch,ip)
Xint fd;
Xchar ch;
Xint *ip;
X{
X if (bufwrite(fd,&ch,1) < 0)
X   return -1;
X if (bufread(fd,&ch,1) < 0)
X   return -1;
X if (ch == ' ')
X   return 1;
X if (bufwrite(fd,(char *) ip,sizeof(int)) < 0)
X   return -1;
X if (bufread(fd,&ch,1) < 0)
X   return -1;
X if (ch != '.')
X   return 1;
X return 0;
X}
X
Xint pty_sendstr(fd,ch,str)
Xint fd;
Xchar ch;
Xchar str[TTYNAMELEN];
X{
X if (bufwrite(fd,&ch,1) < 0)
X   return -1;
X if (bufread(fd,&ch,1) < 0)
X   return -1;
X if (ch == ' ')
X   return 1;
X if (bufwrite(fd,str,TTYNAMELEN) < 0)
X   return -1;
X if (bufread(fd,&ch,1) < 0)
X   return -1;
X if (ch != '.')
X   return 1;
X return 0;
X}
X
Xint pty_sendtty(fd,ch,tmo)
Xint fd;
Xchar ch;
Xstruct ttymodes *tmo;
X{
X if (bufwrite(fd,&ch,1) < 0)
X   return -1;
X if (bufread(fd,&ch,1) < 0)
X   return -1;
X if (ch == ' ')
X   return 1;
X if (bufwrite(fd,(char *) tmo,sizeof(struct ttymodes)) < 0)
X   return -1;
X if (bufread(fd,&ch,1) < 0)
X   return -1;
X if (ch != '.')
X   return 1;
X return 0;
X}
X
Xint pty_putch(fd,ch)
Xint fd;
Xchar *ch;
X{
X return bufwrite(fd,ch,1);
X}
X
Xint pty_getch(fd,ch)
Xint fd;
Xchar *ch;
X{
X return bufread(fd,ch,1);
X}
END_OF_FILE
  if test 5474 -ne `wc -c <'sock.c'`; then
    echo shar: \"'sock.c'\" unpacked with wrong size!
  fi
  # end of 'sock.c'
fi
if test -f 'tty.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'tty.c'\"
else
  echo shar: Extracting \"'tty.c'\" \(8009 characters\)
  sed "s/^X//" >'tty.c' <<'END_OF_FILE'
X/* Copyright 1990, Daniel J. Bernstein. All rights reserved. */
X
X#include "config.h"
X#include <sys/ioctl.h>
X#include "file.h"
X#include "err.h"
X#include "tty.h"
X
X#define generic GENERIC *
X
Xstatic int ioc(fd,req,arg) /* non-interruptable ioctl */
Xint fd;
Xunsigned long req;
Xgeneric arg;
X{
X int result;
X
X do
X   result = ioctl(fd,req,(char *) arg);
X while ((result == -1) && (errno == EINTR));
X return result;
X}
X
Xint tty_getctrl()
X{
X int fd;
X int dummy;
X
X#define ISTTY(f) (ioc(f,(unsigned long) TIOCGPGRP,(generic) &dummy) == 0)
X
X if (ISTTY(3))
X   return 3;
X if (((fd = open("/dev/tty",O_RDWR)) != -1) && ISTTY(fd))
X   return fd;
X if (((fd = dup(0)) != -1) && ISTTY(fd))
X   return fd;
X if (((fd = dup(1)) != -1) && ISTTY(fd))
X   return fd;
X return -1;
X}
X
Xtty_setexcl(fd)
Xint fd;
X{
X return ioc(fd,(unsigned long) TIOCEXCL,(generic) 0);
X /* setting exclusive use is a bit unusual but it works */
X /* opening /dev/tty should still be allowed, though */
X}
X
Xtty_setpgrp(fd,pgrp)
Xint fd;
Xint pgrp;
X{
X return ioc(fd,(unsigned long) TIOCSPGRP,(generic) &pgrp);
X}
X
Xtty_dissoc(fd)
Xint fd;
X{
X return ioc(fd,(unsigned long) TIOCNOTTY,(generic) 0);
X}
X
Xtty_getmodes(fd,tmo)
Xint fd;
Xstruct ttymodes *tmo;
X{
X if (ioc(fd,(unsigned long) TIOCGETD,(generic) &(tmo->di)) == -1) return -1;
X if (ioc(fd,(unsigned long) TIOCGETP,(generic) &(tmo->sg)) == -1) return -1;
X if (ioc(fd,(unsigned long) TIOCGETC,(generic) &(tmo->tc)) == -1) return -1;
X if (ioc(fd,(unsigned long) TIOCLGET,(generic) &(tmo->lb)) == -1) return -1;
X if (ioc(fd,(unsigned long) TIOCGLTC,(generic) &(tmo->lt)) == -1) return -1;
X#ifdef TTY_WINDOWS
X if (ioc(fd,(unsigned long) TIOCGWINSZ,(generic) &(tmo->ws)) == -1) return -1;
X#endif
X#ifdef TTY_AUXCHARS
X if (ioc(fd,(unsigned long) TIOCGAUXC,(generic) &(tmo->au)) == -1) return -1;
X#endif
X return 0;
X}
X
Xtty_setmodes(fd,tmo)
Xint fd;
Xstruct ttymodes *tmo;
X{
X if (ioc(fd,(unsigned long) TIOCSETD,(generic) &(tmo->di)) == -1) return -1;
X if (ioc(fd,(unsigned long) TIOCSETP,(generic) &(tmo->sg)) == -1) return -1;
X if (ioc(fd,(unsigned long) TIOCSETC,(generic) &(tmo->tc)) == -1) return -1;
X if (ioc(fd,(unsigned long) TIOCLSET,(generic) &(tmo->lb)) == -1) return -1;
X if (ioc(fd,(unsigned long) TIOCSLTC,(generic) &(tmo->lt)) == -1) return -1;
X#ifdef TTY_WINDOWS
X if (ioc(fd,(unsigned long) TIOCSWINSZ,(generic) &(tmo->ws)) == -1) return -1;
X#endif
X#ifdef TTY_AUXCHARS
X if (ioc(fd,(unsigned long) TIOCSAUXC,(generic) &(tmo->au)) == -1) return -1;
X#endif
X return 0;
X}
X
Xtty_modifymodes(fd,tmonew,tmoold)
Xint fd;
Xstruct ttymodes *tmonew;
Xstruct ttymodes *tmoold;
X{
X if (tmonew->di != tmoold->di)
X   if (ioc(fd,(unsigned long) TIOCSETD,(generic) &(tmonew->di)) == -1) return -1;
X if ((tmonew->sg.sg_flags != tmoold->sg.sg_flags)
X   ||(tmonew->sg.sg_ispeed != tmoold->sg.sg_ispeed)
X   ||(tmonew->sg.sg_ospeed != tmoold->sg.sg_ospeed)
X   ||(tmonew->sg.sg_erase != tmoold->sg.sg_erase)
X   ||(tmonew->sg.sg_kill != tmoold->sg.sg_kill))
X   if (ioc(fd,(unsigned long) TIOCSETP,(generic) &(tmonew->sg)) == -1) return -1;
X if ((tmonew->tc.t_intrc != tmoold->tc.t_intrc)
X   ||(tmonew->tc.t_quitc != tmoold->tc.t_quitc)
X   ||(tmonew->tc.t_startc != tmoold->tc.t_startc)
X   ||(tmonew->tc.t_stopc != tmoold->tc.t_stopc)
X   ||(tmonew->tc.t_eofc != tmoold->tc.t_eofc)
X   ||(tmonew->tc.t_brkc != tmoold->tc.t_brkc))
X   if (ioc(fd,(unsigned long) TIOCSETC,(generic) &(tmonew->tc)) == -1) return -1;
X if (tmonew->lb != tmoold->lb)
X   if (ioc(fd,(unsigned long) TIOCLSET,(generic) &(tmonew->lb)) == -1) return -1;
X if ((tmonew->lt.t_suspc != tmoold->lt.t_suspc)
X   ||(tmonew->lt.t_dsuspc != tmoold->lt.t_dsuspc)
X   ||(tmonew->lt.t_rprntc != tmoold->lt.t_rprntc)
X   ||(tmonew->lt.t_flushc != tmoold->lt.t_flushc)
X   ||(tmonew->lt.t_werasc != tmoold->lt.t_werasc)
X   ||(tmonew->lt.t_lnextc != tmoold->lt.t_lnextc))
X   if (ioc(fd,(unsigned long) TIOCSLTC,(generic) &(tmonew->lt)) == -1) return -1;
X#ifdef TTY_WINDOWS
X if ((tmonew->ws.ws_xpixel != tmoold->ws.ws_xpixel)
X   ||(tmonew->ws.ws_ypixel != tmoold->ws.ws_ypixel)
X   ||(tmonew->ws.ws_row != tmoold->ws.ws_row)
X   ||(tmonew->ws.ws_col != tmoold->ws.ws_col))
X   if (ioc(fd,(unsigned long)TIOCSWINSZ,(generic)&(tmonew->ws))==-1) return -1;
X#endif
X#ifdef TTY_AUXCHARS
X if ((tmonew->au.t_usemap != tmoold->au.t_usemap)
X   ||(tmonew->au.t_usest != tmoold->au.t_usest))
X   if (ioc(fd,(unsigned long)TIOCSAUXC,(generic)&(tmonew->au))==-1) return -1;
X#endif
X return 0;
X}
X
X/* XXX: Should use copy() for this. */
Xvoid tty_copymodes(tmonew,tmoold)
Xstruct ttymodes *tmonew;
Xstruct ttymodes *tmoold;
X{
X tmonew->di = tmoold->di;
X tmonew->sg.sg_ispeed = tmoold->sg.sg_ispeed;
X tmonew->sg.sg_ospeed = tmoold->sg.sg_ospeed;
X tmonew->sg.sg_erase = tmoold->sg.sg_erase;
X tmonew->sg.sg_kill = tmoold->sg.sg_kill;
X tmonew->sg.sg_flags = tmoold->sg.sg_flags;
X tmonew->tc.t_intrc = tmoold->tc.t_intrc;
X tmonew->tc.t_quitc = tmoold->tc.t_quitc;
X tmonew->tc.t_startc = tmoold->tc.t_startc;
X tmonew->tc.t_stopc = tmoold->tc.t_stopc;
X tmonew->tc.t_eofc = tmoold->tc.t_eofc;
X tmonew->tc.t_brkc = tmoold->tc.t_brkc;
X tmonew->lb = tmoold->lb;
X tmonew->lt.t_suspc = tmoold->lt.t_suspc;
X tmonew->lt.t_dsuspc = tmoold->lt.t_dsuspc;
X tmonew->lt.t_rprntc = tmoold->lt.t_rprntc;
X tmonew->lt.t_flushc = tmoold->lt.t_flushc;
X tmonew->lt.t_werasc = tmoold->lt.t_werasc;
X tmonew->lt.t_lnextc = tmoold->lt.t_lnextc;
X#ifdef TTY_WINDOWS
X tmonew->ws.ws_xpixel = tmoold->ws.ws_xpixel;
X tmonew->ws.ws_ypixel = tmoold->ws.ws_ypixel;
X tmonew->ws.ws_row = tmoold->ws.ws_row;
X tmonew->ws.ws_col = tmoold->ws.ws_col;
X#endif
X#ifdef TTY_AUXCHARS
X tmonew->au.t_usest = tmoold->au.t_usest;
X tmonew->au.t_usemap = tmoold->au.t_usemap;
X#endif
X}
X
Xvoid tty_copywin(tmonew,tmoold)
Xstruct ttymodes *tmonew;
Xstruct ttymodes *tmoold;
X{
X ;
X#ifdef TTY_WINDOWS
X tmonew->ws.ws_xpixel = tmoold->ws.ws_xpixel;
X tmonew->ws.ws_ypixel = tmoold->ws.ws_ypixel;
X tmonew->ws.ws_row = tmoold->ws.ws_row;
X tmonew->ws.ws_col = tmoold->ws.ws_col;
X#endif
X}
X
Xvoid tty_charmode(tmo)
Xstruct ttymodes *tmo;
X{
X tty_mungemodes(tmo,3,0,2,0,3,0);
X}
X
Xvoid tty_mungemodes(tmo,cbreak,new,echo,crmod,raw,crt)
Xstruct ttymodes *tmo;
Xint cbreak;
Xint new;
Xint echo;
Xint crmod;
Xint raw;
Xint crt;
X{
X if (new >= 2)
X   tmo->di = ((new == 3) ? NTTYDISC : OTTYDISC);
X if (crmod >= 2)
X   tmo->sg.sg_flags = (tmo->sg.sg_flags & ~CRMOD) | (CRMOD * (crmod == 3));
X if (echo >= 2)
X   tmo->sg.sg_flags = (tmo->sg.sg_flags & ~ECHO) | (ECHO * (echo == 3));
X if (raw >= 2)
X   tmo->sg.sg_flags = (tmo->sg.sg_flags & ~RAW) | (RAW * (raw == 3));
X if (cbreak >= 2)
X   tmo->sg.sg_flags = (tmo->sg.sg_flags & ~CBREAK) | (CBREAK * (cbreak == 3));
X if (crt >= 2)
X   tmo->lb = (tmo->lb & ~(CRTBS | CRTERA | CRTKIL | CTLECH))
X                      | ((CRTBS | CRTERA | CRTKIL | CTLECH) * (crt == 3));
X}
X
Xvoid tty_initmodes(tmo,cbreak,new,echo,crmod,raw,crt)
Xstruct ttymodes *tmo;
Xint cbreak;
Xint new;
Xint echo;
Xint crmod;
Xint raw;
Xint crt;
X{
X /* Here we specify Ye Standard BSD Terminal Settings. */
X
X tmo->di = ((new % 2) ? NTTYDISC : OTTYDISC);
X tmo->sg.sg_ispeed = EXTB;
X tmo->sg.sg_ospeed = EXTB;
X tmo->sg.sg_erase = 127; /* del */
X tmo->sg.sg_kill = 21; /* ^U */
X tmo->sg.sg_flags = EVENP | ODDP | XTABS
X   | (CRMOD * (crmod % 2)) | (ECHO * (echo % 2))
X   | (RAW * (raw % 2)) | (CBREAK * (cbreak % 2));
X tmo->tc.t_intrc = 3; /* ^C */
X tmo->tc.t_quitc = 28; /* ^\ */
X tmo->tc.t_startc = 17; /* ^Q */
X tmo->tc.t_stopc = 19; /* ^S */
X tmo->tc.t_eofc = 4; /* ^D */
X tmo->tc.t_brkc = -1; /* undef */
X tmo->lb = ((CRTBS | CRTERA | CRTKIL | CTLECH) * (crt % 2)) | DECCTQ;
X tmo->lt.t_suspc = 26; /* ^Z */
X tmo->lt.t_dsuspc = 25; /* ^Y */
X tmo->lt.t_rprntc = 18; /* ^R */
X tmo->lt.t_flushc = 15; /* ^O */
X tmo->lt.t_werasc = 23; /* ^W */
X tmo->lt.t_lnextc = 22; /* ^V */
X#ifdef TTY_WINDOWS
X tmo->ws.ws_xpixel = 0; /* Or read from TERMCAP? Hmmm */
X tmo->ws.ws_ypixel = 0;
X tmo->ws.ws_row = 0;
X tmo->ws.ws_col = 0;
X#endif
X#ifdef TTY_AUXCHARS
X tmo->au.t_usest = 20; /* ^T */
X tmo->au.t_usemap = UST_LOAD1 | UST_LOAD5 | UST_LOAD15 | UST_RAWCPU
X   | UST_UPTIME | UST_PGRP | UST_CHILDS | UST_PCPU | UST_STATE;
X#endif
X}
END_OF_FILE
  if test 8009 -ne `wc -c <'tty.c'`; then
    echo shar: \"'tty.c'\" unpacked with wrong size!
  fi
  # end of 'tty.c'
fi
if test -f 'util/Makefile' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'util/Makefile'\"
else
  echo shar: Extracting \"'util/Makefile'\" \(4818 characters\)
  sed "s/^X//" >'util/Makefile' <<'END_OF_FILE'
XCC=cc
XCCOPTS=-O2 -s
X
XNROFF=nroff
XNROFFOPTS=-man
X
X# This Makefile is exceedingly boring. Then again, it's portable.
X
Xdefault: all
X
Xall: progs mans
X
Xprogs: excloff exclon lock tiocsti who u biff mesg tty write wall sessuser sessname sesskill sesslist disconnect reconnect xsessuser xsessname xsesskill xsesslist xdisconnect xreconnect sessutil.o xsessutil.o
X
Xmans: excloff.man exclon.man lock.man tiocsti.man who.man u.man biff.man mesg.man tty.man write.man wall.man sessuser.man sessname.man sesskill.man sesslist.man disconnect.man reconnect.man script.man script.tidy.man condom.man sess.man
X
Xexcloff: excloff.c Makefile
X	$(CC) $(CCOPTS) -o excloff excloff.c
X
Xexclon: exclon.c Makefile
X	$(CC) $(CCOPTS) -o exclon exclon.c
X
Xlock: lock.c Makefile
X	$(CC) $(CCOPTS) -o lock lock.c -lcurses -ltermcap
X
Xtiocsti: tiocsti.c Makefile
X	$(CC) $(CCOPTS) -o tiocsti tiocsti.c
X
Xwho: who.c Makefile
X	$(CC) $(CCOPTS) -o who who.c
X
Xu: u.c Makefile
X	$(CC) $(CCOPTS) -o u u.c
X
Xtty: tty.c Makefile
X	$(CC) $(CCOPTS) -o tty tty.c
X
Xwrite: write.c Makefile
X	$(CC) $(CCOPTS) -o write write.c
X
Xwall: wall.c Makefile
X	$(CC) $(CCOPTS) -o wall wall.c
X
Xbiff: biff.o sessutil.o Makefile
X	$(CC) $(CCOPTS) -o biff biff.o sessutil.o
X
Xmesg: mesg.o sessutil.o Makefile
X	$(CC) $(CCOPTS) -o mesg mesg.o sessutil.o
X
Xsessuser: sessuser.o sessutil.o Makefile
X	$(CC) $(CCOPTS) -o sessuser sessuser.o sessutil.o
X
Xxsessuser: sessuser.o xsessutil.o Makefile
X	$(CC) $(CCOPTS) -o xsessuser sessuser.o xsessutil.o
X
Xsessname: sessname.o sessutil.o Makefile
X	$(CC) $(CCOPTS) -o sessname sessname.o sessutil.o
X
Xxsessname: sessname.o xsessutil.o Makefile
X	$(CC) $(CCOPTS) -o xsessname sessname.o xsessutil.o
X
Xsesskill: sesskill.o sessutil.o Makefile
X	$(CC) $(CCOPTS) -o sesskill sesskill.o sessutil.o
X
Xxsesskill: sesskill.o xsessutil.o Makefile
X	$(CC) $(CCOPTS) -o xsesskill sesskill.o xsessutil.o
X
Xsesslist: sesslist.o sessutil.o Makefile
X	$(CC) $(CCOPTS) -o sesslist sesslist.o sessutil.o
X
Xxsesslist: sesslist.o xsessutil.o Makefile
X	$(CC) $(CCOPTS) -o xsesslist sesslist.o xsessutil.o
X
Xreconnect: reconnect.o sessutil.o Makefile
X	$(CC) $(CCOPTS) -o reconnect reconnect.o sessutil.o
X
Xxreconnect: reconnect.o xsessutil.o Makefile
X	$(CC) $(CCOPTS) -o xreconnect reconnect.o xsessutil.o
X
Xdisconnect: disconnect.o sessutil.o Makefile
X	$(CC) $(CCOPTS) -o disconnect disconnect.o sessutil.o
X
Xxdisconnect: disconnect.o xsessutil.o Makefile
X	$(CC) $(CCOPTS) -o xdisconnect disconnect.o xsessutil.o
X
Xbiff.o: biff.c sessutil.h Makefile
X	$(CC) $(CCOPTS) -c biff.c
X
Xmesg.o: mesg.c sessutil.h Makefile
X	$(CC) $(CCOPTS) -c mesg.c
X
Xsessuser.o: sessuser.c sessutil.h Makefile
X	$(CC) $(CCOPTS) -c sessuser.c
X
Xsessname.o: sessname.c sessutil.h Makefile
X	$(CC) $(CCOPTS) -c sessname.c
X
Xsesskill.o: sesskill.c sessutil.h Makefile
X	$(CC) $(CCOPTS) -c sesskill.c
X
Xsesslist.o: sesslist.c sessutil.h Makefile
X	$(CC) $(CCOPTS) -c sesslist.c
X
Xdisconnect.o: disconnect.c sessutil.h Makefile
X	$(CC) $(CCOPTS) -c disconnect.c
X
Xreconnect.o: reconnect.c sessutil.h Makefile
X	$(CC) $(CCOPTS) -c reconnect.c
X
Xxsessutil.o: xsessutil.c sessutil.h Makefile
X	$(CC) $(CCOPTS) -c xsessutil.c
X
Xsessutil.o: sessutil.c sessutil.h Makefile
X	$(CC) $(CCOPTS) -c sessutil.c
X
Xexcloff.man: excloff.1 Makefile
X	$(NROFF) $(NROFFOPTS) < excloff.1 > excloff.man
X
Xexclon.man: exclon.1 Makefile
X	$(NROFF) $(NROFFOPTS) < exclon.1 > exclon.man
X
Xlock.man: lock.1 Makefile
X	$(NROFF) $(NROFFOPTS) < lock.1 > lock.man
X
Xtiocsti.man: tiocsti.1 Makefile
X	$(NROFF) $(NROFFOPTS) < tiocsti.1 > tiocsti.man
X
Xwho.man: who.1 Makefile
X	$(NROFF) $(NROFFOPTS) < who.1 > who.man
X
Xbiff.man: biff.1 Makefile
X	$(NROFF) $(NROFFOPTS) < biff.1 > biff.man
X
Xmesg.man: mesg.1 Makefile
X	$(NROFF) $(NROFFOPTS) < mesg.1 > mesg.man
X
Xu.man: u.1 Makefile
X	$(NROFF) $(NROFFOPTS) < u.1 > u.man
X
Xtty.man: tty.1 Makefile
X	$(NROFF) $(NROFFOPTS) < tty.1 > tty.man
X
Xwrite.man: write.1 Makefile
X	$(NROFF) $(NROFFOPTS) < write.1 > write.man
X
Xwall.man: wall.1 Makefile
X	$(NROFF) $(NROFFOPTS) < wall.1 > wall.man
X
Xsess.man: sess.1 Makefile
X	$(NROFF) $(NROFFOPTS) < sess.1 > sess.man
X
Xsessuser.man: sessuser.1 Makefile
X	$(NROFF) $(NROFFOPTS) < sessuser.1 > sessuser.man
X
Xsessname.man: sessname.1 Makefile
X	$(NROFF) $(NROFFOPTS) < sessname.1 > sessname.man
X
Xsesskill.man: sesskill.1 Makefile
X	$(NROFF) $(NROFFOPTS) < sesskill.1 > sesskill.man
X
Xsesslist.man: sesslist.1 Makefile
X	$(NROFF) $(NROFFOPTS) < sesslist.1 > sesslist.man
X
Xcondom.man: condom.1 Makefile
X	$(NROFF) $(NROFFOPTS) < condom.1 > condom.man
X
Xscript.man: script.1 Makefile
X	$(NROFF) $(NROFFOPTS) < script.1 > script.man
X
Xscript.tidy.man: script.tidy.1 Makefile
X	$(NROFF) $(NROFFOPTS) < script.tidy.1 > script.tidy.man
X
Xreconnect.man: reconnect.1 Makefile
X	$(NROFF) $(NROFFOPTS) < reconnect.1 > reconnect.man
X
Xdisconnect.man: disconnect.1 Makefile
X	$(NROFF) $(NROFFOPTS) < disconnect.1 > disconnect.man
END_OF_FILE
  if test 4818 -ne `wc -c <'util/Makefile'`; then
    echo shar: \"'util/Makefile'\" unpacked with wrong size!
  fi
  # end of 'util/Makefile'
fi
echo shar: End of archive 3 \(of 6\).
cp /dev/null ark3isdone
MISSING=""
for I in 1 2 3 4 5 6 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 6 archives.
    rm -f ark[1-9]isdone
else
    echo You still must unpack the following archives:
    echo "        " ${MISSING}
fi
exit 0
exit 0 # Just in case...
-- 
Please send comp.sources.unix-related mail to rsalz at uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.



More information about the Comp.sources.unix mailing list