/*
 *  IBM/Microsoft Confidential
 *
 *  Copyright (c) IBM Corporation 1987, 1991
 *  Copyright (c) Microsoft Corporation 1987, 1989
 *
 *  All Rights Reserved
 *
 */

/*
 * FS_FSCTL.C
 *
 * DESCRIPTION
 *
 *    Allow an extended standard inteface between applications and
 *    FSDs.
 *
 *    In addition to the standard functions, this FSCTL also provides
 *    internal functions, including diagnostics.
 *
 *
 * FUNCTIONS DEFINED
 *
 *    FS_FSCTL() - Extended standard API to FSDs
 *
 *
 *  REVISION HISTORY (in reverse chronological order)
 *  --------------------------------------------------------------------------
 *  3/08/96 Steven Pratt   added support for query daemon function and     
 *          start and stop lazy reader.                                    
 *
 *  4/16/93 Frank Schroeder  Fixed bug where ptr returned from GetStringBuf
 *          was not pointing to the start of the string buffer when issuing
 *          FREE_STRING_BUF causing Trap D.  Free list pointer became corrupted.
 *
 *  3/24/93 Frank Schroeder  Added function 0x8F08 to determine if the CD-ROM
 *          media has changed (faster than open/close on each MSCDEX api).
 *
 *  9/26/92 Frank Schroeder  Added MSCDEX support. VCDROM calls VDHFSCTL for
 *          these functions.
 *
 *  8/21/91 Paul Thayer  Added function 0x8F06 to get the LSN of the first
 *          data sector in a file.  Useful for CDROM XA file streaming.
 *
 *  10/1/90 Brian Andrew  Grab semaphores before attaching to end of
 *          buffer lists when clearing cache.
 *
 *  3/19/90 Brian Andrew  Added function 0x8F04 to clear the cache.
 *
 *  9/14/89 Brian Andrew  Added code to check for initialization
 *
 *  8/23/89 Paul Bingman  First working version
 *
 *  8/4/89  Brian Andrew  Initial Blank Template
 *
 * TO DO
 * --------------------------------------------------------------------------
 * Do we want to add the capability to print the unknown message number
 *   into the text of the message?  If so, each thread will need its own
 *   copy of the message.
 */

#define INCL_DOSERRORS
#define INCL_DOSPROCESS
#define INCL_NOPMAPI
#include <os2.h>
#include <fsd.h>
#include <fsh.h>

#include <mscdex.h>
#include "cdfs.h"
#include "sem.h"
#include "path.h"
#include "vol.h"
#include "file.h"
#include "buffer.h"
#include "mem.h"
#include "heap.h"
#include "message.h"
#include "ea.h"

#ifdef STATS_H
#include "stats.h"
#endif

#include "fsdata.h"
#include "pr_memin.h"
#include "pr_dbcs.h"
#include "pr_cache.h"
#include "memory.h"
#include "readahd.h"

#ifdef DEBUG
#include "pr_DEBUG.h"
#endif


#define FSCTL_START_READAHEAD        0x8000
#define FSCTL_STOP_READAHEAD         0x8001

void i2a(char *, USHORT);
USHORT a2i(PUCHAR);
int pascal mscdex (union argdat far *, USHORT, USHORT, PCHAR,
                   USHORT, PUSHORT, PCHAR, USHORT, PUSHORT);


/***  FS_FSCTL() - Extended Standard API <-> FSD Interface
 *
 * DESCRIPTION
 *
 *  The two standard functions supported here are:
 *    1:  get the text of a new error code
 *    2:  get the limits of Extended Attribute sizes
 *
 *  In addition, functions are provided to start, stop,
 *  and get the statistics of the CDROM read-ahead daemon.
 *
 *  Also, some special debug functions are here.
 *
 *      Additional functions
 *
 *    0x8000 :    Start Lazy Reader (currently unsupported)
 *    0x8001 :    Stop Lazy Reader (currently unsupported)
 *    0x8002 :    Get Lazy Reader Stats (currently unsupported)
 *    0x8F00 :    FSCTL_STATS_GET (in hdrs\stats.h), returns
 *                 the FSD statistics for stat version.
 *    0x8F01 :    FSCTL_STATS_CLR (in hdrs\stats.h), clears all
 *                 the FSD statistics for the stat version.
 *    0x8F02 :    Disable the mount operation.
 *    0x8F03 :    Re-enable the mount operation.
 *    0x8F04 :    Clear the cache.
 *    0x8F05 :    Display the sector cache.
 *    0x8F06 :    Get the first data logical sector number (LSN) of a file
 *    0x8F07 :    MSCDEX support (See MSCDEX() header).
 *    0x8F08 :    Query for media change (Used by vcdrom.sys).
 *
 *  ENTRY
 *  pArgDat       = Pointer to argument:
 *            Arg == 1: ptr to sffsi, ptr to sffsd
 *            Arg == 2: ptr to cdfsi, ptr to cdfsd, ptr to path
 *            Arg == 3: NULL
 *
 *  usArgType     = Argument type:
 *              1 = called with file handle
 *              2 = called with pathname
 *              3 = called with FSD name
 *
 *  usFunc        = Requested function
 *
 *  pcParm        = Address of application input area.  Addressing
 *                  has NOT been validated by the kernel.
 *
 *  usLenParm     = Byte length of the pcParm area (pusLenParmOut may
 *                  be less than this).
 *
 *  pusLenParmOut = On input, length in bytes of parameters passed
 *                  in pcParm. On output, length of bytes of data
 *                  returned by the FSD in pcParm.
 *
 *  pcData        = Address of application output area.  Addressing
 *                  has NOT been validated by the kernel.
 *
 *  usLenData     = Byte length of the pcData area (pusLenDataOut may
 *                  be less than this).
 *
 *  pusLenDataOut = On input, length in bytes of data passed in pcData.
 *                  On output, length of bytes of data returned by the
 *                  FSD in pcData.
 *
 *
 * LOCAL
 *
 *  pbBuf       = Pointer to next buf structure to remove from
 *      cache.
 *
 *  pbPrevBuf     = Temp pointer when clearing cache.
 *
 *
 * EXIT
 *  NO_ERROR if requested function was performed.
 *  ERROR_BUFFER_OVERFLOW if not enough room in p for information.
 *  ERROR_NOT_SUPPORTED for any function not handled here.
 */

