/****************************************************************************/
/*                                      */
/*  WFSYS.C -                                   */
/*                                      */
/*  Routines for Making Bootable Floppies                   */
/*                                      */
/****************************************************************************/

#include "winfile.h"
#include "winnet.h"
#include "lfn.h"
#include "wfcopy.h"

#define CSYSFILES   3   /* Three system files are to be copied */
#define SYSFILENAMELEN  16  /* Example "A:\????????.???\0" */

#define SEEK_END    2   /* Used by _llseek() */

/* Error Codes.  NOTE: Don't change this order! */
#define NOERROR     0   /* No error           */
#define NOMEMORY    1   /* Insufficient memory    */
#define NOSRCFILEBIOS   2   /* BIOS is missing    */
#define NOSRCFILEDOS    3   /* DOS is missing     */
#define NOSRCFILECMD    4   /* Command.Com is missing */
#define COPYFILEBIOS    5   /* Error in copying BIOS  */
#define COPYFILEDOS 6   /* Error in copying DOS   */
#define COPYFILECMD 7   /* Error in copying Command.com */
#define INVALIDBOOTSEC  8
#define INVALIDDSTDRIVE 9
#define DSTDISKERROR    10
#define NOTSYSABLE  11  /* First N clusters are NOT empty */
#define NOTSYSABLE1 12  /* First 2 entries in ROOT are not sys files */
#define NOTSYSABLE2 13  /* First N clusters are not allocated to SYS files */
#define NODISKSPACE 14  /* There is not sufficient disk space */

#define BUFFSIZE    8192
#define SECTORSIZE  512

LONG SysFileSize[CSYSFILES];

CHAR BIOSfile[SYSFILENAMELEN];
CHAR DOSfile[SYSFILENAMELEN];
CHAR COMMANDfile[130];  /* Command.com can have a full path name in COMSPEC= */
CHAR *SysFileNamePtr[CSYSFILES]; /* Ptrs to source file names */

/* SysNameTable contains the names of System files; First for PCDOS, the
 * second set for MSDOS.
 */
CHAR *SysNameTable[2][3] = {
    {"IBMBIO.COM", "IBMDOS.COM", "COMMAND.COM"},
    {"IO.SYS",     "MSDOS.SYS",  "COMMAND.COM"}
};


BOOL
IsSYSable(
         WORD    iSrceDrive,
         WORD    iDestDrive,
         CHAR    DestFileNames[][SYSFILENAMELEN],       /* NOTE: 2-dimensional array */
         LPSTR   lpFileBuff
         );



/*--------------------------------------------------------------------------*/
/*                                      */
/*  SameFilenames() -                               */
/*                                      */
/*--------------------------------------------------------------------------*/

/* This checks whether the two filenames are the same or not.
 * The problem lies in the fact that lpDirFileName points to the
 * filename as it appears in a directory (filename padded with blanks
 * up to eight characters and then followed by extension). But
 * szFileName is an ASCII string with no embedded blanks and has a
 * dot that seperates the extension from file name.
 */

BOOL
SameFilenames(
             LPSTR lpDirFileName,
             LPSTR szFileName
             )
{
    INT   i;
    CHAR  c1;
    CHAR  c2;

    /* lpDirFileName definitely has 11 characters (8+3). Nothing more!
     * Nothing less!
     */
    for (i=0; i < 11; i++) {
        c1 = *lpDirFileName++;
        c2 = *szFileName++;
        if (c2 == '.') {
            /* Skip all the blanks at the end of the filename */
            while (c1 == ' ' && i < 11) {
                c1 = *lpDirFileName++;
                i++;
            }

            c2 = *szFileName++;
        }
        if (c1 != c2)
            break;
    }
    return (i != 11);
}


/*--------------------------------------------------------------------------*/
/*                                      */
/*  HasSystemFiles() -                              */
/*                                      */
/*--------------------------------------------------------------------------*/

/* See if the specified disk has IBMBIO.COM and IBMDOS.COM (or IO.SYS and
 * MSDOS.SYS).  If so, store their sizes in SysFileSize[].
 */

