PLplot: tcpip.c Source File

PLplot 5.10.0

- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

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
746 pldummy_tcpip()
747 {
748 return 0;
749 }
750
751 #endif // defined(PLD_tk) || defined (ENABLE_tkX)