/*static char *SCCSID = "@(#)fs_fsctl.c 6.3 92/01/31";*/
#define _BTXT _BTXT
#define INCL_DOSERRORS
#include <os2def.h>
#define INCL_DOSFILEMGR
#include <bse.h>
#include <fsh.h>

#include "heap.h"
#include "const.h"
#include "chain.h"
#include "misc.h"
#include "buf.h"
#include "dir.h"
#include "storage.h"
#include "volume.h"
#include "tables.h"
#include "pb_ifs.h"
#include "pbmacros.h"
#include "lw.h"
#include "frame.h"
#include "fsdata.h"
#include "ea.h"
#include "rahead.h"
#include "strat2.h"
#include "asyncio.h"

#include "pr_fsd.h"
#include "pr_HEAP.h"
#include "pr_CACHE.h"
#include "pr_LAZY.h"
#include "pr_MISC.h"
#include "pr_DEBUG.h"
#include "pr_fsini.h"
#include "pr_ra.h"

/*
 *      HISTORY
 *          16-Aug-1989 by JR (John Rogers, JohnRo@Microsoft)
 *              - Implement FSC_INVALIDATE so CHKDSK can use FS_READ to get
 *                sectors (which get cached) but use FS_IOCTL to write sectors
 *                (which don't get cached, so the cache gets out of date).
 *          05-Jun-1989 by JR (John Rogers, JohnRo@Microsoft)
 *              - PTR B784101: ifdef'ed Lazy Writer stuff (not applicable in
 *                MINIFSD, which has a read-only interface).
 *          30-May-1989 by JR (John Rogers, JohnRo@Microsoft)
 *              - DCR 259.2: changed names of macros in "frame.h".
 *              - Moved code which stops LW to separate function.
 */

