A set of functions that helps to maintain a PTY-device for modem emulation.

This commit is contained in:
Morten Rolland 1999-10-31 21:16:13 +00:00
parent 76a1ca2f81
commit dfe1586629
2 changed files with 570 additions and 0 deletions

71
include/ifax/misc/pty.h Normal file
View File

@ -0,0 +1,71 @@
/* $Id$
******************************************************************************
Fax program for ISDN.
Handle all communications over a pseudo-tty (pty) to emulate a modem.
Copyright (C) 1999 Morten Rolland [Morten.Rolland@asker.mail.telia.com]
Copyright (C) 1998 Andreas Beck [becka@ggi-project.org]
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
******************************************************************************
*/
#include <sys/types.h>
#include <sys/timeb.h>
#include <ifax/types.h>
#ifndef _MISC_PTY_H
#define _MISC_PTY_H
#define PTY_BUFFERSIZE 512
#define PTY_DEVNAME_SIZE 64
struct PtyHandle {
int ptyfd;
struct timeb lastinput;
enum {
NO_LOGGING=0,
LOG_ADVANCE_READ=1,
LOG_SERVICE_READ=2,
LOG_WRITE=4
} debug;
struct PtyBuffer {
int wp, rp, size;
ifax_uint8 buffer[PTY_BUFFERSIZE];
} tx, rx;
char device[PTY_DEVNAME_SIZE+2];
};
extern struct PtyHandle *pty_initialize(char *device);
extern void pty_reset(struct PtyHandle *ph);
extern int pty_write_max(struct PtyHandle *ph); /* How large write is OK */
extern int pty_write_queued(struct PtyHandle *ph); /* Size of current queue */
extern int pty_read_max(struct PtyHandle *ph); /* How large read is OK */extern void pty_write(struct PtyHandle *ph, void *buf, size_t size);
extern void pty_prepare_select(struct PtyHandle *, int *,fd_set *, fd_set *);
extern void pty_service_write(struct PtyHandle *ph);
extern void pty_service_read(struct PtyHandle *ph);
extern void pty_readbuffer(struct PtyHandle *ph, ifax_uint8 **buf, size_t *sz);
extern void pty_advance(struct PtyHandle *ph, size_t size);
extern void pty_printf(struct PtyHandle *ph, char *format, ... );
#endif

499
misc/pty.c Normal file
View File

