home *** CD-ROM | disk | FTP | other *** search
/ Sprite 1984 - 1993 / Sprite 1984 - 1993.iso / src / benchmarks / pdevtest / server.c < prev    next >
Encoding:
C/C++ Source or Header  |  1989-10-24  |  17.4 KB  |  644 lines

  1. /* 
  2.  * server.c --
  3.  *
  4.  *    The server part of some multi-program synchronization primatives.
  5.  *    The routines here control N client programs.  This just means
  6.  *    telling them all to start, and hearing back from them when they're done.
  7.  *
  8.  * Copyright 1986 Regents of the University of California
  9.  * All rights reserved.
  10.  */
  11.  
  12. #ifndef lint
  13. static char rcsid[] = "$Header: /sprite/src/benchmarks/pdevtest/RCS/server.c,v 1.4 89/10/24 12:37:50 brent Exp $ SPRITE (Berkeley)";
  14. #endif not lint
  15.  
  16.  
  17. #include "sprite.h"
  18. #include "status.h"
  19. #include "errno.h"
  20. #include "fs.h"
  21. #include "dev/pdev.h"
  22. #include "stdio.h"
  23. #include "bit.h"
  24. #include "time.h"
  25. #include "sys.h"
  26. #include "sys/file.h"
  27.  
  28. char *pdev="./pdev";
  29.  
  30. extern Boolean writeBehind;
  31. extern int delay;
  32. extern int requestBufSize;
  33.  
  34. typedef int  (*IntProc)();
  35.  
  36. typedef struct ServerState {
  37.     int cntlStream;    /* Control stream to find out new clientStream's */
  38.     int numClients;
  39.     int *clientStream;    /* Array of client streams */
  40.     int maxStreamID;
  41.     Address *request;    /* Array of client request buffers */
  42.     char *clientState;    /* Array of client state words */
  43.     int *selectMaskPtr;
  44.     int selectMaskBytes;
  45.     IntProc opTable[7];    /* Operation switch table */
  46. } ServerState;
  47.  
  48. #define CLIENT_OPENED    0x1
  49. #define CLIENT_STARTED    0x2
  50. #define CLIENT_FINISHED    0x4
  51.  
  52. /*
  53.  * Need the select flag to know if we should block the client.
  54.  */
  55. extern Boolean selectP;
  56. Boolean blocked = FALSE;
  57.  
  58. /*
  59.  * Forward Declarations
  60.  */
  61. ReturnStatus NullProc();
  62. ReturnStatus ServeOpen();
  63. ReturnStatus ServeRead();
  64. ReturnStatus ServeWrite();
  65. ReturnStatus ServeIOControl();
  66. Pdev_Op ServeRequest();
  67.  
  68.  
  69. /*
  70.  *----------------------------------------------------------------------
  71.  *
  72.  * ServerSetup --
  73.  *
  74.  *    Establish contact with N clients.  A pseudo device is opened
  75.  *    and we are declared its "master", or "server".  After this
  76.  *    other processes can open the pseudo device and we'll get a private
  77.  *    stream back that we use for requests from that process.
  78.  *
  79.  * Results:
  80.  *    A pointer to state about the clients needed by ServerStart and
  81.  *    ServerWait.
  82.  *
  83.  * Side effects:
  84.  *    Opens the pseudo device as the server and waits for numClients
  85.  *    opens by client processes.
  86.  *    This exits (kills the process) upon error.
  87.  *
  88.  *----------------------------------------------------------------------
  89.  */
  90.  
  91. void
  92. ServerSetup(numClients, dataPtr)
  93.     int numClients;
  94.     ClientData *dataPtr;
  95. {
  96.     ServerState *statePtr;
  97.     int client;
  98.     int len;
  99.     int amountRead;
  100.     ReturnStatus status;
  101.     Pdev_Notify notify;
  102.     Pdev_SetBufArgs setBuf;
  103.     int streamID;
  104.     int maxStreamID;
  105.  
  106.     statePtr = (ServerState *)malloc(sizeof(ServerState));
  107.     statePtr->clientStream = (int *)malloc(numClients * sizeof(int));
  108.     statePtr->clientState = (char *)malloc(numClients);
  109.     statePtr->request = (Address *)malloc(numClients * sizeof(Address));
  110.     statePtr->numClients = numClients;
  111.  
  112.     statePtr->opTable[(int)PDEV_OPEN] = ServeOpen;
  113.     statePtr->opTable[(int)PDEV_CLOSE] = NullProc;
  114.     statePtr->opTable[(int)PDEV_READ] = ServeRead;
  115.     statePtr->opTable[(int)PDEV_WRITE] = ServeWrite;
  116.     statePtr->opTable[(int)PDEV_WRITE_ASYNC] = ServeWrite;
  117.     statePtr->opTable[(int)PDEV_IOCTL] = ServeIOControl;
  118.  
  119.     /*
  120.      * Open the pseudo device.
  121.      */
  122.     statePtr->cntlStream = open(pdev, O_RDONLY|O_CREAT|O_MASTER, 0666);
  123.     if (statePtr->cntlStream < 0) {
  124.     perror("Error opening pseudo device as master");
  125.     exit(errno);
  126.     }
  127.     maxStreamID = 0;
  128.     for (client=0 ; client<numClients ; client++) {
  129.     /*
  130.      * Read on our control stream (the one we just opened) messages
  131.      * that contain new streamIDs for the request-response streams
  132.      * back to the client process.
  133.      */
  134.     amountRead = read(statePtr->cntlStream, (Address)¬ify,
  135.                 sizeof(notify));
  136.     if (amountRead < 0) {
  137.         perror("Error reading control stream");
  138.         exit(status);
  139.     } else if (amountRead != sizeof(notify)) {
  140.         fprintf(stderr,
  141.         "Warning, short read (%d) on control stream\n", amountRead);
  142.     }
  143.     streamID = notify.newStreamID;
  144.     if (streamID > statePtr->maxStreamID) {
  145.         statePtr->maxStreamID = streamID;
  146.     }
  147.     /*
  148.      * Tell the kernel where the request buffer is.
  149.      */
  150.     requestBufSize += sizeof(Pdev_Request);
  151.     statePtr->request[client] = malloc(requestBufSize);
  152.     setBuf.requestBufAddr = statePtr->request[client];
  153.     setBuf.requestBufSize = requestBufSize;
  154.     setBuf.readBufAddr = (Address)NULL;
  155.     setBuf.readBufSize = 0;
  156.     Fs_IOControl(streamID, IOC_PDEV_SET_BUF,
  157.             sizeof(Pdev_SetBufArgs), (Address)&setBuf, 0, NULL);
  158.     /*
  159.      * Set(Unset) write-behind by the client.
  160.      */
  161.     Fs_IOControl(streamID, IOC_PDEV_WRITE_BEHIND,
  162.             sizeof(int), (Address)&writeBehind, 0, NULL);
  163.     statePtr->clientStream[client] = streamID;
  164.     statePtr->clientState[client] = CLIENT_OPENED;
  165.     fprintf(stderr, "Got client on stream %d\n",streamID);
  166.     ServeRequest(statePtr->clientStream[client],
  167.              statePtr->request[client],
  168.              statePtr->opTable);
  169.     }
  170.     /*
  171.      * Now that we know the largest stream ID used for a client stream
  172.      * we can allocate and initialize the select mask for the streams.
  173.      */
  174.     statePtr->selectMaskBytes = Bit_NumBytes(statePtr->maxStreamID);
  175.     statePtr->selectMaskPtr = (int *)malloc(statePtr->selectMaskBytes);
  176.     bzero((Address)statePtr->selectMaskPtr, statePtr->selectMaskBytes);
  177.     for (client=0 ; client < numClients ; client++) {
  178.     Bit_Set(statePtr->clientStream[client], statePtr->selectMaskPtr);
  179.     }
  180.     *dataPtr = (ClientData)statePtr;
  181. }
  182.  
  183. /*
  184.  *----------------------------------------------------------------------
  185.  *
  186.  * ServeRequest --
  187.  *
  188.  *    The top level service routine that reads client requests
  189.  *    and branches out to a handler for the request.  This takes
  190.  *    care of error conditions and allocating space for the
  191.  *    request and the reply parameters.
  192.  *
  193.  * Results:
  194.  *    None
  195.  *
  196.  * Side effects:
  197.  *    The server side of the pseudo-device protocol.
  198.  *
  199.  *----------------------------------------------------------------------
  200.  */
  201.  
  202. Pdev_Op
  203. ServeRequest(clientStreamID, myRequestBuf, opTable)
  204.     int clientStreamID;
  205.     Address myRequestBuf;
  206.     IntProc *opTable;
  207. {
  208.     static char *replyBuf = (char *)NULL;
  209.     static int replyBufSize = 0;
  210.     static Pdev_BufPtrs lastBufPtrs;
  211.     Pdev_BufPtrs bufPtrs;
  212.     Pdev_Reply reply;
  213.     register ReturnStatus status;
  214.     register Pdev_Request *requestPtr;
  215.     register Pdev_Op operation;
  216.     int numBytes;
  217.     register char *requestData;
  218.     register Address requestBuf;
  219.     register int i;
  220.  
  221.     /*
  222.      * Read the current pointers for the request buffer.
  223.      */
  224.  
  225.     numBytes = read(clientStreamID, (Address) &bufPtrs, sizeof(Pdev_BufPtrs));
  226.     if (numBytes < 0) {
  227.     } else if (numBytes != sizeof(Pdev_BufPtrs)) {
  228.     panic("ServeRequest: short read %d != sizeof(Pdev_BufPtrs)\n",numBytes);
  229.     }
  230.     if (bufPtrs.magic != PDEV_BUF_PTR_MAGIC) {
  231.     fprintf(stderr, "ServeRequest: bad ptr magic <%x>\n",
  232.         bufPtrs.magic);
  233.     fprintf(stderr, "\tprevPtrs <%d,%d> currentPtrs <%d,%d>\n",
  234.         lastBufPtrs.requestFirstByte, lastBufPtrs.requestLastByte,
  235.         bufPtrs.requestFirstByte, bufPtrs.requestLastByte);
  236.     panic("panic");
  237.     }
  238.     /*
  239.      * While there are still requests in the buffer, service them.
  240.      */
  241.     requestBuf = bufPtrs.requestAddr;
  242.     while (bufPtrs.requestFirstByte < bufPtrs.requestLastByte) {
  243.     requestPtr = (Pdev_Request *)&requestBuf[bufPtrs.requestFirstByte];
  244.     if (requestPtr->hdr.magic != PDEV_REQUEST_MAGIC) {
  245.         fprintf(stderr, "ServeRequest, bad request magic <%x>\n",
  246.                 requestPtr->hdr.magic);
  247.         fprintf(stderr, "\tprevPtrs <%d,%d> currentPtrs <%d,%d>\n",
  248.         lastBufPtrs.requestFirstByte, lastBufPtrs.requestLastByte,
  249.         bufPtrs.requestFirstByte, bufPtrs.requestLastByte);
  250.         panic("panic");
  251.     }
  252.     requestData = (Address)((int)requestPtr + sizeof(Pdev_Request));
  253.  
  254.     if (replyBuf == (char *)NULL) {
  255.         replyBuf = (char *)malloc(requestPtr->hdr.replySize);
  256.         replyBufSize = requestPtr->hdr.replySize;
  257.     } else if (replyBufSize < requestPtr->hdr.replySize) {
  258.         free((char *)replyBuf);
  259.         replyBuf = (char *)malloc(requestPtr->hdr.replySize);
  260.         replyBufSize = requestPtr->hdr.replySize;
  261.     }
  262.     /*
  263.      * Switch out the to the handler for the pdev operation.
  264.      */
  265.     operation = requestPtr->hdr.operation;
  266.     status = (*opTable[(int)operation])(clientStreamID,
  267.         requestPtr, requestData, replyBuf, &reply.selectBits);
  268.  
  269.     if (delay > 0) {
  270.         for (i=delay<<1 ; i>0 ; i--) ;
  271.     }
  272.  
  273.     if (operation != PDEV_WRITE_ASYNC) {
  274.         /*
  275.          * Set up the reply and tell the kernel about it.
  276.          */
  277.     
  278.         reply.magic = PDEV_REPLY_MAGIC;
  279.         reply.status = SUCCESS;
  280.         reply.replySize = requestPtr->hdr.replySize;
  281.         reply.replyBuf = replyBuf;
  282.         reply.signal = 0;
  283.         reply.code = 0;
  284.         status = Fs_IOControl(clientStreamID, IOC_PDEV_REPLY,
  285.                     sizeof(Pdev_Reply),
  286.                     (Address) &reply, 0, NULL);
  287.         if (status != SUCCESS) {
  288.         panic("%s; status \"%s\"",
  289.             "ServeRequest couldn't send reply",
  290.             Stat_GetMsg(status));
  291.         }
  292.     }
  293.     bufPtrs.requestFirstByte += requestPtr->hdr.messageSize;
  294.     }
  295.     Fs_IOControl(clientStreamID, IOC_PDEV_SET_PTRS,
  296.             sizeof(Pdev_BufPtrs), (Address)&bufPtrs,
  297.             0, NULL);
  298.     return(operation);
  299. }
  300.  
  301. /*
  302.  *----------------------------------------------------------------------
  303.  *
  304.  * Serve --
  305.  *
  306.  *    Listen for requests from client's, returning after all clients
  307.  *    have closed their streams.
  308.  *
  309.  * Results:
  310.  *    None
  311.  *
  312.  * Side effects:
  313.  *    Handle all requests by clients.
  314.  *
  315.  *----------------------------------------------------------------------
  316.  */
  317.  
  318. void
  319. Serve(data)
  320.     ClientData data;
  321. {
  322.     ServerState *statePtr;
  323.     int client;
  324.     ReturnStatus status;
  325.     int *selectMaskPtr;
  326.     int numFinishedClients;
  327.     int numReady;
  328.     Pdev_Op operation;
  329.  
  330.     statePtr = (ServerState *)data;
  331.     selectMaskPtr = (int *)malloc(statePtr->selectMaskBytes);
  332.     numFinishedClients = 0;
  333.     do {
  334.     bcopy((Address)statePtr->selectMaskPtr, (Address)selectMaskPtr,
  335.         statePtr->selectMaskBytes);
  336.     status = Fs_Select(statePtr->maxStreamID, NULL, selectMaskPtr,
  337.                 NULL, NULL, &numReady);
  338.     for (client=0 ; client < statePtr->numClients ; client++) {
  339.         /*
  340.          * Look for the each client's bit in the select mask and read the
  341.          * corresponding stream for its initial request.
  342.          */
  343.         if (Bit_IsSet(statePtr->clientStream[client], selectMaskPtr)) {
  344.         /*
  345.          * Handle the client's request.  If it's a close
  346.          * then clear the client's bit from the select mask so
  347.          * don't bother checking it again.
  348.          */
  349.         operation = ServeRequest(statePtr->clientStream[client],
  350.                  statePtr->request[client],
  351.                  statePtr->opTable);
  352.         if (operation == PDEV_CLOSE ||
  353.             operation == (Pdev_Op)-1) {
  354.             fprintf(stderr, "Client %d %s...", client,
  355.             (operation == PDEV_CLOSE) ? "closed" : "error" );
  356.             numFinishedClients++;
  357.             statePtr->clientState[client] |= CLIENT_FINISHED;
  358.             Bit_Clear(statePtr->clientStream[client],
  359.                 statePtr->selectMaskPtr);
  360.         } else if ((operation == PDEV_READ) && selectP) {
  361.             /*
  362.              * If the select flag is set, then we must
  363.              * remember to simulate input for the client
  364.              * every so often.  This tests regular blocking
  365.              * reads, and selects by the client.  This goes
  366.              * with the fact that we only return FS_WRITABLE
  367.              * if the select flag is set.
  368.              */
  369.             Time time;
  370.             int selectBits;
  371.             time.seconds = 0;
  372.             time.microseconds = 400;
  373.             Sync_WaitTime(time);
  374.             printf("Waking up client\n");
  375.             selectBits = FS_READABLE|FS_WRITABLE;
  376.             Fs_IOControl(statePtr->clientStream[client],
  377.                 IOC_PDEV_READY, sizeof(int), &selectBits,
  378.                 0, NULL);
  379.         }
  380.         }
  381.     }
  382.     } while (numFinishedClients < statePtr->numClients);
  383.     fprintf(stderr, "\n");
  384. }
  385.  
  386. /*
  387.  *----------------------------------------------------------------------
  388.  *
  389.  * ServeOne --
  390.  *
  391.  *    A service loop for one client.  More bare-bones test used
  392.  *    for timing.
  393.  *
  394.  * Results:
  395.  *    None
  396.  *
  397.  * Side effects:
  398.  *    Handle all requests one client.
  399.  *
  400.  *----------------------------------------------------------------------
  401.  */
  402.  
  403. void
  404. ServeOne(data)
  405.     ClientData data;
  406. {
  407.     register ServerState *statePtr;
  408.     register int client;
  409.     ReturnStatus status;
  410.     int *selectMaskPtr;
  411.     int numFinishedClients;
  412.     int numReady;
  413.     Pdev_Op operation;
  414.  
  415.     statePtr = (ServerState *)data;
  416.     client = 0;
  417.     do {
  418.     operation = ServeRequest(statePtr->clientStream[client],
  419.              statePtr->request[client],
  420.              statePtr->opTable);
  421.     if (operation == PDEV_CLOSE ||
  422.         operation == (Pdev_Op)-1) {
  423.         fprintf(stderr, "Client %d %s...", client,
  424.         (operation == PDEV_CLOSE) ? "closed" : "error" );
  425.         break;
  426.     } else if ((operation == PDEV_READ) && selectP) {
  427.         /*
  428.          * If the select flag is set, then we must
  429.          * remember to simulate input for the client
  430.          * every so often.  This tests regular blocking
  431.          * reads, and selects by the client.  This goes
  432.          * with the fact that we only return FS_WRITABLE
  433.          * if the select flag is set.
  434.          */
  435.         Time time;
  436.         int selectBits;
  437.         time.seconds = 0;
  438.         time.microseconds = 400;
  439.         Sync_WaitTime(time);
  440.         printf("Waking up client\n");
  441.         selectBits = FS_READABLE|FS_WRITABLE;
  442.         Fs_IOControl(statePtr->clientStream[client],
  443.             IOC_PDEV_READY, sizeof(int), &selectBits,
  444.             0, NULL);
  445.     }
  446.     } while (1);
  447.     fprintf(stderr, "\n");
  448. }
  449.  
  450. /*
  451.  *----------------------------------------------------------------------
  452.  *
  453.  * NullProc --
  454.  *
  455.  *    The do-nothing service procedure.
  456.  *
  457.  * Results:
  458.  *    SUCCESS
  459.  *
  460.  * Side effects:
  461.  *    Zeroes out the reply buffer.
  462.  *
  463.  *----------------------------------------------------------------------
  464.  */
  465.  
  466. ReturnStatus
  467. NullProc(streamID, requestPtr, requestBuf, replyBuf, selectBitsPtr)
  468.     int streamID;
  469.     Pdev_Request *requestPtr;
  470.     Address requestBuf;
  471.     Address replyBuf;
  472.     int *selectBitsPtr;
  473. {
  474.     if (requestPtr->hdr.replySize > 0) {
  475.     bzero(replyBuf, requestPtr->hdr.replySize);
  476.     }
  477.     return(SUCCESS);
  478. }
  479.  
  480. /*
  481.  *----------------------------------------------------------------------
  482.  *
  483.  * ServeOpen --
  484.  *
  485.  *    React to an Open request.  This initializes the
  486.  *    select state to both readable and writable.
  487.  *
  488.  * Results:
  489.  *    SUCCESS
  490.  *
  491.  * Side effects:
  492.  *    Print statement.
  493.  *
  494.  *----------------------------------------------------------------------
  495.  */
  496.  
  497. ReturnStatus
  498. ServeOpen(streamID, requestPtr, requestBuf, replyBuf, selectBitsPtr)
  499.     int streamID;
  500.     Pdev_Request *requestPtr;
  501.     Address requestBuf;
  502.     Address replyBuf;
  503.     int *selectBitsPtr;
  504. {
  505.     fprintf(stderr, "Open request, streamID %d\n", streamID);
  506.     *selectBitsPtr = FS_READABLE | FS_WRITABLE;
  507.     return(SUCCESS);
  508. }
  509.  
  510. /*
  511.  *----------------------------------------------------------------------
  512.  *
  513.  * ServeRead --
  514.  *
  515.  *    Return data for a read request.  This plays a game with the
  516.  *    client if the select flag (-s) is set:  every other read
  517.  *    gets blocked in order to test IOC_PDEV_READY.
  518.  *
  519.  * Results:
  520.  *    SUCCESS
  521.  *
  522.  * Side effects:
  523.  *    Zeroes out the reply buffer.
  524.  *
  525.  *----------------------------------------------------------------------
  526.  */
  527.  
  528. ReturnStatus
  529. ServeRead(streamID, requestPtr, requestBuf, replyBuf, selectBitsPtr)
  530.     int streamID;
  531.     Pdev_Request *requestPtr;
  532.     Address requestBuf;
  533.     Address replyBuf;
  534.     int *selectBitsPtr;
  535. {
  536.     if (selectP && !blocked) {
  537.     blocked = TRUE;
  538.     *selectBitsPtr = FS_WRITABLE;
  539.     return(FS_WOULD_BLOCK);
  540.     } else {
  541.     if (requestPtr->hdr.replySize > 0) {
  542.         bzero(replyBuf, requestPtr->hdr.replySize);
  543.         replyBuf[0] = 'z';
  544.     }
  545.     blocked = FALSE;
  546.     if (! selectP) {
  547.         *selectBitsPtr = FS_WRITABLE | FS_READABLE;
  548.     } else {
  549.         *selectBitsPtr = FS_WRITABLE;
  550.     }
  551.     return(SUCCESS);
  552.     }
  553. }
  554.  
  555. /*
  556.  *----------------------------------------------------------------------
  557.  *
  558.  * ServeWrite --
  559.  *
  560.  *    Handle a write request.
  561.  *
  562.  * Results:
  563.  *    SUCCESS
  564.  *
  565.  * Side effects:
  566.  *    Sets up the select bits.
  567.  *
  568.  *----------------------------------------------------------------------
  569.  */
  570.  
  571. ReturnStatus
  572. ServeWrite(streamID, requestPtr, requestBuf, replyBuf, selectBitsPtr)
  573.     int streamID;
  574.     Pdev_Request *requestPtr;
  575.     Address requestBuf;
  576.     Address replyBuf;
  577.     int *selectBitsPtr;
  578. {
  579.     *selectBitsPtr = FS_WRITABLE;
  580.     if (! selectP) {
  581.     *selectBitsPtr |= FS_READABLE;
  582.     }
  583.     requestPtr->hdr.replySize = sizeof(int);
  584.     *(int *)replyBuf = requestPtr->hdr.requestSize;    /* amountWritten */
  585.     return(SUCCESS);
  586. }
  587.  
  588. /*
  589.  *----------------------------------------------------------------------
  590.  *
  591.  * ServeIOControl --
  592.  *
  593.  *    Handle an IOControl.  This acts like an echo now.
  594.  *
  595.  * Results:
  596.  *    SUCCESS
  597.  *
  598.  * Side effects:
  599.  *    Copies the request buffer to the reply buffer.
  600.  *
  601.  *----------------------------------------------------------------------
  602.  */
  603.  
  604. ReturnStatus
  605. ServeIOControl(streamID, requestPtr, requestBuf, replyBuf, selectBitsPtr)
  606.     int streamID;
  607.     Pdev_Request *requestPtr;
  608.     Address requestBuf;
  609.     Address replyBuf;
  610.     int *selectBitsPtr;
  611. {
  612. #ifdef notdef
  613.     if (requestPtr->hdr.replySize <= requestPtr->requestSize) {
  614.     bcopy(requestBuf, replyBuf, requestPtr->hdr.replySize);
  615.     } else {
  616.     bcopy(requestBuf, replyBuf, requestPtr->requestSize);
  617.     bzero(replyBuf[requestPtr->requestSize],
  618.         requestPtr->hdr.replySize - requestPtr->requestSize);
  619.     }
  620. #endif
  621.     switch (requestPtr->param.ioctl.command) {
  622.     case IOC_PDEV_SET_BUF: {
  623.         /*
  624.          * Let the client trigger our test of the mid-flight
  625.          * setbuf call.
  626.          */
  627.         Pdev_SetBufArgs setBuf;
  628.  
  629.         setBuf.requestBufAddr = malloc(requestBufSize);
  630.         setBuf.requestBufSize = requestBufSize;
  631.         setBuf.readBufAddr = (Address)NULL;
  632.         setBuf.readBufSize = 0;
  633.         Fs_IOControl(streamID, IOC_PDEV_SET_BUF,
  634.                 sizeof(Pdev_SetBufArgs), (Address)&setBuf, 0, NULL);
  635.     
  636.     }
  637.     }
  638.     *selectBitsPtr = FS_WRITABLE;
  639.     if (! selectP) {
  640.     *selectBitsPtr |= FS_READABLE;
  641.     }
  642.     return(SUCCESS);
  643. }
  644.