// Maurice LeBrun // 6-Jan-94 // // Functions to handle a variety of TPC-IP related chores, in particular // socket i/o for data transfer to the Tcl-DP driver. For the latter, the // Tcl-DP routines were modified to use binary records; copyright follows: // // Copyright 1992 Telecom Finland // // Permission to use, copy, modify, and distribute this // software and its documentation for any purpose and without // fee is hereby granted, provided that this copyright // notice appears in all copies. Telecom Finland // makes no representations about the suitability of this // software for any purpose. It is provided "as is" without // express or implied warranty. // // Copyright (c) 1993 The Regents of the University of California. // All rights reserved. // // Permission to use, copy, modify, and distribute this software and its // documentation for any purpose, without fee, and without written agreement is // hereby granted, provided that the above copyright notice and the following // two paragraphs appear in all copies of this software. // // IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR // DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT // OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF // CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY // AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS // ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO // PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. // // // Copyright (C) 2004 Joao Cardoso // // This file is part of PLplot. // // PLplot is free software; you can redistribute it and/or modify // it under the terms of the GNU Library General Public License as published // by the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // PLplot 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 Library General Public License for more details. // // You should have received a copy of the GNU Library General Public License // along with PLplot; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // // // #define DEBUG // #include "plDevs.h" #include "plConfig.h" #if defined ( PLD_tk ) || defined ( ENABLE_tkX ) // This file is meant to be compiled with non-ANSI compilers ("cc"). // The reason for doing it this way is to ensure that the full C // environment of the target machine is visible (at the time of writing // this, parts of this code are not covered by any international // standard). ANSI compilers are required to omit these extra symbols, // and at the moment there is no way to get them back except for by // vendor-specific defines, e.g. _HPUX_SOURCE (HP), _ALL_SOURCE (AIX), // _DGUX_SOURCE (DGUX). This is an omission in the POSIX standard more // than anything else, and will probably be rectified at some point. So // for now, instead of relying on a hodgepodge of vendor specific symbols // I forego the ANSI compiler here and go with good (bad) old "cc". // #ifdef _POSIX_SOURCE #undef _POSIX_SOURCE #endif #ifdef caddr_t #undef caddr_t #endif #include #include #include #include #if defined ( __sgi ) && !defined ( SVR3 ) #include #endif #ifdef PL_HAVE_UNISTD_H #include #endif #include "plplot.h" #include "tcpip.h" #include #include #include #include #include #include #if !defined ( _WIN32 ) #include #endif #include #if defined ( _WIN32 ) // This is the source of the WSAEWOULDBLOCK macro on Windows platforms. #include #endif #if defined ( EWOULDBLOCK ) #define PLPLOT_EWOULDBLOCK EWOULDBLOCK #elif defined ( WSAEWOULDBLOCK ) #define PLPLOT_EWOULDBLOCK WSAEWOULDBLOCK #else #error broken system where neither EWOULDBLOCK nor WSAEWOULDBLOCK macros are #defined #endif // Supply dummy macros for the low-level I/O functions - Windows #if defined ( _WIN32 ) #define read( a, b, c ) 0 #define close( a ) #define write( a, b, c ) 0 #endif //extern int errno; #ifndef MIN #define MIN( a, b ) ( ( ( a ) < ( b ) ) ? ( a ) : ( b ) ) #endif // // This is a "magic number" prepended to the beginning of the packet // Used to help resync the packet machanism in the event of errors. // #define PACKET_MAGIC 0x6feeddcc // // For TCP, it's possible to get a line in pieces. In case everything we // want isn't there, we need a place to store partial results when we're // in non-blocking mode. The partial buffers below are created // dynamically to store incomplete data in these cases. // typedef struct PartialRead { char *buffer; // Buffer of characters int bufSize; // Size of buffer int offset; // Offset of current character within buffer struct PartialRead *next; // Next buffer in chain } PartialRead; #define MAX_OPEN_FILES 128 static PartialRead *partial[MAX_OPEN_FILES]; static void pl_FreeReadBuffer( int fd ); static void pl_Unread( int fd, char *buffer, int numBytes, int copy ); static int pl_Read( int fd, char *buffer, int numReq ); int pl_PacketReceive( Tcl_Interp * interp, PLiodev *iodev, PDFstrm *pdfs ); int pl_PacketSend( Tcl_Interp * interp, PLiodev *iodev, PDFstrm *pdfs ); // //-------------------------------------------------------------------------- // // pl_FreeReadBuffer -- // // This function is called to free up all the memory associated // with a file once the file is closed. // // Results: // None. // // Side effects: // Any data buffered locally will be lost. // //-------------------------------------------------------------------------- // static void pl_FreeReadBuffer( int fd ) { PartialRead *readList; while ( partial[fd] != NULL ) { readList = partial[fd]; partial[fd] = readList->next; free( readList->buffer ); free( readList ); } } // //-------------------------------------------------------------------------- // // pl_Unread -- // // This function puts data back into the read chain on a // file descriptor. It's basically an extended "ungetc". // // Results: // None. // // Side effects: // Subsequent calls to pl_Read on the fd will get this data. // //-------------------------------------------------------------------------- // static void pl_Unread( int fd, char *buffer, int numBytes, int copy ) //int fd; // File descriptor //char *buffer; // Data to unget //int numBytes; // Number of bytes to unget //int copy; // Should we copy the data, or use this // buffer? { PartialRead *new; new = (PartialRead *) malloc( sizeof ( PartialRead ) ); if ( copy ) { new->buffer = (char *) malloc( (size_t) numBytes ); memcpy( new->buffer, buffer, (size_t) numBytes ); } else { new->buffer = buffer; } new->bufSize = numBytes; new->offset = 0; new->next = partial[fd]; partial[fd] = new; } // //-------------------------------------------------------------------------- // // pl_Read -- // // This function implements a "read"-like command, but // buffers partial reads. The semantics are the same as // with read. // // Results: // Number of bytes read, or -1 on error (with errno set). // // Side effects: // All available data is read from the file descriptor. // //-------------------------------------------------------------------------- // static int pl_Read( int fd, char *buffer, int numReq ) //int fd; // File descriptor to read from //char *buffer; // Place to put the data //int numReq; // Number of bytes to get { PartialRead *readList; PartialRead *tmp; int numRead; int numToCopy; readList = partial[fd]; // // If there's no data left over from a previous read, then just do a read // This is the common case. // if ( readList == NULL ) { numRead = (int) read( fd, buffer, (size_t) numReq ); #ifdef DEBUG { int j; fprintf( stderr, "received %d bytes starting with:", numRead ); for ( j = 0; j < MIN( 8, numRead ); j++ ) fprintf( stderr, " %x", 0x000000FF & (unsigned long) buffer[j] ); fprintf( stderr, "\n" ); } #endif return numRead; } // // There's data left over from a previous read. Yank it in and // only call read() if we didn't get enough data (this keeps the fd // readable if they only request as much data as is in the buffers). // numRead = 0; while ( ( readList != NULL ) && ( numRead < numReq ) ) { numToCopy = readList->bufSize - readList->offset; if ( numToCopy + numRead > numReq ) { numToCopy = numReq - numRead; } memcpy( buffer + numRead, readList->buffer + readList->offset, (size_t) numToCopy ); // // Consume the data // tmp = readList; readList = readList->next; tmp->offset += numToCopy; if ( tmp->offset == tmp->bufSize ) { free( tmp->buffer ); free( tmp ); partial[fd] = readList; } numRead += numToCopy; } // // Only call read if at the end of a previously incomplete packet. // if ( ( numRead < numReq ) ) { numToCopy = numReq - numRead; numRead += (int) read( fd, buffer + numRead, (size_t) numToCopy ); } return numRead; } //-------------------------------------------------------------------------- // This part for Tcl-DP only //-------------------------------------------------------------------------- #ifdef PLD_dp #include #include #include #include #include //-------------------------------------------------------------------------- // plHost_ID // // Tcl command -- return the IP address for the current host. // // Derived from source code in "UNIX Network Programming" by W. Richard // Stevens, Prentice Hall, 1990. //-------------------------------------------------------------------------- static char * get_inet( char ** listptr, int length ) { struct in_addr *ptr; while ( ( ptr = (struct in_addr *) *listptr++ ) == NULL ) continue; return inet_ntoa( *ptr ); } int plHost_ID( ClientData clientData, Tcl_Interp *interp, int argc, char **argv ) { register struct hostent *hostptr; char hostname[100]; if ( gethostname( hostname, 100 ) ) { Tcl_AppendResult( interp, "Error -- cannot get host name", (char *) NULL ); return TCL_ERROR; } if ( ( hostptr = gethostbyname( hostname ) ) == NULL ) { Tcl_AppendResult( interp, "Error -- cannot get host info for node ", hostname, (char *) NULL ); return TCL_ERROR; } Tcl_SetResult( interp, get_inet( hostptr->h_addr_list, hostptr->h_length ), TCL_VOLATILE ); return TCL_OK; } #endif // PLD_dp // //-------------------------------------------------------------------------- // // pl_PacketReceive -- // // This procedure is a modified version of Tdp_PacketReceive, // from the Tcl-DP distribution. It reads the socket, // returning a complete packet. If the entire packet cannot // be read, the partial packet is buffered until the rest is // available. Some capabilities have been removed from the // original, such as the check for a non-server TCP socket, // since there's no access to the optFlags array from here, // and the peek capability, since I don't need it. // // Results: // Packet contents stored in pdfs->buffer and pdfs->bp set // to the number of bytes read (zero if incomplete). // // Side effects: // The file descriptor passed in is read. // //-------------------------------------------------------------------------- // int pl_PacketReceive( Tcl_Interp *interp, PLiodev *iodev, PDFstrm *pdfs ) { int j, numRead; unsigned int packetLen, header[2]; int headerSize; unsigned char hbuf[8]; const char *errMsg; pdfs->bp = 0; // // Read in the header (8 bytes) // headerSize = 8; numRead = pl_Read( iodev->fd, (char *) hbuf, headerSize ); if ( numRead <= 0 ) { #ifdef DEBUG fprintf( stderr, "Incorrect header read, numRead = %d\n", numRead ); #endif goto readError; } // // Check for incomplete read. If so, put it back and return. // if ( numRead < headerSize ) { #ifdef DEBUG fprintf( stderr, "Incomplete header read, numRead = %d\n", numRead ); #endif pl_Unread( iodev->fd, (char *) hbuf, numRead, 1 ); Tcl_ResetResult( interp ); return TCL_OK; } // // Convert header character stream into ints. This works when the // connecting machine has a different size int and takes care of the // endian problem to boot. It is also mostly backward compatible since // network byte ordering (big endian) is used. // j = 0; header[0] = 0; header[0] |= (unsigned int) ( hbuf[j++] << 24 ); header[0] |= (unsigned int) ( hbuf[j++] << 16 ); header[0] |= (unsigned int) ( hbuf[j++] << 8 ); header[0] |= hbuf[j++]; header[1] = 0; header[1] |= (unsigned int) ( hbuf[j++] << 24 ); header[1] |= (unsigned int) ( hbuf[j++] << 16 ); header[1] |= (unsigned int) ( hbuf[j++] << 8 ); header[1] |= hbuf[j++]; // // Format of each packet: // // First 4 bytes are PACKET_MAGIC. // Next 4 bytes are packetLen. // Next packetLen-headerSize is zero terminated string // if ( header[0] != PACKET_MAGIC ) { fprintf( stderr, "Badly formatted packet, numRead = %d\n", numRead ); Tcl_AppendResult( interp, "Error reading from ", iodev->typeName, ": badly formatted packet", (char *) NULL ); return TCL_ERROR; } packetLen = header[1] - (unsigned int) headerSize; // // Expand the size of the buffer, as needed. // if ( header[1] > (unsigned) pdfs->bufmax ) { free( (void *) pdfs->buffer ); pdfs->bufmax = header[1] + 32; pdfs->buffer = (unsigned char *) malloc( pdfs->bufmax ); } // // Read in the packet, and if it's not all there, put it back. // // We have to be careful here, because we could block if just the // header came in (making the file readable at the beginning of this // function) but the rest of the packet is still out on the network. // if ( iodev->type == 0 ) { numRead = pl_Read( iodev->fd, (char *) pdfs->buffer, (int) packetLen ); } else { #ifdef PLD_dp if ( Tdp_FDIsReady( iodev->fd ) & TCL_FILE_READABLE ) { numRead = pl_Read( iodev->fd, (char *) pdfs->buffer, packetLen ); } else { #ifdef DEBUG fprintf( stderr, "Packet not ready, putting back header\n" ); #endif pl_Unread( iodev->fd, (char *) hbuf, headerSize, 1 ); Tcl_ResetResult( interp ); return TCL_OK; } #endif } if ( numRead <= 0 ) { goto readError; } if ( (unsigned) numRead != packetLen ) { #ifdef DEBUG fprintf( stderr, "Incomplete packet read, numRead = %d\n", numRead ); #endif pl_Unread( iodev->fd, (char *) pdfs->buffer, numRead, 1 ); pl_Unread( iodev->fd, (char *) hbuf, headerSize, 1 ); return TCL_OK; } pdfs->bp = (size_t) numRead; #ifdef DEBUG fprintf( stderr, "received %d byte packet starting with:", numRead ); for ( j = 0; j < 4; j++ ) { fprintf( stderr, " %x", 0x000000FF & (unsigned long) pdfs->buffer[j] ); } fprintf( stderr, "\n" ); #endif return TCL_OK; readError: // // // If we're in non-blocking mode, and this would block, return. // If the connection is closed (numRead == 0), don't return an // error message. Otherwise, return one. // // In either case, we close the file, delete the file handler, and // return a null string. // if ( errno == PLPLOT_EWOULDBLOCK || errno == EAGAIN ) { Tcl_ResetResult( interp ); return TCL_OK; } // Record the error before closing the file if ( numRead != 0 ) { errMsg = Tcl_PosixError( interp ); } else { errMsg = NULL; // Suppresses spurious compiler warning } // // Remove the file handler and close the file. // if ( iodev->type == 0 ) { // Exclude UNIX-only feature #if !defined ( MAC_TCL ) && !defined ( _WIN32 ) && !defined ( __CYGWIN__ ) Tk_DeleteFileHandler( iodev->fd ); #endif close( iodev->fd ); } pl_FreeReadBuffer( iodev->fd ); Tcl_ResetResult( interp ); if ( numRead == 0 ) { return TCL_OK; } else { Tcl_AppendResult( interp, "pl_PacketReceive -- error reading from ", iodev->typeName, ": ", errMsg, (char *) NULL ); return TCL_ERROR; } } // //-------------------------------------------------------------------------- // // pl_PacketSend -- // // This procedure is a modified version of Tdp_PacketSend, // from the Tcl-DP distribution. It writes a complete packet // to a socket or file-oriented device. // // Results: // A standard tcl result. // // Side effects: // The specified buffer is written to the file descriptor passed // in. // //-------------------------------------------------------------------------- // int pl_PacketSend( Tcl_Interp * interp, PLiodev *iodev, PDFstrm *pdfs ) { int j, numSent; unsigned char hbuf[8]; unsigned int packetLen, header[2]; size_t len; char *buffer, tmp[256]; // // Format up the packet: // First 4 bytes are PACKET_MAGIC. // Next 4 bytes are packetLen. // Next packetLen-8 bytes are buffer contents. // packetLen = (unsigned int) pdfs->bp + 8; header[0] = PACKET_MAGIC; header[1] = packetLen; // // Convert header ints to character stream. // Network byte ordering (big endian) is used. // j = 0; hbuf[j++] = (unsigned char) ( ( header[0] & (unsigned long) 0xFF000000 ) >> 24 ); hbuf[j++] = (unsigned char) ( ( header[0] & (unsigned long) 0x00FF0000 ) >> 16 ); hbuf[j++] = (unsigned char) ( ( header[0] & (unsigned long) 0x0000FF00 ) >> 8 ); hbuf[j++] = (unsigned char) ( header[0] & (unsigned long) 0x000000FF ); hbuf[j++] = (unsigned char) ( ( header[1] & (unsigned long) 0xFF000000 ) >> 24 ); hbuf[j++] = (unsigned char) ( ( header[1] & (unsigned long) 0x00FF0000 ) >> 16 ); hbuf[j++] = (unsigned char) ( ( header[1] & (unsigned long) 0x0000FF00 ) >> 8 ); hbuf[j++] = (unsigned char) ( header[1] & (unsigned long) 0x000000FF ); // // Send it off, with error checking. // Simulate writev using memcpy to put together // the msg so it can go out in a single write() call. // len = pdfs->bp + 8; buffer = (char *) malloc( len ); memcpy( buffer, (char *) hbuf, 8 ); memcpy( buffer + 8, (char *) pdfs->buffer, pdfs->bp ); #ifdef DEBUG fprintf( stderr, "sending %z byte packet starting with:", len ); for ( j = 0; j < 12; j++ ) { fprintf( stderr, " %x", 0x000000FF & (unsigned long) buffer[j] ); } fprintf( stderr, "\n" ); #endif numSent = (int) write( iodev->fd, buffer, len ); free( buffer ); if ( (unsigned) numSent != packetLen ) { if ( ( errno == 0 ) || ( errno == PLPLOT_EWOULDBLOCK || errno == EAGAIN ) ) { // // Non-blocking I/O: return number of bytes actually sent. // Tcl_ResetResult( interp ); sprintf( tmp, "%d", numSent - 8 ); Tcl_SetResult( interp, tmp, TCL_VOLATILE ); return TCL_OK; } else if ( errno == EPIPE ) { // // Got a broken pipe signal, which means the far end closed // the connection. Close the file and return 0 bytes sent. // if ( iodev->type == 0 ) { close( iodev->fd ); } sprintf( tmp, "0" ); Tcl_SetResult( interp, tmp, TCL_VOLATILE ); return TCL_OK; } else { Tcl_AppendResult( interp, "pl_PacketSend -- error writing to ", iodev->typeName, ": ", Tcl_PosixError( interp ), (char *) NULL ); } return TCL_ERROR; } // // Return the number of bytes sent (minus the header). // sprintf( tmp, "%d", numSent - 8 ); Tcl_SetResult( interp, tmp, TCL_VOLATILE ); return TCL_OK; } #else int pldummy_tcpip() { return 0; } #endif // defined(PLD_tk) || defined (ENABLE_tkX)