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