BOOL
APIENTRY
HasSystemFiles(
              WORD iDrive
              )
{
    INT      i;
    HFILE    fh;
    DPB      DPB;
    BOOL     rc;
    CHAR     ch;
    LPSTR    lpStr;
    LPSTR    lpFileBuff;
    OFSTRUCT OFInfo;
    HANDLE   hFileBuff;

    /* Initialise the source filename pointers */
    SysFileNamePtr[0] = &BIOSfile[0];
    SysFileNamePtr[1] = &DOSfile[0];
    SysFileNamePtr[2] = &COMMANDfile[0];

    hFileBuff = NULL;
    lpFileBuff = NULL;

    /* Acertain the presence of BIOS/DOS/COMMAND and grab their sizes.
     * First we will try IBMBIO.COM. If it does not exist, then we will try
     * IO.SYS. It it also does not exist, then it is an error.
     */

    /* Get the DPB */
    if (GetDPB(iDrive, &DPB) != NOERROR)
        goto HSFError;

    /* Check if the iDrive has standard sector size; If it doesn't then report
     * error; (We can allocate a bigger buffer and proceed at this point, but
     * int25 to read an abosolute sector may not work in pmodes, because they
     * assume standard sector sizes;)
     * Fix for Bug #10632  --SANKAR-- 03-21-90
     */
    if (HIWORD(GetClusterInfo(iDrive)) > SECTORSIZE)
        goto HSFError;

    /* Allocate enough memory to read the first cluster of root dir. */
    if (!(hFileBuff = LocalAlloc(LHND, (DWORD)SECTORSIZE)))
        goto HSFError;

    if (!(lpFileBuff = LocalLock(hFileBuff)))
        goto HSFError;

    /* Read the first cluster of the root directory. */
    if (MyInt25(iDrive, lpFileBuff, 1, DPB.dir_sector))
        goto HSFError;

    /* Let us start with the first set of system files. */
    for (i=0; i <= CSYSFILES-1; i++) {
        lstrcpy((LPSTR)SysFileNamePtr[i], "C:\\");
        lstrcat((LPSTR)SysFileNamePtr[i], SysNameTable[0][i]);
        *SysFileNamePtr[i] = (BYTE)('A'+iDrive);
    }
    /* Get the command.com from the COMSPEC= environment variable */
    lpStr = MGetDOSEnvironment();

    /* Find the COMSPEC variable. */
    while (*lpStr != TEXT('\0')) {
        if (lstrlen(lpStr) > 8) {
            ch = lpStr[7];
            lpStr[7] = TEXT('\0');
            if (lstrcmpi(lpStr, (LPSTR)"COMSPEC") == 0) {
                lpStr[7] = ch;
                break;
            }
        }
        lpStr += lstrlen(lpStr)+1;
    }

    /* If no COMSPEC then things are really roached... */
    if (*lpStr == TEXT('\0'))
        goto HSFError;

    /* The environment variable is COMSPEC; Look for '=' char */
    while (*lpStr != '=')
        lpStr = AnsiNext(lpStr);

    /* Copy the command.com with the full pathname */
    lstrcpy((LPSTR)SysFileNamePtr[2], lpStr);

    /* Check if the IBMBIO.COM and IBMDOS.COM exist. */
    if (SameFilenames(lpFileBuff, (LPSTR)(SysFileNamePtr[0]+3)) ||
        SameFilenames(lpFileBuff+sizeof(DIRTYPE), (LPSTR)(SysFileNamePtr[1]+3))) {
        /* Check if at least IO.SYS and MSDOS.SYS exist. */
        lstrcpy((LPSTR)(SysFileNamePtr[0]+3), SysNameTable[1][0]);
        lstrcpy((LPSTR)(SysFileNamePtr[1]+3), SysNameTable[1][1]);
        if (SameFilenames(lpFileBuff, (SysFileNamePtr[0]+3)) ||
            SameFilenames(lpFileBuff+sizeof(DIRTYPE), (SysFileNamePtr[1]+3)))
            goto HSFError;
    }

    /* Check if COMMAND.COM exists in the source drive. */
    if ((fh = MOpenFile((LPSTR)SysFileNamePtr[2], (LPOFSTRUCT)&OFInfo, OF_READ)) == -1)
        goto HSFError;

    /* Get the file sizes. */
    SysFileSize[0] = ((LPDIRTYPE)lpFileBuff)->size;
    SysFileSize[1] = ((LPDIRTYPE)(lpFileBuff+sizeof(DIRTYPE)))->size;
    SysFileSize[2] = M_llseek(fh, 0L, SEEK_END);
    M_lclose(fh);
    rc = TRUE;
    goto HSFExit;

    HSFError:
    rc = FALSE;

    HSFExit:
    if (lpFileBuff)
        LocalUnlock(hFileBuff);
    if (hFileBuff)
        LocalFree(hFileBuff);
    MFreeDOSEnvironment(lpStr);

    return (rc);
}


