diff options
Diffstat (limited to 'gnu/libexec/uucp/libunix/serial.c')
| -rw-r--r-- | gnu/libexec/uucp/libunix/serial.c | 2991 |
1 files changed, 2991 insertions, 0 deletions
diff --git a/gnu/libexec/uucp/libunix/serial.c b/gnu/libexec/uucp/libunix/serial.c new file mode 100644 index 000000000000..cee90fcc7bb3 --- /dev/null +++ b/gnu/libexec/uucp/libunix/serial.c @@ -0,0 +1,2991 @@ +/* serial.c + The serial port communication routines for Unix. + + Copyright (C) 1991, 1992 Ian Lance Taylor + + This file is part of the Taylor UUCP package. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + The author of the program may be contacted at ian@airs.com or + c/o Infinity Development Systems, P.O. Box 520, Waltham, MA 02254. + */ + +#include "uucp.h" + +#if USE_RCS_ID +const char serial_rcsid[] = "$Id: serial.c,v 1.3 1994/02/07 23:47:51 ache Exp $"; +#endif + +#include "uudefs.h" +#include "uuconf.h" +#include "system.h" +#include "conn.h" +#include "sysdep.h" + +#include <errno.h> +#include <ctype.h> + +#if HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif + +#if HAVE_LIMITS_H +#include <limits.h> +#endif + +#if HAVE_TLI +#if HAVE_TIUSER_H +#include <tiuser.h> +#else /* ! HAVE_TIUSER_H */ +#if HAVE_XTI_H +#include <xti.h> +#endif /* HAVE_XTI_H */ +#endif /* ! HAVE_TIUSER_H */ +#endif /* HAVE_TLI */ + +#if HAVE_FCNTL_H +#include <fcntl.h> +#else +#if HAVE_SYS_FILE_H +#include <sys/file.h> +#endif +#endif + +#ifndef O_RDONLY +#define O_RDONLY 0 +#define O_WRONLY 1 +#define O_RDWR 2 +#endif + +#ifndef O_NOCTTY +#define O_NOCTTY 0 +#endif + +#ifndef FD_CLOEXEC +#define FD_CLOEXEC 1 +#endif + +#if HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif + +#if HAVE_BSD_TTY +#include <sys/time.h> +#if HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#endif + +#if HAVE_TIME_H +#if HAVE_SYS_TIME_AND_TIME_H || ! HAVE_BSD_TTY +#include <time.h> +#endif +#endif + +#if HAVE_STRIP_BUG && HAVE_BSD_TTY +#include <termio.h> +#endif + +#if HAVE_SVR4_LOCKFILES +/* Get the right definitions for major and minor. */ +#if MAJOR_IN_MKDEV +#include <sys/mkdev.h> +#endif /* MAJOR_IN_MKDEV */ +#if MAJOR_IN_SYSMACROS +#include <sys/sysmacros.h> +#endif /* MAJOR_IN_SYSMACROS */ +#if ! MAJOR_IN_MKDEV && ! MAJOR_IN_SYSMACROS +#ifndef major +#define major(i) (((i) >> 8) & 0xff) +#endif +#ifndef minor +#define minor(i) ((i) & 0xff) +#endif +#endif /* ! MAJOR_IN_MKDEV && ! MAJOR_IN_SYSMACROS */ +#endif /* HAVE_SVR4_LOCKFILES */ + +/* Get definitions for both O_NONBLOCK and O_NDELAY. */ +#ifndef O_NDELAY +#ifdef FNDELAY +#define O_NDELAY FNDELAY +#else /* ! defined (FNDELAY) */ +#define O_NDELAY 0 +#endif /* ! defined (FNDELAY) */ +#endif /* ! defined (O_NDELAY) */ + +#ifndef O_NONBLOCK +#ifdef FNBLOCK +#define O_NONBLOCK FNBLOCK +#else /* ! defined (FNBLOCK) */ +#define O_NONBLOCK 0 +#endif /* ! defined (FNBLOCK) */ +#endif /* ! defined (O_NONBLOCK) */ + +#if O_NDELAY == 0 && O_NONBLOCK == 0 + #error No way to do nonblocking I/O +#endif + +/* Get definitions for EAGAIN, EWOULDBLOCK and ENODATA. */ +#ifndef EAGAIN +#ifndef EWOULDBLOCK +#define EAGAIN (-1) +#define EWOULDBLOCK (-1) +#else /* defined (EWOULDBLOCK) */ +#define EAGAIN EWOULDBLOCK +#endif /* defined (EWOULDBLOCK) */ +#else /* defined (EAGAIN) */ +#ifndef EWOULDBLOCK +#define EWOULDBLOCK EAGAIN +#endif /* ! defined (EWOULDBLOCK) */ +#endif /* defined (EAGAIN) */ + +#ifndef ENODATA +#define ENODATA EAGAIN +#endif + +/* Make sure we have a definition for MAX_INPUT. */ +#ifndef MAX_INPUT +#define MAX_INPUT (256) +#endif + +/* If we have the TIOCSINUSE ioctl call, we use it to lock a terminal. + Otherwise, if we have the TIOCEXCL ioctl call, we have to open the + terminal before we know that it is unlocked. */ +#ifdef TIOCSINUSE +#define HAVE_TIOCSINUSE 1 +#else +#ifdef TIOCEXCL +#define HAVE_TIOCEXCL 1 +#endif +#endif + +#if HAVE_TLI +extern int t_errno; +extern char *t_errlist[]; +extern int t_nerr; +#endif + +/* Determine bits to clear for the various terminal control fields for + HAVE_SYSV_TERMIO and HAVE_POSIX_TERMIOS. */ +#if HAVE_SYSV_TERMIO +#define ICLEAR_IFLAG (IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK \ + | ISTRIP | INLCR | IGNCR | ICRNL | IUCLC \ + | IXON | IXANY | IXOFF) +#define ICLEAR_OFLAG (OPOST | OLCUC | ONLCR | OCRNL | ONOCR | ONLRET \ + | OFILL | OFDEL | NLDLY | CRDLY | TABDLY | BSDLY \ + | VTDLY | FFDLY) +#define ICLEAR_CFLAG (CBAUD | CLOCAL | CSIZE | PARENB | PARODD) +#define ISET_CFLAG (CS8 | CREAD | HUPCL) +#define ICLEAR_LFLAG (ISIG | ICANON | XCASE | ECHO | ECHOE | ECHOK \ + | ECHONL | NOFLSH) +#endif +#if HAVE_POSIX_TERMIOS +#ifdef IMAXBEL +#define CI_ADD1 IMAXBEL +#else +#define CI_ADD1 0 +#endif +#define ICLEAR_IFLAG (BRKINT | ICRNL | IGNBRK | IGNCR | IGNPAR \ + | INLCR | INPCK | ISTRIP | IXOFF | IXON \ + | PARMRK | CI_ADD1) +#define ICLEAR_OFLAG (OPOST) +#define ICLEAR_CFLAG (CLOCAL | CSIZE | PARENB | PARODD) +#define ISET_CFLAG (CS8 | CREAD | HUPCL) +#ifdef PENDIN +#define CL_ADD1 PENDIN +#else +#define CL_ADD1 0 +#endif +#define ICLEAR_LFLAG (ECHO | ECHOE | ECHOK | ECHONL | ICANON | IEXTEN \ + | ISIG | NOFLSH | TOSTOP | CL_ADD1) +#endif + +/* Local functions. */ + +static RETSIGTYPE usalarm P((int isig)); +static boolean fsserial_init P((struct sconnection *qconn, + const struct sconncmds *qcmds, + const char *zdevice)); +static void usserial_free P((struct sconnection *qconn)); +static boolean fsserial_lockfile P((boolean flok, + const struct sconnection *)); +static boolean fsserial_lock P((struct sconnection *qconn, + boolean fin)); +static boolean fsserial_unlock P((struct sconnection *qconn)); +static boolean fsserial_open P((struct sconnection *qconn, long ibaud, + boolean fwait)); +static boolean fsstdin_open P((struct sconnection *qconn, long ibaud, + boolean fwait)); +static boolean fsmodem_open P((struct sconnection *qconn, long ibaud, + boolean fwait)); +static boolean fsdirect_open P((struct sconnection *qconn, long ibaud, + boolean fwait)); +static boolean fsblock P((struct ssysdep_conn *q, boolean fblock)); +static boolean fsserial_close P((struct ssysdep_conn *q)); +static boolean fsstdin_close P((struct sconnection *qconn, + pointer puuconf, + struct uuconf_dialer *qdialer, + boolean fsuccess)); +static boolean fsmodem_close P((struct sconnection *qconn, + pointer puuconf, + struct uuconf_dialer *qdialer, + boolean fsuccess)); +static boolean fsdirect_close P((struct sconnection *qconn, + pointer puuconf, + struct uuconf_dialer *qdialer, + boolean fsuccess)); +static boolean fsserial_reset P((struct sconnection *qconn)); +static boolean fsstdin_reset P((struct sconnection *qconn)); +static boolean fsstdin_read P((struct sconnection *qconn, + char *zbuf, size_t *pclen, size_t cmin, + int ctimeout, boolean freport)); +static boolean fsstdin_write P((struct sconnection *qconn, + const char *zwrite, size_t cwrite)); +static boolean fsserial_break P((struct sconnection *qconn)); +static boolean fsstdin_break P((struct sconnection *qconn)); +static boolean fsserial_set P((struct sconnection *qconn, + enum tparitysetting tparity, + enum tstripsetting tstrip, + enum txonxoffsetting txonxoff)); +static boolean fsstdin_set P((struct sconnection *qconn, + enum tparitysetting tparity, + enum tstripsetting tstrip, + enum txonxoffsetting txonxoff)); +static boolean fsmodem_carrier P((struct sconnection *qconn, + boolean fcarrier)); +static boolean fsrun_chat P((int oread, int owrite, char **pzprog)); +static boolean fsstdin_chat P((struct sconnection *qconn, + char **pzprog)); +static long isserial_baud P((struct sconnection *qconn)); + +/* The command table for standard input ports. */ + +static const struct sconncmds sstdincmds = +{ + usserial_free, + NULL, /* pflock */ + NULL, /* pfunlock */ + fsstdin_open, + fsstdin_close, + fsstdin_reset, + NULL, /* pfdial */ + fsstdin_read, + fsstdin_write, + fsysdep_conn_io, + fsstdin_break, + fsstdin_set, + NULL, /* pfcarrier */ + fsstdin_chat, + isserial_baud +}; + +/* The command table for modem ports. */ + +static const struct sconncmds smodemcmds = +{ + usserial_free, + fsserial_lock, + fsserial_unlock, + fsmodem_open, + fsmodem_close, + fsserial_reset, + fmodem_dial, + fsysdep_conn_read, + fsysdep_conn_write, + fsysdep_conn_io, + fsserial_break, + fsserial_set, + fsmodem_carrier, + fsysdep_conn_chat, + isserial_baud +}; + +/* The command table for direct ports. */ + +static const struct sconncmds sdirectcmds = +{ + usserial_free, + fsserial_lock, + fsserial_unlock, + fsdirect_open, + fsdirect_close, + fsserial_reset, + NULL, /* pfdial */ + fsysdep_conn_read, + fsysdep_conn_write, + fsysdep_conn_io, + fsserial_break, + fsserial_set, + NULL, /* pfcarrier */ + fsysdep_conn_chat, + isserial_baud +}; + +/* If the system will let us set both O_NDELAY and O_NONBLOCK, we do + so. This is because some ancient drivers on some systems appear to + look for one but not the other. Some other systems will give an + EINVAL error if we attempt to set both, so we use a static global + to hold the value we want to set. If we get EINVAL, we change the + global and try again (if some system gives an error other than + EINVAL, the code will have to be modified). */ +static int iSunblock = O_NDELAY | O_NONBLOCK; + +/* This code handles SIGALRM. See the discussion above + fsysdep_conn_read. Normally we ignore SIGALRM, but the handler + will temporarily be set to this function, which should set fSalarm + and then either longjmp or schedule another SIGALRM. fSalarm is + never referred to outside of this file, but we don't make it static + to try to fool compilers which don't understand volatile. */ + +volatile sig_atomic_t fSalarm; + +static RETSIGTYPE +usalarm (isig) + int isig; +{ +#if ! HAVE_SIGACTION && ! HAVE_SIGVEC && ! HAVE_SIGSET + (void) signal (isig, usalarm); +#endif + + fSalarm = TRUE; + +#if HAVE_RESTARTABLE_SYSCALLS + longjmp (sSjmp_buf, 1); +#else + alarm (1); +#endif +} + +/* We need a simple routine to block SIGINT, SIGQUIT, SIGTERM and + SIGPIPE and another to restore the original state. When these + functions are called (in fsysdep_modem_close) SIGHUP is being + ignored. The routines are isblocksigs, which returns a value of + type HELD_SIG_MASK and usunblocksigs which takes a single argument + of type HELD_SIG_MASK. */ + +#if HAVE_SIGPROCMASK + +/* Use the POSIX sigprocmask call. */ + +#define HELD_SIG_MASK sigset_t + +static sigset_t isblocksigs P((void)); + +static sigset_t +isblocksigs () +{ + sigset_t sblock, sold; + + /* These expressions need an extra set of parentheses to avoid a bug + in SCO 3.2.2. */ + (void) (sigemptyset (&sblock)); + (void) (sigaddset (&sblock, SIGINT)); + (void) (sigaddset (&sblock, SIGQUIT)); + (void) (sigaddset (&sblock, SIGTERM)); + (void) (sigaddset (&sblock, SIGPIPE)); + + (void) sigprocmask (SIG_BLOCK, &sblock, &sold); + return sold; +} + +#define usunblocksigs(s) \ + ((void) sigprocmask (SIG_SETMASK, &(s), (sigset_t *) NULL)) + +#else /* ! HAVE_SIGPROCMASK */ +#if HAVE_SIGBLOCK + +/* Use the BSD sigblock and sigsetmask calls. */ + +#define HELD_SIG_MASK int + +#ifndef sigmask +#define sigmask(i) (1 << ((i) - 1)) +#endif + +#define isblocksigs() \ + sigblock (sigmask (SIGINT) | sigmask (SIGQUIT) \ + | sigmask (SIGTERM) | sigmask (SIGPIPE)) + +#define usunblocksigs(i) ((void) sigsetmask (i)) + +#else /* ! HAVE_SIGBLOCK */ + +#if HAVE_SIGHOLD + +/* Use the SVR3 sighold and sigrelse calls. */ + +#define HELD_SIG_MASK int + +static int isblocksigs P((void)); + +static int +isblocksigs () +{ + sighold (SIGINT); + sighold (SIGQUIT); + sighold (SIGTERM); + sighold (SIGPIPE); + return 0; +} + +static void usunblocksigs P((int)); + +/*ARGSUSED*/ +static void +usunblocksigs (i) + int i; +{ + sigrelse (SIGINT); + sigrelse (SIGQUIT); + sigrelse (SIGTERM); + sigrelse (SIGPIPE); +} + +#else /* ! HAVE_SIGHOLD */ + +/* We have no way to block signals. This system will suffer from a + race condition in fsysdep_modem_close. */ + +#define HELD_SIG_MASK int + +#define isblocksigs() 0 + +#define usunblocksigs(i) + +#endif /* ! HAVE_SIGHOLD */ +#endif /* ! HAVE_SIGBLOCK */ +#endif /* ! HAVE_SIGPROCMASK */ + +/* Initialize a connection for use on a serial port. */ + +static boolean +fsserial_init (qconn, qcmds, zdevice) + struct sconnection *qconn; + const struct sconncmds *qcmds; + const char *zdevice; +{ + struct ssysdep_conn *q; + + q = (struct ssysdep_conn *) xmalloc (sizeof (struct ssysdep_conn)); + if (zdevice == NULL + && qconn->qport != NULL + && qconn->qport->uuconf_ttype != UUCONF_PORTTYPE_STDIN) + zdevice = qconn->qport->uuconf_zname; + if (zdevice == NULL) + q->zdevice = NULL; + else if (*zdevice == '/') + q->zdevice = zbufcpy (zdevice); + else + { + size_t clen; + + clen = strlen (zdevice); + q->zdevice = zbufalc (sizeof "/dev/" + clen); + memcpy (q->zdevice, "/dev/", sizeof "/dev/" - 1); + memcpy (q->zdevice + sizeof "/dev/" - 1, zdevice, clen); + q->zdevice[sizeof "/dev/" + clen - 1] = '\0'; + } + q->o = -1; + q->ftli = FALSE; + qconn->psysdep = (pointer) q; + qconn->qcmds = qcmds; + return TRUE; +} + +/* Initialize a connection for use on standard input. */ + +boolean +fsysdep_stdin_init (qconn) + struct sconnection *qconn; +{ + return fsserial_init (qconn, &sstdincmds, (const char *) NULL); +} + +/* Initialize a connection for use on a modem port. */ + +boolean +fsysdep_modem_init (qconn) + struct sconnection *qconn; +{ + return fsserial_init (qconn, &smodemcmds, + qconn->qport->uuconf_u.uuconf_smodem.uuconf_zdevice); +} + +/* Initialize a connection for use on a direct port. */ + +boolean +fsysdep_direct_init (qconn) + struct sconnection *qconn; +{ + return fsserial_init (qconn, &sdirectcmds, + qconn->qport->uuconf_u.uuconf_sdirect.uuconf_zdevice); +} + +/* Free up a serial port. */ + +static void +usserial_free (qconn) + struct sconnection *qconn; +{ + struct ssysdep_conn *qsysdep; + + qsysdep = (struct ssysdep_conn *) qconn->psysdep; + ubuffree (qsysdep->zdevice); + xfree ((pointer) qsysdep); + qconn->psysdep = NULL; +} + +/* This routine is used for both locking and unlocking. It is the + only routine which knows how to translate a device name into the + name of a lock file. If it can't figure out a name, it does + nothing and returns TRUE. */ + +static boolean +fsserial_lockfile (flok, qconn) + boolean flok; + const struct sconnection *qconn; +{ + struct ssysdep_conn *qsysdep; + const char *z; + char *zalc; + boolean fret; + + qsysdep = (struct ssysdep_conn *) qconn->psysdep; + if (qconn->qport == NULL) + z = NULL; + else + z = qconn->qport->uuconf_zlockname; + zalc = NULL; + if (z == NULL) + { +#if ! HAVE_SVR4_LOCKFILES + { + const char *zbase; + size_t clen; + + zbase = strrchr (qsysdep->zdevice, '/') + 1; + clen = strlen (zbase); + zalc = zbufalc (sizeof "LCK.." + clen); + memcpy (zalc, "LCK..", sizeof "LCK.." - 1); + memcpy (zalc + sizeof "LCK.." - 1, zbase, clen + 1); +#if HAVE_SCO_LOCKFILES + { + char *zl; + + for (zl = zalc + sizeof "LCK.." - 1; *zl != '\0'; zl++) + if (isupper (*zl)) + *zl = tolower (*zl); + } +#endif + z = zalc; + } +#else /* ! HAVE_SVR4_LOCKFILES */ +#if HAVE_SVR4_LOCKFILES + { + struct stat s; + + if (stat (qsysdep->zdevice, &s) != 0) + { + ulog (LOG_ERROR, "stat (%s): %s", qsysdep->zdevice, + strerror (errno)); + return FALSE; + } + zalc = zbufalc (sizeof "LK.123.123.123"); + sprintf (zalc, "LK.%03d.%03d.%03d", major (s.st_dev), + major (s.st_rdev), minor (s.st_rdev)); + z = zalc; + } +#else /* ! HAVE_SVR4_LOCKFILES */ + z = strrchr (qsysdep->zdevice, '/') + 1; +#endif /* ! HAVE_SVR4_LOCKFILES */ +#endif /* ! HAVE_SVR4_LOCKFILES */ + } + + if (flok) + fret = fsdo_lock (z, FALSE, (boolean *) NULL); + else + fret = fsdo_unlock (z, FALSE); + +#if HAVE_COHERENT_LOCKFILES + if (fret) + { + if (flok) + { + if (lockttyexist (z)) + { + ulog (LOG_NORMAL, "%s: port already locked", z+5); + fret = FALSE; + } + else + fret = !(fscoherent_disable_tty (z, &qsysdep->zenable)); + } + else + { + fret = TRUE; + if (qsysdep->zenable != NULL) + { + const char *azargs[3]; + int aidescs[3]; + pid_t ipid; + + azargs[0] = "/etc/enable"; + azargs[1] = qsysdep->zenable; + azargs[2] = NULL; + aidescs[0] = SPAWN_NULL; + aidescs[1] = SPAWN_NULL; + aidescs[2] = SPAWN_NULL; + + ipid = ixsspawn (azargs, aidescs, TRUE, FALSE, + (const char *) NULL, TRUE, TRUE, + (const char *) NULL, (const char *) NULL, + (const char *) NULL); + if (ipid < 0) + { + ulog (LOG_ERROR, "ixsspawn (/etc/enable %s): %s", + qsysdep->zenable, strerror (errno)); + fret = FALSE; + } + else + { + if (ixswait ((unsigned long) ipid, (const char *) NULL) + == 0) + fret = TRUE; + else + fret = FALSE; + } + ubuffree (qsysdep->zenable); + qsysdep->zenable = NULL; + } + } + } +#endif /* HAVE_COHERENT_LOCKFILES */ + + ubuffree (zalc); + return fret; +} + +/* If we can mark a modem line in use, then when we lock a port we + must open it and mark it in use. We can't wait until the actual + open because we can't fail out if it is locked then. */ + +static boolean +fsserial_lock (qconn, fin) + struct sconnection *qconn; + boolean fin; +{ + if (! fsserial_lockfile (TRUE, qconn)) + return FALSE; + +#if HAVE_TIOCSINUSE || HAVE_TIOCEXCL + /* Open the line and try to mark it in use. */ + { + struct ssysdep_conn *qsysdep; + int iflag; + + qsysdep = (struct ssysdep_conn *) qconn->psysdep; + + if (fin) + iflag = 0; + else + iflag = iSunblock; + + qsysdep->o = open (qsysdep->zdevice, O_RDWR | iflag); + if (qsysdep->o < 0) + { +#if O_NONBLOCK != 0 + if (! fin && iSunblock != O_NONBLOCK && errno == EINVAL) + { + iSunblock = O_NONBLOCK; + qsysdep->o = open (qsysdep->zdevice, + O_RDWR | O_NONBLOCK); + } +#endif + if (qsysdep->o < 0) + { + if (errno != EBUSY) + ulog (LOG_ERROR, "open (%s): %s", qsysdep->zdevice, + strerror (errno)); + (void) fsserial_lockfile (FALSE, qconn); + return FALSE; + } + } + +#if HAVE_TIOCSINUSE + /* If we can't mark it in use, return FALSE to indicate that the + lock failed. */ + if (ioctl (qsysdep->o, TIOCSINUSE, 0) < 0) + { + if (errno != EALREADY) + ulog (LOG_ERROR, "ioctl (TIOCSINUSE): %s", strerror (errno)); +#ifdef TIOCNOTTY + (void) ioctl (qsysdep->o, TIOCNOTTY, (char *) NULL); +#endif + (void) close (qsysdep->o); + qsysdep->o = -1; + (void) fsserial_lockfile (FALSE, qconn); + return FALSE; + } +#endif + + if (fcntl (qsysdep->o, F_SETFD, + fcntl (qsysdep->o, F_GETFD, 0) | FD_CLOEXEC) < 0) + { + ulog (LOG_ERROR, "fcntl (FD_CLOEXEC): %s", strerror (errno)); +#ifdef TIOCNOTTY + (void) ioctl (qsysdep->o, TIOCNOTTY, (char *) NULL); +#endif + (void) close (qsysdep->o); + qsysdep->o = -1; + (void) fsserial_lockfile (FALSE, qconn); + return FALSE; + } + +#ifdef TIOCSCTTY + /* On BSD 4.4, make it our controlling terminal. */ + (void) ioctl (qsysdep->o, TIOCSCTTY, 0); +#endif + } +#endif /* HAVE_TIOCSINUSE || HAVE_TIOCEXCL */ + + return TRUE; +} + +/* Unlock a modem or direct port. */ + +static boolean +fsserial_unlock (qconn) + struct sconnection *qconn; +{ + boolean fret; + struct ssysdep_conn *qsysdep; + + fret = TRUE; + + /* The file may have been opened by fsserial_lock, so close it here + if necessary. */ + qsysdep = (struct ssysdep_conn *) qconn->psysdep; + if (qsysdep->o >= 0) + { +#ifdef TIOCNOTTY + (void) ioctl (qsysdep->o, TIOCNOTTY, (char *) NULL); +#endif + if (close (qsysdep->o) < 0) + { + ulog (LOG_ERROR, "close: %s", strerror (errno)); + fret = FALSE; + } + qsysdep->o = -1; + } + + if (! fsserial_lockfile (FALSE, qconn)) + fret = FALSE; + + return fret; +} + +/* Open a serial line. This sets the terminal settings. We begin in + seven bit mode and let the protocol change if necessary. */ + +#if HAVE_POSIX_TERMIOS +typedef speed_t baud_code; +#else +typedef int baud_code; +#endif + +static struct sbaud_table +{ + baud_code icode; + long ibaud; +} asSbaud_table[] = +{ + { B50, 50 }, + { B75, 75 }, + { B110, 110 }, + { B134, 134 }, + { B150, 150 }, + { B200, 200 }, + { B300, 300 }, + { B600, 600 }, + { B1200, 1200 }, + { B1800, 1800 }, + { B2400, 2400 }, + { B4800, 4800 }, + { B9600, 9600 }, +#ifdef B19200 + { B19200, 19200 }, +#else /* ! defined (B19200) */ +#ifdef EXTA + { EXTA, 19200 }, +#endif /* EXTA */ +#endif /* ! defined (B19200) */ +#ifdef B38400 + { B38400, 38400 }, +#else /* ! defined (B38400) */ +#ifdef EXTB + { EXTB, 38400 }, +#endif /* EXTB */ +#endif /* ! defined (B38400) */ +#ifdef B57600 + { B57600, 57600 }, +#endif +#ifdef B76800 + { B76800, 76800 }, +#endif +#ifdef B115200 + { B115200, 115200 }, +#endif + { B0, 0 } +}; + +#define CBAUD_TABLE (sizeof asSbaud_table / sizeof asSbaud_table[0]) + +#if HAVE_SYSV_TERMIO || HAVE_POSIX_TERMIOS +/* Hold the MIN value for the terminal to avoid setting it + unnecessarily. */ +static int cSmin; +#endif /* HAVE_SYSV_TERMIO || HAVE_POSIX_TERMIOS */ + +static boolean +fsserial_open (qconn, ibaud, fwait) + struct sconnection *qconn; + long ibaud; + boolean fwait; +{ + struct ssysdep_conn *q; + baud_code ib; + + q = (struct ssysdep_conn *) qconn->psysdep; + + if (q->zdevice != NULL) + ulog_device (strrchr (q->zdevice, '/') + 1); + else + { + const char *zport; + boolean fdummy; + +#if DEBUG > 0 + if (qconn->qport != NULL && + qconn->qport->uuconf_ttype != UUCONF_PORTTYPE_STDIN) + ulog (LOG_FATAL, "fsserial_open: Can't happen"); +#endif + zport = zsysdep_port_name (&fdummy); + if (zport != NULL) + ulog_device (zport); + } + + ib = B0; + if (ibaud != 0) + { + int i; + + for (i = 0; i < CBAUD_TABLE; i++) + if (asSbaud_table[i].ibaud == ibaud) + break; + if (i >= CBAUD_TABLE) + { + ulog (LOG_ERROR, "Unsupported baud rate %ld", ibaud); + return FALSE; + } + ib = asSbaud_table[i].icode; + } + + /* The port may have already been opened by the locking routine. */ + if (q->o < 0) + { + int iflag; + + if (fwait) + iflag = 0; + else + iflag = iSunblock; + + q->o = open (q->zdevice, O_RDWR | iflag); + if (q->o < 0) + { +#if O_NONBLOCK != 0 + if (! fwait && iSunblock != O_NONBLOCK && errno == EINVAL) + { + iSunblock = O_NONBLOCK; + q->o = open (q->zdevice, O_RDWR | O_NONBLOCK); + } +#endif + if (q->o < 0) + { + ulog (LOG_ERROR, "open (%s): %s", q->zdevice, + strerror (errno)); + return FALSE; + } + } + + if (fcntl (q->o, F_SETFD, fcntl (q->o, F_GETFD, 0) | FD_CLOEXEC) < 0) + { + ulog (LOG_ERROR, "fcntl (FD_CLOEXEC): %s", strerror (errno)); + return FALSE; + } + } + + /* Get the port flags, and make sure the ports are blocking. */ + + q->iflags = fcntl (q->o, F_GETFL, 0); + if (q->iflags < 0) + { + ulog (LOG_ERROR, "fcntl: %s", strerror (errno)); + return FALSE; + } + q->istdout_flags = -1; + + if (! fgetterminfo (q->o, &q->sorig)) + { + q->fterminal = FALSE; + return TRUE; + } + + q->fterminal = TRUE; + + q->snew = q->sorig; + +#if HAVE_BSD_TTY + + q->snew.stty.sg_flags = RAW | ANYP; + if (ibaud == 0) + ib = q->snew.stty.sg_ospeed; + else + { + q->snew.stty.sg_ispeed = ib; + q->snew.stty.sg_ospeed = ib; + } + + /* We don't want to receive any interrupt characters. */ + q->snew.stchars.t_intrc = -1; + q->snew.stchars.t_quitc = -1; + q->snew.stchars.t_eofc = -1; + q->snew.stchars.t_brkc = -1; + q->snew.sltchars.t_suspc = -1; + q->snew.sltchars.t_rprntc = -1; + q->snew.sltchars.t_dsuspc = -1; + q->snew.sltchars.t_flushc = -1; + q->snew.sltchars.t_werasc = -1; + q->snew.sltchars.t_lnextc = -1; + +#ifdef NTTYDISC + /* We want to use the ``new'' terminal driver so that we can use the + local mode bits to control XON/XOFF. */ + { + int iparam; + + if (ioctl (q->o, TIOCGETD, &iparam) >= 0 + && iparam != NTTYDISC) + { + iparam = NTTYDISC; + (void) ioctl (q->o, TIOCSETD, &iparam); + } + } +#endif + +#ifdef TIOCHPCL + /* When the file is closed, hang up the line. This is a safety + measure in case the program crashes. */ + (void) ioctl (q->o, TIOCHPCL, 0); +#endif + +#ifdef TIOCFLUSH + { + int iparam; + + /* Flush pending input. */ +#ifdef FREAD + iparam = FREAD; +#else + iparam = 0; +#endif + (void) ioctl (q->o, TIOCFLUSH, &iparam); + } +#endif /* TIOCFLUSH */ + +#endif /* HAVE_BSD_TTY */ + +#if HAVE_SYSV_TERMIO + + if (ibaud == 0) + ib = q->snew.c_cflag & CBAUD; + + q->snew.c_iflag &=~ ICLEAR_IFLAG; + q->snew.c_oflag &=~ ICLEAR_OFLAG; + q->snew.c_cflag &=~ ICLEAR_CFLAG; + if (!fwait) + q->snew.c_cflag |= CLOCAL; + q->snew.c_cflag |= (ib | ISET_CFLAG); + q->snew.c_lflag &=~ ICLEAR_LFLAG; + cSmin = 1; + q->snew.c_cc[VMIN] = cSmin; + q->snew.c_cc[VTIME] = 0; + +#ifdef TCFLSH + /* Flush pending input. */ + (void) ioctl (q->o, TCFLSH, 0); +#endif + +#endif /* HAVE_SYSV_TERMIO */ + +#if HAVE_POSIX_TERMIOS + + if (ibaud == 0) + ib = cfgetospeed (&q->snew); + + q->snew.c_iflag &=~ ICLEAR_IFLAG; + q->snew.c_oflag &=~ ICLEAR_OFLAG; + q->snew.c_cflag &=~ ICLEAR_CFLAG; + if (!fwait) + q->snew.c_cflag |= CLOCAL; + q->snew.c_cflag |= ISET_CFLAG; + q->snew.c_lflag &=~ ICLEAR_LFLAG; + cSmin = 1; + q->snew.c_cc[VMIN] = cSmin; + q->snew.c_cc[VTIME] = 0; + + (void) cfsetospeed (&q->snew, ib); + (void) cfsetispeed (&q->snew, ib); + + /* Flush pending input. */ + (void) tcflush (q->o, TCIFLUSH); + +#endif /* HAVE_POSIX_TERMIOS */ + + if (! fsetterminfo (q->o, &q->snew)) + { + ulog (LOG_ERROR, "Can't set terminal settings: %s", strerror (errno)); + return FALSE; + } + +#ifdef TIOCSCTTY + /* On BSD 4.4, make it our controlling terminal. */ + (void) ioctl (q->o, TIOCSCTTY, 0); +#endif + + if (ibaud != 0) + q->ibaud = ibaud; + else + { + int i; + + q->ibaud = (long) 1200; + for (i = 0; i < CBAUD_TABLE; i++) + { + if (asSbaud_table[i].icode == ib) + { + q->ibaud = asSbaud_table[i].ibaud; + break; + } + } + + DEBUG_MESSAGE1 (DEBUG_PORT, + "fsserial_open: Baud rate is %ld", q->ibaud); + } + + return TRUE; +} + +/* Open a standard input port. The code alternates q->o between 0 and + 1 as appropriate. It is always 0 before any call to fsblock. */ + +static boolean +fsstdin_open (qconn, ibaud, fwait) + struct sconnection *qconn; + long ibaud; + boolean fwait; +{ + struct ssysdep_conn *q; + + q = (struct ssysdep_conn *) qconn->psysdep; + q->o = 0; + if (! fsserial_open (qconn, ibaud, fwait)) + return FALSE; + q->istdout_flags = fcntl (1, F_GETFL, 0); + if (q->istdout_flags < 0) + { + ulog (LOG_ERROR, "fcntl: %s", strerror (errno)); + return FALSE; + } + return TRUE; +} + +/* Open a modem port. */ + +static boolean +fsmodem_open (qconn, ibaud, fwait) + struct sconnection *qconn; + long ibaud; + boolean fwait; +{ + if (ibaud == (long) 0) + ibaud = qconn->qport->uuconf_u.uuconf_smodem.uuconf_ibaud; + return fsserial_open (qconn, ibaud, fwait); +} + +/* Open a direct port. */ + +static boolean +fsdirect_open (qconn, ibaud, fwait) + struct sconnection *qconn; + long ibaud; + boolean fwait; +{ + if (ibaud == (long) 0) + ibaud = qconn->qport->uuconf_u.uuconf_sdirect.uuconf_ibaud; + return fsserial_open (qconn, ibaud, fwait); +} + +/* Change the blocking status of the port. We keep track of the + current blocking status to avoid calling fcntl unnecessarily; fcntl + turns out to be surprisingly expensive, at least on Ultrix. */ + +static boolean +fsblock (qs, fblock) + struct ssysdep_conn *qs; + boolean fblock; +{ + int iwant; + int isys; + + if (fblock) + iwant = qs->iflags &~ (O_NDELAY | O_NONBLOCK); + else + iwant = qs->iflags | iSunblock; + + if (iwant == qs->iflags) + return TRUE; + + isys = fcntl (qs->o, F_SETFL, iwant); + if (isys < 0) + { +#if O_NONBLOCK != 0 + if (! fblock && iSunblock != O_NONBLOCK && errno == EINVAL) + { + iSunblock = O_NONBLOCK; + iwant = qs->iflags | O_NONBLOCK; + isys = fcntl (qs->o, F_SETFL, iwant); + } +#endif + if (isys < 0) + { + ulog (LOG_ERROR, "fcntl: %s", strerror (errno)); + return FALSE; + } + } + + qs->iflags = iwant; + + if (qs->istdout_flags >= 0) + { + if (fblock) + iwant = qs->istdout_flags &~ (O_NDELAY | O_NONBLOCK); + else + iwant = qs->istdout_flags | iSunblock; + + if (fcntl (1, F_SETFL, iwant) < 0) + { + /* We don't bother to fix up iSunblock here, since we + succeeded above. */ + ulog (LOG_ERROR, "fcntl: %s", strerror (errno)); + return FALSE; + } + + qs->istdout_flags = iwant; + } + + return TRUE; +} + +/* Close a serial port. */ + +static boolean +fsserial_close (q) + struct ssysdep_conn *q; +{ + if (q->o >= 0) + { + /* Use a 30 second timeout to avoid hanging while draining + output. */ + if (q->fterminal) + { + fSalarm = FALSE; + + if (fsysdep_catch ()) + { + usysdep_start_catch (); + usset_signal (SIGALRM, usalarm, TRUE, (boolean *) NULL); + (void) alarm (30); + + (void) fsetterminfodrain (q->o, &q->sorig); + } + + usset_signal (SIGALRM, SIG_IGN, TRUE, (boolean *) NULL); + (void) alarm (0); + usysdep_end_catch (); + + /* If we timed out, use the non draining call. Hopefully + this can't hang. */ + if (fSalarm) + (void) fsetterminfo (q->o, &q->sorig); + } + +#ifdef TIOCNOTTY + /* We don't want this as our controlling terminal any more, so + get rid of it. This is necessary because we don't want to + open /dev/tty, since that can confuse the serial port locking + on some computers. */ + (void) ioctl (q->o, TIOCNOTTY, (char *) NULL); +#endif + + (void) close (q->o); + q->o = -1; + + /* Sleep to give the terminal a chance to settle, in case we are + about to call out again. */ + sleep (2); + } + + return TRUE; +} + +/* Close a stdin port. */ + +/*ARGSUSED*/ +static boolean +fsstdin_close (qconn, puuconf, qdialer, fsuccess) + struct sconnection *qconn; + pointer puuconf; + struct uuconf_dialer *qdialer; + boolean fsuccess; +{ + struct ssysdep_conn *qsysdep; + + qsysdep = (struct ssysdep_conn *) qconn->psysdep; + (void) close (1); + (void) close (2); + qsysdep->o = 0; + return fsserial_close (qsysdep); +} + +/* Close a modem port. */ + +static boolean +fsmodem_close (qconn, puuconf, qdialer, fsuccess) + struct sconnection *qconn; + pointer puuconf; + struct uuconf_dialer *qdialer; + boolean fsuccess; +{ + struct ssysdep_conn *qsysdep; + boolean fret; + struct uuconf_dialer sdialer; + const struct uuconf_chat *qchat; + + qsysdep = (struct ssysdep_conn *) qconn->psysdep; + + fret = TRUE; + + /* Figure out the dialer so that we can run the complete or abort + chat scripts. */ + if (qdialer == NULL) + { + if (qconn->qport->uuconf_u.uuconf_smodem.uuconf_pzdialer != NULL) + { + const char *zdialer; + int iuuconf; + + zdialer = qconn->qport->uuconf_u.uuconf_smodem.uuconf_pzdialer[0]; + iuuconf = uuconf_dialer_info (puuconf, zdialer, &sdialer); + if (iuuconf == UUCONF_SUCCESS) + qdialer = &sdialer; + else + { + ulog_uuconf (LOG_ERROR, puuconf, iuuconf); + fret = FALSE; + } + } + else + qdialer = qconn->qport->uuconf_u.uuconf_smodem.uuconf_qdialer; + } + + /* Get the complete or abort chat script to use. */ + qchat = NULL; + if (qdialer != NULL) + { + if (fsuccess) + qchat = &qdialer->uuconf_scomplete; + else + qchat = &qdialer->uuconf_sabort; + } + + if (qchat != NULL + && (qchat->uuconf_pzprogram != NULL + || qchat->uuconf_pzchat != NULL)) + { + boolean fsighup_ignored; + HELD_SIG_MASK smask; + int i; + sig_atomic_t afhold[INDEXSIG_COUNT]; + + /* We're no longer interested in carrier. */ + (void) fsmodem_carrier (qconn, FALSE); + + /* The port I/O routines check whether any signal has been + received, and abort if one has. While we are closing down + the modem, we don't care if we received a signal in the past, + but we do care if we receive a new signal (otherwise it would + be difficult to kill a uucico which was closing down a + modem). We never care if we get SIGHUP at this point. So we + turn off SIGHUP, remember what signals we've already seen, + and clear our notion of what signals we've seen. We have to + block the signals while we remember and clear the array, + since we might otherwise miss a signal which occurred between + the copy and the clear (old systems can't block signals; they + will just have to suffer the race). */ + usset_signal (SIGHUP, SIG_IGN, FALSE, &fsighup_ignored); + smask = isblocksigs (); + for (i = 0; i < INDEXSIG_COUNT; i++) + { + afhold[i] = afSignal[i]; + afSignal[i] = FALSE; + } + usunblocksigs (smask); + + if (! fchat (qconn, puuconf, qchat, (const struct uuconf_system *) NULL, + (const struct uuconf_dialer *) NULL, (const char *) NULL, + FALSE, qconn->qport->uuconf_zname, + qsysdep->ibaud)) + fret = FALSE; + + /* Restore the old signal array and the SIGHUP handler. It is + not necessary to block signals here, since all we are doing + is exactly what the signal handler itself would do if the + signal occurred. */ + for (i = 0; i < INDEXSIG_COUNT; i++) + if (afhold[i]) + afSignal[i] = TRUE; + if (! fsighup_ignored) + usset_signal (SIGHUP, ussignal, TRUE, (boolean *) NULL); + } + + if (qdialer != NULL + && qdialer == &sdialer) + (void) uuconf_dialer_free (puuconf, &sdialer); + +#if ! HAVE_RESET_BUG + /* Reset the terminal to make sure we drop DTR. It should be + dropped when we close the descriptor, but that doesn't seem to + happen on some systems. Use a 30 second timeout to avoid hanging + while draining output. */ + if (qsysdep->fterminal) + { +#if HAVE_BSD_TTY + qsysdep->snew.stty.sg_ispeed = B0; + qsysdep->snew.stty.sg_ospeed = B0; +#endif +#if HAVE_SYSV_TERMIO + qsysdep->snew.c_cflag = (qsysdep->snew.c_cflag &~ CBAUD) | B0; +#endif +#if HAVE_POSIX_TERMIOS + (void) cfsetospeed (&qsysdep->snew, B0); +#endif + + fSalarm = FALSE; + + if (fsysdep_catch ()) + { + usysdep_start_catch (); + usset_signal (SIGALRM, usalarm, TRUE, (boolean *) NULL); + (void) alarm (30); + + (void) fsetterminfodrain (qsysdep->o, &qsysdep->snew); + } + + usset_signal (SIGALRM, SIG_IGN, TRUE, (boolean *) NULL); + (void) alarm (0); + usysdep_end_catch (); + + /* Let the port settle. */ + sleep (2); + } +#endif /* ! HAVE_RESET_BUG */ + + if (! fsserial_close (qsysdep)) + fret = FALSE; + + return fret; +} + +/* Close a direct port. */ + +/*ARGSUSED*/ +static boolean +fsdirect_close (qconn, puuconf, qdialer, fsuccess) + struct sconnection *qconn; + pointer puuconf; + struct uuconf_dialer *qdialer; + boolean fsuccess; +{ + return fsserial_close ((struct ssysdep_conn *) qconn->psysdep); +} + +/* Reset a serial port by hanging up. */ + +static boolean +fsserial_reset (qconn) + struct sconnection *qconn; +{ + struct ssysdep_conn *q; + sterminal sbaud; + + q = (struct ssysdep_conn *) qconn->psysdep; + + if (! q->fterminal) + return TRUE; + + sbaud = q->snew; + +#if HAVE_BSD_TTY + sbaud.stty.sg_ispeed = B0; + sbaud.stty.sg_ospeed = B0; +#endif +#if HAVE_SYSV_TERMIO + sbaud.c_cflag = (sbaud.c_cflag &~ CBAUD) | B0; +#endif +#if HAVE_POSIX_TERMIOS + if (cfsetospeed (&sbaud, B0) < 0) + { + ulog (LOG_ERROR, "Can't set baud rate: %s", strerror (errno)); + return FALSE; + } +#endif + + if (! fsetterminfodrain (q->o, &sbaud)) + { + ulog (LOG_ERROR, "Can't hangup terminal: %s", strerror (errno)); + return FALSE; + } + + /* Give the terminal a chance to settle. */ + sleep (2); + + if (! fsetterminfo (q->o, &q->snew)) + { + ulog (LOG_ERROR, "Can't reopen terminal: %s", strerror (errno)); + return FALSE; + } + + return TRUE; +} + +/* Reset a standard input port. */ + +static boolean +fsstdin_reset (qconn) + struct sconnection *qconn; +{ + struct ssysdep_conn *qsysdep; + + qsysdep = (struct ssysdep_conn *) qconn->psysdep; + qsysdep->o = 0; + return fsserial_reset (qconn); +} + +/* Begin dialing out on a modem port. This opens the dialer device if + there is one. */ + +boolean +fsysdep_modem_begin_dial (qconn, qdial) + struct sconnection *qconn; + struct uuconf_dialer *qdial; +{ + struct ssysdep_conn *qsysdep; + const char *z; + + qsysdep = (struct ssysdep_conn *) qconn->psysdep; + +#ifdef TIOCMODEM + /* If we can tell the modem to obey modem control, do so. */ + { + int iperm; + + iperm = 0; + (void) ioctl (qsysdep->o, TIOCMODEM, &iperm); + } +#endif /* TIOCMODEM */ + + /* If we supposed to toggle DTR, do so. */ + + if (qdial->uuconf_fdtr_toggle) + { +#ifdef TIOCCDTR + (void) ioctl (qsysdep->o, TIOCCDTR, 0); + sleep (2); + (void) ioctl (qsysdep->o, TIOCSDTR, 0); +#else /* ! defined (TIOCCDTR) */ + (void) fconn_reset (qconn); +#endif /* ! defined (TIOCCDTR) */ + + if (qdial->uuconf_fdtr_toggle_wait) + sleep (2); + } + + if (! fsmodem_carrier (qconn, FALSE)) + return FALSE; + + /* Open the dial device if there is one. */ + z = qconn->qport->uuconf_u.uuconf_smodem.uuconf_zdial_device; + if (z != NULL) + { + char *zfree; + int o; + + qsysdep->ohold = qsysdep->o; + + zfree = NULL; + if (*z != '/') + { + zfree = zbufalc (sizeof "/dev/" + strlen (z)); + sprintf (zfree, "/dev/%s", z); + z = zfree; + } + + o = open ((char *) z, O_RDWR | O_NOCTTY); + if (o < 0) + { + ulog (LOG_ERROR, "open (%s): %s", z, strerror (errno)); + ubuffree (zfree); + return FALSE; + } + ubuffree (zfree); + + if (fcntl (o, F_SETFD, fcntl (o, F_GETFD, 0) | FD_CLOEXEC) < 0) + { + ulog (LOG_ERROR, "fcntl (FD_CLOEXEC): %s", strerror (errno)); + (void) close (o); + return FALSE; + } + + qsysdep->o = o; + } + + return TRUE; +} + +/* Tell the port to require or not require carrier. On BSD this uses + TIOCCAR and TIOCNCAR, which I assume are generally supported (it + can also use the LNOMDM bit supported by IS68K Unix). On System V + it resets or sets CLOCAL. We only require carrier if the port + supports it. This will only be called with fcarrier TRUE if the + dialer supports carrier. */ + +static boolean +fsmodem_carrier (qconn, fcarrier) + struct sconnection *qconn; + boolean fcarrier; +{ + register struct ssysdep_conn *q; + + q = (struct ssysdep_conn *) qconn->psysdep; + + if (! q->fterminal) + return TRUE; + + if (fcarrier) + { + if (qconn->qport->uuconf_u.uuconf_smodem.uuconf_fcarrier) + { +#ifdef TIOCCAR + /* Tell the modem to pay attention to carrier. */ + if (ioctl (q->o, TIOCCAR, 0) < 0) + { + ulog (LOG_ERROR, "ioctl (TIOCCAR): %s", strerror (errno)); + return FALSE; + } +#endif /* TIOCCAR */ + +#if HAVE_BSD_TTY +#ifdef LNOMDM + /* IS68K Unix uses a local LNOMDM bit. */ + { + int iparam; + + iparam = LNOMDM; + if (ioctl (q->o, TIOCLBIC, &iparam) < 0) + { + ulog (LOG_ERROR, "ioctl (TIOCLBIC, LNOMDM): %s", + strerror (errno)); + return FALSE; + } + } +#endif /* LNOMDM */ +#endif /* HAVE_BSD_TTY */ + +#if HAVE_SYSV_TERMIO || HAVE_POSIX_TERMIOS + /* Put the modem into nonlocal mode. */ + q->snew.c_cflag &=~ CLOCAL; + if (! fsetterminfo (q->o, &q->snew)) + { + ulog (LOG_ERROR, "Can't clear CLOCAL: %s", strerror (errno)); + return FALSE; + } +#endif /* HAVE_SYSV_TERMIO || HAVE_POSIX_TERMIOS */ + } + } + else + { +#ifdef TIOCNCAR + /* Tell the modem to ignore carrier. */ + if (ioctl (q->o, TIOCNCAR, 0) < 0) + { + ulog (LOG_ERROR, "ioctl (TIOCNCAR): %s", strerror (errno)); + return FALSE; + } +#endif /* TIOCNCAR */ + +#if HAVE_BSD_TTY +#ifdef LNOMDM + /* IS68K Unix uses a local LNOMDM bit. */ + { + int iparam; + + iparam = LNOMDM; + if (ioctl (q->o, TIOCLBIS, &iparam) < 0) + { + ulog (LOG_ERROR, "ioctl (TIOCLBIS, LNOMDM): %s", + strerror (errno)); + return FALSE; + } + } +#endif /* LNOMDM */ +#endif /* HAVE_BSD_TTY */ + +#if HAVE_SYSV_TERMIO || HAVE_POSIX_TERMIOS + /* Put the modem into local mode (ignore carrier) to start the chat + script. */ + q->snew.c_cflag |= CLOCAL; + if (! fsetterminfo (q->o, &q->snew)) + { + ulog (LOG_ERROR, "Can't set CLOCAL: %s", strerror (errno)); + return FALSE; + } + +#if HAVE_CLOCAL_BUG + /* On SCO and AT&T UNIX PC you have to reopen the port. */ + { + int onew; + + onew = open (q->zdevice, O_RDWR); + if (onew < 0) + { + ulog (LOG_ERROR, "open (%s): %s", q->zdevice, strerror (errno)); + return FALSE; + } + + if (fcntl (onew, F_SETFD, + fcntl (onew, F_GETFD, 0) | FD_CLOEXEC) < 0) + { + ulog (LOG_ERROR, "fcntl (FD_CLOEXEC): %s", strerror (errno)); + (void) close (onew); + return FALSE; + } + + (void) close (q->o); + q->o = onew; + } +#endif /* HAVE_CLOCAL_BUG */ + +#endif /* HAVE_SYSV_TERMIO || HAVE_POSIX_TERMIOS */ + } + + return TRUE; +} + +/* Finish dialing out on a modem by closing any dialer device and waiting + for carrier. */ + +boolean +fsysdep_modem_end_dial (qconn, qdial) + struct sconnection *qconn; + struct uuconf_dialer *qdial; +{ + struct ssysdep_conn *q; + + q = (struct ssysdep_conn *) qconn->psysdep; + + if (qconn->qport->uuconf_u.uuconf_smodem.uuconf_zdial_device != NULL) + { + (void) close (q->o); + q->o = q->ohold; + } + + if (qconn->qport->uuconf_u.uuconf_smodem.uuconf_fcarrier + && qdial->uuconf_fcarrier) + { + /* Tell the port that we need carrier. */ + if (! fsmodem_carrier (qconn, TRUE)) + return FALSE; + +#ifdef TIOCWONLINE + + /* We know how to wait for carrier, so do so. */ + + /* If we already got a signal, just quit now. */ + if (FGOT_QUIT_SIGNAL ()) + return FALSE; + + /* This bit of code handles signals just like fsysdep_conn_read + does. See that function for a longer explanation. */ + + /* Use fsysdep_catch to handle a longjmp from the signal + handler. */ + + fSalarm = FALSE; + + if (fsysdep_catch ()) + { + /* Start catching SIGALRM; normally we ignore it. */ + usysdep_start_catch (); + usset_signal (SIGALRM, usalarm, TRUE, (boolean *) NULL); + (void) alarm (qdial->uuconf_ccarrier_wait); + + /* We really don't care if we get an error, since that will + probably just mean that TIOCWONLINE isn't supported in + which case there's nothing we can do anyhow. If we get + SIGINT we want to keep waiting for carrier, because + SIGINT just means don't start any new sessions. We don't + handle SIGINT correctly if we do a longjmp in the signal + handler; too bad. */ + while (ioctl (q->o, TIOCWONLINE, 0) < 0 + && errno == EINTR) + { + /* Log the signal. */ + ulog (LOG_ERROR, (const char *) NULL); + if (FGOT_QUIT_SIGNAL () || fSalarm) + break; + } + } + + /* Turn off the pending SIGALRM and ignore SIGALARM again. */ + usset_signal (SIGALRM, SIG_IGN, TRUE, (boolean *) NULL); + (void) alarm (0); + usysdep_end_catch (); + + /* If we got a random signal, just return FALSE. */ + if (FGOT_QUIT_SIGNAL ()) + return FALSE; + + /* If we timed out, give an error. */ + if (fSalarm) + { + ulog (LOG_ERROR, "Timed out waiting for carrier"); + return FALSE; + } + +#endif /* TIOCWONLINE */ + } + + return TRUE; +} + +/* Read data from a connection, with a timeout. This routine handles + all types of connections, including TLI. + + This function should return when we have read cmin characters or + the timeout has occurred. We have to work a bit to get Unix to do + this efficiently on a terminal. The simple implementation + schedules a SIGALRM signal and then calls read; if there is a + single character available, the call to read will return + immediately, so there must be a loop which terminates when the + SIGALRM is delivered or the correct number of characters has been + read. This can be very inefficient with a fast CPU or a low baud + rate (or both!), since each call to read may return only one or two + characters. + + Under POSIX or System V, we can specify a minimum number of + characters to read, so there is no serious trouble. + + Under BSD, we figure out how many characters we have left to read, + how long it will take for them to arrive at the current baud rate, + and sleep that long. + + Doing this with a timeout and avoiding all possible race conditions + get very hairy, though. Basically, we're going to schedule a + SIGALRM for when the timeout expires. I don't really want to do a + longjmp in the SIGALRM handler, though, because that may lose data. + Therefore, I have the signal handler set a variable. However, this + means that there will be a span of time between the time the code + checks the variable and the time it calls the read system call; if + the SIGALRM occurs during that time, the read might hang forever. + To avoid this, the SIGALRM handler not only sets a global variable, + it also schedules another SIGALRM for one second in the future + (POSIX specifies that a signal handler is permitted to safely call + alarm). To avoid getting a continual sequence of SIGALRM + interrupts, we change the signal handler to ignore SIGALRM when + we're about to exit the function. This means that every time we + execute fsysdep_conn_read we make at least five system calls. It's + the best I've been able to come up with, though. + + When fsysdep_conn_read finishes, there will be no SIGALRM scheduled + and SIGALRM will be ignored. */ + +boolean +fsysdep_conn_read (qconn, zbuf, pclen, cmin, ctimeout, freport) + struct sconnection *qconn; + char *zbuf; + size_t *pclen; + size_t cmin; + int ctimeout; + boolean freport; +{ + CATCH_PROTECT size_t cwant; + boolean fret; + register struct ssysdep_conn * const q + = (struct ssysdep_conn *) qconn->psysdep; + + cwant = *pclen; + *pclen = 0; + + /* Guard against a bad timeout. We return TRUE when a timeout + expires. It is possible to get a negative timeout here because + the calling code does not check user supplied timeouts for + plausibility. */ + if (ctimeout <= 0) + return TRUE; + + /* We want to do a blocking read. */ + if (! fsblock (q, TRUE)) + return FALSE; + + fSalarm = FALSE; + + /* We're going to set up an alarm signal to last for the entire + read. If the read system call cannot be interrupted, the signal + handler will do a longjmp causing fsysdep_catch (a macro) to + return FALSE. We handle that here. If read can be interrupted, + fsysdep_catch will be defined to TRUE. */ + if (fsysdep_catch ()) + { + /* Prepare to catch SIGALRM and schedule the signal. */ + usysdep_start_catch (); + usset_signal (SIGALRM, usalarm, TRUE, (boolean *) NULL); + alarm (ctimeout); + } + else + { + /* We caught a signal. We don't actually have to do anything, + as all the appropriate checks are made at the start of the + following loop. */ + } + + fret = FALSE; + + while (TRUE) + { + int cgot; + +#if HAVE_SYSV_TERMIO || HAVE_POSIX_TERMIOS + /* If we can tell the terminal not to return until we have a + certain number of characters, do so. */ + if (q->fterminal) + { + int csetmin; + + /* I'm not that confident about setting MIN to values larger + than 127, although up to 255 would probably work. */ + if (cmin < 127) + csetmin = cmin; + else + csetmin = 127; + + if (csetmin != cSmin) + { + q->snew.c_cc[VMIN] = csetmin; + while (! fsetterminfo (q->o, &q->snew)) + { + if (errno != EINTR + || FGOT_QUIT_SIGNAL ()) + { + int ierr; + + /* We turn off the signal before reporting the + error to minimize any problems with + interrupted system calls. */ + ierr = errno; + usset_signal (SIGALRM, SIG_IGN, TRUE, (boolean *) NULL); + alarm (0); + usysdep_end_catch (); + ulog (LOG_ERROR, "Can't set MIN for terminal: %s", + strerror (ierr)); + return FALSE; + } + + if (fSalarm) + { + ulog (LOG_ERROR, + "Timed out when setting MIN to %d; retrying", + csetmin); + fSalarm = FALSE; + alarm (ctimeout); + } + } + cSmin = csetmin; + } + } +#endif /* HAVE_SYSV_TERMIO || HAVE_POSIX_TERMIOS */ + + /* If we've received a signal, get out now. */ + if (FGOT_QUIT_SIGNAL ()) + break; + + /* If we've already gotten a SIGALRM, get out with whatever + we've accumulated. */ + if (fSalarm) + { + fret = TRUE; + break; + } + + /* Right here is the race condition which we avoid by having the + SIGALRM handler schedule another SIGALRM. */ +#if HAVE_TLI + if (q->ftli) + { + int iflags; + + cgot = t_rcv (q->o, zbuf, cwant, &iflags); + if (cgot < 0 && t_errno != TSYSERR) + { + usset_signal (SIGALRM, SIG_IGN, TRUE, (boolean *) NULL); + alarm (0); + usysdep_end_catch (); + + if (freport) + ulog (LOG_ERROR, "t_rcv: %s", + (t_errno >= 0 && t_errno < t_nerr + ? t_errlist[t_errno] + : "unknown TLI error")); + + return FALSE; + } + } + else +#endif + cgot = read (q->o, zbuf, cwant); + + /* If the read returned an error, check for signals. */ + if (cgot < 0) + { + if (errno == EINTR) + { + /* Log the signal. */ + ulog (LOG_ERROR, (const char *) NULL); + } + if (fSalarm) + { + fret = TRUE; + break; + } + if (FGOT_QUIT_SIGNAL ()) + break; + } + + /* If read returned an error, get out. We just ignore EINTR + here, since it must be from some signal we don't care about. + If the read returned 0 then the line must have been hung up + (normally we would have received SIGHUP, but we can't count + on that). We turn off the signals before calling ulog to + reduce problems with interrupted system calls. */ + if (cgot <= 0) + { + if (cgot < 0 && errno == EINTR) + cgot = 0; + else + { + int ierr; + + ierr = errno; + + usset_signal (SIGALRM, SIG_IGN, TRUE, (boolean *) NULL); + alarm (0); + usysdep_end_catch (); + + if (freport) + { + if (cgot == 0) + ulog (LOG_ERROR, "Line disconnected"); + else + ulog (LOG_ERROR, "read: %s", strerror (ierr)); + } + + return FALSE; + } + } + + cwant -= cgot; + if (cgot >= cmin) + cmin = 0; + else + cmin -= cgot; + zbuf += cgot; + *pclen += cgot; + + /* If we have enough data, get out now. */ + if (cmin == 0) + { + fret = TRUE; + break; + } + +#if HAVE_BSD_TTY + /* We still want more data, so sleep long enough for the rest of + it to arrive. We don't this for System V or POSIX because + setting MIN is good enough (we can't sleep longer than it + takes to get MAX_INPUT characters anyhow). + + The baud rate is approximately 10 times the number of + characters which will arrive in one second, so the number of + milliseconds to sleep == + characters * (milliseconds / character) == + characters * (1000 * (seconds / character)) == + characters * (1000 * (1 / (baud / 10))) == + characters * (10000 / baud) + + We arbitrarily reduce the sleep amount by 10 milliseconds to + attempt to account for the amount of time it takes to set up + the sleep. This is how long it takes to get half a character + at 19200 baud. We then don't bother to sleep for less than + 10 milliseconds. We don't sleep if the read was interrupted. + + We use select to sleep. It would be easy to use poll as + well, but it's unlikely that any system with BSD ttys would + have poll but not select. Using select avoids hassles with + the pending SIGALRM; if it hits the select will be + interrupted, and otherwise the select will not affect it. */ + +#if ! HAVE_SELECT + #error This code requires select; feel free to extend it +#endif + + if (q->fterminal && cmin > 1 && cgot > 0) + { + int csleepchars; + int isleep; + + /* We don't try to read all the way up to MAX_INPUT, + since that might drop a character. */ + if (cmin <= MAX_INPUT - 10) + csleepchars = cmin; + else + csleepchars = MAX_INPUT - 10; + + isleep = (int) (((long) csleepchars * 10000L) / q->ibaud); + isleep -= 10; + + if (isleep > 10) + { + struct timeval s; + + s.tv_sec = isleep / 1000; + s.tv_usec = (isleep % 1000) * 1000; + + /* Some versions of select take a pointer to an int, + while some take a pointer to an fd_set. I just cast + the arguments to a generic pointer, and assume that + any machine which distinguishes int * from fd_set * + (I would be amazed if there are any such machines) + have an appropriate prototype somewhere or other. */ + (void) select (0, (pointer) NULL, (pointer) NULL, + (pointer) NULL, &s); + + /* Here either the select finished sleeping or we got a + SIGALRM. If the latter occurred, fSalarm was set to + TRUE; it will be checked at the top of the loop. */ + } + } +#endif /* HAVE_BSD_TTY */ + } + + /* Turn off the pending SIGALRM and return. */ + + usset_signal (SIGALRM, SIG_IGN, TRUE, (boolean *) NULL); + alarm (0); + usysdep_end_catch (); + + return fret; +} + +/* Read from a stdin port. */ + +static boolean +fsstdin_read (qconn, zbuf, pclen, cmin, ctimeout, freport) + struct sconnection *qconn; + char *zbuf; + size_t *pclen; + size_t cmin; + int ctimeout; + boolean freport; +{ + struct ssysdep_conn *qsysdep; + + qsysdep = (struct ssysdep_conn *) qconn->psysdep; + qsysdep->o = 0; + return fsysdep_conn_read (qconn, zbuf, pclen, cmin, ctimeout, freport); +} + +/* Write data to a connection. This routine handles all types of + connections, including TLI. */ + +boolean +fsysdep_conn_write (qconn, zwrite, cwrite) + struct sconnection *qconn; + const char *zwrite; + size_t cwrite; +{ + struct ssysdep_conn *q; + int czero; + + q = (struct ssysdep_conn *) qconn->psysdep; + + /* We want blocking writes here. */ + if (! fsblock (q, TRUE)) + return FALSE; + + czero = 0; + + while (cwrite > 0) + { + int cdid; + + /* Loop until we don't get an interrupt. */ + while (TRUE) + { + /* If we've received a signal, don't continue. */ + if (FGOT_QUIT_SIGNAL ()) + return FALSE; + +#if HAVE_TLI + if (q->ftli) + { + cdid = t_snd (q->o, zwrite, cwrite, 0); + if (cdid < 0 && t_errno != TSYSERR) + { + ulog (LOG_ERROR, "t_snd: %s", + (t_errno >= 0 && t_errno < t_nerr + ? t_errlist[t_errno] + : "unknown TLI error")); + return FALSE; + } + } + else +#endif + cdid = write (q->o, zwrite, cwrite); + + if (cdid >= 0) + break; + if (errno != EINTR) + break; + + /* We were interrupted by a signal. Log it. */ + ulog (LOG_ERROR, (const char *) NULL); + } + + if (cdid < 0) + { + if (errno != EAGAIN && errno != EWOULDBLOCK && errno != ENODATA) + { + ulog (LOG_ERROR, "write: %s", strerror (errno)); + return FALSE; + } + cdid = 0; + } + + if (cdid == 0) + { + /* On some systems write will return 0 if carrier is lost. + If we fail to write anything ten times in a row, we + assume that this has happened. This is hacked in like + this because there seems to be no reliable way to tell + exactly why the write returned 0. */ + ++czero; + if (czero >= 10) + { + ulog (LOG_ERROR, "Line disconnected"); + return FALSE; + } + } + else + { + czero = 0; + + cwrite -= cdid; + zwrite += cdid; + } + } + + return TRUE; +} + +/* Write to a stdin port. */ + +static boolean +fsstdin_write (qconn, zwrite, cwrite) + struct sconnection *qconn; + const char *zwrite; + size_t cwrite; +{ + struct ssysdep_conn *qsysdep; + + qsysdep = (struct ssysdep_conn *) qconn->psysdep; + qsysdep->o = 0; + if (! fsblock (qsysdep, TRUE)) + return FALSE; + qsysdep->o = 1; + return fsysdep_conn_write (qconn, zwrite, cwrite); +} + +/* The fsysdep_conn_io routine is supposed to both read and write data + until it has either filled its read buffer or written out all the + data it was given. This lets us write out large packets without + losing incoming data. It handles all types of connections, + including TLI. */ + +boolean +fsysdep_conn_io (qconn, zwrite, pcwrite, zread, pcread) + struct sconnection *qconn; + const char *zwrite; + size_t *pcwrite; + char *zread; + size_t *pcread; +{ + struct ssysdep_conn *q; + size_t cwrite, cread; + int czero; + + q = (struct ssysdep_conn *) qconn->psysdep; + + cwrite = *pcwrite; + *pcwrite = 0; + cread = *pcread; + *pcread = 0; + + czero = 0; + + while (TRUE) + { + int cgot, cdid; + size_t cdo; + + /* This used to always use nonblocking writes, but it turns out + that some systems don't support them on terminals. + + The current algorithm is: + loop: + unblocked read + if read buffer full, return + if nothing to write, return + if HAVE_UNBLOCKED_WRITES + write all data + else + write up to SINGLE_WRITE bytes + if all data written, return + if no data written + blocked write of up to SINGLE_WRITE bytes + + This algorithm should work whether the system supports + unblocked writes on terminals or not. If the system supports + unblocked writes but HAVE_UNBLOCKED_WRITES is 0, then it will + call write more often than it needs to. If the system does + not support unblocked writes but HAVE_UNBLOCKED_WRITES is 1, + then the write may hang so long that incoming data is lost. + This is actually possible at high baud rates on any system + when a blocking write is done; there is no solution, except + hardware handshaking. */ + + /* If we are running on standard input, we switch the file + descriptors by hand. */ + if (q->istdout_flags >= 0) + q->o = 0; + + /* Do an unblocked read. */ + if (! fsblock (q, FALSE)) + return FALSE; + + /* Loop until we get something (error or data) other than an + acceptable EINTR. */ + while (TRUE) + { + /* If we've received a signal, don't continue. */ + if (FGOT_QUIT_SIGNAL ()) + return FALSE; + +#if HAVE_TLI + if (q->ftli) + { + int iflags; + + cgot = t_rcv (q->o, zread, cread, &iflags); + if (cgot < 0) + { + if (t_errno == TNODATA) + errno = EAGAIN; + else if (t_errno != TSYSERR) + { + ulog (LOG_ERROR, "t_rcv: %s", + (t_errno >= 0 && t_errno < t_nerr + ? t_errlist[t_errno] + : "unknown TLI error")); + return FALSE; + } + } + } + else +#endif + cgot = read (q->o, zread, cread); + + if (cgot >= 0) + break; + if (errno != EINTR) + break; + + /* We got interrupted by a signal. Log it. */ + ulog (LOG_ERROR, (const char *) NULL); + } + + if (cgot < 0) + { + if (errno != EAGAIN && errno != EWOULDBLOCK && errno != ENODATA) + { + ulog (LOG_ERROR, "read: %s", strerror (errno)); + return FALSE; + } + cgot = 0; + } + + cread -= cgot; + zread += cgot; + *pcread += cgot; + + /* If we've filled the read buffer, or we have nothing left to + write, return out. */ + if (cread == 0 || cwrite == 0) + return TRUE; + + /* The port is currently unblocked. Do a write. */ + cdo = cwrite; + +#if ! HAVE_UNBLOCKED_WRITES + if (q->fterminal && cdo > SINGLE_WRITE) + cdo = SINGLE_WRITE; +#endif + + if (q->istdout_flags >= 0) + q->o = 1; + + /* Loop until we get something besides EINTR. */ + while (TRUE) + { + /* If we've received a signal, don't continue. */ + if (FGOT_QUIT_SIGNAL ()) + return FALSE; + +#if HAVE_TLI + if (q->ftli) + { + cdid = t_snd (q->o, zwrite, cdo, 0); + if (cdid < 0) + { + if (t_errno == TFLOW) + errno = EAGAIN; + else if (t_errno != TSYSERR) + { + ulog (LOG_ERROR, "t_snd: %s", + (t_errno >= 0 && t_errno < t_nerr + ? t_errlist[t_errno] + : "unknown TLI error")); + return FALSE; + } + } + } + else +#endif + cdid = write (q->o, zwrite, cdo); + + if (cdid >= 0) + break; + if (errno != EINTR) + break; + + /* We got interrupted by a signal. Log it. */ + ulog (LOG_ERROR, (const char *) NULL); + } + + if (cdid < 0) + { + if (errno != EAGAIN && errno != EWOULDBLOCK && errno != ENODATA) + { + ulog (LOG_ERROR, "write: %s", strerror (errno)); + return FALSE; + } + cdid = 0; + } + + if (cdid > 0) + { + /* We wrote some data. If we wrote everything, return out. + Otherwise loop around and do another read. */ + cwrite -= cdid; + zwrite += cdid; + *pcwrite += cdid; + + if (cwrite == 0) + return TRUE; + + czero = 0; + } + else + { + /* We didn't write any data. Do a blocking write. */ + + if (q->istdout_flags >= 0) + q->o = 0; + + if (! fsblock (q, TRUE)) + return FALSE; + + cdo = cwrite; + if (cdo > SINGLE_WRITE) + cdo = SINGLE_WRITE; + + DEBUG_MESSAGE1 (DEBUG_PORT, + "fsysdep_conn_io: Blocking write of %lud", + (unsigned long) cdo); + + if (q->istdout_flags >= 0) + q->o = 1; + + /* Loop until we get something besides EINTR. */ + while (TRUE) + { + /* If we've received a signal, don't continue. */ + if (FGOT_QUIT_SIGNAL ()) + return FALSE; + +#if HAVE_TLI + if (q->ftli) + { + cdid = t_snd (q->o, zwrite, cdo, 0); + if (cdid < 0 && t_errno != TSYSERR) + { + ulog (LOG_ERROR, "t_snd: %s", + (t_errno >= 0 && t_errno < t_nerr + ? t_errlist[t_errno] + : "unknown TLI error")); + return FALSE; + } + } + else +#endif + cdid = write (q->o, zwrite, cdo); + + if (cdid >= 0) + break; + if (errno != EINTR) + break; + + /* We got interrupted by a signal. Log it. */ + ulog (LOG_ERROR, (const char *) NULL); + } + + if (cdid < 0) + { + ulog (LOG_ERROR, "write: %s", strerror (errno)); + return FALSE; + } + + if (cdid == 0) + { + /* On some systems write will return 0 if carrier is + lost. If we fail to write anything ten times in a + row, we assume that this has happened. This is + hacked in like this because there seems to be no + reliable way to tell exactly why the write returned + 0. */ + ++czero; + if (czero >= 10) + { + ulog (LOG_ERROR, "Line disconnected"); + return FALSE; + } + } + else + { + cwrite -= cdid; + zwrite += cdid; + *pcwrite += cdid; + czero = 0; + } + } + } +} + +/* Send a break character to a serial port. */ + +static boolean +fsserial_break (qconn) + struct sconnection *qconn; +{ + struct ssysdep_conn *q; + + q = (struct ssysdep_conn *) qconn->psysdep; + +#if HAVE_BSD_TTY + (void) ioctl (q->o, TIOCSBRK, 0); + sleep (2); + (void) ioctl (q->o, TIOCCBRK, 0); + return TRUE; +#endif /* HAVE_BSD_TTY */ +#if HAVE_SYSV_TERMIO + (void) ioctl (q->o, TCSBRK, 0); + return TRUE; +#endif /* HAVE_SYSV_TERMIO */ +#if HAVE_POSIX_TERMIOS + return tcsendbreak (q->o, 0) == 0; +#endif /* HAVE_POSIX_TERMIOS */ +} + +/* Send a break character to a stdin port. */ + +static boolean +fsstdin_break (qconn) + struct sconnection *qconn; +{ + struct ssysdep_conn *qsysdep; + + qsysdep = (struct ssysdep_conn *) qconn->psysdep; + qsysdep->o = 1; + return fsserial_break (qconn); +} + +/* Change the setting of a serial port. */ + +/*ARGSUSED*/ +static boolean +fsserial_set (qconn, tparity, tstrip, txonxoff) + struct sconnection *qconn; + enum tparitysetting tparity; + enum tstripsetting tstrip; + enum txonxoffsetting txonxoff; +{ + register struct ssysdep_conn *q; + boolean fchanged, fdo; + int iset = 0; + int iclear = 0; + + q = (struct ssysdep_conn *) qconn->psysdep; + + if (! q->fterminal) + return TRUE; + + fchanged = FALSE; + + /* Set the parity for output characters. */ + +#if HAVE_BSD_TTY + + /* This will also cause parity detection on input characters. */ + + fdo = FALSE; + switch (tparity) + { + case PARITYSETTING_DEFAULT: + break; + case PARITYSETTING_NONE: +#if HAVE_PARITY_BUG + /* The Sony NEWS mishandles this for some reason. */ + iset = 0; + iclear = ANYP; +#else + iset = ANYP; + iclear = 0; +#endif + fdo = TRUE; + break; + case PARITYSETTING_EVEN: + iset = EVENP; + iclear = ODDP; + fdo = TRUE; + break; + case PARITYSETTING_ODD: + iset = ODDP; + iclear = EVENP; + fdo = TRUE; + break; + case PARITYSETTING_MARK: + case PARITYSETTING_SPACE: + /* Not supported. */ + break; + } + + if (fdo) + { + if ((q->snew.stty.sg_flags & iset) != iset + || (q->snew.stty.sg_flags & iclear) != 0) + { + q->snew.stty.sg_flags |= iset; + q->snew.stty.sg_flags &=~ iclear; + fchanged = TRUE; + } + } + +#else /* ! HAVE_BSD_TTY */ + + fdo = FALSE; + switch (tparity) + { + case PARITYSETTING_DEFAULT: + break; + case PARITYSETTING_NONE: + iset = CS8; + iclear = PARENB | PARODD | (CSIZE &~ CS8); + fdo = TRUE; + break; + case PARITYSETTING_EVEN: + iset = PARENB | CS7; + iclear = PARODD | (CSIZE &~ CS7); + fdo = TRUE; + break; + case PARITYSETTING_ODD: + iset = PARENB | PARODD | CS7; + iclear = CSIZE &~ CS7; + fdo = TRUE; + break; + case PARITYSETTING_MARK: + case PARITYSETTING_SPACE: + /* Not supported. */ + break; + } + + if (fdo) + { + if ((q->snew.c_cflag & iset) != iset + || (q->snew.c_cflag & iclear) != 0) + { + q->snew.c_cflag |= iset; + q->snew.c_cflag &=~ iclear; + fchanged = TRUE; + } + } + +#endif /* ! HAVE_BSD_TTY */ + + /* Set whether input characters are stripped to seven bits. */ + +#if HAVE_BSD_TTY + +#ifdef LPASS8 + { + int i; + + i = LPASS8; + if (tstrip == STRIPSETTING_EIGHTBITS) + { + i = LPASS8; + (void) ioctl (q->o, TIOCLBIS, &i); + } + else if (tstrip == STRIPSETTING_SEVENBITS) + { + i = LPASS8; + (void) ioctl (q->o, TIOCLBIC, &i); + } + } +#endif + +#else /* ! HAVE_BSD_TTY */ + + fdo = FALSE; + switch (tstrip) + { + case STRIPSETTING_DEFAULT: + break; + case STRIPSETTING_EIGHTBITS: + iset = 0; + iclear = ISTRIP; + fdo = TRUE; + break; + case STRIPSETTING_SEVENBITS: + iset = ISTRIP; + iclear = 0; + fdo = TRUE; + break; + } + + if (fdo) + { + if ((q->snew.c_iflag & iset) != iset + || (q->snew.c_iflag & iclear) != 0) + { + q->snew.c_iflag |= iset; + q->snew.c_iflag &=~ iclear; + fchanged = TRUE; + } + } + +#endif /* ! HAVE_BSD_TTY */ + + /* Set XON/XOFF handshaking. */ + +#if HAVE_BSD_TTY + + fdo = FALSE; + switch (txonxoff) + { + case XONXOFF_DEFAULT: + break; + case XONXOFF_OFF: + iset = RAW; + iclear = TANDEM | CBREAK; + fdo = TRUE; + break; + case XONXOFF_ON: + iset = CBREAK | TANDEM; + iclear = RAW; + fdo = TRUE; + break; + } + + if (fdo) + { + if ((q->snew.stty.sg_flags & iset) != iset + || (q->snew.stty.sg_flags & iclear) != 0) + { + q->snew.stty.sg_flags |= iset; + q->snew.stty.sg_flags &=~ iclear; + fchanged = TRUE; + } + } + +#else /* ! HAVE_BSD_TTY */ + + fdo = FALSE; + switch (txonxoff) + { + case XONXOFF_DEFAULT: + break; + case XONXOFF_OFF: + iset = 0; + iclear = IXON | IXOFF; + fdo = TRUE; + break; + case XONXOFF_ON: +#ifdef CRTSCTS +#if HAVE_POSIX_TERMIOS + /* This is system dependent, but I haven't figured out a good + way around it yet. If we are doing hardware flow control, we + don't send XON/XOFF characters but we do recognize them. */ + if ((q->snew.c_cflag & CRTSCTS) != 0) + { + iset = IXON; + iclear = IXOFF; + fdo = TRUE; + break; + } +#endif /* HAVE_POSIX_TERMIOS */ +#endif /* defined (CRTSCTS) */ + iset = IXON | IXOFF; + iclear = 0; + fdo = TRUE; + break; + } + + if (fdo) + { + if ((q->snew.c_iflag & iset) != iset + || (q->snew.c_iflag & iclear) != 0) + { + q->snew.c_iflag |= iset; + q->snew.c_iflag &=~ iclear; + fchanged = TRUE; + } + } + +#endif /* ! HAVE_BSD_TTY */ + + if (fchanged) + { + if (! fsetterminfodrain (q->o, &q->snew)) + { + ulog (LOG_ERROR, "Can't change terminal settings: %s", + strerror (errno)); + return FALSE; + } + } + +#if HAVE_BSD_TTY + if (txonxoff == XONXOFF_ON + && (q->snew.stty.sg_flags & ANYP) == ANYP) + { + int i; + + /* At least on Ultrix, we seem to have to set LLITOUT and + LPASS8. This shouldn't foul things up anywhere else. As far + as I can tell, this has to be done after setting the terminal + into cbreak mode, not before. */ +#ifndef LLITOUT +#define LLITOUT 0 +#endif +#ifndef LPASS8 +#define LPASS8 0 +#endif +#ifndef LAUTOFLOW +#define LAUTOFLOW 0 +#endif + i = LLITOUT | LPASS8 | LAUTOFLOW; + (void) ioctl (q->o, TIOCLBIS, &i); + +#if HAVE_STRIP_BUG + /* Ultrix 4.0 has a peculiar problem: setting CBREAK always + causes input characters to be stripped. I hope this does not + apply to other BSD systems. It is possible to work around + this by using the termio call. I wish this sort of stuff was + not necessary!!! */ + { + struct termio s; + + if (ioctl (q->o, TCGETA, &s) >= 0) + { + s.c_iflag &=~ ISTRIP; + (void) ioctl (q->o, TCSETA, &s); + } + } +#endif /* HAVE_STRIP_BUG */ + } +#endif /* HAVE_BSD_TTY */ + + return TRUE; +} + +/* Change settings of a stdin port. */ + +static boolean +fsstdin_set (qconn, tparity, tstrip, txonxoff) + struct sconnection *qconn; + enum tparitysetting tparity; + enum tstripsetting tstrip; + enum txonxoffsetting txonxoff; +{ + struct ssysdep_conn *qsysdep; + + qsysdep = (struct ssysdep_conn *) qconn->psysdep; + qsysdep->o = 0; + return fsserial_set (qconn, tparity, tstrip, txonxoff); +} + +/* Run a chat program. */ + +static boolean +fsrun_chat (oread, owrite, pzprog) + int oread; + int owrite; + char **pzprog; +{ + int aidescs[3]; + FILE *e; + pid_t ipid; + char *z; + size_t c; + + aidescs[0] = oread; + aidescs[1] = owrite; + aidescs[2] = SPAWN_READ_PIPE; + + /* Pass fkeepuid, fkeepenv and fshell as TRUE. This puts the + responsibility of maintaing security on the chat program. */ + ipid = ixsspawn ((const char **) pzprog, aidescs, TRUE, TRUE, + (const char *) NULL, FALSE, TRUE, (const char *) NULL, + (const char *) NULL, (const char *) NULL); + if (ipid < 0) + { + ulog (LOG_ERROR, "ixsspawn (%s): %s", pzprog[0], strerror (errno)); + return FALSE; + } + + e = fdopen (aidescs[2], (char *) "r"); + if (e == NULL) + { + ulog (LOG_ERROR, "fdopen: %s", strerror (errno)); + (void) close (aidescs[2]); + (void) kill (ipid, SIGKILL); + (void) ixswait ((unsigned long) ipid, (const char *) NULL); + return FALSE; + } + + /* The FILE e now is attached to stderr of the program. Forward + every line the program outputs to the log file. */ + z = NULL; + c = 0; + while (getline (&z, &c, e) > 0) + { + size_t clen; + + clen = strlen (z); + if (z[clen - 1] == '\n') + z[clen - 1] = '\0'; + if (*z != '\0') + ulog (LOG_NORMAL, "chat: %s", z); + } + + xfree ((pointer) z); + (void) fclose (e); + + return ixswait ((unsigned long) ipid, "Chat program") == 0; +} + +/* Run a chat program on a stdin port. */ + +/*ARGSUSED*/ +static boolean +fsstdin_chat (qconn, pzprog) + struct sconnection *qconn; + char **pzprog; +{ + return fsrun_chat (0, 1, pzprog); +} + +/* Run a chat program on any general type of connection. */ + +boolean +fsysdep_conn_chat (qconn, pzprog) + struct sconnection *qconn; + char **pzprog; +{ + struct ssysdep_conn *qsysdep; + + qsysdep = (struct ssysdep_conn *) qconn->psysdep; + return fsrun_chat (qsysdep->o, qsysdep->o, pzprog); +} + +/* Return baud rate of a serial port. */ + +static long +isserial_baud (qconn) + struct sconnection *qconn; +{ + struct ssysdep_conn *qsysdep; + + qsysdep = (struct ssysdep_conn *) qconn->psysdep; + return qsysdep->ibaud; +} |