@ -0,0 +1,499 @@
/* $Id$
******************************************************************************
Fax program for ISDN.
Handle all communications over a pseudo-tty (pty) to emulate a modem.
Copyright (C) 1999 Morten Rolland [Morten.Rolland@asker.mail.telia.com]
Copyright (C) 1999 Andreas Beck [becka@ggi-project.org]
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
******************************************************************************
*/
/* This module takes care of initialization and maintenance
* of a pair of tty/pty for emulating a modem on a terminal
* port.
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/timeb.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <ifax/misc/pty.h>
#include <ifax/debug.h>
#include <ifax/misc/globals.h>
#include <ifax/misc/malloc.h>
/* Open and initialize a pty device. This is done both on initial setup
* and later resets.
*/
static void pty_open(struct PtyHandle *ph)
{
struct termios tios;
if ( (ph->ptyfd=open(ph->device,O_RDWR|O_NONBLOCK)) < 0 ) {
ifax_dprintf(DEBUG_LAST,
"%s: Unable to open pty device '%s': %s\n",
progname,ph->device,strerror(errno));
exit(1);
}
ph->rx.rp = ph->rx.wp = ph->rx.size = 0;
ph->tx.rp = ph->tx.wp = ph->tx.size = 0;
if ( tcgetattr(ph->ptyfd,&tios) ) {
ifax_dprintf(DEBUG_LAST,
"%s: Unable to tcgetattr pty device '%s': %s\n",
progname,ph->device,strerror(errno));
exit(1);
}
tios.c_lflag &= ~ECHO;
if ( tcsetattr(ph->ptyfd,TCSANOW,&tios) ) {
ifax_dprintf(DEBUG_LAST,
"%s: Unable to tcsetattr pty device '%s: %s'\n",
progname,ph->device,strerror(errno));
exit(1);
}
}
static void pty_dump(char *prefix, ifax_uint8 *log1, int logsize1,
ifax_uint8 *log2, int logsize2)
{
char buffer[256];
char item[8], *i;
ifax_uint8 c;
int wp, prefixlen, itemlen;
static char *ctrl[33] = {
"NUL","\\001","\\002","ETX","\\004","\\005","\\006","\\007",
"\\010","\\011","\\n","\\013","\\014","\\r","\\016","\\017",
"DLE","\\021","\\022","\\023","\\024","\\025","\\026","\\027",
"\\030","\\031","\\032","ESC","\\034","\\035","\\036","\\037",
"SPC"
};
wp = 0;
prefixlen = strlen(prefix);
while ( logsize1 > 0 ) {
if ( wp == 0 ) {
strcpy(buffer,prefix);
wp = prefixlen;
}
c = *log1++;
logsize1--;
if ( c < 33 ) {
i = ctrl[c];
} else if ( c < 127 ) {
item[0] = c;
item[1] = '\0';
i = item;
} else {
sprintf(item,"%03o",(int)c);
i = item;
}
itemlen = strlen(i);
buffer[wp++] = ' ';
strcpy(&buffer[wp],i);
wp += itemlen;
if ( wp > 75 ) {
buffer[wp++] = '\n';
buffer[wp] = '\0';
ifax_dprintf(DEBUG_DEBUG,buffer);
wp = 0;
}
if ( logsize1 == 0 ) {
logsize1 = logsize2;
log1 = log2;
logsize2 = 0;
}
}
if ( wp ) {
buffer[wp++] = '\n';
buffer[wp] = '\0';
ifax_dprintf(DEBUG_DEBUG,buffer);
}
}
/* Allocate and initialize a pty instance for the given device.
* Once initialized it can be read/written/serviced using all
* functions in this file.
*/
struct PtyHandle *pty_initialize(char *device)
{
struct PtyHandle *ph;
ph = ifax_malloc(sizeof(*ph),"PTY instance");
if ( strlen(device) >= PTY_DEVNAME_SIZE ) {
ifax_dprintf(DEBUG_LAST,
"%s: Name of pty device '%s' too long\n",
progname,device);
exit(1);
}
strcpy(ph->device,device);
ph->debug = 7;
pty_open(ph);
return ph;
}
/* Reset the pty by closing and re-opening the pty-file and initializing
* it again, but maintain the same PtyHandle to it.
*/
void pty_reset(struct PtyHandle *ph)
{
ifax_dprintf(DEBUG_DEBUG, "%s: Resetting pty '%s'\n",
progname,ph->device);
close(ph->ptyfd);
pty_open(ph);
}
/* This function returns how many bytes can be written to the pty
* at this instance. Trying to write more bytes than this function
* reports possible is undefined.
*/
int pty_write_max(struct PtyHandle *ph)
{
return PTY_BUFFERSIZE - ph->tx.size;
}
/* Return number of bytes queued on the write-queue. We may want
* to watch this value to keep modem-to-modem flow control updated
* on progress. The total buffer capacity is found with:
*
* pty_write_queued(ph) + pty_write_max(ph)
*/
int pty_write_queued(struct PtyHandle *ph)
{
return ph->tx.size;
}
/* Return how many bytes can be read from the pty. NOTE: The number of
* bytes returned here may have to be accessed using two pty_reabbuffer
* operations. In other words, this function may return 1000 bytes
* available for reading, but the first call to pty_readbuffer may only
* yield 1 byte... The next however will yield the remaining 999 bytes.
*/
int pty_read_max(struct PtyHandle *ph)
{
return ph->rx.size;
}
/* Write a buffer to the pty. It is important not to write more than
* the software buffer can hold. If more is written, the result is
* undefined.
*/
void pty_write(struct PtyHandle *ph, void *buffer, size_t size)
{
ifax_uint8 *dst, *src = buffer;
size_t chunk;
char prefix[256];
if ( pty_write_max(ph) < size ) {
ifax_dprintf(DEBUG_SEVERE,
"%s: Overruns on output pty device '%s'\n",
progname,ph->device);
return;
}
if ( ph->debug & LOG_WRITE ) {
sprintf(prefix,"PTY-wrt %s:",ph->device);
pty_dump(prefix,buffer,size,0,0);
}
if ( ph->tx.wp >= ph->tx.rp ) {
/* Unused space in both ends of buffer */
dst = &ph->tx.buffer[ph->tx.wp];
chunk = PTY_BUFFERSIZE - ph->tx.wp;
ph->tx.size += size;
if ( chunk > size ) {
ph->tx.wp += size;
memcpy(dst,src,size);
} else {
memcpy(dst,src,chunk);
if ( (ph->tx.wp = size - chunk) > 0 ) {
src += chunk;
memcpy(&ph->tx.buffer[0],src,ph->tx.wp);
}
}
return;
}
/* Unused space in the middle */
memcpy(&ph->tx.buffer[ph->tx.wp],src,size);
ph->tx.wp += size;
ph->tx.size += size;
}
/* When a select is to be called to wait for things to happen, the
* following function should be used to set up the read and write
* fd_set structures so the wakeup conditions will be suitable
* for the pty-service function.
*/
void pty_prepare_select(struct PtyHandle *ph, int *maxfd,
fd_set *rfd, fd_set *wfd)
{
/* read only if rx buffer can hold data */
if ( ph->rx.size < PTY_BUFFERSIZE ) {
FD_SET(ph->ptyfd,rfd);
} else {
FD_CLR(ph->ptyfd,rfd);
}
/* Write only if there is anything queued */
if ( ph->tx.size > 0 ) {
FD_SET(ph->ptyfd,wfd);
} else {
FD_CLR(ph->ptyfd,wfd);
}
if ( ph->ptyfd + 1 > *maxfd )
*maxfd = ph->ptyfd + 1;
}
/* Call this function regularly, preferably after a select has
* indicated there is traffic or possible traffic on the pty.
* This function will try to flush the output queue.
*/
void pty_service_write(struct PtyHandle *ph)
{
int chunk, rc;
/* Try to flush tx-buffer if there is something queued */
if ( ph->tx.size > 0 ) {
chunk = PTY_BUFFERSIZE - ph->tx.rp;
if ( chunk > ph->tx.size )
chunk = ph->tx.size;
rc = write(ph->ptyfd,&ph->tx.buffer[ph->tx.rp],chunk);
if ( rc < 0 ) {
if ( errno == EAGAIN )
return;
goto failed_write;
}
ph->tx.rp += rc;
ph->tx.size -= rc;
if ( rc < chunk )
return;
if ( ph->tx.rp >= PTY_BUFFERSIZE )
ph->tx.rp = 0;
if ( ph->tx.size > 0 ) {
rc = write(ph->ptyfd,&ph->tx.buffer[0],ph->tx.size);
if ( rc < 0 ) {
if ( errno == EAGAIN )
return;
goto failed_write;
}
ph->tx.rp = rc;
ph->tx.size -= rc;
}
}
return;
failed_write:
ifax_dprintf(DEBUG_LAST,
"%s: Can't write on pty '%s': %s\n",
progname,ph->device,strerror(errno));
exit(1);
}
/* Call this function regularly, preferably after a select has
* indicated there is traffic or possible traffic on the pty.
* This function will try to read any new input into the input queue.
*/
void pty_service_read(struct PtyHandle *ph)
{
int chunk;
char prefix[256];
ifax_uint8 *log1 = 0, *log2 = 0;
int logsize1, logsize2;
logsize1 = logsize2 = 0;
/* Try to read if there is space in rx-buffer */
if ( (chunk = PTY_BUFFERSIZE - ph->rx.size) == 0 )
return;
if ( (PTY_BUFFERSIZE - ph->rx.wp) < chunk )
chunk = PTY_BUFFERSIZE - ph->rx.wp;
log1 = &ph->rx.buffer[ph->rx.wp];
logsize1 = read(ph->ptyfd,log1,chunk);
if ( logsize1 < 0 ) {
if ( errno == EAGAIN )
return;
goto failed_read;
}
ph->rx.wp += logsize1;
ph->rx.size += logsize1;
if ( logsize1 < chunk )
goto all_ok;
if ( ph->rx.wp >= PTY_BUFFERSIZE )
ph->rx.wp = 0;
if ( (chunk = PTY_BUFFERSIZE - ph->rx.size) == 0 )
goto all_ok;
ph->rx.wp = 0;
log2 = &ph->rx.buffer[ph->rx.wp];
logsize2 = read(ph->ptyfd,log2,chunk);
if ( logsize2 < 0 ) {
if ( errno == EAGAIN ) {
logsize2 = 0;
goto all_ok;
}
goto failed_read;
}
ph->rx.wp += logsize2;
ph->rx.size += logsize2;
all_ok:
if ( ph->debug & LOG_SERVICE_READ ) {
sprintf(prefix,"PTY-srd %s:",ph->device);
pty_dump(prefix,log1,logsize1,log2,logsize2);
}
return;
failed_read:
ifax_dprintf(DEBUG_DEBUG, "%s: Reading pty '%s' failed: %s\n",
progname,ph->device,strerror(errno));
pty_reset(ph);
return;
}
/* The following function sets up a pointer into a buffer and
* its size, which can be used for direct parsing without copying.
* The size of the buffer returned may be shorter than the total
* size of the receive buffer. Advance the pty by the full size
* and call 'pty_next_buffer' to get access to the next chunk of
* data.
*/
void pty_readbuffer(struct PtyHandle *ph, ifax_uint8 **buffer, size_t *size)
{
size_t chunk;
*buffer = &ph->rx.buffer[ph->rx.rp];
chunk = PTY_BUFFERSIZE - ph->rx.rp;
if ( ph->rx.size < chunk )
chunk = ph->rx.size;
*size = chunk;
}
/* The 'pty_advance' function advances the read buffer without any
* memory copy operations, assuming they have been processed allready
* (by 'pty_readbuffer') or can be discarded.
*/
void pty_advance(struct PtyHandle *ph, size_t size)
{
int logsize1, logsize2;
char prefix[256];
if ( ph->debug & LOG_ADVANCE_READ ) {
sprintf(prefix,"PTY-adv %s:",ph->device);
logsize1 = size;
logsize2 = 0;
if ( ph->rx.rp + logsize1 > PTY_BUFFERSIZE ) {
logsize1 = PTY_BUFFERSIZE - ph->rx.rp;
logsize2 = size - logsize1;
}
pty_dump(prefix,&ph->rx.buffer[ph->rx.rp],logsize1,
&ph->rx.buffer[0],logsize2);
}
ph->rx.size -= size;
ph->rx.rp += size;
if ( ph->rx.rp >= PTY_BUFFERSIZE )
ph->rx.rp -= PTY_BUFFERSIZE;
}
/* Simple helper function to simply output formatted strings to the pty */
void pty_printf(struct PtyHandle *ph, char *format, ... )
{
#define PTY_PRINTF_MAXSIZE 512
va_list args;
char tmp[PTY_PRINTF_MAXSIZE+4];
va_start(args,format);
vsnprintf(tmp, PTY_PRINTF_MAXSIZE, format, args);
va_end(args);
pty_write(ph,tmp,strlen(tmp));
}