int far pascal
FS_FSCTL (union argdat far * pArgDat,
USHORT  usArgType,
USHORT  usFunc,
PCHAR pcParm,
USHORT  usLenParm,
PUSHORT pusLenParmOut,  /* len of data returned in pcParm */
PCHAR pcData,
USHORT  usLenData,
PUSHORT pusLenDataOut)  /* len of data returned in pcData */
{
  USHORT          usRet;
  PBUF            pbBuf;
  PBUF            pbPrevBuf;
  PVPFSI          pvpfsi;
  PVOL_DEP_DATA   pvddVolData;
  LOGICAL_BLK     TmpBlk;
  FSDDAEMON FAR  *daemon_data;

#ifdef DEBUG
  dprintf ("FS_FSCTL(ArgType=%d, Func=%d)\n", usArgType, usFunc);
#endif

  /* Check for initialization */
  if (usRet = CHECK_INIT)
    return usRet;

  /* Check 'pusLenParmOut' and 'pusLenDataOut' */
  if (usRet = FSH_PROBEBUF (
            PB_OPWRITE,
            (UCHAR FAR *) pusLenParmOut,
            sizeof (USHORT)))
  {
    /* Check the other buffer */
    if (!FSH_PROBEBUF (PB_OPWRITE, (UCHAR FAR *) pusLenDataOut,
                       sizeof (USHORT)))
    {
      *pusLenDataOut = 0;
    }
  }

  /* Check the other one */
  else if (usRet = FSH_PROBEBUF (
              PB_OPWRITE ,
              (UCHAR FAR *) pusLenDataOut,
              sizeof (USHORT)))
  {
    *pusLenParmOut = 0;
  }

  /* Else continue with FS_FSCTL */
  else
  {
    switch (usFunc)
    {
      /**********************************************************************
      *                                                                     *
      *  Standard functions                                                 *
      *                                                                     *
      **********************************************************************/

      /*
       * Return text for new error codes.
       * (This FSD has NO new error codes).
       */

      case FSCTL_FUNC_NEW_INFO:

        /* Store the returned parms /*
        *pusLenParmOut = 0;
        *pusLenDataOut = 0;

        /* Check the parameter for reading */
        if (!usLenParm ||
          !(usRet = FSH_PROBEBUF (PB_OPREAD, pcParm, usLenParm)))
        {
          /* Check that the paramater area can hold a USHORT */
          if (usLenParm < sizeof (USHORT))
          {
            usRet = ERROR_INVALID_PARAMETER;
          }

          /* Check the data area holds the return message */
          else if (!usLenData ||
                !(usRet = FSH_PROBEBUF (
                      PB_OPWRITE,
                      pcData,
                      usLenData)))
          {
            if (usLenData < (sizeof (USHORT) + cbUnknownMsg))
            {
              *pusLenDataOut = sizeof (USHORT) + cbUnknownMsg;
              usRet = ERROR_BUFFER_OVERFLOW;
            }
            else
            {
              /* copy the error message string and its length */
              ((MESSAGE FAR *) pcData)->cchMessage = cbUnknownMsg;

              memcpy(((MESSAGE FAR *) pcData)->pszMessage,
                  acUnknownMsg, cbUnknownMsg);

              *pusLenDataOut = sizeof (USHORT) + cbUnknownMsg;

            }
          }
        }

        break;

      /*
       * Return the EA sizes that this FSD supports
       */

      case FSCTL_FUNC_EASIZE:

        /* Store the return value (tentative) */
        *pusLenParmOut = 0;
        *pusLenDataOut = 0;

        /* Check the data area for writing */
        if (!usLenData ||
          !(usRet = FSH_PROBEBUF (
                PB_OPWRITE,
                pcData,
                usLenData)))
        {
          /* see if there really is enough room in buffer */
          if (usLenData < sizeof(EASIZEBUF))
          {
            *pusLenDataOut = sizeof (EASIZEBUF);
            usRet = ERROR_BUFFER_OVERFLOW;
          }

          /* Else store the EA information */
          else
          {
            /* copy the EA size information */

            ((PEASIZEBUF)pcData)->easb_MaxEASize = CB_MAX_EA;
            ((PEASIZEBUF)pcData)->easb_MaxEAListSize = CB_MAX_EALIST;

            *pusLenDataOut = sizeof(EASIZEBUF);
          }
        }

        break;

      /**********************************************************************
      *                                                                     *
      *  Misc debug/utility functions                                       *
      *                                                                     *
      **********************************************************************/

      /*  Clear the cache.  This involves removing all unlocked
       *  buffers from the sector and path table lists.  If the
       *  reserved string is correct, we first walk through the
       *  list of in use sectors.  Any that are unlocked, we remove
       *  from the list, remove their BUF_LIST owners from their
       *  list and return the buffer to the free list.  For the
       *  path table in-use list, we remove the buffers and set
       *  the pointer to the buffers in the owning PATH_TABLE structure
       *  to NULL.  We then put these buffers on the free list.
       */
      case 0x8F04 :

        /*
         *  If the user has passed in a valid string which matches
         *  the string 'szResCacheClear', we will clear the cache
         *  buffer.
         */

        /* Store the return value (tentative) */
        *pusLenParmOut = 0;
        *pusLenDataOut = 0;

        /* Test the buffer for reading */
        if (!usLenParm ||
          !(usRet = FSH_PROBEBUF (
                PB_OPREAD,
                pcParm,
                usLenParm)))
        {
          /* See if the buffer is long enough */
          if (usLenParm < LEN_RES_CACHE_CLEAR)
          {
            *pusLenParmOut = LEN_RES_CACHE_CLEAR;
            usRet = ERROR_BUFFER_OVERFLOW;
          }

          /* Else test the string */
          else
          {
            /* Test the string */
            if (!StrnCmpDBCS (
                pcParm,
                szResCacheClear,
                LEN_RES_CACHE_CLEAR))
            {
          /* Grab the BUF_LIST semaphore */
          SEM_REQ (sBLSec);

          /* Start at bottom of the in use BUF_LIST's */
          pbBuf = pbLastSec;

#ifdef DEBUG
              dprintf ("\tBL before cache clear\n");
              BLInUse ();
#endif
              /* Stop after walking through the entire list */
              while (pbBuf)
              {
                /* Free the buffer if unlocked */
                if (!pbBuf->cUsers)
                {
                  /* Mark this as locked */
                  pbBuf->cUsers++;

                  /* Remember the previous buffer on list */
                  pbPrevBuf = pbBuf->pbPrevBUF;

                  /* Test if this preemptable */
                  if (TestPreemptBuf (pbBuf, TRUE))
                  {
                    /* Remove the buf */
                    RemoveListSec (pbBuf);

                    /* Grab the buffer semaphore */
                    SEM_REQ (sBLSec);

                    /* Attach to free list */
                    FREE_BUF (pbBuf);

                  } /* end if preemptable */

                } /* End if buffer is unlocked */

                /* Copy the previous buffer into pbBuf */
                pbBuf = pbPrevBuf;

              } /* end while */

#ifdef DEBUG
              dprintf ("\tBL after cache clear\n");
              BLInUse ();
#endif

              /* Give up the BUF_LIST semaphore */
              SEM_CLEAR (sBLSec);

          /* Grab the Path Table semaphore */
          SEM_REQ (sPTSec);

          /* Start at bottom of the in-use PATH_TABLE list */
          pbBuf = pbLastPT;

#ifdef DEBUG
              dprintf ("\tPT before cache clear\n");
              PTInUse ();
#endif
              /* Stop after walking through the entire list */
              while (pbBuf)
              {
                /* Free the buffer if unlocked */
                if (!pbBuf->cUsers)
                {
                  /* Mark this as locked */
                  pbBuf->cUsers++;

                  /* Remember the previous buffer on list */
                  pbPrevBuf = pbBuf->pbPrevBUF;

                  /* Test if this preemptable */
                  if (TestPreemptBuf (pbBuf, FALSE))
                  {
                    /* Grab the buffer semaphore */
                    SEM_REQ (sPTSec);

                    /* Remove the buf */
                    RemovePTSec (pbBuf);

                    /* Free the owner */
                    FREE_PATH_TABLE (
                      ((PPATH_TABLE) pbBuf->pblBufOwner));

                    /* Attach to free list */
                    FREE_BUF (pbBuf);

                  } /* end if preemptable */

                } /* End if buffer is unlocked */

                /* Copy the previous buffer into pbBuf */
                pbBuf = pbPrevBuf;

              } /* end while */

#ifdef DEBUG
              dprintf ("\tPT after cache clear\n");
              PTInUse ();
#endif

              /* Give up the Path Table semaphore */
              SEM_CLEAR (sPTSec);

            } /* end if strings match */

            /* Else return invalid parameter */
            else
            {
              usRet = ERROR_INVALID_PARAMETER;
            }
          }

        } /* end if buffer is valid */

        break;

      /*
       *  Print out the sector cache.
       */
      case 0x8F05 :

        /*
         *  If the user has passed in a valid string which matches
         *  the string 'szResCacheClear', we will clear the cache
         *  buffer.
         */

        /* Store the return value (tentative) */
        *pusLenParmOut = 0;
        *pusLenDataOut = 0;

        /* Test the buffer for reading */
        if (!usLenParm ||
          !(usRet = FSH_PROBEBUF (
                PB_OPREAD,
                pcParm,
                usLenParm)))
        {
          /* See if the buffer is long enough */
          if (usLenParm < LEN_RES_CACHE_CLEAR)
          {
            *pusLenParmOut = LEN_RES_CACHE_CLEAR;
            usRet = ERROR_BUFFER_OVERFLOW;
          }

          /* Else test the string */
          else
          {
            /* Test the string */
            if (!StrnCmpDBCS (
                pcParm,
                szResCacheClear,
                LEN_RES_CACHE_CLEAR))
            {
#ifdef DEBUG
              dprintf ("\tBL in use\n");
              BLInUse ();
#endif
#ifdef DEBUG
              dprintf ("\tPT in use\n");
              PTInUse ();
#endif
            } /* end if strings match */

            /* Else return invalid parameter */
            else
            {
              usRet = ERROR_INVALID_PARAMETER;
            }
          }

        } /* end if buffer is valid */

        break;

      /*
       * Return the starting LSN
       */
      case 0x8F06 :

        /* Store the return value (tentative) */
        *pusLenParmOut = 0;
        *pusLenDataOut = 0;

        /* Check the data area for writing */
        if (!usLenData ||
          !(usRet = FSH_PROBEBUF (
                PB_OPWRITE,
                pcData,
                usLenData)))
        {
          /* see if there really is enough room in buffer */
          if (usLenData < sizeof(LOGICAL_BLK))
          {
            *pusLenDataOut = sizeof (LOGICAL_BLK);
            usRet = ERROR_BUFFER_OVERFLOW;
          }

          /* Else get the LSN information */
          else
          {
            /* Get the first LBN, skip past XAR data, convert to LSN */

            /* Get addressability to volume data (need logical block size) */
            if (!(usRet = FSH_GETVOLPARM (
                   ((PSFFSI) pArgDat->sf.psffsi)->sfi_hVPB,
                   &pvpfsi,
                   (PPVPFSD) &pvddVolData)
                 )
               )
            {
              TmpBlk = /* First LBN following XAR data */
                ((PFILE_DEP_DATA) pArgDat->sf.psffsd)->
                  pdeFirstDirRec->lbFileLoc +
                ((PFILE_DEP_DATA) pArgDat->sf.psffsd)->
                  pdeFirstDirRec->clbXARLen;


              (LOGICAL_BLK) *pcData =
                (pvddVolData->pvdVolDesc->cbLogBlk == SECTOR_SIZE ?
                  TmpBlk :
                  ((TmpBlk * pvddVolData->pvdVolDesc->cbLogBlk )
                    / SECTOR_SIZE));

              *pusLenDataOut = sizeof(LOGICAL_BLK);
            }
          }
        }

        break;

      /*
       * MSCDEX support
       */

      case FSCTL_ARG_MSCDEX :

        usRet = mscdex(pArgDat, usArgType, usFunc, pcParm, usLenParm,
                       pusLenParmOut, pcData, usLenData, pusLenDataOut);
        break;

      /*
       * Query for media change.  If the CD-ROM disc has been changed, the
       * FSH_SetVolume call unmounts the previous volume and mounts the new
       * volume.  Specifically, FSH_SetVolume calls OS2CDROM.DMD with a media
       * check request and if a media change occurred, calls the CDFS.IFS
       * FS_MOUNT routine to update its volume data structures for this new
       * media.  The caller (vcdrom.sys) also needs to know about media changes
       * so that it can close its existing handle and open a new (valid) one.
       * Media change/mount for removable media is usually done on an open
       * request, but doing an open/close on each int 2fh (MSCDEX) is too
       * expensive (slow). Providing this routine (short cut) enables
       * vcdrom.sys to only issue opens/closes when the media changes.
       *
       * On Exit:
       *  pcData        = ptr to (USHORT) return code.
       *                  ERROR_VOLUME_CHANGED
       *                  NO_ERROR                 // if volume did not change
       *                  ERROR_INVALID_PARAMETER  // if pData too small
       *  usLenData     = length of return code.
       *  pusLenDataOut = ptr to length of return code.
       */

      case FSCTL_ARG_MEDIACHG :

        /* Test the buffer for writing return code */
        if ((usLenData < sizeof(USHORT)) ||
        (usRet = FSH_PROBEBUF (PB_OPWRITE, pcData, usLenData)))
        {
          *pusLenDataOut = sizeof(USHORT);
          usRet = ERROR_INVALID_PARAMETER;
        }

        *pusLenDataOut = 0x0002;                   // size of return code
        *pcData = (USHORT)FSH_SETVOLUME (((PSFFSI) pArgDat->sf.psffsi)->sfi_hVPB,
                                         (ULONG) 0x00000000);

        usRet = NO_ERROR;
        break;

      /**********************************************************************
      *                                                                     *
      *  Statistical functions                                              *
      *                                                                     *
      **********************************************************************/

#ifdef STATS_H

      /*
      * Copy the current state of the statistics buffer
      * to the caller.
      */

      case FSCTL_STATS_GET:

        /* Store the return value (tentative) */
        *pusLenParmOut = 0;
        *pusLenDataOut = 0;

        /* Check the data area for writing */
        if (!usLenData ||
          !(usRet = FSH_PROBEBUF (PB_OPWRITE, pcData, usLenData)))
        {
          /* make sure we have a valid pointer */
          if (usLenData < sizeof(CDSTATS))
          {
            *pusLenDataOut = sizeof (CDSTATS);

            usRet = ERROR_BUFFER_OVERFLOW;
          }
          else
          {
            /* copy over the requested data */

            memcpy(pcData, (PCDSTATS)&CurSt, sizeof(CurSt));

            /* set lengths of data being returned */

            *pusLenDataOut = sizeof(CurSt);

          }
        }

        break;

      /*
      * Reset all of the counters in the statistics buffer.
      */

      case FSCTL_STATS_CLR:
        CurSt.st_cfptr = 0L;
        CurSt.st_chdir = 0L;
        CurSt.st_close = 0L;
        CurSt.st_fattr = 0L;
        CurSt.st_finfo = 0L;
        CurSt.st_fclose = 0L;
        CurSt.st_ffirst = 0L;
        CurSt.st_fnext = 0L;
        CurSt.st_fsinfo = 0L;
        CurSt.st_ioctl = 0L;
        CurSt.st_mount = 0L;
        CurSt.st_open = 0L;
        CurSt.st_pinfo = 0L;
        CurSt.st_pname = 0L;
        CurSt.st_read = 0L;
        CurSt.st_hits[DIR_SEC] = 0L;
        CurSt.st_hits[PT_SEC] = 0L;
        CurSt.st_hits[DATA_SEC] = 0L;
        CurSt.st_misses[DIR_SEC] = 0L;
        CurSt.st_misses[PT_SEC] = 0L;
        CurSt.st_misses[DATA_SEC] = 0L;
        CurSt.alloc_faults = 0L;

        *pusLenDataOut = *pusLenParmOut = 0;

        break;

#endif

      /**********************************************************************
      *                                                                     *
      *   Enable/Disable the mount process                                  *
      *                                                                     *
      **********************************************************************/

      case 0x8F02 :   /* Disable the mount process  */
      case 0x8F03 :   /* Enable the mount process */

        /*
         *  Enable the mount process.
         *  Disable the mount process.
         *
         *  If the user has passed in a valid string which matches
         *  the string 'szResMount', we will enable/disable the mount
         *  operation.
         */

        /* Store the return value (tentative) */
        *pusLenParmOut = 0;
        *pusLenDataOut = 0;

        /* Test the buffer for reading */
        if (!usLenParm ||
          !(usRet = FSH_PROBEBUF (PB_OPREAD, pcParm, usLenParm)))
        {
          /* See if the buffer is long enough */
          if (usLenParm < LEN_RES_MOUNT)
          {
            *pusLenParmOut = LEN_RES_MOUNT;
            usRet = ERROR_BUFFER_OVERFLOW;
          }

          /* Else test the string */
          else
          {
            /* Test the string */
            if (!StrnCmpDBCS (pcParm, szResMount, LEN_RES_MOUNT))
            {
              fMountEnabled = (usFunc == 0x8F03);

            } /* end if strings match */

            /* Else return invalid parameter */
            else
            {
              usRet = ERROR_INVALID_PARAMETER;
            }
          }

        } /* end if buffer is valid */

        break;

      /**********************************************************************
      *                                                                     *
      *   Deamon query function                                             *
      *                                                                     *
      **********************************************************************/

      case FSCTL_DAEMON_QUERY:                                   /* 0x0004 */
        daemon_data = (FSDDAEMON FAR *)pcData;
        daemon_data->usNumThreads = 1;
        daemon_data->usMoreFlag = FSCTL_QUERY_COMPLETE;
        daemon_data->tdThrds[0].usFunc = FSCTL_START_READAHEAD;
        daemon_data->tdThrds[0].usStackSize = 2048;
        daemon_data->tdThrds[0].ulPriorityClass = PRTYC_IDLETIME;
        daemon_data->tdThrds[0].lPriorityLevel = 0;
        *pusLenDataOut = sizeof(FSDDAEMON);

        break;


      /**********************************************************************
      *                                                                     *
      *  Lazy Reader / Read Ahead Functions                                 *
      *                                                                     *
      **********************************************************************/

      /* Start the Lazy Reader */

      case FSCTL_START_READAHEAD:                                /* 0x8000 */
        readahead_shutdown = FALSE;         /* make sure readahead is on   */
        ReadAheadDaemon();

        break;
      /* Stop the Lazy Reader */

      case FSCTL_STOP_READAHEAD:                                 /* 0x8001 */
        readahead_shutdown = TRUE;
        FSH_SEMCLEAR(&(readahead_pending));        /* signal daeomon to end */
        *pusLenParmOut = 0;
        *pusLenDataOut = 0;
        break;

      /* Get Lazy Reader Statistics */

      case 0x8002:

      default:
        *pusLenParmOut = 0;
        *pusLenDataOut = 0;

        usRet = ERROR_NOT_SUPPORTED;
        break;

    } /* end switch */

  } /* end else */

  return (usRet);

  /*
   * Kill the compiler warning messages.
   */

  pArgDat;
  usArgType;
  usFunc;
  pcParm;
  usLenParm;
  pusLenParmOut;
  pcData;
  usLenData;
  pusLenDataOut;
}