/* put stand-alone fs_xxx functions in separate code segment */

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 = NO_ERROR;
    USHORT usLenDataOut = 0;
    USHORT usLenParmOut = 0;
    FRAME  frm;

    frm.usHardErr = NO_ERROR;
    frm.fRecheckAddr = TRUE;
    frm.fFlag = 0;               // Clear Frame flag  @132506

    /* if we haven't already, initialize the heap */
    if (!fInit) {
        HeapInit(&frm);
        fInit = TRUE;
    }

    switch (usFunc) {

    case FSCTL_FUNC_EASIZE:

        usLenDataOut = sizeof (EASIZEBUF);

        /* return the sizes that Pinball supports */
        if (usLenData < sizeof (EASIZEBUF)) {
            usRet = ERROR_BUFFER_OVERFLOW;
            break;
        }

        FS_MAYBLOCK (
            usRet = FSH_PROBEBUF (PB_OPWRITE, pcData, sizeof (EASIZEBUF));
        );
        if (usRet == NO_ERROR) {
            ((PEASIZEBUF)pcData)->easb_MaxEASize = CB_MAX_EA;
            ((PEASIZEBUF)pcData)->easb_MaxEAListSize = CB_MAX_EALIST;
        }
        break;

    case FSC_INVALIDATE:        /* Invalidate cache for a given sector num. */
        {
            /* Must be a handle-based call. */
            if (usArgType != FSCTL_ARG_FILEINSTANCE) {
                usRet = ERROR_INVALID_FUNCTION;
                break;
            }

            usRet = CacheInvalidate (((PPB_SFD)(pArgDat->sf.psffsd->
                                                     sfd_work))->pfht->poft,
                                     pcParm,
                                     usLenParm,
                                     &usLenParmOut,
                                     &frm);
            break;
        }

#ifdef RAHEAD
    case FSC_RAHEAD:
        ReadAhead(&frm);
    break;

#ifdef RAHEAD_VARS

    case FSC_RA_STOP:
        StopReadAhead();
    break;

    case FSC_RA_SET:
        /* Expect array of 5 integers */
        if (usLenParm != 5 * sizeof (int))
            break;

        FS_MAYBLOCK(usRet = FSH_PROBEBUF(PB_OPREAD,pcParm,usLenParm));

        if (usRet == NO_ERROR) {
            RA_HI =       ((int far *) (pcParm))[0];
            RA_LOW =      ((int far *) (pcParm))[1];
            MIN_READLEN = ((int far *) pcParm)[2];
            MID_READLEN = ((int far *) pcParm)[3];
            MAX_READLEN = ((int far *) pcParm)[4];
        }
    break;

    case FSC_RA_GET:

        /* Return array of 5 integers */
        if (usLenData != 6 * sizeof (int))
            break;

        FS_MAYBLOCK(usRet = FSH_PROBEBUF(PB_OPWRITE,pcData,usLenData));
        if (usRet == NO_ERROR) {
            extern int fRAhead;
            ((int far *) pcParm)[0] = RA_HI;
            ((int far *) pcParm)[1] = RA_LOW;
            ((int far *) pcParm)[2] = MIN_READLEN;
            ((int far *) pcParm)[3] = MID_READLEN;
            ((int far *) pcParm)[4] = MAX_READLEN;
            ((int far *) pcParm)[5] = fRAhead;
        }
    break;

#endif
#endif


#ifdef PERFVIEW

#ifdef NEVER
/* BUGBUG - This is broken as of 5/28/90 because of changes done
 * to pvwxport.h: Text\Data block structures have been changed.
 */

/*************************************************************************
 *
 *                    PVWSHOW stuff
 *
 *      The following ctls form the interface between the PerfView
 *  hooks and our private display tool.
 *
 *************************************************************************/

    /* Adds length of string b to a */
    #define ADDLEN(a,b) {PCHAR _pC = b;          \
                         while (*_pC++) a++;     \
                         a++;                    \
                      }

    /* Gets length of strings in Perfview text block */
    #define GETTXTLEN(Len)  {                                   \
            USHORT _i;                                          \
            Len = 0;                                            \
            for (_i=0; _i < pvwTextHeader.tbh_cusName; _i++)    \
                ADDLEN(Len,apvwName[_i].tbn_pszName);           \
            ADDLEN(Len,pvwTextHeader.tbh_pszTitle);             \
            }

    /* Copies string b to a */
    #define STRCPY(a,b) { PCHAR _pC = b;                             \
                          while (*_pC) *a++ = *_pC++; *a++='\00'; }


    case FSC_PVW_SIZE :
        /* Return sizes of Data and Text blocks */

        if (usLenData != 2 * sizeof (USHORT))
            break;

        FS_MAYBLOCK(usRet = FSH_PROBEBUF(PB_OPWRITE,pcData,usLenData));
        if (usRet == NO_ERROR) {
            PVWNAME  far *apvwName = pvwTextHeader.tbh_ppvwName;
            USHORT       TxtLen    = 0;

            GETTXTLEN(TxtLen);

            ((USHORT far *) pcData)[0] = pvwData.pvdHeader.ddbh_usTotLen;
            ((USHORT far *) pcData)[1] = sizeof(PVWFSDTEXTH) +
                               sizeof(PVWNAME) * pvwTextHeader.tbh_cusName +
                               TxtLen;
            usLenDataOut = usLenData;
        }
    break;

    case FSC_PVW_DATA :
        /* Return Data block */

        if (usLenData != pvwData.pvdHeader.ddbh_usTotLen)
            break;

        FS_MAYBLOCK(usRet = FSH_PROBEBUF(PB_OPWRITE,pcData,usLenData));
        if (usRet == NO_ERROR) {
            memcpy(pcData,&pvwData,usLenData);
            usLenDataOut = usLenData;
        }
    break;

    case FSC_PVW_TEXT : {
        /* Return Text block */
        USHORT      TxtLen    = 0;
        PVWNAME far *apvwName = pvwTextHeader.tbh_ppvwName;
        GETTXTLEN(TxtLen);

        if (usLenData != sizeof(PVWFSDTEXTH) +
                         sizeof(PVWNAME) * pvwTextHeader.tbh_cusName +
                         TxtLen)
            break;

        FS_MAYBLOCK(usRet = FSH_PROBEBUF(PB_OPWRITE,pcData,usLenData));
        if (usRet == NO_ERROR) {
            USHORT i;
            PVWFSDTEXTH far * pHdr  = (PVWFSDTEXTH far *)pcData;
            PVWNAME far * pName = (PVWNAME far *)
                                      ((PBYTE)pcData + sizeof(PVWFSDTEXTH));
            PCHAR         pTxt  = ((PCHAR)pName) +
                                sizeof(PVWNAME) * pvwTextHeader.tbh_cusName;

            pHdr->tbh_usID     = pvwTextHeader.tbh_usID;
            pHdr->tbh_pszTitle = pTxt;
            pHdr->tbh_cusName  = pvwTextHeader.tbh_cusName;
            pHdr->tbh_ppvwName = pName;
            STRCPY(pTxt,pvwTextHeader.tbh_pszTitle);

            for (i=0; i<pvwTextHeader.tbh_cusName; i++) {
                pName[i].tbn_ulFlags = apvwName[i].tbn_ulFlags;
                pName[i].tbn_pszName = pTxt;
                STRCPY(pTxt,apvwName[i].tbn_pszName);
            }
            usLenDataOut = (USHORT)((PBYTE)pTxt - (PBYTE)pHdr);
        }
    break;
    }

