home *** CD-ROM | disk | FTP | other *** search
/ Serving the Web / ServingTheWeb1995.disc1of1.iso / linux / slacksrce / tcl / tcl+tk+t / tclx7.3bl / tclx7 / tclX7.3b / src / tclXselect.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-07-16  |  14.3 KB  |  449 lines

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