/*--------------------------------------------------------------------------*/
/*                                      */
/*  CalcFreeSpace() -                               */
/*                                      */
/*--------------------------------------------------------------------------*/

/* Given an array of filenames and the number of files, this function
 * calculates the freespace that would be created if those files are deleted.
 *
 * NOTE: This function returns TOTAL free space, (i.e) the summation of
 *   already existing free space and the space occupied by those files.
 */

INT
CalcFreeSpace(
             CHAR    DestFiles[][SYSFILENAMELEN],
             INT cFiles,
             INT cbCluster,
             WORD    wFreeClusters,
             WORD    wReqdClusters
             )
{
    INT   i;
    HFILE fh;
    LONG  lFileSize;
    OFSTRUCT OFInfo;

    ENTER("CalcFreeSpace");

    /* Find out the space already occupied by SYS files, if any. */
    for (i=0; i < cFiles; i++) {
        fh = MOpenFile(&DestFiles[i][0], &OFInfo, OF_READ);
        if (fh != (HFILE)-1) {
            /* Get the file size */
            lFileSize = M_llseek(fh, 0L, SEEK_END);

            if (lFileSize != -1L)
                wFreeClusters += LOWORD((lFileSize + cbCluster - 1)/cbCluster);

            M_lclose(fh);

            if (wFreeClusters >= wReqdClusters)
                return (wFreeClusters);
        }
    }
    LEAVE("CalcFreeSpace");
    return (wFreeClusters);
}


/*--------------------------------------------------------------------------*/
/*                                      */
/*  CheckDiskSpace() -                              */
/*                                      */
/*--------------------------------------------------------------------------*/

BOOL
CheckDiskSpace(
              WORD    iDestDrive,
              INT cbCluster,              /* Bytes/Cluster of dest drive */
              CHAR    DestFileNames[][SYSFILENAMELEN],    /* NOTE: 2-dimensional array */
              BOOL    bDifferentSysFiles,
              CHAR    DestSysFiles[][SYSFILENAMELEN]
              )

{
    INT   i;
    INT   wFreeClusters;
    INT   wReqdClusters;

    /* Compute the number of clusters required. */
    wReqdClusters = 0;
    for (i=0; i < CSYSFILES; i++)
        wReqdClusters += LOWORD((SysFileSize[i] + cbCluster - 1) / cbCluster);

    /* Calculate the free disk space in clusters in the destination disk */
    wFreeClusters = LOWORD(GetFreeDiskSpace(iDestDrive) / cbCluster);

    if (wFreeClusters >= wReqdClusters)
        /* We have enough space. */
        return (TRUE);

    wFreeClusters = CalcFreeSpace(DestFileNames, CSYSFILES, cbCluster, (WORD)wFreeClusters, (WORD)wReqdClusters);
    if (wFreeClusters >= wReqdClusters)
        return (TRUE);

    /* Check if the sys files in the dest disk are different. */
    if (bDifferentSysFiles) {
        wFreeClusters = CalcFreeSpace(DestSysFiles, 2, cbCluster, (WORD)wFreeClusters, (WORD)wReqdClusters);
        if (wFreeClusters >= wReqdClusters)
            return (TRUE);
    }

    /* Insufficient disk space even if we delete the sys files. */
    return (FALSE);
}



/*--------------------------------------------------------------------------*/
/*                                      */
/*  IsSYSable() -                               */
/*                                      */
/*--------------------------------------------------------------------------*/

/* The requirements for the destination disk to be sysable are either:
 *
 * 1) first two directory entries are empty
 * 2) the first N clusters free where N = ceil (size IBMBIO/secPerClus)
 * 3) there is enough room on the disk for IBMBIO/IBMDOS/COMMAND
 *
 * - or -
 *
 * 1) the first two directory entries are IBMBIO.COM and IBMDOS.COM
 *                    or  IO.SYS and MSDOS.SYS
 * 2) the first N clusters are alloced to these files where N is defines above.
 * 3) there is enough room on the disk for IBMBIO/IBMDOS/COMMAND after
 *     deleting the IBMBIO/IBMDOS/COMMAND on the disk.
 *
 *  IMPORTANT NOTE:
 *  DestFileNames[][] contain the names of the sys files that would be
 *  created on the Destination diskette;
 *  DestSysFiles[][] contain the names of the sys files already
 *  present in the destination diskette, if any; Please Note that
 *  these two sets of filenames need not be the same, because you can
 *  install MSDOS on to a diskette that already has PCDOS and
 *  vice-versa.
 */