#endif /* NEVER */

#endif
/**************************************************************************/

#ifndef MINIFSD
    case FSC_LW_BOTH:

        /*
         * Start the lazy write daemon.
         */

        usRet = StartLazyWriter(&frm);
        break;

    case FSC_LW2_START:

        /*
         * Start the lazy write daemon.
         */

        usRet = StartLazyWriter2(&frm);
        break;

    case FSC_LW3_START:

        /*
         * Start the lazy write daemon.
         */

        usRet = StartLazyWriter3(&frm);
        break;

    case FSC_LW_VDU_START:

        /*
         * Start the VDU lazy write daemon.
         */

        usRet = StartVDULazyWriter(&frm);
        break;

    case FSC_LW_STOP:

        /*
         * Stop the lazy write daemon.  This includes waiting for the cache to
         * be written to disk, so we can go back to uncached I/O.
         */
        StopLazyWriter(&frm);
        break;

    case FSC_LW_SET:

        /* set the lw params from the user's buffer */

        usLenParm = MIN (usLenParm, sizeof (LWPARMS));

        FS_MAYBLOCK (
            usRet = FSH_PROBEBUF (PB_OPREAD, pcParm, usLenParm);
        );

        if (usRet == NO_ERROR) {
            memcpy (&LWData, pcParm, usLenParm);
        }

        LWForceWakeUpFar(&frm);
        break;

    case FSC_LW_GET:
        {
            USHORT usLenToXfer;

            /* move the lw params into the user's buffer */
            LWData.cbuf = cBufs;
            LWData.fLWDaemon = fLWDaemon;
            usLenToXfer = MIN (usLenData, sizeof (LWPARMS));

            FS_MAYBLOCK (
                usRet = FSH_PROBEBUF (PB_OPWRITE, pcData, usLenToXfer);
            );
            if (usRet == NO_ERROR) {
                memcpy (pcData, &LWData, usLenToXfer);

                /* set size of returned data */
                usLenDataOut = usLenToXfer;
            }
        }
        break;

#else /* MINIFSD */
    case FSC_LW_BOTH:
    case FSC_LW_STOP:
    case FSC_LW_SET:
    case FSC_LW_GET:
    case FSC_LW_VDU_START:
    case FSC_LW_VDU_STOP:
        usRet = ERROR_INVALID_FUNCTION;
        break;
#endif /* MINIFSD */

    case FSC_GETFN:
        {
            /* Must be a handle-based call. */
            if (usArgType != FSCTL_ARG_FILEINSTANCE) {
                usRet = ERROR_INVALID_FUNCTION;
                break;
            }
            usRet = GetFNode (((PPB_SFD)(pArgDat->sf.psffsd->
                                                     sfd_work))->pfht->poft,
                              pcData,
                              usLenData,
                              &usLenDataOut,
                              &frm);

            break;
        }

    case FSC_GETSEC:
        {
            /* Must be a handle-based call. */
            if (usArgType != FSCTL_ARG_FILEINSTANCE) {
                usRet = ERROR_INVALID_FUNCTION;
                break;
            }
            usRet = GetSec (((PPB_SFD)(pArgDat->sf.psffsd->sfd_work))->pfht,
                            pcParm,
                            usLenParm,
                            pcData,
                            usLenData,
                            &usLenDataOut,
                            &frm);

            break;
        }

