1 /*
2  * tclUnixChan.c
3  *
4  *	Common channel driver for Unix channels based on files, command pipes
5  *	and TCP sockets.
6  *
7  * Copyright (c) 1995-1997 Sun Microsystems, Inc.
8  * Copyright (c) 1998-1999 by Scriptics Corporation.
9  *
10  * See the file "license.terms" for information on usage and redistribution
11  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
12  */
13 
14 #include "tclInt.h"	/* Internal definitions for Tcl. */
15 #include "tclIO.h"	/* To get Channel type declaration. */
16 
17 #undef SUPPORTS_TTY
18 #if defined(HAVE_TERMIOS_H)
19 #   define SUPPORTS_TTY 1
20 #   include <termios.h>
21 #   ifdef HAVE_SYS_IOCTL_H
22 #	include <sys/ioctl.h>
23 #   endif /* HAVE_SYS_IOCTL_H */
24 #   ifdef HAVE_SYS_MODEM_H
25 #	include <sys/modem.h>
26 #   endif /* HAVE_SYS_MODEM_H */
27 
28 #   ifdef FIONREAD
29 #	define GETREADQUEUE(fd, int)	ioctl((fd), FIONREAD, &(int))
30 #   elif defined(FIORDCHK)
31 #	define GETREADQUEUE(fd, int)	int = ioctl((fd), FIORDCHK, NULL)
32 #   else
33 #       define GETREADQUEUE(fd, int)    int = 0
34 #   endif
35 
36 #   ifdef TIOCOUTQ
37 #	define GETWRITEQUEUE(fd, int)	ioctl((fd), TIOCOUTQ, &(int))
38 #   else
39 #	define GETWRITEQUEUE(fd, int)	int = 0
40 #   endif
41 
42 #   if !defined(CRTSCTS) && defined(CNEW_RTSCTS)
43 #	define CRTSCTS CNEW_RTSCTS
44 #   endif /* !CRTSCTS&CNEW_RTSCTS */
45 #   if !defined(PAREXT) && defined(CMSPAR)
46 #	define PAREXT CMSPAR
47 #   endif /* !PAREXT&&CMSPAR */
48 
49 #endif	/* HAVE_TERMIOS_H */
50 
51 /*
52  * Helper macros to make parts of this file clearer. The macros do exactly
53  * what they say on the tin. :-) They also only ever refer to their arguments
54  * once, and so can be used without regard to side effects.
55  */
56 
57 #define SET_BITS(var, bits)	((var) |= (bits))
58 #define CLEAR_BITS(var, bits)	((var) &= ~(bits))
59 
60 /*
61  * This structure describes per-instance state of a file based channel.
62  */
63 
64 typedef struct FileState {
65     Tcl_Channel channel;	/* Channel associated with this file. */
66     int fd;			/* File handle. */
67     int validMask;		/* OR'ed combination of TCL_READABLE,
68 				 * TCL_WRITABLE, or TCL_EXCEPTION: indicates
69 				 * which operations are valid on the file. */
70 } FileState;
71 
72 #ifdef SUPPORTS_TTY
73 
74 /*
75  * The following structure is used to set or get the serial port attributes in
76  * a platform-independent manner.
77  */
78 
79 typedef struct TtyAttrs {
80     int baud;
81     int parity;
82     int data;
83     int stop;
84 } TtyAttrs;
85 
86 #endif	/* !SUPPORTS_TTY */
87 
88 #define UNSUPPORTED_OPTION(detail) \
89     if (interp) {							\
90 	Tcl_SetObjResult(interp, Tcl_ObjPrintf(				\
91 		"%s not supported for this platform", (detail)));	\
92 	Tcl_SetErrorCode(interp, "TCL", "UNSUPPORTED", NULL);		\
93     }
94 
95 /*
96  * Static routines for this file:
97  */
98 
99 static int		FileBlockModeProc(ClientData instanceData, int mode);
100 static int		FileCloseProc(ClientData instanceData,
101 			    Tcl_Interp *interp);
102 static int		FileClose2Proc(ClientData instanceData,
103 			    Tcl_Interp *interp, int flags);
104 static int		FileGetHandleProc(ClientData instanceData,
105 			    int direction, ClientData *handlePtr);
106 static int		FileInputProc(ClientData instanceData, char *buf,
107 			    int toRead, int *errorCode);
108 static int		FileOutputProc(ClientData instanceData,
109 			    const char *buf, int toWrite, int *errorCode);
110 static int		FileSeekProc(ClientData instanceData, long offset,
111 			    int mode, int *errorCode);
112 static int		FileTruncateProc(ClientData instanceData,
113 			    Tcl_WideInt length);
114 static Tcl_WideInt	FileWideSeekProc(ClientData instanceData,
115 			    Tcl_WideInt offset, int mode, int *errorCode);
116 static void		FileWatchProc(ClientData instanceData, int mask);
117 #ifdef SUPPORTS_TTY
118 static void		TtyGetAttributes(int fd, TtyAttrs *ttyPtr);
119 static int		TtyGetOptionProc(ClientData instanceData,
120 			    Tcl_Interp *interp, const char *optionName,
121 			    Tcl_DString *dsPtr);
122 static int		TtyGetBaud(speed_t speed);
123 static speed_t		TtyGetSpeed(int baud);
124 static void		TtyInit(int fd);
125 static void		TtyModemStatusStr(int status, Tcl_DString *dsPtr);
126 static int		TtyParseMode(Tcl_Interp *interp, const char *mode,
127 			    TtyAttrs *ttyPtr);
128 static void		TtySetAttributes(int fd, TtyAttrs *ttyPtr);
129 static int		TtySetOptionProc(ClientData instanceData,
130 			    Tcl_Interp *interp, const char *optionName,
131 			    const char *value);
132 #endif	/* SUPPORTS_TTY */
133 
134 /*
135  * This structure describes the channel type structure for file based IO:
136  */
137 
138 static const Tcl_ChannelType fileChannelType = {
139     "file",			/* Type name. */
140     TCL_CHANNEL_VERSION_5,	/* v5 channel */
141     FileCloseProc,		/* Close proc. */
142     FileInputProc,		/* Input proc. */
143     FileOutputProc,		/* Output proc. */
144     FileSeekProc,		/* Seek proc. */
145     NULL,			/* Set option proc. */
146     NULL,			/* Get option proc. */
147     FileWatchProc,		/* Initialize notifier. */
148     FileGetHandleProc,		/* Get OS handles out of channel. */
149     FileClose2Proc,			/* close2proc. */
150     FileBlockModeProc,		/* Set blocking or non-blocking mode.*/
151     NULL,			/* flush proc. */
152     NULL,			/* handler proc. */
153     FileWideSeekProc,		/* wide seek proc. */
154     NULL,
155     FileTruncateProc		/* truncate proc. */
156 };
157 
158 #ifdef SUPPORTS_TTY
159 /*
160  * This structure describes the channel type structure for serial IO.
161  * Note that this type is a subclass of the "file" type.
162  */
163 
164 static const Tcl_ChannelType ttyChannelType = {
165     "tty",			/* Type name. */
166     TCL_CHANNEL_VERSION_5,	/* v5 channel */
167     FileCloseProc,		/* Close proc. */
168     FileInputProc,		/* Input proc. */
169     FileOutputProc,		/* Output proc. */
170     NULL,			/* Seek proc. */
171     TtySetOptionProc,		/* Set option proc. */
172     TtyGetOptionProc,		/* Get option proc. */
173     FileWatchProc,		/* Initialize notifier. */
174     FileGetHandleProc,		/* Get OS handles out of channel. */
175     FileClose2Proc,			/* close2proc. */
176     FileBlockModeProc,		/* Set blocking or non-blocking mode.*/
177     NULL,			/* flush proc. */
178     NULL,			/* handler proc. */
179     NULL,			/* wide seek proc. */
180     NULL,			/* thread action proc. */
181     NULL			/* truncate proc. */
182 };
183 #endif	/* SUPPORTS_TTY */
184 
185 /*
186  *----------------------------------------------------------------------
187  *
188  * FileBlockModeProc --
189  *
190  *	Helper function to set blocking and nonblocking modes on a file based
191  *	channel. Invoked by generic IO level code.
192  *
193  * Results:
194  *	0 if successful, errno when failed.
195  *
196  * Side effects:
197  *	Sets the device into blocking or non-blocking mode.
198  *
199  *----------------------------------------------------------------------
200  */
201 
202 static int
FileBlockModeProc(ClientData instanceData,int mode)203 FileBlockModeProc(
204     ClientData instanceData,	/* File state. */
205     int mode)			/* The mode to set. Can be TCL_MODE_BLOCKING
206 				 * or TCL_MODE_NONBLOCKING. */
207 {
208     FileState *fsPtr = instanceData;
209 
210     if (TclUnixSetBlockingMode(fsPtr->fd, mode) < 0) {
211 	return errno;
212     }
213 
214     return 0;
215 }
216 
217 /*
218  *----------------------------------------------------------------------
219  *
220  * FileInputProc --
221  *
222  *	This function is invoked from the generic IO level to read input from
223  *	a file based channel.
224  *
225  * Results:
226  *	The number of bytes read is returned or -1 on error. An output
227  *	argument contains a POSIX error code if an error occurs, or zero.
228  *
229  * Side effects:
230  *	Reads input from the input device of the channel.
231  *
232  *----------------------------------------------------------------------
233  */
234 
235 static int
FileInputProc(ClientData instanceData,char * buf,int toRead,int * errorCodePtr)236 FileInputProc(
237     ClientData instanceData,	/* File state. */
238     char *buf,			/* Where to store data read. */
239     int toRead,			/* How much space is available in the
240 				 * buffer? */
241     int *errorCodePtr)		/* Where to store error code. */
242 {
243     FileState *fsPtr = instanceData;
244     int bytesRead;		/* How many bytes were actually read from the
245 				 * input device? */
246 
247     *errorCodePtr = 0;
248 
249     /*
250      * Assume there is always enough input available. This will block
251      * appropriately, and read will unblock as soon as a short read is
252      * possible, if the channel is in blocking mode. If the channel is
253      * nonblocking, the read will never block.
254      */
255 
256     do {
257 	bytesRead = read(fsPtr->fd, buf, (size_t) toRead);
258     } while ((bytesRead < 0) && (errno == EINTR));
259 
260     if (bytesRead < 0) {
261 	*errorCodePtr = errno;
262 	return -1;
263     }
264     return bytesRead;
265 }
266 
267 /*
268  *----------------------------------------------------------------------
269  *
270  * FileOutputProc--
271  *
272  *	This function is invoked from the generic IO level to write output to
273  *	a file channel.
274  *
275  * Results:
276  *	The number of bytes written is returned or -1 on error. An output
277  *	argument contains a POSIX error code if an error occurred, or zero.
278  *
279  * Side effects:
280  *	Writes output on the output device of the channel.
281  *
282  *----------------------------------------------------------------------
283  */
284 
285 static int
FileOutputProc(ClientData instanceData,const char * buf,int toWrite,int * errorCodePtr)286 FileOutputProc(
287     ClientData instanceData,	/* File state. */
288     const char *buf,		/* The data buffer. */
289     int toWrite,		/* How many bytes to write? */
290     int *errorCodePtr)		/* Where to store error code. */
291 {
292     FileState *fsPtr = instanceData;
293     int written;
294 
295     *errorCodePtr = 0;
296 
297     if (toWrite == 0) {
298 	/*
299 	 * SF Tcl Bug 465765. Do not try to write nothing into a file. STREAM
300 	 * based implementations will considers this as EOF (if there is a
301 	 * pipe behind the file).
302 	 */
303 
304 	return 0;
305     }
306     written = write(fsPtr->fd, buf, (size_t) toWrite);
307     if (written > -1) {
308 	return written;
309     }
310     *errorCodePtr = errno;
311     return -1;
312 }
313 
314 /*
315  *----------------------------------------------------------------------
316  *
317  * FileCloseProc --
318  *
319  *	This function is called from the generic IO level to perform
320  *	channel-type-specific cleanup when a file based channel is closed.
321  *
322  * Results:
323  *	0 if successful, errno if failed.
324  *
325  * Side effects:
326  *	Closes the device of the channel.
327  *
328  *----------------------------------------------------------------------
329  */
330 
331 static int
FileCloseProc(ClientData instanceData,Tcl_Interp * interp)332 FileCloseProc(
333     ClientData instanceData,	/* File state. */
334     Tcl_Interp *interp)		/* For error reporting - unused. */
335 {
336     FileState *fsPtr = instanceData;
337     int errorCode = 0;
338 
339     Tcl_DeleteFileHandler(fsPtr->fd);
340 
341     /*
342      * Do not close standard channels while in thread-exit.
343      */
344 
345     if (!TclInThreadExit()
346 	    || ((fsPtr->fd != 0) && (fsPtr->fd != 1) && (fsPtr->fd != 2))) {
347 	if (close(fsPtr->fd) < 0) {
348 	    errorCode = errno;
349 	}
350     }
351     ckfree(fsPtr);
352     return errorCode;
353 }
354 static int
FileClose2Proc(ClientData instanceData,Tcl_Interp * interp,int flags)355 FileClose2Proc(
356     ClientData instanceData,	/* File state. */
357     Tcl_Interp *interp,		/* For error reporting - unused. */
358 	int flags)
359 {
360     if ((flags & (TCL_CLOSE_READ | TCL_CLOSE_WRITE)) == 0) {
361 	return FileCloseProc(instanceData, interp);
362     }
363     return EINVAL;
364 }
365 
366 /*
367  *----------------------------------------------------------------------
368  *
369  * FileSeekProc --
370  *
371  *	This function is called by the generic IO level to move the access
372  *	point in a file based channel.
373  *
374  * Results:
375  *	-1 if failed, the new position if successful. An output argument
376  *	contains the POSIX error code if an error occurred, or zero.
377  *
378  * Side effects:
379  *	Moves the location at which the channel will be accessed in future
380  *	operations.
381  *
382  *----------------------------------------------------------------------
383  */
384 
385 static int
FileSeekProc(ClientData instanceData,long offset,int mode,int * errorCodePtr)386 FileSeekProc(
387     ClientData instanceData,	/* File state. */
388     long offset,		/* Offset to seek to. */
389     int mode,			/* Relative to where should we seek? Can be
390 				 * one of SEEK_START, SEEK_SET or SEEK_END. */
391     int *errorCodePtr)		/* To store error code. */
392 {
393     FileState *fsPtr = instanceData;
394     Tcl_WideInt oldLoc, newLoc;
395 
396     /*
397      * Save our current place in case we need to roll-back the seek.
398      */
399 
400     oldLoc = TclOSseek(fsPtr->fd, (Tcl_SeekOffset) 0, SEEK_CUR);
401     if (oldLoc == Tcl_LongAsWide(-1)) {
402 	/*
403 	 * Bad things are happening. Error out...
404 	 */
405 
406 	*errorCodePtr = errno;
407 	return -1;
408     }
409 
410     newLoc = TclOSseek(fsPtr->fd, (Tcl_SeekOffset) offset, mode);
411 
412     /*
413      * Check for expressability in our return type, and roll-back otherwise.
414      */
415 
416     if (newLoc > Tcl_LongAsWide(INT_MAX)) {
417 	*errorCodePtr = EOVERFLOW;
418 	TclOSseek(fsPtr->fd, (Tcl_SeekOffset) oldLoc, SEEK_SET);
419 	return -1;
420     } else {
421 	*errorCodePtr = (newLoc == Tcl_LongAsWide(-1)) ? errno : 0;
422     }
423     return (int) Tcl_WideAsLong(newLoc);
424 }
425 
426 /*
427  *----------------------------------------------------------------------
428  *
429  * FileWideSeekProc --
430  *
431  *	This function is called by the generic IO level to move the access
432  *	point in a file based channel, with offsets expressed as wide
433  *	integers.
434  *
435  * Results:
436  *	-1 if failed, the new position if successful. An output argument
437  *	contains the POSIX error code if an error occurred, or zero.
438  *
439  * Side effects:
440  *	Moves the location at which the channel will be accessed in future
441  *	operations.
442  *
443  *----------------------------------------------------------------------
444  */
445 
446 static Tcl_WideInt
FileWideSeekProc(ClientData instanceData,Tcl_WideInt offset,int mode,int * errorCodePtr)447 FileWideSeekProc(
448     ClientData instanceData,	/* File state. */
449     Tcl_WideInt offset,		/* Offset to seek to. */
450     int mode,			/* Relative to where should we seek? Can be
451 				 * one of SEEK_START, SEEK_CUR or SEEK_END. */
452     int *errorCodePtr)		/* To store error code. */
453 {
454     FileState *fsPtr = instanceData;
455     Tcl_WideInt newLoc;
456 
457     newLoc = TclOSseek(fsPtr->fd, (Tcl_SeekOffset) offset, mode);
458 
459     *errorCodePtr = (newLoc == -1) ? errno : 0;
460     return newLoc;
461 }
462 
463 /*
464  *----------------------------------------------------------------------
465  *
466  * FileWatchProc --
467  *
468  *	Initialize the notifier to watch the fd from this channel.
469  *
470  * Results:
471  *	None.
472  *
473  * Side effects:
474  *	Sets up the notifier so that a future event on the channel will
475  *	be seen by Tcl.
476  *
477  *----------------------------------------------------------------------
478  */
479 
480 static void
FileWatchProc(ClientData instanceData,int mask)481 FileWatchProc(
482     ClientData instanceData,	/* The file state. */
483     int mask)			/* Events of interest; an OR-ed combination of
484 				 * TCL_READABLE, TCL_WRITABLE and
485 				 * TCL_EXCEPTION. */
486 {
487     FileState *fsPtr = instanceData;
488 
489     /*
490      * Make sure we only register for events that are valid on this file. Note
491      * that we are passing Tcl_NotifyChannel directly to Tcl_CreateFileHandler
492      * with the channel pointer as the client data.
493      */
494 
495     mask &= fsPtr->validMask;
496     if (mask) {
497 	Tcl_CreateFileHandler(fsPtr->fd, mask,
498 		(Tcl_FileProc *) Tcl_NotifyChannel, fsPtr->channel);
499     } else {
500 	Tcl_DeleteFileHandler(fsPtr->fd);
501     }
502 }
503 
504 /*
505  *----------------------------------------------------------------------
506  *
507  * FileGetHandleProc --
508  *
509  *	Called from Tcl_GetChannelHandle to retrieve OS handles from a file
510  *	based channel.
511  *
512  * Results:
513  *	Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if there is no
514  *	handle for the specified direction.
515  *
516  * Side effects:
517  *	None.
518  *
519  *----------------------------------------------------------------------
520  */
521 
522 static int
FileGetHandleProc(ClientData instanceData,int direction,ClientData * handlePtr)523 FileGetHandleProc(
524     ClientData instanceData,	/* The file state. */
525     int direction,		/* TCL_READABLE or TCL_WRITABLE */
526     ClientData *handlePtr)	/* Where to store the handle. */
527 {
528     FileState *fsPtr = instanceData;
529 
530     if (direction & fsPtr->validMask) {
531 	*handlePtr = INT2PTR(fsPtr->fd);
532 	return TCL_OK;
533     }
534     return TCL_ERROR;
535 }
536 
537 #ifdef SUPPORTS_TTY
538 /*
539  *----------------------------------------------------------------------
540  *
541  * TtyModemStatusStr --
542  *
543  *	Converts a RS232 modem status list of readable flags
544  *
545  *----------------------------------------------------------------------
546  */
547 
548 static void
TtyModemStatusStr(int status,Tcl_DString * dsPtr)549 TtyModemStatusStr(
550     int status,			/* RS232 modem status */
551     Tcl_DString *dsPtr)		/* Where to store string */
552 {
553 #ifdef TIOCM_CTS
554     Tcl_DStringAppendElement(dsPtr, "CTS");
555     Tcl_DStringAppendElement(dsPtr, (status & TIOCM_CTS) ? "1" : "0");
556 #endif /* TIOCM_CTS */
557 #ifdef TIOCM_DSR
558     Tcl_DStringAppendElement(dsPtr, "DSR");
559     Tcl_DStringAppendElement(dsPtr, (status & TIOCM_DSR) ? "1" : "0");
560 #endif /* TIOCM_DSR */
561 #ifdef TIOCM_RNG
562     Tcl_DStringAppendElement(dsPtr, "RING");
563     Tcl_DStringAppendElement(dsPtr, (status & TIOCM_RNG) ? "1" : "0");
564 #endif /* TIOCM_RNG */
565 #ifdef TIOCM_CD
566     Tcl_DStringAppendElement(dsPtr, "DCD");
567     Tcl_DStringAppendElement(dsPtr, (status & TIOCM_CD) ? "1" : "0");
568 #endif /* TIOCM_CD */
569 }
570 
571 /*
572  *----------------------------------------------------------------------
573  *
574  * TtySetOptionProc --
575  *
576  *	Sets an option on a channel.
577  *
578  * Results:
579  *	A standard Tcl result. Also sets the interp's result on error if
580  *	interp is not NULL.
581  *
582  * Side effects:
583  *	May modify an option on a device. Sets Error message if needed (by
584  *	calling Tcl_BadChannelOption).
585  *
586  *----------------------------------------------------------------------
587  */
588 
589 static int
TtySetOptionProc(ClientData instanceData,Tcl_Interp * interp,const char * optionName,const char * value)590 TtySetOptionProc(
591     ClientData instanceData,	/* File state. */
592     Tcl_Interp *interp,		/* For error reporting - can be NULL. */
593     const char *optionName,	/* Which option to set? */
594     const char *value)		/* New value for option. */
595 {
596     FileState *fsPtr = instanceData;
597     unsigned int len, vlen;
598     TtyAttrs tty;
599     int argc;
600     const char **argv;
601     struct termios iostate;
602 
603     len = strlen(optionName);
604     vlen = strlen(value);
605 
606     /*
607      * Option -mode baud,parity,databits,stopbits
608      */
609 
610     if ((len > 2) && (strncmp(optionName, "-mode", len) == 0)) {
611 	if (TtyParseMode(interp, value, &tty) != TCL_OK) {
612 	    return TCL_ERROR;
613 	}
614 
615 	/*
616 	 * system calls results should be checked there. - dl
617 	 */
618 
619 	TtySetAttributes(fsPtr->fd, &tty);
620 	return TCL_OK;
621     }
622 
623 
624     /*
625      * Option -handshake none|xonxoff|rtscts|dtrdsr
626      */
627 
628     if ((len > 1) && (strncmp(optionName, "-handshake", len) == 0)) {
629 	/*
630 	 * Reset all handshake options. DTR and RTS are ON by default.
631 	 */
632 
633 	tcgetattr(fsPtr->fd, &iostate);
634 	CLEAR_BITS(iostate.c_iflag, IXON | IXOFF | IXANY);
635 #ifdef CRTSCTS
636 	CLEAR_BITS(iostate.c_cflag, CRTSCTS);
637 #endif /* CRTSCTS */
638 	if (Tcl_UtfNcasecmp(value, "NONE", vlen) == 0) {
639 	    /*
640 	     * Leave all handshake options disabled.
641 	     */
642 	} else if (Tcl_UtfNcasecmp(value, "XONXOFF", vlen) == 0) {
643 	    SET_BITS(iostate.c_iflag, IXON | IXOFF | IXANY);
644 	} else if (Tcl_UtfNcasecmp(value, "RTSCTS", vlen) == 0) {
645 #ifdef CRTSCTS
646 	    SET_BITS(iostate.c_cflag, CRTSCTS);
647 #else /* !CRTSTS */
648 	    UNSUPPORTED_OPTION("-handshake RTSCTS");
649 	    return TCL_ERROR;
650 #endif /* CRTSCTS */
651 	} else if (Tcl_UtfNcasecmp(value, "DTRDSR", vlen) == 0) {
652 	    UNSUPPORTED_OPTION("-handshake DTRDSR");
653 	    return TCL_ERROR;
654 	} else {
655 	    if (interp) {
656 		Tcl_SetObjResult(interp, Tcl_NewStringObj(
657 			"bad value for -handshake: must be one of"
658 			" xonxoff, rtscts, dtrdsr or none", -1));
659 		Tcl_SetErrorCode(interp, "TCL", "OPERATION", "FCONFIGURE",
660 			"VALUE", NULL);
661 	    }
662 	    return TCL_ERROR;
663 	}
664 	tcsetattr(fsPtr->fd, TCSADRAIN, &iostate);
665 	return TCL_OK;
666     }
667 
668     /*
669      * Option -xchar {\x11 \x13}
670      */
671 
672     if ((len > 1) && (strncmp(optionName, "-xchar", len) == 0)) {
673 	Tcl_DString ds;
674 
675 	if (Tcl_SplitList(interp, value, &argc, &argv) == TCL_ERROR) {
676 	    return TCL_ERROR;
677 	} else if (argc != 2) {
678 	    if (interp) {
679 		Tcl_SetObjResult(interp, Tcl_NewStringObj(
680 			"bad value for -xchar: should be a list of"
681 			" two elements", -1));
682 		Tcl_SetErrorCode(interp, "TCL", "OPERATION", "FCONFIGURE",
683 			"VALUE", NULL);
684 	    }
685 	    ckfree(argv);
686 	    return TCL_ERROR;
687 	}
688 
689 	tcgetattr(fsPtr->fd, &iostate);
690 
691 	Tcl_UtfToExternalDString(NULL, argv[0], -1, &ds);
692 	iostate.c_cc[VSTART] = *(const cc_t *) Tcl_DStringValue(&ds);
693 	TclDStringClear(&ds);
694 
695 	Tcl_UtfToExternalDString(NULL, argv[1], -1, &ds);
696 	iostate.c_cc[VSTOP] = *(const cc_t *) Tcl_DStringValue(&ds);
697 	Tcl_DStringFree(&ds);
698 	ckfree(argv);
699 
700 	tcsetattr(fsPtr->fd, TCSADRAIN, &iostate);
701 	return TCL_OK;
702     }
703 
704     /*
705      * Option -timeout msec
706      */
707 
708     if ((len > 2) && (strncmp(optionName, "-timeout", len) == 0)) {
709 	int msec;
710 
711 	tcgetattr(fsPtr->fd, &iostate);
712 	if (Tcl_GetInt(interp, value, &msec) != TCL_OK) {
713 	    return TCL_ERROR;
714 	}
715 	iostate.c_cc[VMIN] = 0;
716 	iostate.c_cc[VTIME] = (msec==0) ? 0 : (msec<100) ? 1 : (msec+50)/100;
717 	tcsetattr(fsPtr->fd, TCSADRAIN, &iostate);
718 	return TCL_OK;
719     }
720 
721     /*
722      * Option -ttycontrol {DTR 1 RTS 0 BREAK 0}
723      */
724     if ((len > 4) && (strncmp(optionName, "-ttycontrol", len) == 0)) {
725 #if defined(TIOCMGET) && defined(TIOCMSET)
726 	int i, control, flag;
727 
728 	if (Tcl_SplitList(interp, value, &argc, &argv) == TCL_ERROR) {
729 	    return TCL_ERROR;
730 	}
731 	if ((argc % 2) == 1) {
732 	    if (interp) {
733 		Tcl_SetObjResult(interp, Tcl_NewStringObj(
734 			"bad value for -ttycontrol: should be a list of"
735 			" signal,value pairs", -1));
736 		Tcl_SetErrorCode(interp, "TCL", "OPERATION", "FCONFIGURE",
737 			"VALUE", NULL);
738 	    }
739 	    ckfree(argv);
740 	    return TCL_ERROR;
741 	}
742 
743 	ioctl(fsPtr->fd, TIOCMGET, &control);
744 	for (i = 0; i < argc-1; i += 2) {
745 	    if (Tcl_GetBoolean(interp, argv[i+1], &flag) == TCL_ERROR) {
746 		ckfree(argv);
747 		return TCL_ERROR;
748 	    }
749 	    if (Tcl_UtfNcasecmp(argv[i], "DTR", strlen(argv[i])) == 0) {
750 		if (flag) {
751 		    SET_BITS(control, TIOCM_DTR);
752 		} else {
753 		    CLEAR_BITS(control, TIOCM_DTR);
754 		}
755 	    } else if (Tcl_UtfNcasecmp(argv[i], "RTS", strlen(argv[i])) == 0) {
756 		if (flag) {
757 		    SET_BITS(control, TIOCM_RTS);
758 		} else {
759 		    CLEAR_BITS(control, TIOCM_RTS);
760 		}
761 	    } else if (Tcl_UtfNcasecmp(argv[i], "BREAK", strlen(argv[i])) == 0) {
762 #if defined(TIOCSBRK) && defined(TIOCCBRK)
763 		if (flag) {
764 		    ioctl(fsPtr->fd, TIOCSBRK, NULL);
765 		} else {
766 		    ioctl(fsPtr->fd, TIOCCBRK, NULL);
767 		}
768 #else /* TIOCSBRK & TIOCCBRK */
769 		UNSUPPORTED_OPTION("-ttycontrol BREAK");
770 		ckfree(argv);
771 		return TCL_ERROR;
772 #endif /* TIOCSBRK & TIOCCBRK */
773 	    } else {
774 		if (interp) {
775 		    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
776 			    "bad signal \"%s\" for -ttycontrol: must be"
777 			    " DTR, RTS or BREAK", argv[i]));
778 		    Tcl_SetErrorCode(interp, "TCL", "OPERATION", "FCONFIGURE",
779 			"VALUE", NULL);
780 		}
781 		ckfree(argv);
782 		return TCL_ERROR;
783 	    }
784 	} /* -ttycontrol options loop */
785 
786 	ioctl(fsPtr->fd, TIOCMSET, &control);
787 	ckfree(argv);
788 	return TCL_OK;
789 #else /* TIOCMGET&TIOCMSET */
790 	UNSUPPORTED_OPTION("-ttycontrol");
791 #endif /* TIOCMGET&TIOCMSET */
792     }
793 
794     return Tcl_BadChannelOption(interp, optionName,
795 	    "mode handshake timeout ttycontrol xchar");
796 }
797 
798 /*
799  *----------------------------------------------------------------------
800  *
801  * TtyGetOptionProc --
802  *
803  *	Gets a mode associated with an IO channel. If the optionName arg is
804  *	non-NULL, retrieves the value of that option. If the optionName arg is
805  *	NULL, retrieves a list of alternating option names and values for the
806  *	given channel.
807  *
808  * Results:
809  *	A standard Tcl result. Also sets the supplied DString to the string
810  *	value of the option(s) returned.  Sets error message if needed
811  *	(by calling Tcl_BadChannelOption).
812  *
813  *----------------------------------------------------------------------
814  */
815 
816 static int
TtyGetOptionProc(ClientData instanceData,Tcl_Interp * interp,const char * optionName,Tcl_DString * dsPtr)817 TtyGetOptionProc(
818     ClientData instanceData,	/* File state. */
819     Tcl_Interp *interp,		/* For error reporting - can be NULL. */
820     const char *optionName,	/* Option to get. */
821     Tcl_DString *dsPtr)		/* Where to store value(s). */
822 {
823     FileState *fsPtr = instanceData;
824     unsigned int len;
825     char buf[3*TCL_INTEGER_SPACE + 16];
826     int valid = 0;		/* Flag if valid option parsed. */
827 
828     if (optionName == NULL) {
829 	len = 0;
830     } else {
831 	len = strlen(optionName);
832     }
833     if (len == 0) {
834 	Tcl_DStringAppendElement(dsPtr, "-mode");
835     }
836     if (len==0 || (len>2 && strncmp(optionName, "-mode", len)==0)) {
837 	TtyAttrs tty;
838 
839 	valid = 1;
840 	TtyGetAttributes(fsPtr->fd, &tty);
841 	sprintf(buf, "%d,%c,%d,%d", tty.baud, tty.parity, tty.data, tty.stop);
842 	Tcl_DStringAppendElement(dsPtr, buf);
843     }
844 
845     /*
846      * Get option -xchar
847      */
848 
849     if (len == 0) {
850 	Tcl_DStringAppendElement(dsPtr, "-xchar");
851 	Tcl_DStringStartSublist(dsPtr);
852     }
853     if (len==0 || (len>1 && strncmp(optionName, "-xchar", len)==0)) {
854 	struct termios iostate;
855 	Tcl_DString ds;
856 
857 	valid = 1;
858 	tcgetattr(fsPtr->fd, &iostate);
859 	Tcl_DStringInit(&ds);
860 
861 	Tcl_ExternalToUtfDString(NULL, (char *) &iostate.c_cc[VSTART], 1, &ds);
862 	Tcl_DStringAppendElement(dsPtr, Tcl_DStringValue(&ds));
863 	TclDStringClear(&ds);
864 
865 	Tcl_ExternalToUtfDString(NULL, (char *) &iostate.c_cc[VSTOP], 1, &ds);
866 	Tcl_DStringAppendElement(dsPtr, Tcl_DStringValue(&ds));
867 	Tcl_DStringFree(&ds);
868     }
869     if (len == 0) {
870 	Tcl_DStringEndSublist(dsPtr);
871     }
872 
873     /*
874      * Get option -queue
875      * Option is readonly and returned by [fconfigure chan -queue] but not
876      * returned by unnamed [fconfigure chan].
877      */
878 
879     if ((len > 1) && (strncmp(optionName, "-queue", len) == 0)) {
880 	int inQueue=0, outQueue=0, inBuffered, outBuffered;
881 
882 	valid = 1;
883 	GETREADQUEUE(fsPtr->fd, inQueue);
884 	GETWRITEQUEUE(fsPtr->fd, outQueue);
885 	inBuffered = Tcl_InputBuffered(fsPtr->channel);
886 	outBuffered = Tcl_OutputBuffered(fsPtr->channel);
887 
888 	sprintf(buf, "%d", inBuffered+inQueue);
889 	Tcl_DStringAppendElement(dsPtr, buf);
890 	sprintf(buf, "%d", outBuffered+outQueue);
891 	Tcl_DStringAppendElement(dsPtr, buf);
892     }
893 
894 #if defined(TIOCMGET)
895     /*
896      * Get option -ttystatus
897      * Option is readonly and returned by [fconfigure chan -ttystatus] but not
898      * returned by unnamed [fconfigure chan].
899      */
900     if ((len > 4) && (strncmp(optionName, "-ttystatus", len) == 0)) {
901 	int status;
902 
903 	valid = 1;
904 	ioctl(fsPtr->fd, TIOCMGET, &status);
905 	TtyModemStatusStr(status, dsPtr);
906     }
907 #endif /* TIOCMGET */
908 
909     if (valid) {
910 	return TCL_OK;
911     }
912     return Tcl_BadChannelOption(interp, optionName, "mode"
913 	    " queue ttystatus xchar"
914 	    );
915 }
916 
917 
918 static const struct {int baud; speed_t speed;} speeds[] = {
919 #ifdef B0
920     {0, B0},
921 #endif
922 #ifdef B50
923     {50, B50},
924 #endif
925 #ifdef B75
926     {75, B75},
927 #endif
928 #ifdef B110
929     {110, B110},
930 #endif
931 #ifdef B134
932     {134, B134},
933 #endif
934 #ifdef B150
935     {150, B150},
936 #endif
937 #ifdef B200
938     {200, B200},
939 #endif
940 #ifdef B300
941     {300, B300},
942 #endif
943 #ifdef B600
944     {600, B600},
945 #endif
946 #ifdef B1200
947     {1200, B1200},
948 #endif
949 #ifdef B1800
950     {1800, B1800},
951 #endif
952 #ifdef B2400
953     {2400, B2400},
954 #endif
955 #ifdef B4800
956     {4800, B4800},
957 #endif
958 #ifdef B9600
959     {9600, B9600},
960 #endif
961 #ifdef B14400
962     {14400, B14400},
963 #endif
964 #ifdef B19200
965     {19200, B19200},
966 #endif
967 #ifdef EXTA
968     {19200, EXTA},
969 #endif
970 #ifdef B28800
971     {28800, B28800},
972 #endif
973 #ifdef B38400
974     {38400, B38400},
975 #endif
976 #ifdef EXTB
977     {38400, EXTB},
978 #endif
979 #ifdef B57600
980     {57600, B57600},
981 #endif
982 #ifdef _B57600
983     {57600, _B57600},
984 #endif
985 #ifdef B76800
986     {76800, B76800},
987 #endif
988 #ifdef B115200
989     {115200, B115200},
990 #endif
991 #ifdef _B115200
992     {115200, _B115200},
993 #endif
994 #ifdef B153600
995     {153600, B153600},
996 #endif
997 #ifdef B230400
998     {230400, B230400},
999 #endif
1000 #ifdef B307200
1001     {307200, B307200},
1002 #endif
1003 #ifdef B460800
1004     {460800, B460800},
1005 #endif
1006 #ifdef B500000
1007     {500000, B500000},
1008 #endif
1009 #ifdef B576000
1010     {576000, B576000},
1011 #endif
1012 #ifdef B921600
1013     {921600, B921600},
1014 #endif
1015 #ifdef B1000000
1016     {1000000, B1000000},
1017 #endif
1018 #ifdef B1152000
1019     {1152000, B1152000},
1020 #endif
1021 #ifdef B1500000
1022     {1500000,B1500000},
1023 #endif
1024 #ifdef B2000000
1025     {2000000, B2000000},
1026 #endif
1027 #ifdef B2500000
1028     {2500000,B2500000},
1029 #endif
1030 #ifdef B3000000
1031     {3000000,B3000000},
1032 #endif
1033 #ifdef B3500000
1034     {3500000,B3500000},
1035 #endif
1036 #ifdef B4000000
1037     {4000000,B4000000},
1038 #endif
1039     {-1, 0}
1040 };
1041 
1042 /*
1043  *---------------------------------------------------------------------------
1044  *
1045  * TtyGetSpeed --
1046  *
1047  *	Given an integer baud rate, get the speed_t value that should be
1048  *	used to select that baud rate.
1049  *
1050  * Results:
1051  *	As above.
1052  *
1053  *---------------------------------------------------------------------------
1054  */
1055 
1056 static speed_t
TtyGetSpeed(int baud)1057 TtyGetSpeed(
1058     int baud)			/* The baud rate to look up. */
1059 {
1060     int bestIdx, bestDiff, i, diff;
1061 
1062     bestIdx = 0;
1063     bestDiff = 1000000;
1064 
1065     /*
1066      * If the baud rate does not correspond to one of the known mask values,
1067      * choose the mask value whose baud rate is closest to the specified baud
1068      * rate.
1069      */
1070 
1071     for (i = 0; speeds[i].baud >= 0; i++) {
1072 	diff = speeds[i].baud - baud;
1073 	if (diff < 0) {
1074 	    diff = -diff;
1075 	}
1076 	if (diff < bestDiff) {
1077 	    bestIdx = i;
1078 	    bestDiff = diff;
1079 	}
1080     }
1081     return speeds[bestIdx].speed;
1082 }
1083 
1084 /*
1085  *---------------------------------------------------------------------------
1086  *
1087  * TtyGetBaud --
1088  *
1089  *	Return the integer baud rate corresponding to a given speed_t value.
1090  *
1091  * Results:
1092  *	As above. If the mask value was not recognized, 0 is returned.
1093  *
1094  *---------------------------------------------------------------------------
1095  */
1096 
1097 static int
TtyGetBaud(speed_t speed)1098 TtyGetBaud(
1099     speed_t speed)		/* Speed mask value to look up. */
1100 {
1101     int i;
1102 
1103     for (i = 0; speeds[i].baud >= 0; i++) {
1104 	if (speeds[i].speed == speed) {
1105 	    return speeds[i].baud;
1106 	}
1107     }
1108     return 0;
1109 }
1110 
1111 /*
1112  *---------------------------------------------------------------------------
1113  *
1114  * TtyGetAttributes --
1115  *
1116  *	Get the current attributes of the specified serial device.
1117  *
1118  * Results:
1119  *	None.
1120  *
1121  * Side effects:
1122  *	None.
1123  *
1124  *---------------------------------------------------------------------------
1125  */
1126 
1127 static void
TtyGetAttributes(int fd,TtyAttrs * ttyPtr)1128 TtyGetAttributes(
1129     int fd,			/* Open file descriptor for serial port to be
1130 				 * queried. */
1131     TtyAttrs *ttyPtr)		/* Buffer filled with serial port
1132 				 * attributes. */
1133 {
1134     struct termios iostate;
1135     int baud, parity, data, stop;
1136 
1137     tcgetattr(fd, &iostate);
1138 
1139     baud = TtyGetBaud(cfgetospeed(&iostate));
1140 
1141     parity = 'n';
1142 #ifdef PAREXT
1143     switch ((int) (iostate.c_cflag & (PARENB | PARODD | PAREXT))) {
1144     case PARENB			  : parity = 'e'; break;
1145     case PARENB | PARODD	  : parity = 'o'; break;
1146     case PARENB |	   PAREXT : parity = 's'; break;
1147     case PARENB | PARODD | PAREXT : parity = 'm'; break;
1148     }
1149 #else /* !PAREXT */
1150     switch ((int) (iostate.c_cflag & (PARENB | PARODD))) {
1151     case PARENB		 : parity = 'e'; break;
1152     case PARENB | PARODD : parity = 'o'; break;
1153     }
1154 #endif /* PAREXT */
1155 
1156     data = iostate.c_cflag & CSIZE;
1157     data = (data == CS5) ? 5 : (data == CS6) ? 6 : (data == CS7) ? 7 : 8;
1158 
1159     stop = (iostate.c_cflag & CSTOPB) ? 2 : 1;
1160 
1161     ttyPtr->baud    = baud;
1162     ttyPtr->parity  = parity;
1163     ttyPtr->data    = data;
1164     ttyPtr->stop    = stop;
1165 }
1166 
1167 /*
1168  *---------------------------------------------------------------------------
1169  *
1170  * TtySetAttributes --
1171  *
1172  *	Set the current attributes of the specified serial device.
1173  *
1174  * Results:
1175  *	None.
1176  *
1177  * Side effects:
1178  *	None.
1179  *
1180  *---------------------------------------------------------------------------
1181  */
1182 
1183 static void
TtySetAttributes(int fd,TtyAttrs * ttyPtr)1184 TtySetAttributes(
1185     int fd,			/* Open file descriptor for serial port to be
1186 				 * modified. */
1187     TtyAttrs *ttyPtr)		/* Buffer containing new attributes for serial
1188 				 * port. */
1189 {
1190     struct termios iostate;
1191     int parity, data, flag;
1192 
1193     tcgetattr(fd, &iostate);
1194     cfsetospeed(&iostate, TtyGetSpeed(ttyPtr->baud));
1195     cfsetispeed(&iostate, TtyGetSpeed(ttyPtr->baud));
1196 
1197     flag = 0;
1198     parity = ttyPtr->parity;
1199     if (parity != 'n') {
1200 	SET_BITS(flag, PARENB);
1201 #ifdef PAREXT
1202 	CLEAR_BITS(iostate.c_cflag, PAREXT);
1203 	if ((parity == 'm') || (parity == 's')) {
1204 	    SET_BITS(flag, PAREXT);
1205 	}
1206 #endif /* PAREXT */
1207 	if ((parity == 'm') || (parity == 'o')) {
1208 	    SET_BITS(flag, PARODD);
1209 	}
1210     }
1211     data = ttyPtr->data;
1212     SET_BITS(flag,
1213 	    (data == 5) ? CS5 :
1214 	    (data == 6) ? CS6 :
1215 	    (data == 7) ? CS7 : CS8);
1216     if (ttyPtr->stop == 2) {
1217 	SET_BITS(flag, CSTOPB);
1218     }
1219 
1220     CLEAR_BITS(iostate.c_cflag, PARENB | PARODD | CSIZE | CSTOPB);
1221     SET_BITS(iostate.c_cflag, flag);
1222 
1223     tcsetattr(fd, TCSADRAIN, &iostate);
1224 }
1225 
1226 /*
1227  *---------------------------------------------------------------------------
1228  *
1229  * TtyParseMode --
1230  *
1231  *	Parse the "-mode" argument to the fconfigure command. The argument is
1232  *	of the form baud,parity,data,stop.
1233  *
1234  * Results:
1235  *	The return value is TCL_OK if the argument was successfully parsed,
1236  *	TCL_ERROR otherwise. If TCL_ERROR is returned, an error message is
1237  *	left in the interp's result (if interp is non-NULL).
1238  *
1239  *---------------------------------------------------------------------------
1240  */
1241 
1242 static int
TtyParseMode(Tcl_Interp * interp,const char * mode,TtyAttrs * ttyPtr)1243 TtyParseMode(
1244     Tcl_Interp *interp,		/* If non-NULL, interp for error return. */
1245     const char *mode,		/* Mode string to be parsed. */
1246     TtyAttrs *ttyPtr)		/* Filled with data from mode string */
1247 {
1248     int i, end;
1249     char parity;
1250     const char *bad = "bad value for -mode";
1251 
1252     i = sscanf(mode, "%d,%c,%d,%d%n",
1253 	    &ttyPtr->baud,
1254 	    &parity,
1255 	    &ttyPtr->data,
1256 	    &ttyPtr->stop, &end);
1257     if ((i != 4) || (mode[end] != '\0')) {
1258 	if (interp != NULL) {
1259 	    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
1260 		    "%s: should be baud,parity,data,stop", bad));
1261 	    Tcl_SetErrorCode(interp, "TCL", "VALUE", "SERIALMODE", NULL);
1262 	}
1263 	return TCL_ERROR;
1264     }
1265 
1266     /*
1267      * Only allow setting mark/space parity on platforms that support it Make
1268      * sure to allow for the case where strchr is a macro. [Bug: 5089]
1269      *
1270      * We cannot if/else/endif the strchr arguments, it has to be the whole
1271      * function. On AIX this function is apparently a macro, and macros do
1272      * not allow pre-processor directives in their arguments.
1273      */
1274 
1275     if (
1276 #if defined(PAREXT)
1277         strchr("noems", parity)
1278 #else
1279         strchr("noe", parity)
1280 #endif /* PAREXT */
1281                                == NULL) {
1282 	if (interp != NULL) {
1283 	    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
1284 		    "%s parity: should be %s", bad,
1285 #if defined(PAREXT)
1286 		    "n, o, e, m, or s"
1287 #else
1288 		    "n, o, or e"
1289 #endif /* PAREXT */
1290 		    ));
1291 	    Tcl_SetErrorCode(interp, "TCL", "VALUE", "SERIALMODE", NULL);
1292 	}
1293 	return TCL_ERROR;
1294     }
1295     ttyPtr->parity = parity;
1296     if ((ttyPtr->data < 5) || (ttyPtr->data > 8)) {
1297 	if (interp != NULL) {
1298 	    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
1299 		    "%s data: should be 5, 6, 7, or 8", bad));
1300 	    Tcl_SetErrorCode(interp, "TCL", "VALUE", "SERIALMODE", NULL);
1301 	}
1302 	return TCL_ERROR;
1303     }
1304     if ((ttyPtr->stop < 0) || (ttyPtr->stop > 2)) {
1305 	if (interp != NULL) {
1306 	    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
1307 		    "%s stop: should be 1 or 2", bad));
1308 	    Tcl_SetErrorCode(interp, "TCL", "VALUE", "SERIALMODE", NULL);
1309 	}
1310 	return TCL_ERROR;
1311     }
1312     return TCL_OK;
1313 }
1314 
1315 /*
1316  *---------------------------------------------------------------------------
1317  *
1318  * TtyInit --
1319  *
1320  *	Given file descriptor that refers to a serial port, initialize the
1321  *	serial port to a set of sane values so that Tcl can talk to a device
1322  *	located on the serial port.
1323  *
1324  * Side effects:
1325  *	Serial device initialized to non-blocking raw mode, similar to sockets
1326  *	All other modes can be simulated on top of this in Tcl.
1327  *
1328  *---------------------------------------------------------------------------
1329  */
1330 
1331 static void
TtyInit(int fd)1332 TtyInit(
1333     int fd)	/* Open file descriptor for serial port to be initialized. */
1334 {
1335     struct termios iostate;
1336     tcgetattr(fd, &iostate);
1337 
1338     if (iostate.c_iflag != IGNBRK
1339 	    || iostate.c_oflag != 0
1340 	    || iostate.c_lflag != 0
1341 	    || iostate.c_cflag & CREAD
1342 	    || iostate.c_cc[VMIN] != 1
1343 	    || iostate.c_cc[VTIME] != 0)
1344     {
1345 	iostate.c_iflag = IGNBRK;
1346 	iostate.c_oflag = 0;
1347 	iostate.c_lflag = 0;
1348 	iostate.c_cflag |= CREAD;
1349 	iostate.c_cc[VMIN] = 1;
1350 	iostate.c_cc[VTIME] = 0;
1351 
1352 	tcsetattr(fd, TCSADRAIN, &iostate);
1353     }
1354 }
1355 #endif	/* SUPPORTS_TTY */
1356 
1357 /*
1358  *----------------------------------------------------------------------
1359  *
1360  * TclpOpenFileChannel --
1361  *
1362  *	Open an file based channel on Unix systems.
1363  *
1364  * Results:
1365  *	The new channel or NULL. If NULL, the output argument errorCodePtr is
1366  *	set to a POSIX error and an error message is left in the interp's
1367  *	result if interp is not NULL.
1368  *
1369  * Side effects:
1370  *	May open the channel and may cause creation of a file on the file
1371  *	system.
1372  *
1373  *----------------------------------------------------------------------
1374  */
1375 
1376 Tcl_Channel
TclpOpenFileChannel(Tcl_Interp * interp,Tcl_Obj * pathPtr,int mode,int permissions)1377 TclpOpenFileChannel(
1378     Tcl_Interp *interp,		/* Interpreter for error reporting; can be
1379 				 * NULL. */
1380     Tcl_Obj *pathPtr,		/* Name of file to open. */
1381     int mode,			/* POSIX open mode. */
1382     int permissions)		/* If the open involves creating a file, with
1383 				 * what modes to create it? */
1384 {
1385     int fd, channelPermissions;
1386     FileState *fsPtr;
1387     const char *native, *translation;
1388     char channelName[16 + TCL_INTEGER_SPACE];
1389     const Tcl_ChannelType *channelTypePtr;
1390 
1391     switch (mode & (O_RDONLY | O_WRONLY | O_RDWR)) {
1392     case O_RDONLY:
1393 	channelPermissions = TCL_READABLE;
1394 	break;
1395     case O_WRONLY:
1396 	channelPermissions = TCL_WRITABLE;
1397 	break;
1398     case O_RDWR:
1399 	channelPermissions = (TCL_READABLE | TCL_WRITABLE);
1400 	break;
1401     default:
1402 	/*
1403 	 * This may occurr if modeString was "", for example.
1404 	 */
1405 
1406 	Tcl_Panic("TclpOpenFileChannel: invalid mode value");
1407 	return NULL;
1408     }
1409 
1410     native = Tcl_FSGetNativePath(pathPtr);
1411     if (native == NULL) {
1412 	if (interp != (Tcl_Interp *) NULL) {
1413 	    Tcl_AppendResult(interp, "couldn't open \"",
1414 	    TclGetString(pathPtr), "\": filename is invalid on this platform",
1415 	    NULL);
1416 	}
1417 	return NULL;
1418     }
1419 
1420 #ifdef DJGPP
1421     SET_BITS(mode, O_BINARY);
1422 #endif
1423 
1424     fd = TclOSopen(native, mode, permissions);
1425 
1426     if (fd < 0) {
1427 	if (interp != NULL) {
1428 	    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
1429 		    "couldn't open \"%s\": %s",
1430 		    TclGetString(pathPtr), Tcl_PosixError(interp)));
1431 	}
1432 	return NULL;
1433     }
1434 
1435     /*
1436      * Set close-on-exec flag on the fd so that child processes will not
1437      * inherit this fd.
1438      */
1439 
1440     fcntl(fd, F_SETFD, FD_CLOEXEC);
1441 
1442     sprintf(channelName, "file%d", fd);
1443 
1444 #ifdef SUPPORTS_TTY
1445     if (strcmp(native, "/dev/tty") != 0 && isatty(fd)) {
1446 	/*
1447 	 * Initialize the serial port to a set of sane parameters. Especially
1448 	 * important if the remote device is set to echo and the serial port
1449 	 * driver was also set to echo -- as soon as a char were sent to the
1450 	 * serial port, the remote device would echo it, then the serial
1451 	 * driver would echo it back to the device, etc.
1452 	 *
1453 	 * Note that we do not do this if we're dealing with /dev/tty itself,
1454 	 * as that tends to cause Bad Things To Happen when you're working
1455 	 * interactively. Strictly a better check would be to see if the FD
1456 	 * being set up is a device and has the same major/minor as the
1457 	 * initial std FDs (beware reopening!) but that's nearly as messy.
1458 	 */
1459 
1460 	translation = "auto crlf";
1461 	channelTypePtr = &ttyChannelType;
1462 	TtyInit(fd);
1463     } else
1464 #endif	/* SUPPORTS_TTY */
1465     {
1466 	translation = NULL;
1467 	channelTypePtr = &fileChannelType;
1468     }
1469 
1470     fsPtr = ckalloc(sizeof(FileState));
1471     fsPtr->validMask = channelPermissions | TCL_EXCEPTION;
1472     fsPtr->fd = fd;
1473 
1474     fsPtr->channel = Tcl_CreateChannel(channelTypePtr, channelName,
1475 	    fsPtr, channelPermissions);
1476 
1477     if (translation != NULL) {
1478 	/*
1479 	 * Gotcha. Most modems need a "\r" at the end of the command sequence.
1480 	 * If you just send "at\n", the modem will not respond with "OK"
1481 	 * because it never got a "\r" to actually invoke the command. So, by
1482 	 * default, newlines are translated to "\r\n" on output to avoid "bug"
1483 	 * reports that the serial port isn't working.
1484 	 */
1485 
1486 	if (Tcl_SetChannelOption(interp, fsPtr->channel, "-translation",
1487 		translation) != TCL_OK) {
1488 	    Tcl_Close(NULL, fsPtr->channel);
1489 	    return NULL;
1490 	}
1491     }
1492 
1493     return fsPtr->channel;
1494 }
1495 
1496 /*
1497  *----------------------------------------------------------------------
1498  *
1499  * Tcl_MakeFileChannel --
1500  *
1501  *	Makes a Tcl_Channel from an existing OS level file handle.
1502  *
1503  * Results:
1504  *	The Tcl_Channel created around the preexisting OS level file handle.
1505  *
1506  * Side effects:
1507  *	None.
1508  *
1509  *----------------------------------------------------------------------
1510  */
1511 
1512 Tcl_Channel
Tcl_MakeFileChannel(ClientData handle,int mode)1513 Tcl_MakeFileChannel(
1514     ClientData handle,		/* OS level handle. */
1515     int mode)			/* ORed combination of TCL_READABLE and
1516 				 * TCL_WRITABLE to indicate file mode. */
1517 {
1518     FileState *fsPtr;
1519     char channelName[16 + TCL_INTEGER_SPACE];
1520     int fd = PTR2INT(handle);
1521     const Tcl_ChannelType *channelTypePtr;
1522     struct sockaddr sockaddr;
1523     socklen_t sockaddrLen = sizeof(sockaddr);
1524 
1525     if (mode == 0) {
1526 	return NULL;
1527     }
1528 
1529     sockaddr.sa_family = AF_UNSPEC;
1530 
1531 #ifdef SUPPORTS_TTY
1532     if (isatty(fd)) {
1533 	channelTypePtr = &ttyChannelType;
1534 	sprintf(channelName, "serial%d", fd);
1535     } else
1536 #endif /* SUPPORTS_TTY */
1537     if ((getsockname(fd, (struct sockaddr *)&sockaddr, &sockaddrLen) == 0)
1538 	&& (sockaddrLen > 0)
1539 	&& (sockaddr.sa_family == AF_INET || sockaddr.sa_family == AF_INET6)) {
1540 	return TclpMakeTcpClientChannelMode(INT2PTR(fd), mode);
1541     } else {
1542 	channelTypePtr = &fileChannelType;
1543 	sprintf(channelName, "file%d", fd);
1544     }
1545 
1546     fsPtr = ckalloc(sizeof(FileState));
1547     fsPtr->fd = fd;
1548     fsPtr->validMask = mode | TCL_EXCEPTION;
1549     fsPtr->channel = Tcl_CreateChannel(channelTypePtr, channelName,
1550 	    fsPtr, mode);
1551 
1552     return fsPtr->channel;
1553 }
1554 
1555 /*
1556  *----------------------------------------------------------------------
1557  *
1558  * TclpGetDefaultStdChannel --
1559  *
1560  *	Creates channels for standard input, standard output or standard error
1561  *	output if they do not already exist.
1562  *
1563  * Results:
1564  *	Returns the specified default standard channel, or NULL.
1565  *
1566  * Side effects:
1567  *	May cause the creation of a standard channel and the underlying file.
1568  *
1569  *----------------------------------------------------------------------
1570  */
1571 
1572 Tcl_Channel
TclpGetDefaultStdChannel(int type)1573 TclpGetDefaultStdChannel(
1574     int type)			/* One of TCL_STDIN, TCL_STDOUT, TCL_STDERR. */
1575 {
1576     Tcl_Channel channel = NULL;
1577     int fd = 0;			/* Initializations needed to prevent */
1578     int mode = 0;		/* compiler warning (used before set). */
1579     const char *bufMode = NULL;
1580 
1581     /*
1582      * Some #def's to make the code a little clearer!
1583      */
1584 
1585 #define ZERO_OFFSET	((Tcl_SeekOffset) 0)
1586 #define ERROR_OFFSET	((Tcl_SeekOffset) -1)
1587 
1588     switch (type) {
1589     case TCL_STDIN:
1590 	if ((TclOSseek(0, ZERO_OFFSET, SEEK_CUR) == ERROR_OFFSET)
1591 		&& (errno == EBADF)) {
1592 	    return NULL;
1593 	}
1594 	fd = 0;
1595 	mode = TCL_READABLE;
1596 	bufMode = "line";
1597 	break;
1598     case TCL_STDOUT:
1599 	if ((TclOSseek(1, ZERO_OFFSET, SEEK_CUR) == ERROR_OFFSET)
1600 		&& (errno == EBADF)) {
1601 	    return NULL;
1602 	}
1603 	fd = 1;
1604 	mode = TCL_WRITABLE;
1605 	bufMode = "line";
1606 	break;
1607     case TCL_STDERR:
1608 	if ((TclOSseek(2, ZERO_OFFSET, SEEK_CUR) == ERROR_OFFSET)
1609 		&& (errno == EBADF)) {
1610 	    return NULL;
1611 	}
1612 	fd = 2;
1613 	mode = TCL_WRITABLE;
1614 	bufMode = "none";
1615 	break;
1616     default:
1617 	Tcl_Panic("TclGetDefaultStdChannel: Unexpected channel type");
1618 	break;
1619     }
1620 
1621 #undef ZERO_OFFSET
1622 #undef ERROR_OFFSET
1623 
1624     channel = Tcl_MakeFileChannel(INT2PTR(fd), mode);
1625     if (channel == NULL) {
1626 	return NULL;
1627     }
1628 
1629     /*
1630      * Set up the normal channel options for stdio handles.
1631      */
1632 
1633     if (Tcl_GetChannelType(channel) == &fileChannelType) {
1634 	Tcl_SetChannelOption(NULL, channel, "-translation", "auto");
1635     } else {
1636 	Tcl_SetChannelOption(NULL, channel, "-translation", "auto crlf");
1637     }
1638     Tcl_SetChannelOption(NULL, channel, "-buffering", bufMode);
1639     return channel;
1640 }
1641 
1642 /*
1643  *----------------------------------------------------------------------
1644  *
1645  * Tcl_GetOpenFile --
1646  *
1647  *	Given a name of a channel registered in the given interpreter, returns
1648  *	a FILE * for it.
1649  *
1650  * Results:
1651  *	A standard Tcl result. If the channel is registered in the given
1652  *	interpreter and it is managed by the "file" channel driver, and it is
1653  *	open for the requested mode, then the output parameter filePtr is set
1654  *	to a FILE * for the underlying file. On error, the filePtr is not set,
1655  *	TCL_ERROR is returned and an error message is left in the interp's
1656  *	result.
1657  *
1658  * Side effects:
1659  *	May invoke fdopen to create the FILE * for the requested file.
1660  *
1661  *----------------------------------------------------------------------
1662  */
1663 
1664 int
Tcl_GetOpenFile(Tcl_Interp * interp,const char * chanID,int forWriting,int checkUsage,ClientData * filePtr)1665 Tcl_GetOpenFile(
1666     Tcl_Interp *interp,		/* Interpreter in which to find file. */
1667     const char *chanID,		/* String that identifies file. */
1668     int forWriting,		/* 1 means the file is going to be used for
1669 				 * writing, 0 means for reading. */
1670     int checkUsage,		/* 1 means verify that the file was opened in
1671 				 * a mode that allows the access specified by
1672 				 * "forWriting". Ignored, we always check that
1673 				 * the channel is open for the requested
1674 				 * mode. */
1675     ClientData *filePtr)	/* Store pointer to FILE structure here. */
1676 {
1677     Tcl_Channel chan;
1678     int chanMode, fd;
1679     const Tcl_ChannelType *chanTypePtr;
1680     ClientData data;
1681     FILE *f;
1682 
1683     chan = Tcl_GetChannel(interp, chanID, &chanMode);
1684     if (chan == NULL) {
1685 	return TCL_ERROR;
1686     }
1687     if (forWriting && !(chanMode & TCL_WRITABLE)) {
1688 	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
1689 		"\"%s\" wasn't opened for writing", chanID));
1690 	Tcl_SetErrorCode(interp, "TCL", "VALUE", "CHANNEL", "NOT_WRITABLE",
1691 		NULL);
1692 	return TCL_ERROR;
1693     } else if (!forWriting && !(chanMode & TCL_READABLE)) {
1694 	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
1695 		"\"%s\" wasn't opened for reading", chanID));
1696 	Tcl_SetErrorCode(interp, "TCL", "VALUE", "CHANNEL", "NOT_READABLE",
1697 		NULL);
1698 	return TCL_ERROR;
1699     }
1700 
1701     /*
1702      * We allow creating a FILE * out of file based, pipe based and socket
1703      * based channels. We currently do not allow any other channel types,
1704      * because it is likely that stdio will not know what to do with them.
1705      */
1706 
1707     chanTypePtr = Tcl_GetChannelType(chan);
1708     if ((chanTypePtr == &fileChannelType)
1709 #ifdef SUPPORTS_TTY
1710 	    || (chanTypePtr == &ttyChannelType)
1711 #endif /* SUPPORTS_TTY */
1712 	    || (strcmp(chanTypePtr->typeName, "tcp") == 0)
1713 	    || (strcmp(chanTypePtr->typeName, "pipe") == 0)) {
1714 	if (Tcl_GetChannelHandle(chan,
1715 		(forWriting ? TCL_WRITABLE : TCL_READABLE), &data) == TCL_OK) {
1716 	    fd = PTR2INT(data);
1717 
1718 	    /*
1719 	     * The call to fdopen below is probably dangerous, since it will
1720 	     * truncate an existing file if the file is being opened for
1721 	     * writing....
1722 	     */
1723 
1724 	    f = fdopen(fd, (forWriting ? "w" : "r"));
1725 	    if (f == NULL) {
1726 		Tcl_SetObjResult(interp, Tcl_ObjPrintf(
1727 			"cannot get a FILE * for \"%s\"", chanID));
1728 		Tcl_SetErrorCode(interp, "TCL", "VALUE", "CHANNEL",
1729 			"FILE_FAILURE", NULL);
1730 		return TCL_ERROR;
1731 	    }
1732 	    *filePtr = f;
1733 	    return TCL_OK;
1734 	}
1735     }
1736 
1737     Tcl_SetObjResult(interp, Tcl_ObjPrintf(
1738 	    "\"%s\" cannot be used to get a FILE *", chanID));
1739     Tcl_SetErrorCode(interp, "TCL", "VALUE", "CHANNEL", "NO_DESCRIPTOR",
1740 	    NULL);
1741     return TCL_ERROR;
1742 }
1743 
1744 #ifndef HAVE_COREFOUNDATION	/* Darwin/Mac OS X CoreFoundation notifier is
1745 				 * in tclMacOSXNotify.c */
1746 /*
1747  *----------------------------------------------------------------------
1748  *
1749  * TclUnixWaitForFile --
1750  *
1751  *	This function waits synchronously for a file to become readable or
1752  *	writable, with an optional timeout.
1753  *
1754  * Results:
1755  *	The return value is an OR'ed combination of TCL_READABLE,
1756  *	TCL_WRITABLE, and TCL_EXCEPTION, indicating the conditions that are
1757  *	present on file at the time of the return. This function will not
1758  *	return until either "timeout" milliseconds have elapsed or at least
1759  *	one of the conditions given by mask has occurred for file (a return
1760  *	value of 0 means that a timeout occurred). No normal events will be
1761  *	serviced during the execution of this function.
1762  *
1763  * Side effects:
1764  *	Time passes.
1765  *
1766  *----------------------------------------------------------------------
1767  */
1768 
1769 int
TclUnixWaitForFile(int fd,int mask,int timeout)1770 TclUnixWaitForFile(
1771     int fd,			/* Handle for file on which to wait. */
1772     int mask,			/* What to wait for: OR'ed combination of
1773 				 * TCL_READABLE, TCL_WRITABLE, and
1774 				 * TCL_EXCEPTION. */
1775     int timeout)		/* Maximum amount of time to wait for one of
1776 				 * the conditions in mask to occur, in
1777 				 * milliseconds. A value of 0 means don't wait
1778 				 * at all, and a value of -1 means wait
1779 				 * forever. */
1780 {
1781     Tcl_Time abortTime = {0, 0}, now; /* silence gcc 4 warning */
1782     struct timeval blockTime, *timeoutPtr;
1783     int numFound, result = 0;
1784     fd_set readableMask;
1785     fd_set writableMask;
1786     fd_set exceptionMask;
1787 
1788 #ifndef _DARWIN_C_SOURCE
1789     /*
1790      * Sanity check fd.
1791      */
1792 
1793     if (fd >= FD_SETSIZE) {
1794 	Tcl_Panic("TclUnixWaitForFile can't handle file id %d", fd);
1795 	/* must never get here, or select masks overrun will occur below */
1796     }
1797 #endif
1798 
1799     /*
1800      * If there is a non-zero finite timeout, compute the time when we give
1801      * up.
1802      */
1803 
1804     if (timeout > 0) {
1805 	Tcl_GetTime(&now);
1806 	abortTime.sec = now.sec + timeout/1000;
1807 	abortTime.usec = now.usec + (timeout%1000)*1000;
1808 	if (abortTime.usec >= 1000000) {
1809 	    abortTime.usec -= 1000000;
1810 	    abortTime.sec += 1;
1811 	}
1812 	timeoutPtr = &blockTime;
1813     } else if (timeout == 0) {
1814 	timeoutPtr = &blockTime;
1815 	blockTime.tv_sec = 0;
1816 	blockTime.tv_usec = 0;
1817     } else {
1818 	timeoutPtr = NULL;
1819     }
1820 
1821     /*
1822      * Initialize the select masks.
1823      */
1824 
1825     FD_ZERO(&readableMask);
1826     FD_ZERO(&writableMask);
1827     FD_ZERO(&exceptionMask);
1828 
1829     /*
1830      * Loop in a mini-event loop of our own, waiting for either the file to
1831      * become ready or a timeout to occur.
1832      */
1833 
1834     while (1) {
1835 	if (timeout > 0) {
1836 	    blockTime.tv_sec = abortTime.sec - now.sec;
1837 	    blockTime.tv_usec = abortTime.usec - now.usec;
1838 	    if (blockTime.tv_usec < 0) {
1839 		blockTime.tv_sec -= 1;
1840 		blockTime.tv_usec += 1000000;
1841 	    }
1842 	    if (blockTime.tv_sec < 0) {
1843 		blockTime.tv_sec = 0;
1844 		blockTime.tv_usec = 0;
1845 	    }
1846 	}
1847 
1848 	/*
1849 	 * Setup the select masks for the fd.
1850 	 */
1851 
1852 	if (mask & TCL_READABLE) {
1853 	    FD_SET(fd, &readableMask);
1854 	}
1855 	if (mask & TCL_WRITABLE) {
1856 	    FD_SET(fd, &writableMask);
1857 	}
1858 	if (mask & TCL_EXCEPTION) {
1859 	    FD_SET(fd, &exceptionMask);
1860 	}
1861 
1862 	/*
1863 	 * Wait for the event or a timeout.
1864 	 */
1865 
1866 	numFound = select(fd + 1, &readableMask, &writableMask,
1867 		&exceptionMask, timeoutPtr);
1868 	if (numFound == 1) {
1869 	    if (FD_ISSET(fd, &readableMask)) {
1870 		SET_BITS(result, TCL_READABLE);
1871 	    }
1872 	    if (FD_ISSET(fd, &writableMask)) {
1873 		SET_BITS(result, TCL_WRITABLE);
1874 	    }
1875 	    if (FD_ISSET(fd, &exceptionMask)) {
1876 		SET_BITS(result, TCL_EXCEPTION);
1877 	    }
1878 	    result &= mask;
1879 	    if (result) {
1880 		break;
1881 	    }
1882 	}
1883 	if (timeout == 0) {
1884 	    break;
1885 	}
1886 	if (timeout < 0) {
1887 	    continue;
1888 	}
1889 
1890 	/*
1891 	 * The select returned early, so we need to recompute the timeout.
1892 	 */
1893 
1894 	Tcl_GetTime(&now);
1895 	if ((abortTime.sec < now.sec)
1896 		|| (abortTime.sec==now.sec && abortTime.usec<=now.usec)) {
1897 	    break;
1898 	}
1899     }
1900     return result;
1901 }
1902 #endif /* HAVE_COREFOUNDATION */
1903 
1904 /*
1905  *----------------------------------------------------------------------
1906  *
1907  * FileTruncateProc --
1908  *
1909  *	Truncates a file to a given length.
1910  *
1911  * Results:
1912  *	0 if the operation succeeded, and -1 if it failed (in which case
1913  *	*errorCodePtr will be set to errno).
1914  *
1915  * Side effects:
1916  *	The underlying file is potentially truncated. This can have a wide
1917  *	variety of side effects, including moving file pointers that point at
1918  *	places later in the file than the truncate point.
1919  *
1920  *----------------------------------------------------------------------
1921  */
1922 
1923 static int
FileTruncateProc(ClientData instanceData,Tcl_WideInt length)1924 FileTruncateProc(
1925     ClientData instanceData,
1926     Tcl_WideInt length)
1927 {
1928     FileState *fsPtr = instanceData;
1929     int result;
1930 
1931 #ifdef HAVE_TYPE_OFF64_T
1932     /*
1933      * We assume this goes with the type for now...
1934      */
1935 
1936     result = ftruncate64(fsPtr->fd, (off64_t) length);
1937 #else
1938     result = ftruncate(fsPtr->fd, (off_t) length);
1939 #endif
1940     if (result) {
1941 	return errno;
1942     }
1943     return 0;
1944 }
1945 
1946 /*
1947  * Local Variables:
1948  * mode: c
1949  * c-basic-offset: 4
1950  * fill-column: 78
1951  * End:
1952  */
1953