/***  mscdex() - MSCDEX DOS Session INT 2Fh Support.
 *
 * DESCRIPTION
 *
 *  The MSCDEX routine performs the file system specific MSCDEX support.
 *
 *  Vcdrom issues a VDHFSCtl (usFunc=FSCTL_ARG_MSCDEX) call which calls
 *  FS_FSCtl which calls mscdex() to perform the work.
 *
 *  When allocating buffers (e.g. GetStringBuf), must make sure that pointer
 *  points to start of buffer when deallocating buffer (FREE_STRING_BUF).
 *
 *    Requested function:
 *
 *    0x00 :   Get number of CD-ROM drive letters. (handled by vcdrom)
 *    0x01 :   Get CD-ROM drive device list. (handled by vcdrom)
 *    0x02 :   Get copyright file name.
 *                pcParm    = on input, pointer to a buffer containing:
 *                            ULONG   ulFunction = 0x00000002;
 *                usLenParm = length of pcParm (4 bytes)
 *                pcData    = pointer to a 38 byte buffer
 *                usLenData = Length of pcData (file name length in bytes)
 *    0x03 :   Get abstract file name.
 *                pcParm    = on input, pointer to a buffer containing:
 *                            ULONG   ulFunction = 0x00000003;
 *                usLenParm = length of pcParm (4 bytes)
 *                pcData    = pointer to a 38 byte buffer
 *                usLenData = Length of pcData (file name length in bytes)
 *    0x04 :   Get bibliographic file name.
 *                pcParm    = on input, pointer to a buffer containing:
 *                            ULONG   ulFunction = 0x00000004;
 *                usLenParm = length of pcParm (4 bytes)
 *                pcData    = pointer to a 38 byte buffer
 *                usLenData = Length of pcData (file name length in bytes)
 *    0x05 :   Read VTOC.
 *                pcParm    = on input, pointer to a buffer containing:
 *                            ULONG   ulFunction = 0x00000005;
 *                            ULONG   ulSectorIndex;
 *                          = on output, pointer to a buffer containing:
 *                            UCHAR   ucDescType;
 *                usLenParm = length of pcParm (8 bytes)
 *                pcData    = pointer to a 2048 byte buffer
 *                usLenData = Length of pcData (2048 bytes)
 *    0x06 :   Reserved. (Turn debugging on)
 *    0x07 :   Reserved. (Turn debugging off)
 *    0x08 :   Absolute disk read.
 *                pcParm    = on input, pointer to a buffer containing:
 *                            ULONG   ulFunction = 0x00000008;
 *                            ULONG   ulSectorCount;
 *                            USHORT  usSectStartLo;
 *                            USHORT  usSectStartHi;
 *                usLenParm = length of pcParm (12 bytes)
 *                pcData    = pointer to sector buffer
 *                usLenData = length of pcData (ulSectorCount * SECTOR_SIZE)
 *    0x09 :   Absolute disk write. (unsupported)
 *    0x0A :   Reserved.
 *    0x0B :   CD-ROM drive check. (handled by vcdrom)
 *    0x0C :   MSCDEX version. (handled by vcdrom)
 *    0x0D :   Get CD-ROM drive letters. (handled by vcdrom)
 *    0x0E :   Get/Set volume descriptor preference.
 *                pcParm    = on input, pointer to a buffer containing:
 *                            ULONG   ulFunction = 0x0000000E;
 *                            ULONG   ulPrefReq;
 *                                     = on input, 0 = Get Preference
 *                                                 1 = Set Preference
 *                                     = on output, ulPrefReq is unchanged
 *                            ULONG   ulPrefVal;
 *                                     if ulPrefReq == Get Preference
 *                                     = on input, ulPrefVal == 0
 *                                     = on output, ulPrefVal = preference
 *                                     if ulPrefReq == Set Preference
 *                                     = on input,
 *                                        Primary Volume Descriptor = 1
 *                                        Supplementary Volume Descriptor = 2
 *                                        if (UCHAR)ulPrefVal+3 == 1
 *                                          (UCHAR)ulPrefVal+4 = 0
 *                                        if (UCHAR)ulPrefVal+3 == 2
 *                                          (UCHAR)ulPrefVal+4 = 1 (shift kanji)
 *                                     = on output, ulPrefVal is unchanged
 *                usLenParm = length of pcParm
 *                pcData    = undefined
 *                usLenData = undefined
 *    0x0F :   Get directory entry.
 *                pcParm    = on input, pointer to a buffer containing:
 *                            ULONG   ulFunction = 0x0000000F;
 *                            ULONG   ulFlags;
 *                                     on input, bit 0: 0 = direct copy
 *                                                      1 = copied to structure
 *                                     on output,       0 = High Sierra
 *                                                      1 = ISO-9660
 *                            UCHAR   szPathName; (asciiz path name)
 *                usLenParm = length of pcParm
 *                pcData    = pointer to directory entry structure
 *                usLenData = length of pcData
 *    0x10 :   Send device request. (handled by vcdrom)
 *
 *
 *  ENTRY
 *  pArgDat       = Pointer to argument:
 *      usArgType == 1: pArgDat = ptr to sffsi, ptr to sffsd
 *      usArgType == 2: pArgDat = ptr to cdfsi, ptr to cdfsd, ptr to path
 *      usArgType == 3: pArgDat = NULL
 *
 *  usArgType     = Argument type:
 *              1 = called with file handle
 *              2 = called with pathname
 *              3 = called with FSD name
 *
 *  usFunc        = Requested function
 *
 *  pcParm        = Address of application input area.  Addressing
 *                  has NOT been validated by the kernel.
 *
 *  usLenParm     = Byte length of the pcParm area (pusLenParmOut may
 *                  be less than this).
 *
 *  pusLenParmOut = On input, length in bytes of parameters passed
 *                  in pcParm. On output, length of bytes of data
 *                  returned by the FSD in pcParm.
 *
 *  pcData        = Address of application output area.  Addressing
 *                  has NOT been validated by the kernel.
 *
 *  usLenData     = Byte length of the pcData area (pusLenDataOut may
 *                  be less than this).
 *
 *  pusLenDataOut = On input, length in bytes of data passed in pcData.
 *                  On output, length of bytes of data returned by the
 *                  FSD in pcData.
 *
 * EXIT
 *  NO_ERROR               if requested function was performed.
 *  ERROR_BUFFER_OVERFLOW  if not enough room in p for information.
 *  ERROR_NOT_SUPPORTED    for any function not handled here.
 */
