home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Source Code 1993 July / THE_SOURCE_CODE_CD_ROM.iso / languages / tcl / tclX6.5c / src / tclXselect.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-12-19  |  13.5 KB  |  422 lines

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