1/*
2 * tclXwinDup.c
3 *
4 * Support for the dup command on Windows.
5 *-----------------------------------------------------------------------------
6 * Copyright 1991-1999 Karl Lehenbauer and Mark Diekhans.
7 *
8 * Permission to use, copy, modify, and distribute this software and its
9 * documentation for any purpose and without fee is hereby granted, provided
10 * that the above copyright notice appear in all copies.  Karl Lehenbauer and
11 * Mark Diekhans make no representations about the suitability of this
12 * software for any purpose.  It is provided "as is" without express or
13 * implied warranty.
14 *-----------------------------------------------------------------------------
15 * $Id: tclXwinDup.c,v 1.1 2001/10/24 23:31:50 hobbs Exp $
16 *-----------------------------------------------------------------------------
17 */
18
19#include "tclExtdInt.h"
20
21
22/*-----------------------------------------------------------------------------
23 * ConvertChannelName --
24 *
25 *   Convert a requested channel name to one of the standard channel ids.
26 *
27 * Parameters:
28 *   o interp - Errors are returned in result.
29 *   o channelName - Desired channel, one of "stdin", "stdout" or "stderr".
30 *   o handleIdPtr - One of STD_{INPUT|OUTPUT|ERROR}_HANDLE is returned.
31 * Returns:
32 *   TCL_OK or TCL_ERROR.
33 * FIX: Make Unix version parallel this one.
34 *-----------------------------------------------------------------------------
35 */
36static int
37ConvertChannelName (Tcl_Interp *interp,
38                    char       *channelName,
39                    DWORD      *handleIdPtr)
40{
41    if (channelName [0] == 's') {
42        if (STREQU (channelName, "stdin"))
43            *handleIdPtr = STD_INPUT_HANDLE;
44        else if (STREQU (channelName, "stdout"))
45            *handleIdPtr = STD_OUTPUT_HANDLE;
46        else if (STREQU (channelName, "stderr"))
47            *handleIdPtr = STD_ERROR_HANDLE;
48    } else if (STRNEQU (channelName, "file", 4) ||
49               STRNEQU (channelName, "sock", 4)) {
50        TclX_AppendObjResult (interp, "on MS Windows, only stdin, ",
51                              "stdout or stderr maybe the dup target",
52                              (char *) NULL);
53        return TCL_ERROR;
54    } else {
55        TclX_AppendObjResult (interp, "invalid channel id: ",
56                              channelName, (char *) NULL);
57        return TCL_ERROR;
58    }
59    return TCL_OK;
60}
61
62/*-----------------------------------------------------------------------------
63 * TclXOSDupChannel --
64 *   OS dependent duplication of a channel.
65 *
66 * Parameters:
67 *   o interp (I) - If an error occures, the error message is in result.
68 *   o srcChannel (I) - The channel to dup.
69 *   o mode (I) - The channel mode.
70 *   o targetChannelId (I) - The id for the new file.  NULL if any id maybe
71 *     used.
72 * Returns:
73 *   The unregistered new channel, or NULL if an error occured.
74 *-----------------------------------------------------------------------------
75 */
76Tcl_Channel
77TclXOSDupChannel (interp, srcChannel, mode, targetChannelId)
78    Tcl_Interp *interp;
79    Tcl_Channel srcChannel;
80    int         mode;
81    char       *targetChannelId;
82{
83    Tcl_Channel newChannel = NULL;
84    int direction;
85    int result;
86    HANDLE srcFileHand, newFileHand = INVALID_HANDLE_VALUE;
87    int sockType;
88    int sockTypeLen = sizeof(sockType);
89
90    /*
91     * On Windows, the channels we can dup share the same file for the read and
92     * write directions, so use either.
93     */
94    if (mode & TCL_READABLE) {
95	direction = TCL_READABLE;
96    } else {
97	direction = TCL_WRITABLE;
98    }
99
100    result = (Tcl_GetChannelHandle (srcChannel, direction,
101				    (ClientData *) &srcFileHand));
102    if (result != TCL_OK) {
103        TclX_AppendObjResult (interp, "channel \"",
104                              Tcl_GetChannelName (srcChannel),
105                              "\" has no device handle", (char *) NULL);
106	return NULL;
107    }
108
109    switch (GetFileType (srcFileHand))
110    {
111    case FILE_TYPE_PIPE:
112	if (getsockopt((SOCKET)srcFileHand, SOL_SOCKET, SO_TYPE,
113		       (void *)&sockType, &sockTypeLen) == 0) {
114	    TclXNotAvailableError (interp, "duping a socket");
115	    return NULL;
116	}
117	break;
118
119    default:
120	break;
121    }
122
123    /*
124     * Duplicate the channel's file.
125     */
126    if (!DuplicateHandle (GetCurrentProcess (),
127                          srcFileHand,
128                          GetCurrentProcess (),
129                          &newFileHand,
130                          0, FALSE,
131                          DUPLICATE_SAME_ACCESS)) {
132	TclWinConvertError (GetLastError ());
133        TclX_AppendObjResult (interp, "dup failed: ",
134                              Tcl_PosixError (interp), (char *) NULL);
135        goto errorExit;
136    }
137
138    /*
139     * If a standard target channel is specified, close the target if its open
140     * and make the new channel one of the std channels.
141     */
142    if (targetChannelId != NULL) {
143        Tcl_Channel oldChannel;
144        DWORD stdHandleId;
145
146        if (ConvertChannelName (interp, targetChannelId,
147                                &stdHandleId) != TCL_OK)
148            goto errorExit;
149
150        oldChannel = Tcl_GetChannel (interp, targetChannelId, NULL);
151        if (oldChannel != NULL) {
152            Tcl_UnregisterChannel (interp, oldChannel);
153        }
154        SetStdHandle (stdHandleId, newFileHand);
155     }
156
157    newChannel = Tcl_MakeFileChannel ((ClientData) newFileHand, mode);
158    return newChannel;
159
160  errorExit:
161    if (newFileHand != INVALID_HANDLE_VALUE)
162        CloseHandle (newFileHand);
163    return NULL;
164}
165
166/*-----------------------------------------------------------------------------
167 * TclXOSBindOpenFile --
168 *   Bind a open file number of a channel.
169 *
170 * Parameters:
171 *   o interp (I) - If an error occures, the error message is in result.
172 *   o fileNum (I) - The file number of the open file.
173 * Returns:
174 *   The unregistered channel or NULL if an error occurs.
175 *-----------------------------------------------------------------------------
176 */
177Tcl_Channel
178TclXOSBindOpenFile (interp, fileNum)
179    Tcl_Interp *interp;
180    int         fileNum;
181{
182    HANDLE fileHandle;
183    int mode, isSocket;
184    char channelName[20];
185    char fileNumStr[20];
186    Tcl_Channel channel = NULL;
187    int sockType;
188    int sockTypeLen = sizeof(sockType);
189
190    /*
191     * Make sure file is open and determine the access mode and file type.
192     * Currently, we just make sure it's open, and assume both read and write.
193     * FIX: find an API under Windows that returns the read/write info.
194     */
195    fileHandle = (HANDLE) fileNum;
196    switch (GetFileType (fileHandle))
197    {
198    case FILE_TYPE_UNKNOWN:
199        TclWinConvertError (GetLastError ());
200        goto posixError;
201    case FILE_TYPE_PIPE:
202	isSocket = getsockopt((SOCKET)fileHandle, SOL_SOCKET, SO_TYPE,
203			       (void *)&sockType, &sockTypeLen) == 0;
204   	break;
205    default:
206	isSocket = 0;
207	break;
208    }
209
210    mode = TCL_READABLE | TCL_WRITABLE;
211
212    sprintf (fileNumStr, "%d", fileNum);
213
214    if (isSocket)
215        sprintf (channelName, "sock%s", fileNumStr);
216    else
217        sprintf (channelName, "file%s", fileNumStr);
218
219    if (Tcl_GetChannel (interp, channelName, NULL) != NULL) {
220        Tcl_ResetResult (interp);
221        TclX_AppendObjResult (interp, "file number \"", fileNumStr,
222                              "\" is already bound to a Tcl channel",
223                              (char *) NULL);
224        return NULL;
225    }
226    Tcl_ResetResult (interp);
227
228    if (isSocket) {
229        channel = Tcl_MakeTcpClientChannel ((ClientData) fileNum);
230    } else {
231        channel = Tcl_MakeFileChannel ((ClientData) fileNum, mode);
232    }
233    Tcl_RegisterChannel (interp, channel);
234
235    return channel;
236
237  posixError:
238    TclX_AppendObjResult (interp, "binding open file ", fileNumStr,
239                          " to Tcl channel failed: ", Tcl_PosixError (interp),
240                          (char *) NULL);
241
242    if (channel != NULL) {
243        Tcl_UnregisterChannel (interp, channel);
244    }
245    return NULL;
246}
247
248
249