BOOL
IsSYSable(
         WORD    iSrceDrive,
         WORD    iDestDrive,
         CHAR    DestFileNames[][SYSFILENAMELEN],       /* NOTE: 2-dimensional array */
         LPSTR   lpFileBuff
         )
{
#ifdef LATER
    INT       i;
    DPB       DPB;
    WORD      clusTmp1, clusTmp2;
    WORD      clusBIOS, clusDOS;
    INT       cBytesPerCluster;
    INT       cBIOSsizeInClusters;
    BOOL      bDifferentDestFiles = FALSE;
    CHAR      chVolLabel[11];     /* This is NOT null terminated */
    DWORD     dwSerialNo;
    CHAR      DestSysFiles[2][SYSFILENAMELEN];
    INT       cContigClusters;
    DWORD     dwClusterInfo;
    CHAR      szTemp[SYSFILENAMELEN];

    /* Grab DPB for destination. */
    if (GetDPB(iDestDrive, &DPB))
        return (FALSE);

    /* Has the user aborted? */
    if (WFQueryAbort())
        return (FALSE);

    /* Get bytes per cluster for destination. */
    dwClusterInfo = GetClusterInfo(iDestDrive);
    /* Bytes per cluster = sectors per cluster * size of a sector   */
    cBytesPerCluster = LOWORD(dwClusterInfo) * HIWORD(dwClusterInfo);
    if (!cBytesPerCluster)
        return (FALSE);

    /* Has the user aborted? */
    if (WFQueryAbort())
        return (FALSE);

    /* Convert size of BIOS into full clusters */
    cBIOSsizeInClusters = LOWORD((SysFileSize[0] + cBytesPerCluster - 1) / cBytesPerCluster);

    /* Number of clusters required to be contiguous depends on DOS versions.
     * DOS 3.2 and below expect all clusters of BIOS to be contiguos.
     * But 3.3 and above expect only the first stub loader (<2K) to be contiguous.
     */
    cContigClusters = (GetDOSVersion() > 0x314) ?
                      ((2048 + cBytesPerCluster - 1)/cBytesPerCluster) :
                      cBIOSsizeInClusters;

    /* Grab first sector of destination root directory */
    if (MyInt25(iDestDrive, lpFileBuff, 1, DPB.dir_sector))
        return (FALSE);

    /* Has the user aborted? */
    if (WFQueryAbort())
        return (FALSE);

    /* Are the first two directory entries empty? */
    if ((lpFileBuff[0]           == 0 || (BYTE)lpFileBuff[0]           == 0xE5) &&
        (lpFileBuff[sizeof(DIRTYPE)] == 0 || (BYTE)lpFileBuff[sizeof(DIRTYPE)] == 0xE5)) {
        /* Any of first N (= BIOS size) clusters not empty? */
        for (i=0; i < cContigClusters; i++) {
            /* Has the user aborted? */
            if (WFQueryAbort())
                return (FALSE);
        }
    } else {
        /* Are the first two directory entries NOT BIOS/DOS? */
        for (i=0; i < 2; i++) {
            if ((!SameFilenames(lpFileBuff, SysNameTable[i][0])) ||
                (!SameFilenames(lpFileBuff+sizeof(DIRTYPE), SysNameTable[i][1]))) {
                /* Check if the destination files are the same as the source files */
                if (lstrcmpi(&DestFileNames[0][3], SysNameTable[i][0])) {
                    /* No! Delete the other set of filenames. */
                    DestSysFiles[0][0] = DestSysFiles[1][0] = (BYTE)('A'+iDestDrive);
                    lstrcpy(&DestSysFiles[0][1], ":\\");
                    lstrcpy(&DestSysFiles[0][3], SysNameTable[i][0]);
                    lstrcpy(&DestSysFiles[1][1], ":\\");
                    lstrcpy(&DestSysFiles[1][3], SysNameTable[i][1]);
                    bDifferentDestFiles = TRUE;
                }
                break;
            }
        }

        /* Did we find a match? */
        if (i == 2)
            /* Nope, the 2 entries are occupied by non-system files. */
            return (FALSE);

        /* Any of first N clusters NOT allocated to BIOS/DOS? */
        clusBIOS = ((LPDIRTYPE)lpFileBuff)->first;
        clusDOS = ((LPDIRTYPE)(lpFileBuff + sizeof(DIRTYPE)))->first;

        /* Do it the hard way, for each cluster 2..N+2 see if it is in the chain.
         */
        for (i=0; i < cContigClusters; i++) {
            clusTmp1 = clusBIOS;
            clusTmp2 = clusDOS;

            /* Check if cluster #i+2 is allocated to either of these files. */
            while (TRUE) {
                if (i+2 == (INT)clusTmp1 || i+2 == (INT)clusTmp2)
                    break;

//            if (clusTmp1 != -1)
                if (clusTmp1 < 0xFFF0)
                    clusTmp1 = 0;
//            if (clusTmp2 != -1)
                if (clusTmp2 < 0xFFF0)
                    clusTmp2 = 0;
//            if (clusTmp1 == -1 && clusTmp2 == -1)
                if (clusTmp1 >= 0xFFF0 && clusTmp2 >= 0xFFF0)
                    return FALSE;

                /* Did the user abort? */
                if (WFQueryAbort())
                    return FALSE;
            }
        }
    }

    /* Let us check if there is enough space on the dest disk. */
    if (CheckDiskSpace(iDestDrive, cBytesPerCluster, DestFileNames, bDifferentDestFiles, DestSysFiles) == FALSE)
        return (FALSE);

    /* Has the user aborted? */
    if (WFQueryAbort())
        return (FALSE);

    /* Get the Present Volume label and preserve it. */
    GetVolumeLabel(iDestDrive, (LPSTR)chVolLabel, FALSE);

    /*** NOTE: chVolLabel remains in OEM characters! ***/

    /* Get the serial no if any and preserve it. */
    dwSerialNo = ReadSerialNumber(iDestDrive, lpFileBuff);

    /* Copy and adjust boot sector from source to destination */
    if (WriteBootSector(iSrceDrive, iDestDrive, NULL, lpFileBuff) != NOERROR)
        return (FALSE);

    /* Restore the old volume label and serial number in the boot rec. */
    if (ModifyVolLabelInBootSec(iDestDrive, (LPSTR)chVolLabel, dwSerialNo, lpFileBuff))
        return (FALSE);

    /* Delete destination BIOS/DOS/COMMAND. */
    for (i=0; i < CSYSFILES; i++) {
        AnsiToOem(DestFileNames[i], szTemp);
        SetFileAttributes(szTemp, 0);
        DosDelete(szTemp);
        if ((bDifferentDestFiles) && (i < 2)) {
            SetFileAttributes(szTemp, 0);
            DosDelete(szTemp);
        }

        /* Has the user aborted? */
        if (WFQueryAbort())
            return (FALSE);
    }

    /* Reset the DPB_next_free field of the DPB to 2, sothat when IBMBIO.COM is
     * copied into this disk, the clusters will get allocated starting from 2.
     */
    ModifyDPB(iDestDrive);
#endif // LATER

    return (TRUE);
}