#ifdef DEBUG
    case FSC_MAKEBADSEC:
        {
            if (usArgType != FSCTL_ARG_CURDIR) {
                usRet = ERROR_INVALID_FUNCTION;
                break;
            }

            usRet = MakeBadSec (pArgDat->cd.pcdfsi->cdi_hVPB,
                                pcParm,
                                usLenParm,
                                &usLenParmOut);
            break;
        }
    case FSC_IOLIST:
        {
            if (usArgType != FSCTL_ARG_FILEINSTANCE) {
                usRet = ERROR_INVALID_FUNCTION;
                break;
            }

            usRet = IOList (pArgDat->sf.psffsi->sfi_hVPB,
                            pArgDat->sf.psffsi->sfi_selfsfn,
                            ((PPB_SFD)(pArgDat->sf.psffsd->
                                                     sfd_work))->pfht->poft,
                            pcParm,
                            usLenParm,
                            &usLenParmOut,
                            pcData,
                            usLenData,
                            &usLenDataOut);
            break;
        }
#endif

#ifdef STATS
    case FSC_GETSTAT:
        usRet = ERROR_NOT_SUPPORTED;
        break;

    case FSC_CLEAR:
        usRet = ERROR_NOT_SUPPORTED;
        break;
#endif

    case FSC_SECTORIO:
      {
          POFT poft;

          if (usArgType != FSCTL_ARG_FILEINSTANCE) {
              usRet = ERROR_INVALID_FUNCTION;
              break;
          }

          /* get oft from handle */
          poft = ((PPB_SFD)(pArgDat->sf.psffsd->sfd_work))->pfht->poft;

          /* make sure it is a DASD open */
          if( (poft->bSFlag & OFS_DASD) == 0 ) {
              usRet = ERROR_INVALID_HANDLE;
              break;
          }

          /* verify that it is a trusted app */
          if( usLenParm != sizeof(FSCP_PASSWD) ) {
              usRet = ERROR_INVALID_FUNCTION;
              break;
          }

          /*
           * The length of the password is correct, is the password
           * itself OK?
           */
          FS_MAYBLOCK (
              usRet = FSH_PROBEBUF (PB_OPREAD, pcParm, usLenParm );
          );
          if (usRet != NO_ERROR) {
              break;
          }

          if( *((PULONG)pcParm) != FSCP_PASSWD ) {
              /* password not correct */
              usRet = ERROR_INVALID_FUNCTION;
              break;
          }

          /*
           * The calling app has sent us a valid handle, and has
           * proven that it knows what it is doing.
           * Allow it to use sector I/O
           */

          /* set the Sector I/O bit in the oft */
          poft->bSFlag |= OFS_SECTORIO;

#if defined( DEBUG_DASD ) || defined( DEBUG )
          /* verify OFT right quick */
          if( poft->ulFAllocLen != poft->ulFActualLen ||
              poft->ulFAllocLen != poft->fst.ulVLen   ||
              poft->ulFAllocLen != pArgDat->sf.psffsi->sfi_size ||
              (poft->ulFAllocLen & SECMASK) != 0 ) {
              INTERR( "fs_fsctl: DASD OFT is screwed up" );
          }
#endif
          /*
           * If this is a normal size disk, then we need to convert
           * the sizes in the handle to be sector based and not byte based.
           */
          if( (poft->bSFlag & OFS_ONLYSECIO) == 0 ) {
              pArgDat->sf.psffsi->sfi_size =
              poft->ulFActualLen =
              poft->ulFAllocLen =
              poft->fst.ulVLen = poft->ulFAllocLen / SECSIZE;
          }

          break;
      }
    default:
        usRet = ERROR_INVALID_FUNCTION;
    }

    /* now update the length of the parm returned to the caller */

        {   USHORT usTemp;
            FS_MAYBLOCK (
                usTemp = FSH_PROBEBUF (PB_OPWRITE,
                                      (PCHAR) pusLenParmOut,
                                      sizeof (USHORT))
            );
            if (usTemp == NO_ERROR) {
                *pusLenParmOut = usLenParmOut;
            }
        }

    /* now update the length of the data returned to the caller */
        {   USHORT usTemp;

            FS_MAYBLOCK (
                usTemp = FSH_PROBEBUF (PB_OPWRITE,
                                      (PCHAR) pusLenDataOut,
                                      sizeof (USHORT));
            );
            if (usTemp == NO_ERROR) {
                *pusLenDataOut = usLenDataOut;
            }
        }

    return (usRet);

    /* for the compiler /W3 */
    pArgDat; usArgType; pcParm; usLenParm;
}
