1/* 2 * tclXselect.c 3 * 4 * Select command. This is the generic code associated with the select system 5 * call. It relies on the Unix style select, which operates on bit sets of 6 * file numbers. Platform specific code is called to translate channels into 7 * file numbers, but all operations are generic. On Win32, this only works 8 * on sockets. Ideally, it would push more code into the platform specific 9 * modules and work on more file types. However, right now, I don't see a 10 * good way to do this on Win32. 11 *----------------------------------------------------------------------------- 12 * Copyright 1991-1999 Karl Lehenbauer and Mark Diekhans. 13 * 14 * Permission to use, copy, modify, and distribute this software and its 15 * documentation for any purpose and without fee is hereby granted, provided 16 * that the above copyright notice appear in all copies. Karl Lehenbauer and 17 * Mark Diekhans make no representations about the suitability of this 18 * software for any purpose. It is provided "as is" without express or 19 * implied warranty. 20 *----------------------------------------------------------------------------- 21 * $Id: tclXselect.c,v 1.7 2005/07/27 22:31:15 hobbs Exp $ 22 *----------------------------------------------------------------------------- 23 */ 24 25#ifndef NO_SELECT 26 27#include "tclExtdInt.h" 28 29/* 30 * A few systems (A/UX 2.0) have select but no macros, define em in this case. 31 */ 32#ifndef FD_SET 33# define FD_SET(fd,fdset) (fdset)->fds_bits[0] |= (1<<(fd)) 34# define FD_CLR(fd,fdset) (fdset)->fds_bits[0] &= ~(1<<(fd)) 35# define FD_ZERO(fdset) (fdset)->fds_bits[0] = 0 36# define FD_ISSET(fd,fdset) (((fdset)->fds_bits[0]) & (1<<(fd))) 37#endif 38 39/* 40 * Data kept about a file channel. 41 */ 42typedef struct { 43 Tcl_Obj *channelIdObj; 44 Tcl_Channel channel; 45#ifdef WIN32 46 /* 47 * XXX Not strictly correct, according to TclX's usage of fds, but we 48 * XXX expect noone to really being using select hardcore on Windows 49 */ 50 unsigned int readFd; 51 unsigned int writeFd; 52#else 53 int readFd; 54 int writeFd; 55#endif 56} channelData_t; 57 58/* 59 * Prototypes of internal functions. 60 */ 61static int 62ParseSelectFileList _ANSI_ARGS_((Tcl_Interp *interp, 63 int chanAccess, 64 Tcl_Obj *handleList, 65 fd_set *fileSetPtr, 66 channelData_t **channelListPtr, 67 int *maxFileIdPtr)); 68 69static int 70FindPendingData _ANSI_ARGS_((int fileDescCnt, 71 channelData_t *channelList, 72 fd_set *fileDescSetPtr)); 73 74static Tcl_Obj * 75ReturnSelectedFileList _ANSI_ARGS_((fd_set *fileDescSetPtr, 76 int fileDescCnt, 77 channelData_t *channelListPtr)); 78 79static int 80TclX_SelectObjCmd _ANSI_ARGS_((ClientData clientData, 81 Tcl_Interp *interp, 82 int objc, 83 Tcl_Obj *CONST objv[])); 84 85 86/*----------------------------------------------------------------------------- 87 * ParseSelectFileList -- 88 * 89 * Parse a list of file handles for select. 90 * 91 * Parameters: 92 * o interp - Error messages are returned in the result. 93 * o chanAccess - TCL_READABLE for read direction, TCL_WRITABLE for write 94 * direction or both for both files. 95 * o handleList (I) - The list of file handles to parse, may be empty. 96 * o fileSetPtr - The select fd_set for the parsed handles is 97 * filled in. 98 * o channelListPtr - A pointer to a dynamically allocated list of 99 * the channels that are in the set. If the list is empty, NULL is 100 * returned. 101 * o maxFileIdPtr (I/O) - If a file id greater than the current value is 102 * encountered, it will be set to that file id. 103 * Returns: 104 * The number of files in the list, or -1 if an error occured. 105 * FIX: Should really pass in access and only get channels that are 106 * requested. 107 *----------------------------------------------------------------------------- 108 */ 109static int 110ParseSelectFileList (interp, chanAccess, handleList, fileSetPtr, 111 channelListPtr, maxFileIdPtr) 112 Tcl_Interp *interp; 113 int chanAccess; 114 Tcl_Obj *handleList; 115 fd_set *fileSetPtr; 116 channelData_t **channelListPtr; 117 int *maxFileIdPtr; 118{ 119 int handleCnt, idx; 120 Tcl_Obj **handleObjv; 121 channelData_t *channelList; 122 123 /* 124 * Optimize empty list handling. 125 */ 126 if (TclX_IsNullObj (handleList)) { 127 *channelListPtr = NULL; 128 return 0; 129 } 130 131 if (Tcl_ListObjGetElements (interp, handleList, &handleCnt, 132 &handleObjv) != TCL_OK) { 133 return -1; 134 } 135 136 /* 137 * Handle case of an empty list. 138 */ 139 if (handleCnt == 0) { 140 *channelListPtr = NULL; 141 return 0; 142 } 143 144 channelList = 145 (channelData_t*) ckalloc (sizeof (channelData_t) * handleCnt); 146 147 for (idx = 0; idx < handleCnt; idx++) { 148 channelList [idx].channelIdObj = handleObjv [idx]; 149 channelList [idx].channel = 150 TclX_GetOpenChannelObj (interp, 151 handleObjv [idx], 152 chanAccess); 153 if (channelList [idx].channel == NULL) 154 goto errorExit; 155 156 if (chanAccess & TCL_READABLE) { 157 if (TclXOSGetSelectFnum (interp, channelList [idx].channel, 158 TCL_READABLE, 159 &channelList [idx].readFd) != TCL_OK) 160 goto errorExit; 161 FD_SET (channelList [idx].readFd, fileSetPtr); 162 if ((int)channelList [idx].readFd > *maxFileIdPtr) 163 *maxFileIdPtr = (int)channelList [idx].readFd; 164 } else { 165 channelList [idx].readFd = -1; 166 } 167 168 if (chanAccess & TCL_WRITABLE) { 169 if (TclXOSGetSelectFnum (interp, channelList [idx].channel, 170 TCL_WRITABLE, 171 &channelList [idx].writeFd) != TCL_OK) 172 goto errorExit; 173 FD_SET (channelList [idx].writeFd, fileSetPtr); 174 if ((int)channelList [idx].writeFd > *maxFileIdPtr) 175 *maxFileIdPtr = (int)channelList [idx].writeFd; 176 } else { 177 channelList [idx].writeFd = -1; 178 } 179 } 180 181 *channelListPtr = channelList; 182 return handleCnt; 183 184 errorExit: 185 ckfree ((char *) channelList); 186 return -1; 187 188} 189 190/*----------------------------------------------------------------------------- 191 * FindPendingData -- 192 * 193 * Scan a list of read files to determine if any of them have data pending 194 * in their buffers. 195 * 196 * Parameters: 197 * o fileDescCnt (I) - Number of descriptors in the list. 198 * o channelListPtr (I) - A pointer to a list of the channel data for 199 * the channels to check. 200 * o fileDescSetPtr (I) - A select fd_set with will have a bit set for 201 * every file that has data pending it its buffer. 202 * Returns: 203 * TRUE if any where found that had pending data, FALSE if none were found. 204 *----------------------------------------------------------------------------- 205 */ 206static int 207FindPendingData (fileDescCnt, channelList, fileDescSetPtr) 208 int fileDescCnt; 209 channelData_t *channelList; 210 fd_set *fileDescSetPtr; 211{ 212 int idx, found = FALSE; 213 214 FD_ZERO (fileDescSetPtr); 215 216 for (idx = 0; idx < fileDescCnt; idx++) { 217 if (Tcl_InputBuffered (channelList [idx].channel)) { 218 FD_SET (channelList [idx].readFd, fileDescSetPtr); 219 found = TRUE; 220 } 221 } 222 return found; 223} 224 225/*----------------------------------------------------------------------------- 226 * ReturnSelectedFileList -- 227 * 228 * Take the resulting file descriptor sets from a select, and the 229 * list of file descritpors and build up a list of Tcl file handles. 230 * 231 * Parameters: 232 * o fileDescSetPtr (I) - The select fd_set. 233 * o fileDescCnt (I) - Number of descriptors in the list. 234 * o channelListPtr (I) - A pointer to a list of the FILE pointers for 235 * files that are in the set. 236 * Returns: 237 * List of file handles. 238 *----------------------------------------------------------------------------- 239 */ 240static Tcl_Obj * 241ReturnSelectedFileList (fileDescSetPtr, fileDescCnt, channelList) 242 fd_set *fileDescSetPtr; 243 int fileDescCnt; 244 channelData_t *channelList; 245{ 246 int idx, handleCnt; 247 Tcl_Obj *fileHandleList = Tcl_NewListObj (0, NULL); 248 249 handleCnt = 0; 250 for (idx = 0; idx < fileDescCnt; idx++) { 251 if (((channelList [idx].readFd >= 0) && 252 FD_ISSET (channelList [idx].readFd, fileDescSetPtr)) || 253 ((channelList [idx].writeFd >= 0) && 254 FD_ISSET (channelList [idx].writeFd, fileDescSetPtr))) { 255 Tcl_ListObjAppendElement (NULL, fileHandleList, 256 channelList [idx].channelIdObj); 257 handleCnt++; 258 } 259 } 260 261 return fileHandleList; 262} 263 264/*----------------------------------------------------------------------------- 265 * TclX_SelectObjCmd -- 266 * Implements the select TCL command: 267 * select readhandles ?writehandles? ?excepthandles? ?timeout? 268 * 269 * This command is extra smart in the fact that it checks for read data 270 * pending in the stdio buffer first before doing a select. 271 * 272 * Results: 273 * A list in the form: 274 * {readhandles writehandles excepthandles} 275 * or {} it the timeout expired. 276 *----------------------------------------------------------------------------- 277 */ 278static int 279TclX_SelectObjCmd (clientData, interp, objc, objv) 280 ClientData clientData; 281 Tcl_Interp *interp; 282 int objc; 283 Tcl_Obj *CONST objv[]; 284{ 285 static int chanAccess [] = {TCL_READABLE, TCL_WRITABLE, 0}; 286 int idx; 287 fd_set fdSets [3], readPendingFDSet; 288 int descCnts [3]; 289 channelData_t *descLists [3]; 290 Tcl_Obj *handleSetList [3]; 291 int numSelected, maxFileId = 0, pending; 292 int result = TCL_ERROR; 293 struct timeval timeoutRec; 294 struct timeval *timeoutRecPtr; 295 296 if (objc < 2) { 297 return TclX_WrongArgs (interp, objv [0], 298 " readFileIds ?writeFileIds? ?exceptFileIds? ?timeout?"); 299 } 300 301 /* 302 * Initialize. 0 == read, 1 == write and 2 == exception. 303 */ 304 for (idx = 0; idx < 3; idx++) { 305 FD_ZERO (&fdSets [idx]); 306 descCnts [idx] = 0; 307 descLists [idx] = NULL; 308 } 309 310 /* 311 * Parse the file handles and set everything up for the select call. 312 */ 313 for (idx = 0; (idx < 3) && (idx < objc - 1); idx++) { 314 descCnts [idx] = ParseSelectFileList (interp, 315 chanAccess [idx], 316 objv [idx + 1], 317 &fdSets [idx], 318 &descLists [idx], 319 &maxFileId); 320 if (descCnts [idx] < 0) 321 goto exitPoint; 322 } 323 324 /* 325 * Get the time out. Zero is different that not specified. 326 */ 327 timeoutRecPtr = NULL; 328 if ((objc > 4) && !TclX_IsNullObj (objv [4])) { 329 double timeout, seconds, microseconds; 330 331 if (Tcl_GetDoubleFromObj (interp, objv [4], &timeout) != TCL_OK) 332 goto exitPoint; 333 if (timeout < 0.0) { 334 TclX_AppendObjResult (interp, "timeout must be greater than ", 335 "or equal to zero", (char *) NULL); 336 goto exitPoint; 337 } 338 seconds = floor (timeout); 339 microseconds = (timeout - seconds) * 1000000.0; 340 timeoutRec.tv_sec = (long) seconds; 341 timeoutRec.tv_usec = (long) microseconds; 342 timeoutRecPtr = &timeoutRec; 343 } 344 345 /* 346 * Check if any data is pending in the read buffers. If there is, 347 * then do the select, but don't block in it. 348 */ 349 pending = FindPendingData (descCnts [0], descLists [0], &readPendingFDSet); 350 if (pending) { 351 timeoutRec.tv_sec = 0; 352 timeoutRec.tv_usec = 0; 353 timeoutRecPtr = &timeoutRec; 354 } 355 356 /* 357 * All set, do the select. 358 */ 359 numSelected = select (maxFileId + 1, 360 &fdSets [0], &fdSets [1], &fdSets [2], 361 timeoutRecPtr); 362 if (numSelected < 0) { 363 TclX_AppendObjResult (interp, "select error: ", 364 Tcl_PosixError (interp), (char *) NULL); 365 goto exitPoint; 366 } 367 368 /* 369 * If there is read data pending in the buffers, force the bits to be set 370 * in the read fdSet. 371 */ 372 if (pending) { 373 for (idx = 0; idx < descCnts [0]; idx++) { 374 if (FD_ISSET (descLists [0] [idx].readFd, &readPendingFDSet)) 375 FD_SET (descLists [0] [idx].readFd, &(fdSets [0])); 376 } 377 } 378 379 /* 380 * Return the result, either a 3 element list, or leave the result 381 * empty if the timeout occured. 382 */ 383 if (numSelected > 0 || pending) { 384 for (idx = 0; idx < 3; idx++) { 385 handleSetList [idx] = 386 ReturnSelectedFileList (&fdSets [idx], 387 descCnts [idx], 388 descLists [idx]); 389 } 390 Tcl_SetObjResult (interp, Tcl_NewListObj (3, handleSetList)); 391 } 392 393 result = TCL_OK; 394 395 exitPoint: 396 for (idx = 0; idx < 3; idx++) { 397 if (descLists [idx] != NULL) 398 ckfree ((char *) descLists [idx]); 399 } 400 return result; 401} 402#else /* NO_SELECT */ 403/*----------------------------------------------------------------------------- 404 * TclX_SelectCmd -- 405 * Dummy select command that returns an error for systems that don't 406 * have select. 407 *----------------------------------------------------------------------------- 408 */ 409static int 410TclX_SelectObjCmd (clientData, interp, objc, objv) 411 ClientData clientData; 412 Tcl_Interp *interp; 413 int objc; 414 Tcl_Obj *CONST objv[]; 415{ 416 Tcl_AppendResult(interp, Tcl_GetString(objv[0]), 417 " is not available on this OS", (char *) NULL); 418 return TCL_ERROR; 419} 420#endif /* NO_SELECT */ 421 422 423/*----------------------------------------------------------------------------- 424 * TclX_SelectInit -- 425 * Initialize the select command. 426 *----------------------------------------------------------------------------- 427 */ 428void 429TclX_SelectInit (interp) 430 Tcl_Interp *interp; 431{ 432 Tcl_CreateObjCommand (interp, 433 "select", 434 TclX_SelectObjCmd, 435 (ClientData) NULL, 436 (Tcl_CmdDeleteProc*) NULL); 437} 438 439