/*--------------------------------------------------------------------------*/
/*                                      */
/*  MakeSystemDiskette() -                          */
/*                                      */
/*--------------------------------------------------------------------------*/

/* This routine is intended to mimic the functions of the SYS command
 * under MSDOS:  to transfer a version of the operating system from a source
 * disk to a destination such that the destination will be bootable.
 *
 *  The requirements of the source disk is that it contain:
 *
 *      1) a command processor (COMMAND.COM)
 *      2) a default set of device drivers (IBMBIO.COM)
 *      3) an operating system (IBMDOS.COM)
 *      4) a boot sector appropriate to the device drivers
 *
 *  The requirements for the destination disk are either:
 *
 *      1) first two directory entries are empty
 *      2) the first N clusters free where N = ceil (size IBMBIO/secPerClus)
 *      3) there is enough room on the disk for IBMBIO/IBMDOS/COMMAND
 *
 *  - or -
 *
 *      1) the first two directory entries are IBMBIO.COM and IBMDOS.COM
 *                                         or  IO.SYS and MSDOS.SYS
 *      2) the first N clusters are alloced to these files where N is defined
 *          above
 *      3) there is enough room on the disk for IBMBIO/IBMDOS/COMMAND after
 *      deleting the IBMBIO/IBMDOS/COMMAND on the disk.
 *
 *  Inputs:
 *        iDestDrive 0-based drive number of formatted drive
 *                       for destination.
 *        bEmptyFloppy : TRUE if the floppy is empty; Useful when
 *          the floppy is just formatted; No need to check if
 *          it is Sysable;
 *  Returns:    0       Successful transferral of boot sector and files
 *      <> 0    error code.
 */