int pascal
mscdex (union argdat far * pArgDat,
USHORT  usArgType,
USHORT  usFunc,
PCHAR   pcParm,
USHORT  usLenParm,
PUSHORT pusLenParmOut,
PCHAR   pcData,
USHORT  usLenData,
PUSHORT pusLenDataOut)
{
  PVOL_DEP_DATA      pvddVolData;         /* ptr to volume dependent data */
  USHORT             usRet;               /* return code */
  PVPFSI             pvpfsi;              /* ptr to file system independent */
  ULONG              ulSectorCount;       /* sector count to read */
  ULONG              ulStartSector;       /* starting sector number */
  USHORT             usSectorsRead;       /* sector count read */
  SEL                sSel;                /* selector to temp memory */
  PBUF               pbBuf;               /* used to get sector from cache */
  PCHAR              pName;               /* ptr to asciiz path name */
  USHORT             cbNameLen;           /* length of path and file name */
  USHORT             cbPathLen;           /* length of path excluding file name */
  PPATH_ENTRY        ppePathEntry;        /* ptr to condensed disc path entry */
  PDIR_ENTRY         pdeDstDE;            /* ptr to condensed directory entry */
  PDIR_ENTRY         pdePrevDE;           /* ptr to previous directory entry */
  PDIR_ENTRY         pdeTempDE;           /* ptr to do directory entry switch */
  PRAW_DIR_REC       prdrRawDirRec;       /* ptr to raw disc directory record */
  PSTRING_BUF        psbStr1;             /* storage for file name from parm */
  PSTRING_BUF        psbStr2;             /* storage for file name from disk */
  USHORT             fFound=FALSE;        /* file found flag */
  BOOL               fAllocPE=FALSE;      /* allocated path entry flag */
  BOOL               fAllocDE1=FALSE;     /* allocated directory entry 1 flag */
  BOOL               fAllocDE2=FALSE;     /* allocated directory entry 2 flag */
  BOOL               fAllocRDR=FALSE;     /* allocated raw directory entry flag */
  BOOL               fAllocStr1=FALSE;    /* allocated string buffer 1 flag */
  BOOL               fAllocStr2=FALSE;    /* allocated string buffer 2 flag */
  USHORT             i;                   /* loop control variable */
  PUCHAR             pctmp1;              /* ptr used for moving strings */
  PUCHAR             pctmp2;              /* ptr used for moving strings */
  USHORT             usTmpMaxLen;         /* max length of file names */


    /* Test the buffer for reading function code */
    if ((usLenParm < 4) ||
    (usRet = FSH_PROBEBUF (
          PB_OPREAD,
          pcParm,
          usLenParm)))
    {
      *pusLenParmOut = 4;
      usRet = ERROR_INVALID_PARAMETER;
      return(usRet);
    }

    switch (*pcParm)
    {
      /*
       *  Return the copyright file name from VTOC
       *  Return the abstract file name from VTOC
       *  Return the bibliographic doc file name from VTOC
       */
      case GET_COPYRIGHT_FILE_NAME :
      case GET_ABSTRACT_FILE_NAME :
      case GET_BIBLIOGRAPHIC_FILE_NAME :

        /* Store the return value (tentative) */
        *pusLenDataOut = 0;

        /* Get addressability to volume descriptor */
        if (!(usRet = FSH_GETVOLPARM (((PSFFSI) pArgDat->sf.psffsi)->sfi_hVPB,
                                      &pvpfsi,
                                      (PPVPFSD) &pvddVolData)))
        {
           if (*pcParm == GET_COPYRIGHT_FILE_NAME)
           {
              usTmpMaxLen = MAX_CPYR_ID_LEN;
              pctmp1 = pvddVolData->pvdVolDesc->szCpyr_ID;
           }
           else if (*pcParm == GET_ABSTRACT_FILE_NAME)
                {
                   usTmpMaxLen = MAX_ABST_ID_LEN;
                   pctmp1 = pvddVolData->pvdVolDesc->szAbst_ID;
                }
                else    /* (*pcParm == GET_BIBLIOGRAPHIC_FILE_NAME) */
                {
                   usTmpMaxLen = MAX_BIBL_ID_LEN;
                   pctmp1 = pvddVolData->pvdVolDesc->szBibl_ID;
                }

           /* see if there is enough room in buffer */
           if (usLenData < usTmpMaxLen)
           {
             *pusLenDataOut = usTmpMaxLen;
             usRet = ERROR_BUFFER_OVERFLOW;
           }
           else  /* Check the data area for writing */
           {
              if (!(usRet = FSH_PROBEBUF (PB_OPWRITE,
                                          pcData,
                                          usLenData)))
              {
                 memcpy (pcData,pctmp1,usTmpMaxLen);
                 *pusLenDataOut = istrlen(pcData);
              }

           } /* not enough room in buffer */
        } /* FSH_GETVOLPARM error */
        break;

      /*
       * Read VTOC
       */
      case READ_VTOC :
        /* Test the buffer for reading sector index */
        if ((usLenParm < sizeof(READVTOC)) ||
        (usRet = FSH_PROBEBUF (PB_OPREAD,
                               pcParm,
                               usLenParm)))
        {
          *pusLenParmOut = sizeof(READVTOC);
          usRet = ERROR_INVALID_PARAMETER;
          return(usRet);
        }

        /* Store the return value (tentative) */
        *pusLenDataOut = 0;
        *pusLenParmOut = 0;

        /* Check the data area for writing */
        if (!usLenData ||
          !(usRet = FSH_PROBEBUF (PB_OPWRITE,
                                  pcData,
                                  usLenData)))
        {
          /* see if there is enough room in buffer */
          if (usLenData < SECTOR_SIZE)
          {
            *pusLenDataOut = SECTOR_SIZE;
            usRet = ERROR_BUFFER_OVERFLOW;
          }
          /* read the VTOC */
          else
          {
            /* Get addressability to volume descriptor */
            if (!(usRet = FSH_GETVOLPARM (((PSFFSI) pArgDat->sf.psffsi)->sfi_hVPB,
                                          &pvpfsi,
                                          (PPVPFSD) &pvddVolData)))
            {
              ulSectorCount = 1;
              ulStartSector = (((PREADVTOC)pcParm)->ulSectorIndex+VTOC_ST_SECTOR);

              /* Get the sector either from cache or from disc */
              if (!(usRet = FindBuf (pvddVolData,
                                     TRUE,
                                     ulStartSector,
                                     ((PSFFSI) pArgDat->sf.psffsi)->sfi_hVPB,
                                     (PPBUF) &pbBuf,
                                     ulStartSector
#ifdef STATS_H
                                     ,DATA_SEC
#endif
                                     )))
              {
                 /* copy VTOC to user's buffer, return size */
                 memcpy (pcData, (PCHAR)pbBuf->sSector, SECTOR_SIZE);
                 *pusLenDataOut = ulSectorCount * SECTOR_SIZE;

                 /* return descriptor type and size */
                 *pcParm = RV_DESC_TYPE (((PRAW_ISO_VTOC)pcData),pvddVolData->pvdVolDesc->fISOVol);
                 *pusLenParmOut = 1;

                 /* Release pbBuf */
                 if (pbBuf)
                     RELEASE_BUF (pbBuf);
              }
            }
          }
        }
        break;

      /*
       * Absolute Disk Read
       */
      case ABSOLUTE_DISK_READ :
        /* Test the buffer for reading sector count and sector start */
        if ((usLenParm < sizeof(ABSREAD)) ||
        (usRet = FSH_PROBEBUF (PB_OPREAD,
                               pcParm,
                               usLenParm)))
        {
          *pusLenParmOut = sizeof(ABSREAD);
          usRet = ERROR_INVALID_PARAMETER;
          return(usRet);
        }

        /* Store the return value (tentative) */
        *pusLenDataOut = 0;

        /* Check the data area for writing */
        if (!usLenData ||
          !(usRet = FSH_PROBEBUF (PB_OPWRITE,
                                  pcData,
                                  usLenData)))
        {
          ulSectorCount = ((PABSREAD)pcParm)->ulSectorCount;
          ulStartSector = *(((PULONG)pcParm)+2);

          /* see if there is enough room in buffer */
          if (usLenData < (ulSectorCount * SECTOR_SIZE))
          {
            *pusLenDataOut = ulSectorCount * SECTOR_SIZE;
            usRet = ERROR_BUFFER_OVERFLOW;
          }
          /* read the sectors */
          else
          {
            if (!(usRet = FSH_SEGALLOC (0x0000,     // GDT, non swap, ring 0
                                        ulSectorCount * SECTOR_SIZE,
                                        &sSel)))
            {
              pctmp1 = MAKEP (sSel, 0);

              usSectorsRead = ulSectorCount;
              if (!(usRet = FSH_DOVOLIO (DOVOLIO_OP,
                                         DOVOLIO_ALLOWED,
                                         ((PSFFSI)pArgDat->sf.psffsi)->sfi_hVPB,
                                         pctmp1,
                                         &usSectorsRead,
                                         ulStartSector)))
              {
                *pusLenDataOut = usSectorsRead * SECTOR_SIZE;
                memcpy (pcData, pctmp1, *pusLenDataOut);

                FSH_SEGFREE (sSel);
              } /* end if */
            } /* end if */
          }
        }
        break;

      /*
       * Get/Set Volume Descriptor Preference
       */
      case VOLUME_DESCRIPTOR_PREFERENCE :
        /* Test the buffer for preference request and preference value */
        if ((usLenParm < sizeof(VOLDESCPREF)) ||
        (usRet = FSH_PROBEBUF (PB_OPREAD,
                               pcParm,
                               usLenParm)))
        {
          *pusLenParmOut = sizeof(VOLDESCPREF);
          usRet = ERROR_INVALID_PARAMETER;
          return(usRet);
        }

        /* Store the return value */
        *pusLenDataOut = 0;

        /* get/set volume descriptor preference currently works on a   */
        /* per-volume basis and not on a per-session per-volume basis. */

        /* return volume descriptor preference */
        if (((PVOLDESCPREF)pcParm)->ulPrefReq == GET_VOL_DESC_PREF)
        {
           /* Test the buffer for write access */
           if ((usLenParm < sizeof(VOLDESCPREF)) ||
           (usRet = FSH_PROBEBUF (PB_OPWRITE,
                                  pcParm,
                                  usLenParm)))
           {
             *pusLenParmOut = sizeof(VOLDESCPREF);
             usRet = ERROR_INVALID_PARAMETER;
             return(usRet);
           }
           if (fUsePrimaryVD)
              ((PVOLDESCPREF)pcParm)->ulPrefVal = USE_PRIMARY_VOL_DESC;
           else
              ((PVOLDESCPREF)pcParm)->ulPrefVal = USE_SUPPLEM_VOL_DESC;
        }
        else /* set volume descriptor preference */
        {
           /* Set volume descriptor preference is not fully functional based */
           /* upon the MSCDEX spec.  A comparision is not being made to      */
           /* determine if the supplementary request is shift kanji and if   */
           /* not, to return ERROR_INVALID_FUNCTION.                         */

           if (((PVOLDESCPREF)pcParm)->ulPrefReq == SET_VOL_DESC_PREF)
           {
               if ((UCHAR)((PVOLDESCPREF)pcParm)->ulPrefVal == USE_PRIMARY_VOL_DESC)
                  fUsePrimaryVD = TRUE;
               else
                  fUsePrimaryVD = FALSE;
           }
           else /* invalid preference */
           {
              usRet = ERROR_INVALID_FUNCTION;
              return(usRet);
           } /* end if */
        } /* end if */

        /* set return buffer size */
        *pusLenParmOut = sizeof(VOLDESCPREF);
        break;

      /*
       * Get Directory Entry
       */
      case GET_DIRECTORY_ENTRY :
        /* Test the buffer for reading flags and asciiz filename */
        if ((usLenParm < sizeof(GETDIRENTRY)) ||
        (usRet = FSH_PROBEBUF (PB_OPREAD,
                               pcParm,
                               usLenParm)))
        {
          *pusLenParmOut = sizeof(GETDIRENTRY);
          usRet = ERROR_INVALID_PARAMETER;
          return(usRet);
        }

        /* Store the return value (tentative) */
        *pusLenDataOut = 0;

        /* Check the data area for writing */
        if (!usLenData ||
          !(usRet = FSH_PROBEBUF (PB_OPWRITE,
                                  pcData,
                                  usLenData)))
        {
            /* Get addressability to volume descriptor */
            if (!(usRet = FSH_GETVOLPARM(((PSFFSI) pArgDat->sf.psffsi)->sfi_hVPB,
                                         &pvpfsi,
                                         (PPVPFSD) &pvddVolData)))
            {

               /* Try to allocate a path entry structure */
               if (!(usRet = GetPathEntry (&ppePathEntry)))
               {
                  fAllocPE = TRUE;
                  pName = pcParm;
                  pName += sizeof(GETDIRENTRY);                   /* point to start of path name */

                  cbNameLen = istrlen (pName);
                  cbPathLen = ParentEndLenDBCS (pName, cbNameLen);

                  /* Try to allocate the STRING_BUF structure for parameter */
                  if (!(usRet = GetStringBuf (&psbStr1)))
                  {
                     fAllocStr1 = TRUE;
                     FSH_UPPERCASE (pName, sizeof (STRING_BUF), psbStr1);

                     /* if root directory, make pseudo path entry */
                     /* path entry needed when GetFirstDE calls MakePseudoDE */
                     if (!cbPathLen) {
                       ppePathEntry->lbLocDir=pvddVolData->pdeRootDir->lbFileLoc;
                       ppePathEntry->clbXAR_Len=pvddVolData->pdeRootDir->clbXARLen;
                     } else
                         /* retrieve parameter path table entry from disc */
                         usRet = TracePath(pvddVolData,
                                           ROOT_DIR_NUM,
                                           psbStr1,
                                           cbPathLen,
                                           ppePathEntry,
                                           ((PSFFSI) pArgDat->sf.psffsi)->sfi_hVPB);

                     /* if path table setup ok */
                     if (!(usRet))
                     {

                        /* Try to allocate a directory record structure */
                        if (!(usRet = GetDirEntry (&pdeDstDE)))
                        {
                           fAllocDE1 = TRUE;

                           /* Try to allocate a directory record structure */
                           if (!(usRet = GetDirEntry (&pdePrevDE)))
                           {
                              fAllocDE2 = TRUE;

                              /* Try to allocate a raw directory record structure */
                              if (!(usRet = GetRawDirRec (&prdrRawDirRec)))
                              {
                                 fAllocRDR = TRUE;

                                 /* retrieve raw directory entry from disc */
                                 if (!(usRet = GetFirstDE (pdeDstDE,
                                                           ppePathEntry,
                                                           pvddVolData,
                                                           ((PSFFSI) pArgDat->sf.psffsi)->sfi_hVPB,
                                                           1,
                                                           prdrRawDirRec)))
                                 {

                                    /* point past directories to file name */
                                    pctmp2 = psbStr1;
                                    pctmp2 += cbPathLen;
                                    if (*pctmp2 == '\\')
                                      pctmp2++;

                                    /* Try to allocate the STRING_BUF structure for disk file */
                                    if (!(usRet = GetStringBuf (&psbStr2)))
                                    {
                                       fAllocStr2 = TRUE;

                                       /* loop looking for directory record */
                                       do {

                                          /* uppercase filename */
                                          FSH_UPPERCASE ((pdeDstDE->szFileID),
                                                         sizeof (STRING_BUF),
                                                         psbStr2);

                                          /* strip off version number */
                                          pctmp1=psbStr2;
                                          while (*pctmp1 != NULL) {
                                            if (*pctmp1 == ';')
                                              *pctmp1 = NULL;
                                            pctmp1++;
                                          } /* endwhile */

                                          if (StrCmpDBCS (pctmp2, psbStr2))
                                            fFound = FALSE;
                                          else
                                            fFound = TRUE;

                                          if (!fFound)
                                          {
                                             /* Swap the pointers to the DE structs */
                                             pdeTempDE = pdeDstDE;
                                             pdeDstDE = pdePrevDE;
                                             pdePrevDE = pdeTempDE;

                                             usRet = GetNextDE (pdeDstDE,
                                                                pdePrevDE,
                                                                pvddVolData,
                                                                ((PSFFSI) pArgDat->sf.psffsi)->sfi_hVPB,
                                                                1,
                                                                prdrRawDirRec);
                                          }
                                       } while ((!usRet)&&(!fFound));

                                       if (fFound)
                                       {
                                          /* need to copy raw directory record to structure? */
                                          if (((PGETDIRENTRY)pcParm)->ulFlags == 1)
                                          {
                                             ((PMSCDEX_DIR_REC)pcData)->clbXARLen=prdrRawDirRec->clbXARLen;
                                             ((PMSCDEX_DIR_REC)pcData)->lbFileLoc=prdrRawDirRec->lbFileLoc;
                                             ((PMSCDEX_DIR_REC)pcData)->uslbsize=pvddVolData->pvdVolDesc->cbLogBlk;
                                             ((PMSCDEX_DIR_REC)pcData)->cbDataLen=prdrRawDirRec->cbDataLen;

                                             for (i=0;i<7;i++)
                                               ((PMSCDEX_DIR_REC)pcData)->abRecordTime[i]=prdrRawDirRec->abRecordTime[i];

                                             ((PMSCDEX_DIR_REC)pcData)->afFlags=R_FILE_FLAGS(pvddVolData->pvdVolDesc->fISOVol,prdrRawDirRec);
                                             ((PMSCDEX_DIR_REC)pcData)->clbIntLeaveSize=prdrRawDirRec->clbIntLeaveSize;
                                             ((PMSCDEX_DIR_REC)pcData)->clbIntLeaveSkip=prdrRawDirRec->clbIntLeaveSkip;
                                             ((PMSCDEX_DIR_REC)pcData)->vsnVSSN=prdrRawDirRec->vsnVSSN;
                                             ((PMSCDEX_DIR_REC)pcData)->usFileVer=pdeDstDE->sFileVer;

                                             for (i=0;i<prdrRawDirRec->cbFileIDLen;i++) {
                                               if (prdrRawDirRec->sFileID[i] != ';') {
                                                 ((PMSCDEX_DIR_REC)pcData)->sFileID[i]=prdrRawDirRec->sFileID[i];
                                               } else {
                                                 ((PMSCDEX_DIR_REC)pcData)->cbFileIDLen=i;
                                                 break;
                                               }
                                             } /* endfor */

                                             /* make string asciiz - clear remaining bytes in sFileID */
                                             for (i=((PMSCDEX_DIR_REC)pcData)->cbFileIDLen;i<MAX_FILE_ID_LEN;i++)
                                                 ((PMSCDEX_DIR_REC)pcData)->sFileID[i]=NULL;

                                             /* LenSU = raw dir rec length - (fixed rec length + variable rec length) */
                                             ((PMSCDEX_DIR_REC)pcData)->ucLenSU=prdrRawDirRec->cbDirLen-(RAW_DIR_LEN+prdrRawDirRec->cbFileIDLen);

                                             /* if file len is even, then padding field present, must subtract one */
                                             i = prdrRawDirRec->cbFileIDLen%2;
                                             i = (i==0) ? 1 : 0;
                                             ((PMSCDEX_DIR_REC)pcData)->ucLenSU-=i;

                                             memcpy (((PMSCDEX_DIR_REC)pcData)->ucsu_data,
                                                     prdrRawDirRec->sFileID+prdrRawDirRec->cbFileIDLen+i,
                                                     ((PMSCDEX_DIR_REC)pcData)->ucLenSU);

                                             *pusLenDataOut = MSCDEX_DIR_LEN + ((PMSCDEX_DIR_REC)pcData)->ucLenSU;
                                          }
                                          else
                                          {
                                             /* copy raw directory record to user's buffer */
                                             memcpy (pcData, prdrRawDirRec, prdrRawDirRec->cbDirLen);
                                             *pusLenDataOut = prdrRawDirRec->cbDirLen;
                                          } /* end if */

                                          if (pvddVolData->pvdVolDesc->fISOVol)
                                             ((PGETDIRENTRY)pcParm)->ulFlags=1;            /* ISO-9660 */
                                          else
                                             ((PGETDIRENTRY)pcParm)->ulFlags=0;            /* ISO-9660 */

                                       /* GetNextDE unsuccessful */
                                       } else
                                        usRet = ERROR_FILE_NOT_FOUND;

                                    /* GetStringBuf unsuccessful */
                                    } else
                                        usRet = ERROR_NO_MORE_FILES;

                                 /* GetFirstDE unsuccessful */
                                 } else
                                     usRet = ERROR_FILE_NOT_FOUND;

                              /* GetRawDirEntry unsuccessful */
                              } else
                                  usRet = ERROR_NO_MORE_FILES;

                           /* GetDirEntry unsuccessful */
                           } else
                               usRet = ERROR_NO_MORE_FILES;

                        /* GetDirEntry unsuccessful */
                        } else
                            usRet = ERROR_NO_MORE_FILES;

                     /* TracePath unsuccessful */
                     } else
                         usRet = ERROR_FILE_NOT_FOUND;

                  /* GetStringBuf unsuccessful */
                  } else
                      usRet = ERROR_NO_MORE_FILES;

               /* GetPathEntry unsuccessful */
               } else
                   usRet = ERROR_NO_MORE_FILES;

            /* FSH_GETVOLPARM unsuccessful */
            } else
                usRet = ERROR_NOT_READY;
        }
        /* buffers must be freed in order allocated */
        if (fAllocStr2)
            FREE_STRING_BUF(psbStr2);

        if (fAllocRDR)
            FREE_RAW_DIR_REC(prdrRawDirRec);

        if (fAllocDE2)
            FREE_DIR_ENTRY(pdePrevDE);

        if (fAllocDE1)
            FREE_DIR_ENTRY(pdeDstDE);

        if (fAllocStr1)
            FREE_STRING_BUF(psbStr1);

        if (fAllocPE)
            FREE_PATH_ENTRY(ppePathEntry);

        break;

      default:
        *pusLenDataOut = 0;

        usRet = ERROR_NOT_SUPPORTED;
        break;

    } /* end switch */

  return(usRet);
}


/*
 * i2a - convert an unsigned int to a right-justified string of
 *   decimal digits in a target string.
 */

void
i2a(p, n)
char *p;
USHORT n;
{
  if (n/10)
    i2a(p-1, n/10);
  *p = (char)((n % 10) + '0');
}

/* end fs_fsctl.c */
