]>
Commit | Line | Data |
---|---|---|
6a5fa4e0 MG |
1 | /* |
2 | * tclXselect.c | |
3 | * | |
4 | * Extended Tcl file I/O commands. | |
5 | *----------------------------------------------------------------------------- | |
6 | * Copyright 1992 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: tclXselect.c,v 2.0 1992/10/16 04:51:10 markd Rel $ | |
16 | *----------------------------------------------------------------------------- | |
17 | */ | |
18 | ||
19 | #include "tclxint.h" | |
20 | ||
21 | #ifdef MSDOS | |
22 | typedef struct { | |
23 | long fds_bits[1]; | |
24 | } fd_set; /* GRB for Micropolis */ | |
25 | #endif | |
26 | ||
27 | extern | |
28 | double floor (); | |
29 | ||
30 | #ifdef TCL_USE_BZERO_MACRO | |
31 | # define bzero(to,length) memset(to,'\0',length) | |
32 | #endif | |
33 | ||
34 | /* | |
35 | * Macro to probe the stdio buffer to see if any data is pending in the | |
36 | * buffer. Different versions are provided for System V and BSD stdio. | |
37 | */ | |
38 | ||
39 | #ifdef __SLBF | |
40 | # define READ_DATA_PENDING(fp) (fp->_r > 0) | |
41 | #else | |
42 | # define READ_DATA_PENDING(fp) (fp->_cnt != 0) | |
43 | #endif | |
44 | ||
45 | /* | |
46 | * A few systems (A/UX 2.0) have select but no macros, define em in this case. | |
47 | */ | |
48 | #if !defined(TCL_NO_SELECT) && !defined(FD_SET) | |
49 | # define FD_SET(fd,fdset) (fdset)->fds_bits[0] |= (1<<(fd)) | |
50 | # define FD_CLR(fd,fdset) (fdset)->fds_bits[0] &= ~(1<<(fd)) | |
51 | # define FD_ZERO(fdset) (fdset)->fds_bits[0] = 0 | |
52 | # define FD_ISSET(fd,fdset) (((fdset)->fds_bits[0]) & (1<<(fd))) | |
53 | #endif | |
54 | ||
55 | /* | |
56 | * Prototypes of internal functions. | |
57 | */ | |
58 | static int | |
59 | ParseSelectFileList _ANSI_ARGS_((Tcl_Interp *interp, | |
60 | char *handleList, | |
61 | fd_set *fileDescSetPtr, | |
62 | FILE ***fileDescListPtr, | |
63 | int *maxFileIdPtr)); | |
64 | ||
65 | static int | |
66 | FindPendingData _ANSI_ARGS_((int fileDescCnt, | |
67 | FILE **fileDescList, | |
68 | fd_set *fileDescSetPtr)); | |
69 | ||
70 | static char * | |
71 | ReturnSelectedFileList _ANSI_ARGS_((fd_set *fileDescSetPtr, | |
72 | fd_set *fileDescSet2Ptr, | |
73 | int fileDescCnt, | |
74 | FILE **fileDescList)); | |
75 | ||
76 | #ifndef TCL_NO_SELECT | |
77 | \f | |
78 | /* | |
79 | *----------------------------------------------------------------------------- | |
80 | * | |
81 | * ParseSelectFileList -- | |
82 | * | |
83 | * Parse a list of file handles for select. | |
84 | * | |
85 | * Parameters: | |
86 | * o interp (O) - Error messages are returned in the result. | |
87 | * o handleList (I) - The list of file handles to parse, may be empty. | |
88 | * o fileDescSetPtr (O) - The select fd_set for the parsed handles is | |
89 | * filled in. Should be cleared before this procedure is called. | |
90 | * o fileDescListPtr (O) - A pointer to a dynamically allocated list of | |
91 | * the FILE ptrs that are in the set. If the list is empty, NULL is | |
92 | * returned. | |
93 | * o maxFileIdPtr (I/O) - If a file id greater than the current value is | |
94 | * encountered, it will be set to that file id. | |
95 | * Returns: | |
96 | * The number of files in the list, or -1 if an error occured. | |
97 | *----------------------------------------------------------------------------- | |
98 | */ | |
99 | static int | |
100 | ParseSelectFileList (interp, handleList, fileDescSetPtr, fileDescListPtr, | |
101 | maxFileIdPtr) | |
102 | Tcl_Interp *interp; | |
103 | char *handleList; | |
104 | fd_set *fileDescSetPtr; | |
105 | FILE ***fileDescListPtr; | |
106 | int *maxFileIdPtr; | |
107 | { | |
108 | int handleCnt, idx; | |
109 | char **handleArgv; | |
110 | FILE **fileDescList; | |
111 | ||
112 | /* | |
113 | * Optimize empty list handling. | |
114 | */ | |
115 | if (handleList [0] == '\0') { | |
116 | *fileDescListPtr = NULL; | |
117 | return 0; | |
118 | } | |
119 | ||
120 | if (Tcl_SplitList (interp, handleList, &handleCnt, &handleArgv) != TCL_OK) | |
121 | return -1; | |
122 | ||
123 | /* | |
124 | * Handle case of an empty list. | |
125 | */ | |
126 | if (handleCnt == 0) { | |
127 | *fileDescListPtr = NULL; | |
128 | ckfree ((char *) handleArgv); | |
129 | return 0; | |
130 | } | |
131 | ||
132 | fileDescList = (FILE **) ckalloc (sizeof (FILE *) * handleCnt); | |
133 | ||
134 | for (idx = 0; idx < handleCnt; idx++) { | |
135 | OpenFile *filePtr; | |
136 | int fileId; | |
137 | ||
138 | if (TclGetOpenFile (interp, handleArgv [idx], &filePtr) != TCL_OK) { | |
139 | ckfree ((char *) handleArgv); | |
140 | ckfree ((char *) fileDescList); | |
141 | return -1; | |
142 | } | |
143 | fileId = fileno (filePtr->f); | |
144 | fileDescList [idx] = filePtr->f; | |
145 | ||
146 | FD_SET (fileId, fileDescSetPtr); | |
147 | if (fileId > *maxFileIdPtr) | |
148 | *maxFileIdPtr = fileId; | |
149 | } | |
150 | ||
151 | *fileDescListPtr = fileDescList; | |
152 | ckfree ((char *) handleArgv); | |
153 | return handleCnt; | |
154 | } | |
155 | \f | |
156 | /* | |
157 | *----------------------------------------------------------------------------- | |
158 | * | |
159 | * FindPendingData -- | |
160 | * | |
161 | * Scan a list of read file descriptors to determine if any of them | |
162 | * have data pending in their stdio buffers. | |
163 | * | |
164 | * Parameters: | |
165 | * o fileDescCnt (I) - Number of descriptors in the list. | |
166 | * o fileDescListPtr (I) - A pointer to a list of the FILE pointers for | |
167 | * files that are in the set. | |
168 | * o fileDescSetPtr (I) - A select fd_set with will have a bit set for | |
169 | * every file that has data pending it its buffer. | |
170 | * Returns: | |
171 | * TRUE if any where found that had pending data, FALSE if none were found. | |
172 | *----------------------------------------------------------------------------- | |
173 | */ | |
174 | static int | |
175 | FindPendingData (fileDescCnt, fileDescList, fileDescSetPtr) | |
176 | int fileDescCnt; | |
177 | FILE **fileDescList; | |
178 | fd_set *fileDescSetPtr; | |
179 | { | |
180 | int idx, found = FALSE; | |
181 | ||
182 | FD_ZERO (fileDescSetPtr); | |
183 | ||
184 | #ifndef IS_LINUX | |
185 | for (idx = 0; idx < fileDescCnt; idx++) { | |
186 | if (READ_DATA_PENDING (fileDescList [idx])) { | |
187 | FD_SET (fileno (fileDescList [idx]), fileDescSetPtr); | |
188 | found = TRUE; | |
189 | } | |
190 | } | |
191 | #endif | |
192 | return found; | |
193 | } | |
194 | \f | |
195 | /* | |
196 | *----------------------------------------------------------------------------- | |
197 | * | |
198 | * ReturnSelectedFileList -- | |
199 | * | |
200 | * Take the resulting file descriptor sets from a select, and the | |
201 | * list of file descritpors and build up a list of Tcl file handles. | |
202 | * | |
203 | * Parameters: | |
204 | * o fileDescSetPtr (I) - The select fd_set. | |
205 | * o fileDescSet2Ptr (I) - Pointer to a second descriptor to also check | |
206 | * (their may be overlap). NULL if no second set. | |
207 | * o fileDescCnt (I) - Number of descriptors in the list. | |
208 | * o fileDescListPtr (I) - A pointer to a list of the FILE pointers for | |
209 | * files that are in the set. If the list is empty, NULL is returned. | |
210 | * Returns: | |
211 | * A dynamicly allocated list of file handles. If the handles are empty, | |
212 | * it still returns a NULL list to make clean up easy. | |
213 | *----------------------------------------------------------------------------- | |
214 | */ | |
215 | static char * | |
216 | ReturnSelectedFileList (fileDescSetPtr, fileDescSet2Ptr, fileDescCnt, | |
217 | fileDescList) | |
218 | fd_set *fileDescSetPtr; | |
219 | fd_set *fileDescSet2Ptr; | |
220 | int fileDescCnt; | |
221 | FILE **fileDescList; | |
222 | { | |
223 | int idx, handleCnt, fileNum; | |
224 | char *fileHandleList; | |
225 | char **fileHandleArgv, *nextByte; | |
226 | ||
227 | /* | |
228 | * Special case the empty list. | |
229 | */ | |
230 | if (fileDescCnt == 0) { | |
231 | fileHandleList = ckalloc (1); | |
232 | fileHandleList [0] = '\0'; | |
233 | return fileHandleList; | |
234 | } | |
235 | ||
236 | /* | |
237 | * Allocate enough room to hold the argv plus all the `fileNNN' strings | |
238 | */ | |
239 | fileHandleArgv = (char **) | |
240 | ckalloc ((fileDescCnt * sizeof (char *)) + (9 * fileDescCnt)); | |
241 | nextByte = ((char *) fileHandleArgv) + (fileDescCnt * sizeof (char *)); | |
242 | ||
243 | handleCnt = 0; | |
244 | for (idx = 0; idx < fileDescCnt; idx++) { | |
245 | fileNum = fileno (fileDescList [idx]); | |
246 | ||
247 | if (FD_ISSET (fileNum, fileDescSetPtr) || | |
248 | (fileDescSet2Ptr != NULL && | |
249 | FD_ISSET (fileNum, fileDescSet2Ptr))) { | |
250 | ||
251 | fileHandleArgv [handleCnt] = nextByte; /* Allocate storage */ | |
252 | nextByte += 8; | |
253 | sprintf (fileHandleArgv [handleCnt], "file%d", fileNum); | |
254 | handleCnt++; | |
255 | } | |
256 | } | |
257 | ||
258 | fileHandleList = Tcl_Merge (handleCnt, fileHandleArgv); | |
259 | ckfree ((char *) fileHandleArgv); | |
260 | ||
261 | return fileHandleList; | |
262 | } | |
263 | \f | |
264 | /* | |
265 | *----------------------------------------------------------------------------- | |
266 | * | |
267 | * Tcl_SelectCmd -- | |
268 | * Implements the select TCL command: | |
269 | * select readhandles [writehandles] [excepthandles] [timeout] | |
270 | * | |
271 | * This command is extra smart in the fact that it checks for read data | |
272 | * pending in the stdio buffer first before doing a select. | |
273 | * | |
274 | * Results: | |
275 | * A list in the form: | |
276 | * {readhandles writehandles excepthandles} | |
277 | * or {} it the timeout expired. | |
278 | *----------------------------------------------------------------------------- | |
279 | */ | |
280 | int | |
281 | Tcl_SelectCmd (clientData, interp, argc, argv) | |
282 | ClientData clientData; | |
283 | Tcl_Interp *interp; | |
284 | int argc; | |
285 | char **argv; | |
286 | { | |
287 | ||
288 | fd_set readFdSet, writeFdSet, exceptFdSet; | |
289 | int readDescCnt = 0, writeDescCnt = 0, exceptDescCnt = 0; | |
290 | FILE **readDescList = NULL,**writeDescList = NULL,**exceptDescList = NULL; | |
291 | fd_set readFdSet2; | |
292 | char *retListArgv [3]; | |
293 | ||
294 | int numSelected, maxFileId = 0, pending; | |
295 | int result = TCL_ERROR; | |
296 | struct timeval timeoutRec; | |
297 | struct timeval *timeoutRecPtr; | |
298 | ||
299 | ||
300 | if (argc < 2) { | |
301 | Tcl_AppendResult (interp, tclXWrongArgs, argv [0], | |
302 | " readhandles [writehandles] [excepthandles]", | |
303 | " [timeout]", (char *) NULL); | |
304 | return TCL_ERROR; | |
305 | } | |
306 | ||
307 | /* | |
308 | * Parse the file handles and set everything up for the select call. | |
309 | */ | |
310 | FD_ZERO (&readFdSet); | |
311 | FD_ZERO (&writeFdSet); | |
312 | FD_ZERO (&exceptFdSet); | |
313 | readDescCnt = ParseSelectFileList (interp, argv [1], &readFdSet, | |
314 | &readDescList, &maxFileId); | |
315 | if (readDescCnt < 0) | |
316 | goto exitPoint; | |
317 | if (argc > 2) { | |
318 | writeDescCnt = ParseSelectFileList (interp, argv [2], &writeFdSet, | |
319 | &writeDescList, &maxFileId); | |
320 | if (writeDescCnt < 0) | |
321 | goto exitPoint; | |
322 | } | |
323 | if (argc > 3) { | |
324 | exceptDescCnt = ParseSelectFileList (interp, argv [3], &exceptFdSet, | |
325 | &exceptDescList, &maxFileId); | |
326 | if (exceptDescCnt < 0) | |
327 | goto exitPoint; | |
328 | } | |
329 | ||
330 | /* | |
331 | * Get the time out. Zero is different that not specified. | |
332 | */ | |
333 | timeoutRecPtr = NULL; | |
334 | if ((argc > 4) && (argv [4][0] != '\0')) { | |
335 | double timeout, seconds, microseconds; | |
336 | ||
337 | if (Tcl_GetDouble (interp, argv [4], &timeout) != TCL_OK) | |
338 | goto exitPoint; | |
339 | if (timeout < 0) { | |
340 | Tcl_AppendResult (interp, "timeout must be greater than or equal", | |
341 | " to zero", (char *) NULL); | |
342 | goto exitPoint; | |
343 | } | |
344 | seconds = floor (timeout); | |
345 | microseconds = (timeout - seconds) * 1000000.0; | |
346 | timeoutRec.tv_sec = seconds; | |
347 | timeoutRec.tv_usec = microseconds; | |
348 | timeoutRecPtr = &timeoutRec; | |
349 | } | |
350 | ||
351 | /* | |
352 | * Check if any data is pending in the read stdio buffers. If there is, | |
353 | * then do the select, but don't block in it. | |
354 | */ | |
355 | ||
356 | pending = FindPendingData (readDescCnt, readDescList, &readFdSet2); | |
357 | if (pending) { | |
358 | timeoutRec.tv_sec = 0; | |
359 | timeoutRec.tv_usec = 0; | |
360 | timeoutRecPtr = &timeoutRec; | |
361 | } | |
362 | ||
363 | /* | |
364 | * All set, do the select. | |
365 | */ | |
366 | numSelected = select (maxFileId + 1, &readFdSet, &writeFdSet, &exceptFdSet, | |
367 | timeoutRecPtr); | |
368 | if (numSelected < 0) { | |
369 | interp->result = Tcl_UnixError (interp); | |
370 | goto exitPoint; | |
371 | } | |
372 | ||
373 | /* | |
374 | * Return the result, either a 3 element list, or leave the result | |
375 | * empty if the timeout occured. | |
376 | */ | |
377 | if (numSelected > 0) { | |
378 | retListArgv [0] = ReturnSelectedFileList (&readFdSet, | |
379 | &readFdSet2, | |
380 | readDescCnt, | |
381 | readDescList); | |
382 | retListArgv [1] = ReturnSelectedFileList (&writeFdSet, | |
383 | NULL, | |
384 | writeDescCnt, | |
385 | writeDescList); | |
386 | retListArgv [2] = ReturnSelectedFileList (&exceptFdSet, | |
387 | NULL, | |
388 | exceptDescCnt, | |
389 | exceptDescList); | |
390 | Tcl_SetResult (interp, Tcl_Merge (3, retListArgv), TCL_DYNAMIC); | |
391 | ckfree ((char *) retListArgv [0]); | |
392 | ckfree ((char *) retListArgv [1]); | |
393 | ckfree ((char *) retListArgv [2]); | |
394 | } | |
395 | ||
396 | result = TCL_OK; | |
397 | ||
398 | exitPoint: | |
399 | if (readDescList != NULL) | |
400 | ckfree ((char *) readDescList); | |
401 | if (writeDescList != NULL) | |
402 | ckfree ((char *) writeDescList); | |
403 | if (exceptDescList != NULL) | |
404 | ckfree ((char *) exceptDescList); | |
405 | return result; | |
406 | ||
407 | } | |
408 | #else | |
409 | /* | |
410 | *----------------------------------------------------------------------------- | |
411 | * | |
412 | * Tcl_SelectCmd -- | |
413 | * Dummy select command that returns an error for systems that don't | |
414 | * have select. | |
415 | *----------------------------------------------------------------------------- | |
416 | */ | |
417 | int | |
418 | Tcl_SelectCmd (clientData, interp, argc, argv) | |
419 | ClientData clientData; | |
420 | Tcl_Interp *interp; | |
421 | int argc; | |
422 | char **argv; | |
423 | { | |
424 | Tcl_AppendResult (interp, | |
425 | "select is not available on this version of Unix", | |
426 | (char *) NULL); | |
427 | return TCL_ERROR; | |
428 | } | |
429 | #endif |