BOOL
APIENTRY
MakeSystemDiskette(
                  WORD iDestDrive,
                  BOOL bEmptyFloppy
                  )
{
    INT       i;
    HANDLE    hFileBuff;  /* Buffer to read in file contents etc., */
    LPSTR     lpFileBuff;
    CHAR      DestFileName[CSYSFILES][SYSFILENAMELEN];
    CHAR      szTemp1[SYSFILENAMELEN];
    CHAR      szTemp2[SYSFILENAMELEN];
    WORD      nSource;

    nSource = (WORD)GetBootDisk();


    if (!HasSystemFiles(nSource)) {
        LoadString(hAppInstance, IDS_SYSDISKNOFILES, szMessage, sizeof(szMessage));
        MessageBox(hdlgProgress, szMessage, szTitle, MB_OK | MB_ICONSTOP);
        bUserAbort = TRUE;
        return FALSE;
    }

    if (iDestDrive == nSource) {
        LoadString(hAppInstance, IDS_SYSDISKSAMEDRIVE, szMessage, sizeof(szMessage));
        MessageBox(hdlgProgress, szMessage, szTitle, MB_OK | MB_ICONSTOP);
        bUserAbort = TRUE;
        return FALSE;
    }

    /* Initialize variables for cleanup. */
    hFileBuff = NULL;
    lpFileBuff = NULL;

    /* Flush the DOS buffers. */
    DiskReset();

    if (!(hFileBuff = LocalAlloc(LHND, (DWORD)BUFFSIZE)))
        return (1);

    lpFileBuff = LocalLock(hFileBuff);

    for (i=0; i < (CSYSFILES - 1); i++) {
        /* Create the destination file names */
        lstrcpy((LPSTR)&DestFileName[i][0], (LPSTR)SysFileNamePtr[i]);
        DestFileName[i][0] = (BYTE)('A' + iDestDrive);
    }

    /* Copy just the Command.COM without any path name */
    lstrcpy((LPSTR)DestFileName[2], "X:\\");
    lstrcat((LPSTR)DestFileName[2], (LPSTR)SysNameTable[0][2]);
    DestFileName[2][0] = (BYTE)('A' + iDestDrive);

    /* Check if it is an empty floppy; If so, there is no need to check if it
     * is 'SYSable'. It is bound to be 'Sysable'. So, skip all the checks and
     * go ahead with copying the sys files.
     */
    if (!bEmptyFloppy) {

        /* Check if the Destination floppy is SYS-able */
        if (!IsSYSable(nSource, iDestDrive, DestFileName, lpFileBuff))
            goto MSDErrExit;

        /* Did the user abort? */
        if (WFQueryAbort())
            goto MSDErrExit;
    }

    /* Copy files */

    bCopyReport = FALSE;

    DisableFSC();

    for (i=0; i < CSYSFILES; i++) {
        /* Copy all files except command.com with sys attributes */
        AnsiToOem(SysFileNamePtr[i], szTemp1);
        AnsiToOem(DestFileName[i], szTemp2);

        /* Make sure the destination file is deleted first */
        SetFileAttributes(szTemp2, ATTR_ALL);
        WFRemove(szTemp2);

        // copy code preserves the attributes
        if (FileCopy(szTemp1, szTemp2))
            goto MSDErrExit2;

        if (WFQueryAbort())
            goto MSDErrExit2;
    }


    if (EndCopy())          // empty the copy queue
        goto MSDErrExit2;

    EnableFSC();

    /* Normal Exit. */

    LocalUnlock(hFileBuff);
    LocalFree(hFileBuff);

    return FALSE;     // success

    MSDErrExit2:

    EnableFSC();

    MSDErrExit:

    CopyAbort();      // Purge any copy commands in copy queue
    LocalUnlock(hFileBuff);
    LocalFree(hFileBuff);

    return TRUE;      // failure
}