PLplot  5.10.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros
tcpip.c
Go to the documentation of this file.
1 // $Id: tcpip.c 12938 2014-01-13 03:27:10Z airwin $
2 //
3 // Maurice LeBrun
4 // 6-Jan-94
5 //
6 // Functions to handle a variety of TPC-IP related chores, in particular
7 // socket i/o for data transfer to the Tcl-DP driver. For the latter, the
8 // Tcl-DP routines were modified to use binary records; copyright follows:
9 //
10 // Copyright 1992 Telecom Finland
11 //
12 // Permission to use, copy, modify, and distribute this
13 // software and its documentation for any purpose and without
14 // fee is hereby granted, provided that this copyright
15 // notice appears in all copies. Telecom Finland
16 // makes no representations about the suitability of this
17 // software for any purpose. It is provided "as is" without
18 // express or implied warranty.
19 //
20 // Copyright (c) 1993 The Regents of the University of California.
21 // All rights reserved.
22 //
23 // Permission to use, copy, modify, and distribute this software and its
24 // documentation for any purpose, without fee, and without written agreement is
25 // hereby granted, provided that the above copyright notice and the following
26 // two paragraphs appear in all copies of this software.
27 //
28 // IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
29 // DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
30 // OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
31 // CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 //
33 // THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
34 // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
35 // AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
36 // ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
37 // PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
38 //
39 //
40 // Copyright (C) 2004 Joao Cardoso
41 //
42 // This file is part of PLplot.
43 //
44 // PLplot is free software; you can redistribute it and/or modify
45 // it under the terms of the GNU Library General Public License as published
46 // by the Free Software Foundation; either version 2 of the License, or
47 // (at your option) any later version.
48 //
49 // PLplot is distributed in the hope that it will be useful,
50 // but WITHOUT ANY WARRANTY; without even the implied warranty of
51 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
52 // GNU Library General Public License for more details.
53 //
54 // You should have received a copy of the GNU Library General Public License
55 // along with PLplot; if not, write to the Free Software
56 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
57 //
58 
59 //
60 // #define DEBUG
61 //
62 
63 #include "plDevs.h"
64 #include "plConfig.h"
65 
66 #if defined ( PLD_tk ) || defined ( ENABLE_tkX )
67 
68 // This file is meant to be compiled with non-ANSI compilers ("cc").
69 // The reason for doing it this way is to ensure that the full C
70 // environment of the target machine is visible (at the time of writing
71 // this, parts of this code are not covered by any international
72 // standard). ANSI compilers are required to omit these extra symbols,
73 // and at the moment there is no way to get them back except for by
74 // vendor-specific defines, e.g. _HPUX_SOURCE (HP), _ALL_SOURCE (AIX),
75 // _DGUX_SOURCE (DGUX). This is an omission in the POSIX standard more
76 // than anything else, and will probably be rectified at some point. So
77 // for now, instead of relying on a hodgepodge of vendor specific symbols
78 // I forego the ANSI compiler here and go with good (bad) old "cc".
79 //
80 
81 #ifdef _POSIX_SOURCE
82 #undef _POSIX_SOURCE
83 #endif
84 #ifdef caddr_t
85 #undef caddr_t
86 #endif
87 
88 #include <stdio.h>
89 #include <stdlib.h>
90 #include <math.h>
91 #include <string.h>
92 #if defined ( __sgi ) && !defined ( SVR3 )
93 #include <sys/select.h>
94 #endif
95 #ifdef PL_HAVE_UNISTD_H
96 #include <unistd.h>
97 #endif
98 
99 #include "plplot.h"
100 #include "tcpip.h"
101 #include <tcl.h>
102 #include <tk.h>
103 
104 #include <sys/stat.h>
105 #include <sys/types.h>
106 #include <fcntl.h>
107 #include <ctype.h>
108 #if !defined ( __WIN32__ )
109 #include <sys/uio.h>
110 #endif
111 #include <errno.h>
112 
113 #if defined ( __WIN32__ )
114 // This is the source of the WSAEWOULDBLOCK macro on Windows platforms.
115  #include <winerror.h>
116 #endif
117 
118 #if defined ( EWOULDBLOCK )
119  #define PLPLOT_EWOULDBLOCK EWOULDBLOCK
120 #elif defined ( WSAEWOULDBLOCK )
121  #define PLPLOT_EWOULDBLOCK WSAEWOULDBLOCK
122 #else
123  #error broken system where neither EWOULDBLOCK nor WSAEWOULDBLOCK macros are #defined
124 #endif
125 
126 // Supply dummy macros for the low-level I/O functions - Windows
127 #if defined ( __WIN32__ )
128 #define read( a, b, c ) 0
129 #define close( a )
130 #define write( a, b, c ) 0
131 #endif
132 
133 //extern int errno;
134 
135 #ifndef MIN
136 #define MIN( a, b ) ( ( ( a ) < ( b ) ) ? ( a ) : ( b ) )
137 #endif
138 
139 //
140 // This is a "magic number" prepended to the beginning of the packet
141 // Used to help resync the packet machanism in the event of errors.
142 //
143 #define PACKET_MAGIC 0x6feeddcc
144 
145 //
146 // For TCP, it's possible to get a line in pieces. In case everything we
147 // want isn't there, we need a place to store partial results when we're
148 // in non-blocking mode. The partial buffers below are created
149 // dynamically to store incomplete data in these cases.
150 //
151 
152 typedef struct PartialRead
153 {
154  char *buffer; // Buffer of characters
155  int bufSize; // Size of buffer
156  int offset; // Offset of current character within buffer
157  struct PartialRead *next; // Next buffer in chain
158 } PartialRead;
159 
160 #define MAX_OPEN_FILES 128
161 
162 static PartialRead *partial[MAX_OPEN_FILES];
163 
164 static void pl_FreeReadBuffer( int fd );
165 static void pl_Unread( int fd, char *buffer, int numBytes, int copy );
166 static int pl_Read( int fd, char *buffer, int numReq );
167 
168 int pl_PacketReceive( Tcl_Interp * interp, PLiodev *iodev, PDFstrm *pdfs );
169 int pl_PacketSend( Tcl_Interp * interp, PLiodev *iodev, PDFstrm *pdfs );
170 
171 //
172 //--------------------------------------------------------------------------
173 //
174 // pl_FreeReadBuffer --
175 //
176 // This function is called to free up all the memory associated
177 // with a file once the file is closed.
178 //
179 // Results:
180 // None.
181 //
182 // Side effects:
183 // Any data buffered locally will be lost.
184 //
185 //--------------------------------------------------------------------------
186 //
187 
188 static void
189 pl_FreeReadBuffer( int fd )
190 {
191  PartialRead *readList;
192 
193  while ( partial[fd] != NULL )
194  {
195  readList = partial[fd];
196  partial[fd] = readList->next;
197  free( readList->buffer );
198  free( readList );
199  }
200 }
201 
202 //
203 //--------------------------------------------------------------------------
204 //
205 // pl_Unread --
206 //
207 // This function puts data back into the read chain on a
208 // file descriptor. It's basically an extended "ungetc".
209 //
210 // Results:
211 // None.
212 //
213 // Side effects:
214 // Subsequent calls to pl_Read on the fd will get this data.
215 //
216 //--------------------------------------------------------------------------
217 //
218 
219 static void
220 pl_Unread( int fd, char *buffer, int numBytes, int copy )
221 //int fd; // File descriptor
222 //char *buffer; // Data to unget
223 //int numBytes; // Number of bytes to unget
224 //int copy; // Should we copy the data, or use this
225 // buffer?
226 {
227  PartialRead *new;
228 
229  new = (PartialRead *) malloc( sizeof ( PartialRead ) );
230  if ( copy )
231  {
232  new->buffer = (char *) malloc( (size_t) numBytes );
233  memcpy( new->buffer, buffer, (size_t) numBytes );
234  }
235  else
236  {
237  new->buffer = buffer;
238  }
239  new->bufSize = numBytes;
240  new->offset = 0;
241  new->next = partial[fd];
242  partial[fd] = new;
243 }
244 
245 //
246 //--------------------------------------------------------------------------
247 //
248 // pl_Read --
249 //
250 // This function implements a "read"-like command, but
251 // buffers partial reads. The semantics are the same as
252 // with read.
253 //
254 // Results:
255 // Number of bytes read, or -1 on error (with errno set).
256 //
257 // Side effects:
258 // All available data is read from the file descriptor.
259 //
260 //--------------------------------------------------------------------------
261 //
262 
263 static int
264 pl_Read( int fd, char *buffer, int numReq )
265 //int fd; // File descriptor to read from
266 //char *buffer; // Place to put the data
267 //int numReq; // Number of bytes to get
268 {
269  PartialRead *readList;
270  PartialRead *tmp;
271  int numRead;
272  int numToCopy;
273 
274  readList = partial[fd];
275 
276  //
277  // If there's no data left over from a previous read, then just do a read
278  // This is the common case.
279  //
280  if ( readList == NULL )
281  {
282  numRead = (int) read( fd, buffer, (size_t) numReq );
283 #ifdef DEBUG
284  {
285  int j;
286  fprintf( stderr, "received %d bytes starting with:", numRead );
287  for ( j = 0; j < MIN( 8, numRead ); j++ )
288  fprintf( stderr, " %x", 0x000000FF & (unsigned long) buffer[j] );
289  fprintf( stderr, "\n" );
290  }
291 #endif
292  return numRead;
293  }
294 
295  //
296  // There's data left over from a previous read. Yank it in and
297  // only call read() if we didn't get enough data (this keeps the fd
298  // readable if they only request as much data as is in the buffers).
299  //
300  numRead = 0;
301  while ( ( readList != NULL ) && ( numRead < numReq ) )
302  {
303  numToCopy = readList->bufSize - readList->offset;
304  if ( numToCopy + numRead > numReq )
305  {
306  numToCopy = numReq - numRead;
307  }
308  memcpy( buffer + numRead, readList->buffer + readList->offset, (size_t) numToCopy );
309 
310  //
311  // Consume the data
312  //
313  tmp = readList;
314  readList = readList->next;
315  tmp->offset += numToCopy;
316  if ( tmp->offset == tmp->bufSize )
317  {
318  free( tmp->buffer );
319  free( tmp );
320  partial[fd] = readList;
321  }
322  numRead += numToCopy;
323  }
324 
325  //
326  // Only call read if at the end of a previously incomplete packet.
327  //
328  if ( ( numRead < numReq ) )
329  {
330  numToCopy = numReq - numRead;
331  numRead += (int) read( fd, buffer + numRead, (size_t) numToCopy );
332  }
333 
334  return numRead;
335 }
336 
337 //--------------------------------------------------------------------------
338 // This part for Tcl-DP only
339 //--------------------------------------------------------------------------
340 
341 #ifdef PLD_dp
342 
343 #include <dp.h>
344 
345 #include <arpa/inet.h>
346 #include <netdb.h>
347 #include <netinet/in.h>
348 #include <sys/socket.h>
349 
350 //--------------------------------------------------------------------------
351 // plHost_ID
352 //
353 // Tcl command -- return the IP address for the current host.
354 //
355 // Derived from source code in "UNIX Network Programming" by W. Richard
356 // Stevens, Prentice Hall, 1990.
357 //--------------------------------------------------------------------------
358 
359 static char *
360 get_inet( char ** listptr, int length )
361 {
362  struct in_addr *ptr;
363 
364  while ( ( ptr = (struct in_addr *) *listptr++ ) == NULL )
365  continue;
366 
367  return inet_ntoa( *ptr );
368 }
369 
370 int
371 plHost_ID( ClientData clientData, Tcl_Interp *interp, int argc, char **argv )
372 {
373  register struct hostent *hostptr;
374  char hostname[100];
375 
376  if ( gethostname( hostname, 100 ) )
377  {
378  Tcl_AppendResult( interp, "Error -- cannot get host name",
379  (char *) NULL );
380  return TCL_ERROR;
381  }
382 
383  if ( ( hostptr = gethostbyname( hostname ) ) == NULL )
384  {
385  Tcl_AppendResult( interp, "Error -- cannot get host info for node ",
386  hostname, (char *) NULL );
387  return TCL_ERROR;
388  }
389 
390  Tcl_SetResult( interp,
391  get_inet( hostptr->h_addr_list, hostptr->h_length ),
392  TCL_VOLATILE );
393 
394  return TCL_OK;
395 }
396 
397 #endif // PLD_dp
398 
399 //
400 //--------------------------------------------------------------------------
401 //
402 // pl_PacketReceive --
403 //
404 // This procedure is a modified version of Tdp_PacketReceive,
405 // from the Tcl-DP distribution. It reads the socket,
406 // returning a complete packet. If the entire packet cannot
407 // be read, the partial packet is buffered until the rest is
408 // available. Some capabilities have been removed from the
409 // original, such as the check for a non-server TCP socket,
410 // since there's no access to the optFlags array from here,
411 // and the peek capability, since I don't need it.
412 //
413 // Results:
414 // Packet contents stored in pdfs->buffer and pdfs->bp set
415 // to the number of bytes read (zero if incomplete).
416 //
417 // Side effects:
418 // The file descriptor passed in is read.
419 //
420 //--------------------------------------------------------------------------
421 //
422 int
423 pl_PacketReceive( Tcl_Interp *interp, PLiodev *iodev, PDFstrm *pdfs )
424 {
425  int j, numRead;
426  unsigned int packetLen, header[2];
427  int headerSize;
428  unsigned char hbuf[8];
429  const char *errMsg;
430 
431  pdfs->bp = 0;
432 
433  //
434  // Read in the header (8 bytes)
435  //
436  headerSize = 8;
437  numRead = pl_Read( iodev->fd, (char *) hbuf, headerSize );
438 
439  if ( numRead <= 0 )
440  {
441 #ifdef DEBUG
442  fprintf( stderr, "Incorrect header read, numRead = %d\n", numRead );
443 #endif
444  goto readError;
445  }
446 
447  //
448  // Check for incomplete read. If so, put it back and return.
449  //
450  if ( numRead < headerSize )
451  {
452 #ifdef DEBUG
453  fprintf( stderr, "Incomplete header read, numRead = %d\n", numRead );
454 #endif
455  pl_Unread( iodev->fd, (char *) hbuf, numRead, 1 );
456  Tcl_ResetResult( interp );
457  return TCL_OK;
458  }
459 
460  //
461  // Convert header character stream into ints. This works when the
462  // connecting machine has a different size int and takes care of the
463  // endian problem to boot. It is also mostly backward compatible since
464  // network byte ordering (big endian) is used.
465  //
466 
467  j = 0;
468 
469  header[0] = 0;
470  header[0] |= (unsigned int) ( hbuf[j++] << 24 );
471  header[0] |= (unsigned int) ( hbuf[j++] << 16 );
472  header[0] |= (unsigned int) ( hbuf[j++] << 8 );
473  header[0] |= hbuf[j++];
474 
475  header[1] = 0;
476  header[1] |= (unsigned int) ( hbuf[j++] << 24 );
477  header[1] |= (unsigned int) ( hbuf[j++] << 16 );
478  header[1] |= (unsigned int) ( hbuf[j++] << 8 );
479  header[1] |= hbuf[j++];
480 
481  //
482  // Format of each packet:
483  //
484  // First 4 bytes are PACKET_MAGIC.
485  // Next 4 bytes are packetLen.
486  // Next packetLen-headerSize is zero terminated string
487  //
488  if ( header[0] != PACKET_MAGIC )
489  {
490  fprintf( stderr, "Badly formatted packet, numRead = %d\n", numRead );
491  Tcl_AppendResult( interp, "Error reading from ", iodev->typeName,
492  ": badly formatted packet", (char *) NULL );
493  return TCL_ERROR;
494  }
495  packetLen = header[1] - (unsigned int) headerSize;
496 
497  //
498  // Expand the size of the buffer, as needed.
499  //
500 
501  if ( header[1] > (unsigned) pdfs->bufmax )
502  {
503  free( (void *) pdfs->buffer );
504  pdfs->bufmax = header[1] + 32;
505  pdfs->buffer = (unsigned char *) malloc( pdfs->bufmax );
506  }
507 
508  //
509  // Read in the packet, and if it's not all there, put it back.
510  //
511  // We have to be careful here, because we could block if just the
512  // header came in (making the file readable at the beginning of this
513  // function) but the rest of the packet is still out on the network.
514  //
515 
516  if ( iodev->type == 0 )
517  {
518  numRead = pl_Read( iodev->fd, (char *) pdfs->buffer, (int) packetLen );
519  }
520  else
521  {
522 #ifdef PLD_dp
523  if ( Tdp_FDIsReady( iodev->fd ) & TCL_FILE_READABLE )
524  {
525  numRead = pl_Read( iodev->fd, (char *) pdfs->buffer, packetLen );
526  }
527  else
528  {
529 #ifdef DEBUG
530  fprintf( stderr, "Packet not ready, putting back header\n" );
531 #endif
532  pl_Unread( iodev->fd, (char *) hbuf, headerSize, 1 );
533  Tcl_ResetResult( interp );
534  return TCL_OK;
535  }
536 #endif
537  }
538 
539  if ( numRead <= 0 )
540  {
541  goto readError;
542  }
543 
544  if ( (unsigned) numRead != packetLen )
545  {
546 #ifdef DEBUG
547  fprintf( stderr, "Incomplete packet read, numRead = %d\n", numRead );
548 #endif
549  pl_Unread( iodev->fd, (char *) pdfs->buffer, numRead, 1 );
550  pl_Unread( iodev->fd, (char *) hbuf, headerSize, 1 );
551  return TCL_OK;
552  }
553 
554  pdfs->bp = (size_t) numRead;
555 #ifdef DEBUG
556  fprintf( stderr, "received %d byte packet starting with:", numRead );
557  for ( j = 0; j < 4; j++ )
558  {
559  fprintf( stderr, " %x", 0x000000FF & (unsigned long) pdfs->buffer[j] );
560  }
561  fprintf( stderr, "\n" );
562 #endif
563 
564  return TCL_OK;
565 
566 readError:
567  //
568  //
569  // If we're in non-blocking mode, and this would block, return.
570  // If the connection is closed (numRead == 0), don't return an
571  // error message. Otherwise, return one.
572  //
573  // In either case, we close the file, delete the file handler, and
574  // return a null string.
575  //
576 
577  if ( errno == PLPLOT_EWOULDBLOCK || errno == EAGAIN )
578  {
579  Tcl_ResetResult( interp );
580  return TCL_OK;
581  }
582 
583  // Record the error before closing the file
584  if ( numRead != 0 )
585  {
586  errMsg = Tcl_PosixError( interp );
587  }
588  else
589  {
590  errMsg = NULL; // Suppresses spurious compiler warning
591  }
592 
593  //
594  // Remove the file handler and close the file.
595  //
596  if ( iodev->type == 0 )
597  {
598 // Exclude UNIX-only feature
599 #if !defined ( MAC_TCL ) && !defined ( __WIN32__ ) && !defined ( __CYGWIN__ )
600  Tk_DeleteFileHandler( iodev->fd );
601 #endif
602  close( iodev->fd );
603  }
604  pl_FreeReadBuffer( iodev->fd );
605 
606  Tcl_ResetResult( interp );
607  if ( numRead == 0 )
608  {
609  return TCL_OK;
610  }
611  else
612  {
613  Tcl_AppendResult( interp, "pl_PacketReceive -- error reading from ",
614  iodev->typeName, ": ", errMsg, (char *) NULL );
615  return TCL_ERROR;
616  }
617 }
618 
619 //
620 //--------------------------------------------------------------------------
621 //
622 // pl_PacketSend --
623 //
624 // This procedure is a modified version of Tdp_PacketSend,
625 // from the Tcl-DP distribution. It writes a complete packet
626 // to a socket or file-oriented device.
627 //
628 // Results:
629 // A standard tcl result.
630 //
631 // Side effects:
632 // The specified buffer is written to the file descriptor passed
633 // in.
634 //
635 //--------------------------------------------------------------------------
636 //
637 
638 int
639 pl_PacketSend( Tcl_Interp * interp, PLiodev *iodev, PDFstrm *pdfs )
640 {
641  int j, numSent;
642  unsigned char hbuf[8];
643  unsigned int packetLen, header[2];
644  size_t len;
645  char *buffer, tmp[256];
646 
647  //
648  // Format up the packet:
649  // First 4 bytes are PACKET_MAGIC.
650  // Next 4 bytes are packetLen.
651  // Next packetLen-8 bytes are buffer contents.
652  //
653 
654  packetLen = (unsigned int) pdfs->bp + 8;
655 
656  header[0] = PACKET_MAGIC;
657  header[1] = packetLen;
658 
659  //
660  // Convert header ints to character stream.
661  // Network byte ordering (big endian) is used.
662  //
663 
664  j = 0;
665 
666  hbuf[j++] = (unsigned char) ( ( header[0] & (unsigned long) 0xFF000000 ) >> 24 );
667  hbuf[j++] = (unsigned char) ( ( header[0] & (unsigned long) 0x00FF0000 ) >> 16 );
668  hbuf[j++] = (unsigned char) ( ( header[0] & (unsigned long) 0x0000FF00 ) >> 8 );
669  hbuf[j++] = (unsigned char) ( header[0] & (unsigned long) 0x000000FF );
670 
671  hbuf[j++] = (unsigned char) ( ( header[1] & (unsigned long) 0xFF000000 ) >> 24 );
672  hbuf[j++] = (unsigned char) ( ( header[1] & (unsigned long) 0x00FF0000 ) >> 16 );
673  hbuf[j++] = (unsigned char) ( ( header[1] & (unsigned long) 0x0000FF00 ) >> 8 );
674  hbuf[j++] = (unsigned char) ( header[1] & (unsigned long) 0x000000FF );
675 
676  //
677  // Send it off, with error checking.
678  // Simulate writev using memcpy to put together
679  // the msg so it can go out in a single write() call.
680  //
681 
682  len = pdfs->bp + 8;
683  buffer = (char *) malloc( len );
684 
685  memcpy( buffer, (char *) hbuf, 8 );
686  memcpy( buffer + 8, (char *) pdfs->buffer, pdfs->bp );
687 
688 #ifdef DEBUG
689  fprintf( stderr, "sending %z byte packet starting with:", len );
690  for ( j = 0; j < 12; j++ )
691  {
692  fprintf( stderr, " %x", 0x000000FF & (unsigned long) buffer[j] );
693  }
694  fprintf( stderr, "\n" );
695 #endif
696  numSent = (int) write( iodev->fd, buffer, len );
697 
698  free( buffer );
699 
700  if ( (unsigned) numSent != packetLen )
701  {
702  if ( ( errno == 0 ) || ( errno == PLPLOT_EWOULDBLOCK || errno == EAGAIN ) )
703  {
704  //
705  // Non-blocking I/O: return number of bytes actually sent.
706  //
707  Tcl_ResetResult( interp );
708  sprintf( tmp, "%d", numSent - 8 );
709  Tcl_SetResult( interp, tmp, TCL_VOLATILE );
710  return TCL_OK;
711  }
712  else if ( errno == EPIPE )
713  {
714  //
715  // Got a broken pipe signal, which means the far end closed
716  // the connection. Close the file and return 0 bytes sent.
717  //
718  if ( iodev->type == 0 )
719  {
720  close( iodev->fd );
721  }
722  sprintf( tmp, "0" );
723  Tcl_SetResult( interp, tmp, TCL_VOLATILE );
724  return TCL_OK;
725  }
726  else
727  {
728  Tcl_AppendResult( interp, "pl_PacketSend -- error writing to ",
729  iodev->typeName, ": ",
730  Tcl_PosixError( interp ), (char *) NULL );
731  }
732 
733  return TCL_ERROR;
734  }
735 
736  //
737  // Return the number of bytes sent (minus the header).
738  //
739  sprintf( tmp, "%d", numSent - 8 );
740  Tcl_SetResult( interp, tmp, TCL_VOLATILE );
741  return TCL_OK;
742 }
743 
744 #else
745 int
747 {
748  return 0;
749 }
750 
751 #endif // defined(PLD_tk) || defined (ENABLE_tkX)