/***    diamond.c - Main program for DIAMOND.EXE
 *
 *      Microsoft Confidential
 *      Copyright (C) Microsoft Corporation 1993-1994
 *      All Rights Reserved.
 *
 *  Author:
 *      Benjamin W. Slivka
 *
 *  History:
 *      10-Aug-1993 bens    Initial version
 *      11-Aug-1993 bens    Improved assertion checking technology
 *      14-Aug-1993 bens    Removed message test code
 *      20-Aug-1993 bens    Add banner and command-line help
 *      21-Aug-1993 bens    Add pass 1 and pass 2 variable lists
 *      10-Feb-1994 bens    Start of real pass 1/2 work
 *      11-Feb-1994 bens    .SET and file copy commands -- try calling FCI!
 *      14-Feb-1994 bens    Call FCI for the first time - it works!
 *      15-Feb-1994 bens    Support /D and single-file compression
 *      16-Feb-1994 bens    Update for improved FCI interfaces; disk labels;
 *                              ensure output directories exist
 *      20-Feb-1994 bens    Move general file routines to fileutil.* so
 *                              extract.c can use them.
 *      23-Feb-1994 bens    Generate INF file
 *      28-Feb-1994 bens    Supply new FCI tempfile callback
 *      01-Mar-1994 bens    Add timing and generate summary report file
 *      15-Mar-1994 bens    Add RESERVE support
 *      21-Mar-1994 bens    Updated to renamed FCI.H definitions
 *      22-Mar-1994 bens    Add english error messages for FCI errors
 *      28-Mar-1994 bens    Add cabinet setID support
 *      29-Mar-1994 bens    Fix bug in compressing files w/o extensions
 *      30-Mar-1994 bens    Layout files outside of cabinets
 *      18-Apr-1994 bens    Add /L switch
 *      20-Apr-1994 bens    Fix cabinet/disk size accounting
 *      21-Apr-1994 bens    Print out c run-time errno in FCI failures
 *      22-Apr-1994 bens    Add checking for unique file names in cabinet set
 *      03-May-1994 bens    Add customizable INF stuff
 *      27-May-1994 bens    Add Quantum support
 *      03-Jun-1994 bens    Add .Option Explicit, .Define support
 *      13-Jul-1994 bens    Add DoNotCopyFiles
 *      27-Jul-1994 bens    Support quotes in .InfWrite[Xxx]; /SIZE qualifier
 *                              for reserving space.
 *      14-Dec-1994 bens    Implement *csum* support
 */

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <malloc.h>
#include <fcntl.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <io.h>
#include <errno.h>
#include <direct.h>

#ifdef BIT16
#include <dos.h>
#else // !BIT16
//** Get minimal Win32 definitions
//   In particular, variable.h defines VARTYPE and VARFLAGS, which
//   the OLE folks have also defined for OLE automation.
//#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#undef ERROR    // Override stupid "#define ERROR 0" in wingdi.h
#endif // !BIT16

#include "types.h"
#include "asrt.h"
#include "error.h"
#include "mem.h"
#include "message.h"

#include "dfparse.h"
#include "inf.h"
#include "filelist.h"
#include "fileutil.h"
#include "misc.h"
#include "glist.h"

#include "diamond.msg"
#include "dfparse.msg"      // Get standard variable names

#include "crc32.h"


#ifdef BIT16
#include "chuck\fci.h"
#else // !BIT16
#include "chuck\nt\fci.h"
#endif // !BIT16

#ifndef BIT16
#include "filever.h"
#endif // !BIT16


//** Macros

#define cbDF_BUFFER          4096   // Buffer size for reading directives files

#define cbFILE_COPY_BUFFER  32768   // Buffer size for copying files


//** Types

typedef struct {
    PSESSION    psess;
    PERROR      perr;
} SESSIONANDERROR; /* sae */
typedef SESSIONANDERROR *PSESSIONANDERROR; /* psae */


//** Function Prototypes

FNASSERTFAILURE(fnafReport);
FNDIRFILEPARSE(fndfpPassONE);
FNDIRFILEPARSE(fndfpPassTWO);

BOOL      addCmdLineVar(PSESSION psess, char *pszArg, PERROR perr);
HFILESPEC addDirectiveFile(PSESSION psess, char *pszArg, PERROR perr);
HGENERIC  addFileToSession(PSESSION  psess,
                           char     *pszSrc,
                           char     *pszDst,
                           long      cbFile,
                           PCOMMAND  pcmd,
                           PERROR    perr);
BOOL      buildInfAndRpt(PSESSION psess, PERROR perr);
BOOL      ccabFromSession(PCCAB pccab,
                          PSESSION psess,
                          ULONG cbPrevCab,
                          PERROR perr);
BOOL      checkDiskClusterSize(PSESSION psess, PERROR perr);
BOOL      checkReferences(PSESSION psess, PERROR perr);
BOOL      checkVariableDefinitions(PSESSION psess, PERROR perr);
void      computeSetID(PSESSION psess, char *psz);
BOOL      doDefine(PSESSION psess,PCOMMAND pcmd, BOOL fPass2, PERROR perr);
BOOL      doDelete(PSESSION psess, PCOMMAND pcmd, BOOL fPass2, PERROR perr);
BOOL      doDump(PSESSION psess,PCOMMAND pcmd, BOOL fPass2, PERROR perr);
BOOL      doFile(PSESSION psess, PCOMMAND pcmd, BOOL fPass2, PERROR perr);
BOOL      doNew(PSESSION psess, PCOMMAND pcmd, BOOL fPass2, PERROR perr);
BOOL      doOption(PSESSION psess, PCOMMAND pcmd, BOOL fPass2, PERROR perr);
BOOL      doReference(PSESSION psess, PCOMMAND pcmd, BOOL fPass2, PERROR perr);
BOOL      ensureCabinet(PSESSION psess, PERROR perr);
BOOL      ensureCabinetsFlushed(PSESSION psess, PERROR perr);
BOOL      executeCommand(PSESSION psess,PCOMMAND pcmd,BOOL fPass2,PERROR perr);
BOOL      getCompressedFileName(PSESSION psess,
                                char *   pszResult,
                                int      cbResult,
                                char *   pszSrc,
                                PERROR   perr);
BOOL      getFileChecksum(char  *pszFile, ULONG *pchecksum, PERROR perr);
long      getMaxDiskSize(PSESSION psess, PERROR perr);
BOOL      getVarWithOverride(PSESSION  psess,
                             char     *pchDst,
                             int       cbDst,
                             char     *pszPattern,
                             char     *pszVar,
                             int       i,
                             char     *pszKind,
                             PERROR    perr);
BOOL      inCabinet(PSESSION psess, PERROR perr);
char     *mapCRTerrno(int errno);
BOOL      modeInfAddLine(PSESSION  psess,
                         INFAREA   inf,
                         char     *pszLine,
                         PERROR    perr);
BOOL      modeInfAddFile(PSESSION  psess,
                         char     *pszFile,
                         int       iDisk,
                         int       iCabinet,
                         PERROR    perr);
BOOL      newDiskIfNecessary(PSESSION psess,
                             long     cbConsume,
                             BOOL     fSubOnNewDisk,
                             PERROR   perr);
BOOL      parseCommandLine(PSESSION psess,int cArg,char *apszArg[],PERROR perr);
void      printError(PSESSION psess, PERROR perr);
BOOL      processDirectives(PSESSION psess, PERROR perr);
BOOL      processFile(PSESSION psess, PERROR perr);
void      resetSession(PSESSION psess);
BOOL      setCabinetReserve(PCCAB pccab, PSESSION psess, PERROR perr);
BOOL      setDiskParameters(PSESSION  psess,
                            char     *pszDiskSize,
                            long      cbDisk,
                            PERROR    perr);
BOOL      setVariable(PSESSION  psess,
                      char     *pszName,
                      char     *pszValue,
                      PERROR    perr);
int       tcompFromSession(PSESSION psess, PERROR perr);
void      updateHgenLast(PSESSION psess, char *pszDst);

FNOVERRIDEFILEPROPERTIES(fnofpDiamond);


//** FCI callbacks
FNALLOC(fciAlloc);
FNFREE(fciFree);
FNFCIGETNEXTCABINET(fciGetNextCabinet);
FNFCIGETNEXTCABINET(fciGetNextCabinet_NOT);
FNFCIFILEPLACED(fciFilePlaced);
FNFCIGETOPENINFO(fciOpenInfo);
FNFCISTATUS(fciStatus);
FNFCIGETTEMPFILE(fciTempFile);

void mapFCIError(PERROR perr, PSESSION psess, char *pszCall, PERF perf);


//** Functions

/***    main - Diamond main program
 *
 *  See DIAMOND.DOC for spec and operation.
 *
 *  NOTE: We're sloppy, and don't free resources allocated by
 *        functions we call, on the assumption that program exit
 *        will clean up memory and file handles for us.
 */
int __cdecl main (int cArg, char *apszArg[])
{
    ERROR       err;
    HVARLIST    hvlist;                 // Variable list for Pass 1
    PSESSION    psess;
    int         rc;                     // Return code

    AssertRegisterFunc(fnafReport);     // Register assertion reporter
    MemSetCheckHeap(FALSE);             // Turn off slow heap checking

    ErrClear(&err);                     // No error
    err.pszFile = NULL;                 // No file being processed, yet

    //** Initialize session
    psess = MemAlloc(sizeof(SESSION));
    if (!psess) {
        ErrSet(&err,pszDIAERR_NO_SESSION);
        printError(psess,&err);
        exit(1);
    }
    SetAssertSignature((psess),sigSESSION);
#ifndef REMOVE_CHICAGO_M6_HACK
    psess->fFailOnIncompressible = FALSE; // Don't fail on incompressible data
#endif
    psess->fExplicitVarDefine  = FALSE;  // Don't require .Define
    psess->fGetVerInfo         = FALSE;  // Don't get version info
    psess->fGetFileChecksum    = FALSE;  // Don't compute file checksums
    psess->fPass2              = FALSE;  // Pass 1
    psess->hflistDirectives    = NULL;
    psess->hvlist              = NULL;
    psess->hvlistPass2         = NULL;
    psess->levelVerbose        = vbNONE; // Default to no status
    psess->hfci                = NULL;
    psess->cbTotalFileBytes    = 0;      // Total bytes in all files
    psess->cFiles              = 0;
    psess->fNoLineFeed         = 0;      // TRUE if last printf did not have \n
    psess->cchLastLine         = 0;
    psess->hinf                = NULL;
    psess->hglistFiles         = NULL;
    psess->setID               = 0;      // No set ID, yet
    psess->achCurrOutputDir[0] = '\0';   // Default is current directory
    psess->fForceNewDisk       = FALSE;

    memset(psess->achBlanks,' ',cchSCREEN_WIDTH);
    psess->achBlanks[cchSCREEN_WIDTH] = '\0';
    resetSession(psess);                // Reset pass variables

    //** Print Diamond banner
    MsgSet(psess->achMsg,pszBANNER,"%s",pszDIAMOND_VERSION);
    printf(psess->achMsg);

    //** Initialize Directive File processor (esp. predefined variables)
    if (!(hvlist = DFPInit(psess,&err))) {
        printError(psess,&err);
        return 1;
    }

    //** Parse command line
    //   NOTE: Must do this after DFPInit, to define standard variables.
    psess->hvlist = hvlist;             // Command line may modify variables
    if (!parseCommandLine(psess,cArg,apszArg,&err)) {
        printError(psess,&err);
        return 1;
    }

    //** Quick out if command line help is requested
    if (psess->act == actHELP) {        // Do help if any args, for now
        printf("\n");                   // Separate banner from help
        printf(pszCMD_LINE_HELP);
#ifdef ASSERT
        printf(pszCMD_LINE_HELP_DBG);
#endif
        return 0;
    }

    //** Get time at start
    psess->clkStart = clock();

    //** Process command
    switch (psess->act) {
        case actFILE:
            //** Check for any non-standard variable names
            if (!checkVariableDefinitions(psess,&err)) {
                return 1;               // Errors already printed
            }
            //** Compress the file
            if (!processFile(psess,&err)) {
                printError(psess,&err);
                return 1;
            }
            break;

        case actDIRECTIVE:
            if (!processDirectives(psess,&err)) {
                printError(psess,&err);
                return 1;
            }
            break;

        default:
            Assert(0);                  // Should never get here!
    }

    //** Determine return code
    if (psess->cErrors > 0) {
        rc = 1;
    }
    else {
        rc = 0;
    }

    //** Free resources
    AssertSess(psess);
    ClearAssertSignature((psess));
    MemFree(psess);

    //** Indicate result
    return rc;
} /* main */


/***    processFile - Process single file compression action
 *
 *  Entry:
 *      psess - Description of operation to perform
 *      perr  - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; file compressed.
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in with details.
 */
BOOL processFile(PSESSION psess, PERROR perr)
{
    char            achDst[cbFILE_NAME_MAX];// Destination file name
    char            achDef[cbFILE_NAME_MAX];// Default destination name
    long            cbFile;             // Size of source file
    CCAB            ccab;               // Cabinet parameters for FCI
    BOOL            f;
    HFILESPEC       hfspec;
    char           *pszSrc;             // Source filespec
    char           *pszDst;             // Destination (cabinet) file spec
    char           *pszFilename;        // Name to store in cabinet
    SESSIONANDERROR sae;                // Context for FCI calls
    TCOMP           tcomp;

    //** Store context to pass through FCI calls
    sae.psess = psess;
    sae.perr  = perr;

    //** Get src/dst file names
    hfspec = FLFirstFile(psess->hflistDirectives);
    Assert(hfspec != NULL);             // Must have at least one file
    pszSrc = FLGetSource(hfspec);
    pszDst = FLGetDestination(hfspec);
    if ((pszDst == NULL) || (*pszDst == '\0')) { // No destination
        //** Generate destination file name
        if (!getCompressedFileName(psess,achDef,sizeof(achDef),pszSrc,perr)) {
            return FALSE;               // perr already filled in
        }
        pszDst = achDef;                // Use constructed name
    }

    //** Construct complete filespec for destination file
    if (!catDirAndFile(achDst,                  // gets location+destination
                       sizeof(achDst),
                       psess->achCurrOutputDir, // /L argument
                       pszDst,                  // destination
                       "",                      // no fall back
                       perr)) {
        return FALSE;                   // perr set already
    }
    pszDst = achDst;

    //** Make sure source file exists
    cbFile = getFileSize(pszSrc,perr);
    if (cbFile == -1) {
        return FALSE;                   // perr already filled in
    }
    psess->cbTotalFileBytes = cbFile;   // Save for status callbacks

    //** Get name to store inside of cabinet
    pszFilename = getJustFileNameAndExt(pszSrc,perr);
    if (pszFilename == NULL) {
        return FALSE;                   // perr already filled in
    }

    //** Cabinet controls
    ccab.szDisk[0]         = '\0';      // No disk label
    strcpy(ccab.szCab,pszDst);          // Compressed file name (cabinet name)
    ccab.szCabPath[0]      = '\0';      // No path for cabinet
    ccab.cb                = 0;         // No limit on cabinet size
    ccab.cbFolderThresh    = ccab.cb;   // No folder size limit
    ccab.setID             = 0;         // Set ID does not matter, but make
                                        // it deterministic!
#ifndef REMOVE_CHICAGO_M6_HACK
    //** Pass hack flag on to FCI
    ccab.fFailOnIncompressible = psess->fFailOnIncompressible;
#endif

    //** Set reserved sizes (from variable settings)
    if (!setCabinetReserve(&ccab,psess,perr)) {
        return FALSE;
    }

    //** Create cabinet
    psess->fGenerateInf = FALSE;        // Remember we are NOT creating INF
    psess->hfci = FCICreate(
                    &psess->erf,        // error code return structure
                    fciFilePlaced,      // callback for file placement notify
                    fciAlloc,
                    fciFree,
                    fciTempFile,
                    &ccab
                   );
    if (psess->hfci == NULL) {
        mapFCIError(perr,psess,szFCI_CREATE,&psess->erf);
        return FALSE;
    }

    //** Get compression setting
    tcomp = tcompFromSession(psess,perr);

    //** Add file
    strcpy(psess->achCurrFile,pszFilename); // Info for fciStatus
    psess->cFiles    = 1;               // Info for fciStatus
    psess->iCurrFile = 1;               // Info for fciStatus
    fciStatus(statusFile,0,0,&sae);     // Show new file name, ignore rc
    f = FCIAddFile(
                psess->hfci,
                pszSrc,                 // filename to add to cabinet
                pszFilename,            // name to store into cabinet file
                FALSE,
                fciGetNextCabinet_NOT,  // Should never go to a next cabinet!
                fciStatus,              // Status callback
                fciOpenInfo,            // Open/get attribs/etc. callback
                tcomp,                  // compression type
                &sae                    // context
                );
    if (!f) {
        //** Only set error if we didn't already do so in FCIAddFile callback
        if (!ErrIsError(sae.perr)) {
            mapFCIError(perr,psess,szFCI_ADD_FILE,&psess->erf);
        }
        return FALSE;
    }

    //** Complete cabinet file
    if (!FCIFlushCabinet(psess->hfci,FALSE,
                            fciGetNextCabinet_NOT,fciStatus,&sae)) {
        //** Only set error if we didn't already do so in FCIAddFile callback
        if (!ErrIsError(sae.perr)) {
            mapFCIError(perr,psess,szFCI_FLUSH_CABINET,&psess->erf);
        }
        return FALSE;
    }

    //** Destroy FCI context
    if (!FCIDestroy(psess->hfci)) {
        mapFCIError(perr,psess,szFCI_DESTROY,&psess->erf);
        return FALSE;
    }
    psess->hfci = NULL;                 // Clear out FCI context

    //** Success
    return TRUE;
} /* processFile() */


/***  fciGetNextCabinet_NOT - FCI calls this to get new cabinet info
 *
 *  NOTE: This should never get called, as we are compressing a single
 *        file into a cabinet in this case.  So set an error!
 *
 *  Entry:
 *      pccab     - Points to previous current-cabinet structure
 *      cbPrevCab - Size of previous cabinet
 *      pv        - Really a psae
 *
 *  Exit:
 *      returns FALSE, we should never be called here
 */
FNFCIGETNEXTCABINET(fciGetNextCabinet_NOT)
{
    PSESSION    psess = ((PSESSIONANDERROR)pv)->psess;
    PERROR      perr  = ((PSESSIONANDERROR)pv)->perr;
    HFILESPEC   hfspec;
    char       *pszSrc;                 // Source filespec

    //** Get source filespec for error message
    AssertSess(psess);
    hfspec = FLFirstFile(psess->hflistDirectives);
    Assert(hfspec != NULL);             // Must have at least one file
    pszSrc = FLGetSource(hfspec);

    //** Set the error message
    ErrSet(perr,pszDIAERR_MULTIPLE_CABINETS,"%s",pszSrc);

    //** Failure
    return FALSE;
} /* fnGetNextCab() */


/***    processDirectives - Process directive file(s)
 *
 *  Entry:
 *      psess - Description of operation to perform
 *      perr  - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; directives processed successfully.
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in with details.
 */
BOOL processDirectives(PSESSION psess, PERROR perr)
{
    ERROR           errTmp;             // Toss away error
    HFILESPEC       hfspec;
    HTEXTFILE       htf=NULL;
    HVARIABLE       hvar;
    char           *pszFile;

    //** Must have at least one directives file
    AssertSess(psess);
    Assert(psess->hflistDirectives != NULL);

    //** Tailor status based on verbosity level
    if (psess->levelVerbose == vbNONE) {
        //NOTE: This line gets over written below
        lineOut(psess,pszDIA_PARSING_DIRECTIVES,FALSE);
    }
    else {
        lineOut(psess,pszDIA_PASS_1_HEADER1,TRUE);
        lineOut(psess,pszDIA_PASS_1_HEADER2,TRUE);
    }

    //** Save a copy of the variable list in this state for Pass 2
    if (!(psess->hvlistPass2 = VarCloneList(psess->hvlist,perr))) {
        goto error;                     // perr already filled in
    }

/*
 ** Pass ONE, make sure everything is OK
 */
    hfspec = FLFirstFile(psess->hflistDirectives);
    Assert(hfspec != NULL);             // Must have at least one file
    for (; hfspec != NULL; hfspec = FLNextFile(hfspec)) {
        pszFile = FLGetSource(hfspec);
        perr->pszFile = pszFile;        // Set file for error messages

        //** Open file
        if (!(htf = TFOpen(pszFile,tfREAD_ONLY,cbDF_BUFFER,perr))) {
            goto error;                 // perr already filled in
        }

        //** Parse it
        if (!DFPParse(psess,htf,fndfpPassONE,perr)) {
            goto error;                 // perr already filled in
        }

        //** Close it
        TFClose(htf);
        htf = NULL;                     // Clear so error path avoids close
    }

    //** Not processing directive files; avoid bogus file/line number info
    //   in any error messages that might be generated below.
    perr->pszFile = NULL;

    //** If .Option Explicit, make sure no variables have been .Set without
    //   being .Defined.
    //   NOTE: No need to check return value, since any error will increment
    //         psess->cErrors, and cause us to bail out just below.
    if (psess->fExplicitVarDefine) {
        checkVariableDefinitions(psess,perr);
    }

    //** If in relational mode, make sure there are no unreferenced files
    //   NOTE: No need to check return value, since any error will increment
    //         psess->cErrors, and cause us to bail out just below.
    checkReferences(psess,perr);

    //** Bail out if any errors in pass 1
    if (psess->cErrors > 0) {
        ErrSet(perr,pszDIAERR_ERRORS_IN_PASS_1,"%d",psess->cErrors);
        perr->pszFile = NULL;           // Not file-specific
        goto error;
    }

    //** Make sure we can create INF and RPT files *before* we spend any
    //   time doing compression!  We have to do this at the end of processing
    //   the directive file during pass 1, so that we make sure the INT and
    //   RPT file names have been specified.  Note that only the last
    //   setting will be used.
    //
    hvar = VarFind(psess->hvlist,pszVAR_INF_FILE_NAME,perr);
    Assert(!perr->fError);              // Must be defined
    pszFile = VarGetString(hvar);
    if (!ensureFile(pszFile,pszDIA_INF_FILE,perr)) {
        goto error;
    }

    hvar = VarFind(psess->hvlist,pszVAR_RPT_FILE_NAME,perr);
    Assert(!perr->fError);              // Must be defined
    pszFile = VarGetString(hvar);
    if (!ensureFile(pszFile,pszDIA_RPT_FILE,perr)) {
        goto error;
    }

    //** Initialize for INF generation
    //   NOTE: We use the variable state at the *end* of pass 1, as this
    //         permits the INF area header variables to be defined anywhere!
    //
    //   NOTE: We check the InfXxxLineFormat variables for undefined
    //         parameters (so that we can error out before we spend a lot
    //         of time compressing files!).
    //
    //   NOTE: If any of the ver info parameters (*ver*, *vers*, *lang*)
    //         are used in the InfFileLineFormat variable, then we note this
    //         and collect ver info during pass 2.  Otherwise, we skip it to
    //         speed up pass 2.
    if (!infCreate(psess,perr)) {
        goto error;
    }

/*
 ** Pass TWO, do the layout!
 */
    psess->fPass2 = TRUE;               // Remember for asserts, mostly

    //** Tailor status based on verbosity level
    if (psess->levelVerbose >= vbNONE) {
        MsgSet(psess->achMsg,pszDIA_STATS_BEFORE,"%,ld%,ld",
                                    psess->cbTotalFileBytes,psess->cFiles);
        lineOut(psess,psess->achMsg,TRUE);
        //NOTE: This line gets over written below
        lineOut(psess,pszDIA_EXECUTING_DIRECTIVES,FALSE);
    }
    else {
        lineOut(psess,pszDIA_PASS_2_HEADER1,TRUE);
        lineOut(psess,pszDIA_PASS_2_HEADER2,TRUE);
    }

    //** Reset to initial state for pass 2
    if (!VarDestroyList(psess->hvlist,perr)) {
        goto error;                     // perr already filled in
    }
    psess->hvlist = psess->hvlistPass2; // Use variables saved for pass 2
    psess->hvlistPass2 = NULL;          // Clear so error path does not free
    resetSession(psess);                // Reset pass variables

    //** Process directive files for pass 2
    hfspec = FLFirstFile(psess->hflistDirectives);
    Assert(hfspec != NULL);             // Must have at least one file
    for (; hfspec != NULL; hfspec = FLNextFile(hfspec)) {
        pszFile = FLGetSource(hfspec);
        perr->pszFile = pszFile;        // Set file for error messages

        //** Open file
        if (!(htf = TFOpen(pszFile,tfREAD_ONLY,cbDF_BUFFER,perr))) {
            goto error;                 // perr already filled in
        }

        //** Parse it
        if (!DFPParse(psess,htf,fndfpPassTWO,perr)) {
            goto error;                 // perr already filled in
        }

        //** Close it
        TFClose(htf);
        htf = NULL;                     // Clear so error path avoids close
    }

    //** No longer processing directive files; reset ERROR
    perr->pszFile = NULL;

    //** Flush out cabinets, if we have not already done so
    if (!ensureCabinetsFlushed(psess,perr)) {
        goto error;
    }

    //** Get ending time, generate INF and RPT files
    psess->clkEnd = clock();
    if (!buildInfAndRpt(psess,perr)) {
        goto error;
    }

    //** Success
    return TRUE;

error:
    if (psess->hinf != NULL) {
        infDestroy(psess->hinf,&errTmp);    // Ignore errors
    }

    if (htf != NULL) {
        TFClose(htf);
    }

    if (psess->hvlistPass2 != NULL) {
        VarDestroyList(psess->hvlistPass2,&errTmp);  // Ignore any error
    }

    //** Failure
    return FALSE;
}


/***    buildInfAndRpt - Create INF and RPT output files
 *
 *  Entry:
 *      psess - Description of operation to perform
 *      perr  - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; files created
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in with details.
 */
BOOL buildInfAndRpt(PSESSION psess, PERROR perr)
{
    int         hours;
    HVARIABLE   hvar;
    double      kbPerSec;
    int         minutes;
    char       *pszFile;
    double      percent;
    FILE       *pfileRpt;               // Report file
    double      seconds;
    double      secondsTotal;
    time_t      timeNow;

    Assert(psess->fGenerateInf);

    //** Compute running time and throughput
    secondsTotal = (psess->clkEnd - psess->clkStart) / (float)CLOCKS_PER_SEC;
    if (secondsTotal == 0.0) {                 // Don't divide by zero!
        secondsTotal = 1.0;
    }
    kbPerSec = psess->cbFileBytes/secondsTotal/1024L;
    hours = (int)(secondsTotal/(60*60)); // Hours
    minutes = (int)((secondsTotal - hours*60*60)/60); // Minutes
    seconds = secondsTotal - hours*60*60 - minutes*60; // Seconds

    //** Get date/time stamp
    time(&timeNow);

    //** Generate INF file
    hvar = VarFind(psess->hvlist,pszVAR_INF_FILE_NAME,perr);
    Assert(!perr->fError);              // Must be defined
    pszFile = VarGetString(hvar);
    if (!infGenerate(psess,pszFile,&timeNow,pszDIAMOND_VERSION,perr)) {
        return FALSE;
    }
    if (!infDestroy(psess->hinf,perr)) {
        return FALSE;
    }
    psess->hinf = NULL;                 // So caller knows it is gone

    //** Display summary of results and write report file
    hvar = VarFind(psess->hvlist,pszVAR_RPT_FILE_NAME,perr);
    Assert(!perr->fError);              // Must be defined
    pszFile = VarGetString(hvar);
    pfileRpt = fopen(pszFile,"wt");     // Create setup.rpt
    if (pfileRpt == NULL) {             // Could not create
        ErrSet(perr,pszDIAERR_CANT_CREATE_RPT,"%s",pszFile);
        printError(psess,perr);
        ErrClear(perr);                 // But, continue
    }

    //** Only put header in report file
    MsgSet(psess->achMsg,pszDIA_RPT_HEADER,"%s",ctime(&timeNow));
    if (pfileRpt) {
        fprintf(pfileRpt,"%s\n",psess->achMsg);
    }

    //** Show stats on stdout and to report file
    MsgSet(psess->achMsg,pszDIA_STATS_AFTER1,"%,13ld",psess->cFiles);
    lineOut(psess,psess->achMsg,TRUE);
    if (pfileRpt) {
        fprintf(pfileRpt,"%s\n",psess->achMsg);
    }

    MsgSet(psess->achMsg,pszDIA_STATS_AFTER2,"%,13ld",psess->cbFileBytes);
    lineOut(psess,psess->achMsg,TRUE);
    if (pfileRpt) {
        fprintf(pfileRpt,"%s\n",psess->achMsg);
    }

    MsgSet(psess->achMsg,pszDIA_STATS_AFTER3,"%,13ld",psess->cbFileBytesComp);
    lineOut(psess,psess->achMsg,TRUE);
    if (pfileRpt) {
        fprintf(pfileRpt,"%s\n",psess->achMsg);
    }

    //** Compute percentage complete
    if (psess->cbFileBytes > 0) {
        percent = psess->cbFileBytesComp/(float)psess->cbFileBytes;
        percent *= 100.0;           // Make it 0..100
    }
    else {
        //** No files, I guess!
        percent = 0.0;
    }
    MsgSet(psess->achMsg,pszDIA_STATS_AFTER4,"%6.2f",percent);
    lineOut(psess,psess->achMsg,TRUE);
    if (pfileRpt) {
        fprintf(pfileRpt,"%s\n",psess->achMsg);
    }

    MsgSet(psess->achMsg,pszDIA_STATS_AFTER5,"%9.2f%2d%2d%5.2f",
            secondsTotal,hours,minutes,seconds);
    lineOut(psess,psess->achMsg,TRUE);
    if (pfileRpt) {
        fprintf(pfileRpt,"%s\n",psess->achMsg);
    }

    MsgSet(psess->achMsg,pszDIA_STATS_AFTER6,"%9.2f",kbPerSec);
    lineOut(psess,psess->achMsg,TRUE);
    if (pfileRpt) {
        fprintf(pfileRpt,"%s\n",psess->achMsg);
    }

    //** Success
    return TRUE;
} /* buildInfAndRpt() */


/***    fndfpPassONE - First pass of directives file
 *
 *  NOTE: See dfparse.h for entry/exit conditions.
 */
FNDIRFILEPARSE(fndfpPassONE)
{
    long        cMaxErrors;
    HVARIABLE   hvar;
    static char achDDFName[cbFILE_NAME_MAX] = "";

    AssertSess(psess);

    //** Update line count status occassionaly
    if ((psess->levelVerbose == vbNONE) &&
        (!(perr->iLine % 50) || _stricmp(achDDFName,perr->pszFile))) {
        //** Minimal verbosity, and we've processed a 50-line chunk,
        //   or we've switched DDF files.
        MsgSet(psess->achMsg,pszDIA_PARSING_PROGRESS,"%s%d",
                             perr->pszFile,perr->iLine);
        lineOut(psess,psess->achMsg,FALSE);
        //** Remember this DDF file name if it changed
        if (perr->iLine % 50) {
            strcpy(achDDFName,perr->pszFile);
        }
    }

    //** Execute only if we have no parse error so far
    if (!ErrIsError(perr)) {
        //** Execute command for pass ONE
        executeCommand(psess,pcmd,FALSE,perr);
    }

    //** Handle error reporting
    if (ErrIsError(perr)) {
        //** Print out error
        printError(psess,perr);

        //** Make sure we don't exceed our limit
        ErrClear(perr);
        hvar = VarFind(psess->hvlist,pszVAR_MAX_ERRORS,perr);
        Assert(!perr->fError);      // MaxErrors must be defined
        cMaxErrors = VarGetLong(hvar);
        if ((cMaxErrors != 0) &&    // There is a limit *and*
            (psess->cErrors >= cMaxErrors)) { // the limit is exceeded
            ErrSet(perr,pszDIAERR_MAX_ERRORS,"%d",psess->cErrors);
            perr->pszFile = NULL;   // Not specific to a directive file
            return FALSE;
        }
        //** Reset error so we can continue
        ErrClear(perr);
    }


    //** Success
    return TRUE;
} /* fndfpPassONE() */


/***    fndfpPassTWO - Second pass of directives file
 *
 *  NOTE: See dfparse.h for entry/exit conditions.
 */
FNDIRFILEPARSE(fndfpPassTWO)
{
    AssertSess(psess);

    //** Execute only if we have no parse error so far
    if (!ErrIsError(perr)) {
        //** Execute command for pass TWO
        executeCommand(psess,pcmd,TRUE,perr);
    }

    if (ErrIsError(perr)) {
        //** Print out error, set abort message and fail
        printError(psess,perr);
        ErrSet(perr,pszDIAERR_ERRORS_IN_PASS_2);
        return FALSE;
    }

    //** Success
    return TRUE;
} /* fndfpPassTWO() */


/***    executeCommand - execute a parse command
 *
 *  Entry:
 *      psess  - Session
 *      pcmd   - Command to process
 *      fPass2 - TRUE if this is pass 2, FALSE if pass 1
 *      perr   - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; psess updated.
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in with error.
 */
executeCommand(PSESSION   psess,
               PCOMMAND   pcmd,
               BOOL       fPass2,
               PERROR     perr)
{
    AssertSess(psess);
    AssertCmd(pcmd);

    //** Execute line
    switch (pcmd->ct) {
        case ctCOMMENT:
            return TRUE;

        case ctDELETE:
            return doDelete(psess,pcmd,fPass2,perr);

        case ctDUMP:
            return doDump(psess,pcmd,fPass2,perr);

        case ctINF_BEGIN:               // Nothing to do
        case ctINF_END:                 // Nothing to do
            return TRUE;

        case ctINF_WRITE:
            return modeInfAddLine(psess,
                                  pcmd->inf.inf,
                                  pcmd->inf.achLine,
                                  perr);

        case ctNEW:
            return doNew(psess,pcmd,fPass2,perr);

        case ctOPTION:
            return doOption(psess,pcmd,fPass2,perr);

        case ctFILE:
            if (!doFile(psess,pcmd,fPass2,perr) || !fPass2) {
                //** Failed or pass 1, toss parameter list
                if (pcmd->file.hglist) {
                    GLDestroyList(pcmd->file.hglist);
                }
                return FALSE;
            }
            return TRUE;

        case ctREFERENCE:
            if (!doReference(psess,pcmd,fPass2,perr) || !fPass2) {
                //** Failed or pass 1, toss parameter list
                if (pcmd->ref.hglist) {
                    GLDestroyList(pcmd->ref.hglist);
                }
                return FALSE;
            }
            return TRUE;

        case ctDEFINE:
            return doDefine(psess,pcmd,fPass2,perr);

        case ctSET:
            return setVariable(psess,
                               pcmd->set.achVarName,
                               pcmd->set.achValue,
                               perr);

        case ctBAD:
        case ctINF_WRITE_CAB:   // dfparse.c maps to ctINF_WRITE
        case ctINF_WRITE_DISK:  // dfparse.c maps to ctINF_WRITE
        default:
            Assert(0);              // Should never get here
            return FALSE;
    }

    //** Should never get here
    Assert(0);
    return FALSE;
} /* executeCommand() */


/***    setVariable - wrapper around VarSet to do special processing
 *
 *  Entry:
 *      psess    - Session
 *      pszName  - Variable name
 *      pszValue - New value
 *      perr     - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE, variable is created (if necessary) and value set.
 *
 *  Exit-Failure:
 *      Returns FALSE, cannot set variable value.
 *      ERROR structure filled in with details of error.
 *
 *  Notes:
 *      (1) For *all* variables, we convert special disk size strings into
 *          their numeric equivalent.  This is the easiest way to ensure
 *          that we catch MaxDiskSize[n] variables!
 *      (2) We check for variables with the special vflCOPY flag, and if
 *          this is *pass 1*, we also set these variables in the *pass 2*
 *          list; this permits InfDisk/CabinetLineFormat variables to
 *          be defined in the *inf* section of the DDF, but get used in
 *          the *layout* section!  (Whew, pretty squirrely!)
 */
BOOL setVariable(PSESSION  psess,
                 char     *pszName,
                 char     *pszValue,
                 PERROR    perr)
{
    char        achSize[50];
    long	cbDisk;
    HVARIABLE   hvar;
    char       *psz;

    //** Do special disk size list procesing
    if (cbDisk = IsSpecialDiskSize(psess,pszValue)) {
        _ltoa(cbDisk,achSize,10);       // Convert number to string
        psz = achSize;
    }
    else {                              // Not special
        psz = pszValue;
    }

    //** Set the variable
    if (!(hvar = VarSet(psess->hvlist,pszName,psz,perr))) {
        return FALSE;
    }

    //** Set it in the pass 2 list if:
    //      We are not already in pass 2             -and-
    //      We have already created the pass 2 list  -and-
    //      The variable is supposed to be copied

    if (!psess->fPass2     &&
        psess->hvlistPass2 &&
        (VarGetFlags(hvar) & vflCOPY)) {
        if (!(hvar = VarSet(psess->hvlistPass2,pszName,psz,perr))) {
            return FALSE;
        }
    }

    //** If MaxDiskSize, update other variables if appropriate
    if (_stricmp(pszName,pszVAR_MAX_DISK_SIZE) == 0) {
        return setDiskParameters(psess,psz,0,perr);
    }

    //** If GenerateInf, do special goofy context processing
    if (_stricmp(pszName,pszVAR_GENERATE_INF) == 0) {
        switch (psess->ddfmode) {
            case ddfmodeUNKNOWN:
                //** Let change occur; we don't make up our mind until
                //   the first file copy command.
                return TRUE;

            case ddfmodeUNIFIED:        // Doing INF in parallel with file copy
                ErrSet(perr,pszDIA_BAD_INF_MODE);
                return FALSE;

            case ddfmodeRELATIONAL:
                hvar = VarFind(psess->hvlist,pszVAR_GENERATE_INF,perr);
                Assert(!perr->fError);          // Must be defined
                //** Don't allow turning off twice!
                if (!VarGetBool(hvar)) {
                    ErrSet(perr,pszDIA_BAD_INF_MODE);
                    return FALSE;
                }
                //** Now we read reference commands
                psess->fExpectFileCommand = FALSE;
                return TRUE;

            default:
                Assert(0);
                return FALSE;
    	}
    }

    return TRUE;
} /* setVariable() */


/***    doDump - Process a .DUMP command (dump all variables)
 *
 *  Entry:
 *      psess  - Session
 *      pcmd   - Command to process (ct == ctDUMP)
 *      fPass2 - TRUE if this is pass 2
 *      perr   - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; psess updated.
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in with error.
 */
BOOL doDump(PSESSION psess,PCOMMAND pcmd, BOOL fPass2, PERROR perr)
{
    HVARIABLE   hvar;
    VARFLAGS    vfl;
    char       *pszName;
    char       *pszValue;

    AssertSess(psess);
    Assert(pcmd->ct == ctDUMP);

    //** Print variable dump header (two line feeds to make sure
    //      we get a blank line if status output preceded us).
    printf("\n\n%s\n",pszDIA_VAR_DUMP1);
    printf("%s\n",pszDIA_VAR_DUMP2);

    //** Print out all variables
    for (hvar = VarFirstVar(psess->hvlist);
         hvar;
         hvar = VarNextVar(hvar)) {

        //** Get variable info
        vfl      = VarGetFlags(hvar);
        pszName  = VarGetName(hvar);
        pszValue = VarGetString(hvar);

        //** Print name and flag (indent for readability)
        printf("    %s",pszName);
        if (vfl != vflNONE) {
            printf("(");
            if (vfl & vflPERM) {
                printf("%s",pszDIA_VAR_PERMANENT);
            }
            else if (vfl & vflDEFINE) {
                printf("%s",pszDIA_VAR_DEFINED);
            }
            else {
                Assert(0);  // Unknown flag
            }
            printf(")");
        }

        //** Print value
        printf("=<%s>\n",pszValue);
    }

    //** Success
    return TRUE;
} /* doDump() */


/***    doDefine - Process a .DEFINE command
 *
 *  Entry:
 *      psess  - Session to update
 *      pcmd   - Command to process (ct == ctDEFINE)
 *      fPass2 - TRUE if this is pass 2, where we do the real work!
 *      perr   - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; psess updated.
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in with error.
 */
BOOL doDefine(PSESSION psess,PCOMMAND pcmd, BOOL fPass2, PERROR perr)
{
    HVARIABLE   hvar;
    VARFLAGS    vfl;

    AssertSess(psess);
    Assert(pcmd->ct == ctDEFINE);

    //** Create variable if necessary
    hvar = VarFind(psess->hvlist,pcmd->set.achVarName,perr);
    if (hvar == NULL) {                 // Have to create it ourselves
        hvar = VarCreate(psess->hvlist,        // var list
                         pcmd->set.achVarName, // var name
                         "",                   // default value
                         vtypeSTR,             // var type
                         vflDEFINE,            // var is DEFINED
                         NULL,                 // No validation function
                         perr);
        if (hvar == NULL) {
            return FALSE;               // Could not create variable
        }
    }
    else {
        //** Variable already exists, check if .DEFINE is OK
        vfl = VarGetFlags(hvar);
        if (vfl & vflDEFINE) {
            ErrSet(perr,pszDIAERR_REDEFINE,"%s%s",
                                           pszCMD_DEFINE,pcmd->set.achVarName);
            return FALSE;
        }
        else if (vfl & vflPERM) {
            ErrSet(perr,pszDIAERR_DEFINE_PERM,"%s%s",
                                           pszCMD_DEFINE,pcmd->set.achVarName);
            return FALSE;
        }
    }

    //** Everything is fine, set the variable value
    return setVariable(psess,
                       pcmd->set.achVarName,
                       pcmd->set.achValue,
                       perr);
} /* doDefine() */


/***    doDelete - Process a .DELETE command
 *
 *  Entry:
 *      psess  - Session to update
 *      pcmd   - Command to process (ct == ctDELETE)
 *      fPass2 - TRUE if this is pass 2, where we do the real work!
 *      perr   - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; psess updated.
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in with error.
 */
BOOL doDelete(PSESSION psess, PCOMMAND pcmd, BOOL fPass2, PERROR perr)
{
    HVARIABLE   hvar;

    AssertSess(psess);
    Assert(pcmd->ct == ctDELETE);

    //** Make sure variable exists
    if (!(hvar = VarFind(psess->hvlist,pcmd->delete.achVarName,perr))) {
        return FALSE;                   // Variable does not exist
    }

    //** Delete it
    VarDelete(hvar);
    return TRUE;
} /* doDelete() */


/***    doNew - Process a .NEW command
 *
 *  Entry:
 *      psess  - Session to update
 *      pcmd   - Command to process (ct == ctNEW)
 *      fPass2 - TRUE if this is pass 2, where we do the real work!
 *      perr   - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; psess updated.
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in with error.
 */
BOOL doNew(PSESSION psess,PCOMMAND pcmd, BOOL fPass2, PERROR perr)
{
    SESSIONANDERROR  sae;               // Context for FCI calls
    char            *pszKind;

    AssertSess(psess);
    Assert(pcmd->ct == ctNEW);
/*
 ** Pass 1, check for invalid context
 */
    if (!fPass2) {
        //** Don't permit .New commands in INF section of RELATIONAL DDF
        if (!psess->fExpectFileCommand) {
            ErrSet(perr,pszDIAERR_BAD_CMD_IN_INF_SECT,"%s",pszCMD_NEW);
            return FALSE;
        }

        //** Don't permit .New cabinet/folder when not in cabinet
        switch (pcmd->new.nt) {
            case newFOLDER:
            case newCABINET:
                if (inCabinet(psess,perr)) {
                    return TRUE;
                }
                pszKind = (pcmd->new.nt == newFOLDER) ?
                                pszNEW_FOLDER : pszNEW_CABINET;
                ErrSet(perr,pszDIAERR_BAD_NEW_CMD,"%s%s",pszCMD_NEW,pszKind);
                return FALSE;

            case newDISK:
                return TRUE;
        }
        Assert(0);                      // Should never get here
        return FALSE;
    }

/*
 ** Pass 2, finish the current folder, cabinet, or disk
 */
    //** Store context to pass through FCI calls
    sae.psess = psess;
    sae.perr  = perr;
    switch (pcmd->new.nt) {
        case newFOLDER:
            if (!psess->hfci) {         // No FCI context yet, so NOP
                return TRUE;
            }
            if (!FCIFlushFolder(psess->hfci,fciGetNextCabinet,fciStatus,&sae)) {
                //** Only set error if we didn't already do so
                if (!ErrIsError(sae.perr)) {
                    mapFCIError(perr,psess,szFCI_FLUSH_FOLDER,&psess->erf);
                }
                return FALSE;
            }
            psess->cFilesInFolder = 0;  // Reset files in folder count
            break;

        case newDISK:
                //** Our technique is a lazy one -- we set the flag asking for
                //   a new disk, and (if in a cabinet) we flush the current
                //   cabinet.  Either way, the next file or cabinet will start
                //   on a new disk.
                psess->fForceNewDisk = TRUE;
                if (!inCabinet(psess,perr)) {
                    return TRUE;        // Not in a cabinet, we're done
                }

                //** OK, we're in a cabinet; We want to flush it and assume
                //   that more files are coming for the next cabinet, so
                //   just fall through to the .New Cabinet code!
                //
                //  ATTENTION: FALLING THROUGH!

        case newCABINET:
            if (!psess->hfci) {         // No FCI context yet, so NOP
                return TRUE;
            }
            //** Flush current cabinet, but tell FCI more are coming!
            if (!FCIFlushCabinet(psess->hfci,TRUE,
                                     fciGetNextCabinet,fciStatus,&sae)) {
                //** Only set error if we didn't already do so
                if (!ErrIsError(sae.perr)) {
                    mapFCIError(perr,psess,szFCI_FLUSH_CABINET,&psess->erf);
                }
                return FALSE;
            }
            psess->cFilesInCabinet = 0; // Reset files in folder count
            break;

        default:
            Assert(0);
            return FALSE;
    }
    return TRUE;
} /* doNew() */


/***    doOption - Process a .OPTION command
 *
 *  Entry:
 *      psess  - Session to update
 *      pcmd   - Command to process (ct == ctOPTION)
 *      fPass2 - TRUE if this is pass 2
 *      perr   - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; psess updated.
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in with error.
 */
BOOL doOption(PSESSION psess,PCOMMAND pcmd, BOOL fPass2, PERROR perr)
{
    AssertSess(psess);
    Assert(pcmd->ct == ctOPTION);

    //** Check for a change to the Explicit variable definition setting
    if (pcmd->opt.ofMask & optEXPLICIT) {
        //** Change to .Option Explicit
        psess->fExplicitVarDefine = ((pcmd->opt.of & optEXPLICIT) != 0);
    }

    //** Make sure no other bits get snuck in
    Assert((pcmd->opt.ofMask & ~optEXPLICIT) == 0);
    return TRUE;
} /* doOption() */


/***    doReference - Process an INF file reference
 *
 *  Entry:
 *      psess  - Session to update
 *      pcmd   - Command to process (ct == ctREFERENCE)
 *      fPass2 - TRUE if this is pass 2, where we do the real work!
 *      perr   - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; psess updated.
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in with error.
 */
BOOL doReference(PSESSION psess,PCOMMAND pcmd, BOOL fPass2, PERROR perr)
{
    HGENERIC    hgen;
    PFILEINFO   pfinfo;
    FILEINFO    finfoIgnore;            // For ValidateParms

    AssertSess(psess);
    Assert(!psess->fExpectFileCommand);
    Assert(psess->ddfmode == ddfmodeRELATIONAL);

    //** Find referenced file
    if (!(hgen = GLFind(psess->hglistFiles,pcmd->ref.achDst,"",perr))) {
        ErrSet(perr,pszDIAERR_REF_FILE_NOT_FOUND,"%s",pcmd->ref.achDst);
        return FALSE;
    }

    //** Get file info structure
    pfinfo = GLGetValue(hgen);
    AssertFinfo(pfinfo);

    //** Merge copy of parms from file into reference line
    if (!GLCopyToList(&(pcmd->ref.hglist),  // Destination
                      pfinfo->hglistParm,   // Source
                      DuplicateFileParm,
                      pszDIA_FILE_PARM,
                      perr)) {
        return FALSE;
    }

/*
 ** PASS 1 Processing
 */
    if (!fPass2) {
        //** Validate standard parameters in parameter list
        //   NOTE: ValidateParms is usually used for File Copy commands,
        //         so /date, /time, etc. parameters will actually make
        //         modifications to the finfo structure.  However, we don't
        //         want any modifications to the fileinfo structure for a
        //         File Reference command.  So, we copy the info to a temporary
        //         structure, and ignore any changes ValidateParms makes.
        finfoIgnore = *pfinfo;          // Scratch copy of fileinfo
        if (!ValidateParms(psess,pcmd->ref.hglist,&finfoIgnore,perr)) {
            return FALSE;
        }
        pfinfo->flags |= fifREFERENCED; // Mark referenced bit
        return TRUE;                    // Everything is fine so far
    }

/*
 ** PASS 2 Processing
 */
    //** Make sure cabinets are flushed so all file info is filled in
    if (!ensureCabinetsFlushed(psess,perr)) {
        return FALSE;
    }

    //** Add line to file area of INF
    if (!infAddFile(psess,pcmd->ref.achDst,pfinfo,pcmd->ref.hglist,perr)) {
        return FALSE;                   // perr already filled in
    }

    //** Success
    return TRUE;
} /* doReference() */


/***    doFile - Process a file copy command
 *
 *  Entry:
 *      psess  - Session to update
 *      pcmd   - Command to process (ct == ctFILE)
 *      fPass2 - TRUE if this is pass 2, where we do the real work!
 *      perr   - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; psess updated.
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in with error.
 */
BOOL doFile(PSESSION psess,PCOMMAND pcmd, BOOL fPass2, PERROR perr)
{
    //** Minimize stack usage
    static char     achSrc[cbFILE_NAME_MAX]; // Full source file name
    static char     achDst[cbFILE_NAME_MAX]; // Destination file name
    static char     achFull[cbFILE_NAME_MAX]; // Full destination file name
    long            cbFile;
    long            cMaxFiles;
    BOOL            fCopyFile;          // See DoNotCopyFiles variable
    BOOL            fSuccess;
    HGENERIC	    hgen;	
    HVARIABLE       hvar;
    char           *pch;
    PFILEINFO       pfinfo;
    SESSIONANDERROR sae;                // Context for FCI calls
    TCOMP           tcomp;              // Compression type

    AssertSess(psess);
    Assert(psess->fExpectFileCommand);

/*
 ** Determine INF generation mode on first copy file command
 */
    if (psess->ddfmode == ddfmodeUNKNOWN) {
        hvar = VarFind(psess->hvlist,pszVAR_GENERATE_INF,perr);
        Assert(!perr->fError);          // Must be defined
        if (VarGetBool(hvar)) {         // Generate INF in parallel
            psess->ddfmode = ddfmodeUNIFIED;
        }
        else {
            psess->ddfmode = ddfmodeRELATIONAL;
            //** Make sure UniqueFiles is ON
            hvar = VarFind(psess->hvlist,pszVAR_UNIQUE_FILES,perr);
            Assert(!perr->fError);      // Must be defined
            if (!VarGetBool(hvar)) {    // Must be unique
                ErrSet(perr,pszDIAERR_MUST_BE_UNIQUE);
                return FALSE;
            }
        }
    }

    //** Store context to pass through FCI calls
    sae.psess = psess;
    sae.perr  = perr;

    //** Construct final source and destination filespecs
    hvar = VarFind(psess->hvlist,pszVAR_DIR_SRC,perr);
    Assert(!perr->fError);              // DestinationDir must be defined
    pch = VarGetString(hvar);           // Get destination dir
    if (!catDirAndFile(achSrc,sizeof(achSrc),
                pch,pcmd->file.achSrc,NULL,perr)) {
        return FALSE;                   // perr set already
    }

    hvar = VarFind(psess->hvlist,pszVAR_DIR_DEST,perr);
    Assert(!perr->fError);              // SourceDir must be defined
    pch = VarGetString(hvar);           // Get source dir
    if (!catDirAndFile(achDst,sizeof(achDst),
                pch,pcmd->file.achDst,pcmd->file.achSrc,perr)) {
        return FALSE;                   // perr set already
    }

/*
 ** PASS 1 Processing
 */
    if (!fPass2) {
        if (psess->levelVerbose >= vbFULL) {
            MsgSet(psess->achMsg,pszDIA_FILE_COPY,"%s%s",achSrc,achDst);
            lineOut(psess,psess->achMsg,TRUE);
        }

        //** Make sure MaxDiskSize is a multiple of the ClusterSize
        //   NOTE: This only catches cases where MaxDiskSize/ClusterSize
        //         are not compatible.  MaxDiskSizeN variables cannot
        //         be checked until pass 2, when we know what disk we are
        //         actually on!  Usually we won't get an error in that
        //         case, since we update the ClusterSize automatically.
        if (!checkDiskClusterSize(psess,perr)) {
            return FALSE;
        }

        //** Make sure file exists
        if (-1 == (cbFile = getFileSize(achSrc,perr))) {
            return FALSE;               // perr already filled in
        }
        computeSetID(psess,achDst);     // Accumulate cabinet setID
        psess->cbTotalFileBytes += cbFile;  // Count up total file sizes
        psess->cFiles++;                // Count files

        //** Keep track of file names, handle uniqueness checking
        if (!(hgen = addFileToSession(psess,achSrc,achDst,cbFile,pcmd,perr))) {
            return FALSE;
        }

        //** Make sure standard parameters have valid format
        pfinfo = GLGetValue(hgen);
        AssertFinfo(pfinfo);
        if (!ValidateParms(psess,pfinfo->hglistParm,pfinfo,perr)) {
            return FALSE;
        }

        //** Allow InfDate, InfTime, InfAttr overrides
        //   NOTE: This must be called *after* ValidateParms(), so that
        //         it will not override parms specified on the file copy
        //         command!
        if (!CheckForInfVarOverrides(psess,pfinfo,perr)) {
            return FALSE;
        }
        return TRUE;
    }

/*
 ** PASS 2 Processing
 */

    //** Give user status
    strcpy(psess->achCurrFile,achDst);  // Info for fciStatus
    psess->iCurrFile++;                 // Info for fciStatus
    fciStatus(statusFile,0,0,&sae);     // Show new file name, ignore rc

    //** Update psess->hgenLast for INF generation
    updateHgenLast(psess,achDst);

#ifndef BIT16
    //** Get Version/Language info (only if needed for INF file)
    //   NOTE: We only do this in Win32-land, because the Win32 API can
    //         (usually) handle both 16-bit and 32-bit EXE formats, but
    //         the 16-bit API cannot handle 32-bit EXE formats.
    //         Also, we wait until PASS 2 so we know whether the version
    //         information is needed for the INF file, since it slows us
    //         down a little to gather it.
    if (psess->fGetVerInfo) {
        hgen = psess->hgenFileLast;     // Item for this file
        pfinfo = GLGetValue(hgen);      // Get file info
        AssertFinfo(pfinfo);
        if (!getFileVerAndLang(achSrc,
                               &(pfinfo->verMS),
                               &(pfinfo->verLS),
                               &(pfinfo->pszVersion),
                               &(pfinfo->pszLang),
                               perr)) {
            return FALSE;
        }
    }
#endif // !BIT16

    //** Compute file checksum (only if needed for INF file)
    if (psess->fGetFileChecksum) {
        hgen = psess->hgenFileLast;     // Item for this file
        pfinfo = GLGetValue(hgen);      // Get file info
        AssertFinfo(pfinfo);
        if (!getFileChecksum(achSrc,&(pfinfo->checksum),perr)) {
            return FALSE;
        }
    }

    //** Get compression type
    tcomp = tcompFromSession(psess,perr);

    //** Determine if we are putting file in cabinet, or straight to disk
    if (inCabinet(psess,perr)) {
        if (!ensureCabinet(psess,perr)) { // Make sure we have a cabinet
            return FALSE;
        }

        //** Make sure MaxDiskSize is a multiple of the ClusterSize
        if (!checkDiskClusterSize(psess,perr)) {
            return FALSE;
        }

        //** Get limits on files per folder
        hvar = VarFind(psess->hvlist,pszVAR_FOLDER_FILE_COUNT_THRESHOLD,perr);
        Assert(!perr->fError);          // Must be defined
        cMaxFiles = VarGetLong(hvar);   // Get file count limit

        //** Check for overrun of files in folder limit
        if ((cMaxFiles > 0) &&          // A limit is set
            (psess->cFilesInFolder >= cMaxFiles)) { // Limit is exceeded
            if (!FCIFlushFolder(psess->hfci,fciGetNextCabinet,fciStatus,&sae)) {
                //** Only set error if we didn't already do so
                if (!ErrIsError(sae.perr)) {
                    mapFCIError(perr,psess,szFCI_FLUSH_FOLDER,&psess->erf);
                }
                return FALSE;
            }
            psess->cFilesInFolder = 0;  // Reset files in folder count
        }

        //** Add file to folder
        if (!FCIAddFile(                // Add file to cabinet
                    psess->hfci,
                    achSrc,             // filename to add to cabinet
                    achDst,             // name to store into cabinet file
                    pcmd->file.fRunFlag, // Flag indicating execute on extract
                    fciGetNextCabinet,  // callback for continuation cabinet
                    fciStatus,          // status callback
                    fciOpenInfo,        // Open/get attribs/etc. callback
                    tcomp,              // Compression type
                    &sae
                    )) {
            //** Only set error if we didn't already do so
            if (!ErrIsError(sae.perr)) {
                mapFCIError(perr,psess,szFCI_ADD_FILE,&psess->erf);
            }
            return FALSE;
        }
        //** Update counts *after* FCIAddFile(), since large files may cause
        //   us to overflow to a new cabinet (or cabinets), and we want our
        //   fciGetNextCabinet() callback to reset these counts!
        psess->cFilesInFolder++;        // Added a file to the current folder
        psess->cFilesInCabinet++;       // Added a file to the current cabinet
        return TRUE;
    }

/*
 ** OK, we're putting the file on the disk (not in a cabinet)
 */
    //** Check for error from inCabinet() call
    if (ErrIsError(perr)) {
        return FALSE;
    }

//BUGBUG 19-Apr-1994 bens cabinet=on/cabinet=off disk space accounting broken
//  If we are have an open cabinet, and then the DDF file tells us to go
//  out of cabinet mode (regardless of the compression setting), then we
//  really need to close the open cabinet and update the remaining free space.
//
//  This means that if cabinet=on is set later, there will be no connection
//  in the cabinet files between the new cabinet set and the old one!
//
//  NEED TO DOCUMENT THIS BEHAVIOR IN THE SPEC!


//BUGBUG 30-Mar-1994 bens Don't support compressing individual files
    if (tcomp != tcompTYPE_NONE) {
        ErrSet(perr,pszDIAERR_SINGLE_COMPRESS,"%s",pcmd->file.achSrc);
        return FALSE;
    }

    //** See if we are copying files; this feature of not copying files
    //   was implemented for the ACME group, so that their clients can
    //   generate "ADMIN" INF files quickly -- they do one Diamond run
    //   with one version of InfFileLineFormat with cabinets and compression
    //   on, and that generates the "disk" INF.  Then they do another
    //   Diamond run with a different InfFileLineFormat, and they set
    //   DoNotCopyFiles=TRUE, and turn off cabinets and compression, so
    //   Diamond goes through all the motions to generate an INF, but
    //   skips the step of actually copying any files!

    hvar = VarFind(psess->hvlist,pszVAR_DO_NOT_COPY_FILES,perr);
    Assert(!perr->fError);              // Must be defined
    fCopyFile = !VarGetBool(hvar);      // Invert setting

    //** Get file info for this file
    hgen = psess->hgenFileLast;         // Item for this file
    pfinfo = GLGetValue(hgen);          // Get file info
    AssertFinfo(pfinfo);

    //** Switch to new disk if necessary, account for file
    if (!newDiskIfNecessary(psess,pfinfo->cbFile,TRUE,perr)) {
        return FALSE;
    }

    //** Make sure MaxDiskSize is a multiple of the ClusterSize
    if (!checkDiskClusterSize(psess,perr)) {
        return FALSE;
    }

    //** Construct complete filespec for destination file
    if (!catDirAndFile(achFull,                 // gets "disk1\foo\setup.exe"
                       sizeof(achFull),
                       psess->achCurrOutputDir, // "disk1"
                       achDst,                  // "foo\setup.exe"
                       "",
                       perr)) {
        return FALSE;                   // perr set already
    }

    //** If copying files, make sure destination directory is created
    if (fCopyFile && !ensureDirectory(achFull,TRUE,perr)) {
        return FALSE;                   // perr already filled in
    }

    //** Copy file (or just merge file date/time/attr values)
//BUGBUG 01-Apr-1994 bens Pass status to CopyOneFile for more granularity
//          Also, should think about separating out data for files that are
//          not compressed versus those that are, so we can provide accurate
//          statistics!
    fSuccess = CopyOneFile(achFull,     // Destination file name
                           achSrc,      // Source file name
                           fCopyFile,   // Control whether file is copied
                           (UINT)cbFILE_COPY_BUFFER, // Copy buffer size
                           fnofpDiamond, // Overrides date/time/attr
                           psess,       // Context for fnofpDiamond
                           perr);       // ERROR structure

    //** Use true file size for status, ignore return code
    fciStatus(statusFile,pfinfo->cbFile,pfinfo->cbFile,&sae);

    //** Add file to INF (cabinet=0 ==> not inside a cabinet)
    if (!modeInfAddFile(psess,achDst,psess->iDisk,0,perr)) {
        return FALSE;                   // perr already filled in
    }

    return fSuccess;
} /* doFile() */


/***    ensureCabinetsFlushed - Make sure FCI is flushed out
 *
 *  Entry:
 *      psess - Session
 *      perr  - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; cabinets flushed (if necessary)
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in
 */
BOOL ensureCabinetsFlushed(PSESSION psess, PERROR perr)
{
    SESSIONANDERROR sae;                // Context for FCI calls

    AssertSess(psess);

    //** NOP if we already flushed FCI
    if (!psess->hfci) {
        return TRUE;
    }

    //** Store context to pass through FCI calls
    sae.psess = psess;
    sae.perr  = perr;

    //** Flush out any final cabinet remnants
    if (!FCIFlushCabinet(psess->hfci,FALSE,fciGetNextCabinet,fciStatus,&sae)) {
        //** Only set error if we didn't already do so in FCIAddFile callback
        if (!ErrIsError(sae.perr)) {
            mapFCIError(perr,psess,szFCI_FLUSH_CABINET,&psess->erf);
        }
        return FALSE;
    }

    //** Destroy FCI context
    if (!FCIDestroy(psess->hfci)) {
        mapFCIError(perr,psess,szFCI_DESTROY,&psess->erf);
        return FALSE;
    }
    psess->hfci = NULL;                 // Clear out FCI context

    return TRUE;
} /* ensureCabinetsFlushed() */


/***    computeSetID - accumulate cabinet set ID
 *
 *  The cabinet set ID is used by FDI at decompress time to ensure
 *  that it received the correct continuation cabinet.  We construct
 *  the set ID for a cabinet set by doing a computation on all of the
 *  destination files (during pass 1).  This is likely to give unique
 *  set IDs, assuming our function is a good one (which it probably is
 *  is not).
 *
 *  Entry:
 *      psess - session to update
 *      psz   - String to "add" to set ID
 *
 *  Exit:
 *      psess->setID updated;
 */
void computeSetID(PSESSION psess, char *psz)
{
    //** Just add up all the characters, ignoring overflow
    while (*psz) {
        psess->setID += (USHORT)*psz++;
    }
} /* computeSetID() */


/***    inCabinet - Returns indication if cabinet mode is on
 *
 *  Entry:
 *      psess - Session to check
 *      perr  - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; cabinet mode is on
 *
 *  Exit-Failure:
 *      Returns FALSE; cabinet mode is off, or an error (if perr is set)
 */
BOOL inCabinet(PSESSION psess, PERROR perr)
{
    HVARIABLE       hvar;

    hvar = VarFind(psess->hvlist,pszVAR_CABINET,perr);
    Assert(!perr->fError);              // Must be defined
    return VarGetBool(hvar);            // Get current setting
} /* inCabinet() */


/***    ensureCabinet - Make sure FCI has a cabinet open
 *
 *  This function is called to create the FCI context.  A normal DDF will
 *  only cause a single FCICreate() call to be made.  But a DDF that turns
 *  cabinet mode on and off several times will cause several FCICreate()
 *  calls to be made.
 *
 *  Entry:
 *      psess - Session to update
 *      perr  - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; cabinet is ready to receive files
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in
 */
BOOL ensureCabinet(PSESSION psess, PERROR perr)
{
    CCAB    ccab;

    AssertSess(psess);
    if (psess->hfci == NULL) {          // Have to create context
        //** Set CCAB for FCICreate
        if (!ccabFromSession(&ccab,psess,0,perr)) { // No previous cabinet size
            return FALSE;
        }

        //** Set reserved sizes (from variable settings)
        if (!setCabinetReserve(&ccab,psess,perr)) {
            return FALSE;
        }

        //** Create the FCI context
        psess->hfci = FCICreate(
                        &psess->erf,    // error code return structure
                        fciFilePlaced,  // callback for file placement notify
                        fciAlloc,
                        fciFree,
                        fciTempFile,
                        &ccab
                       );
        if (psess->hfci == NULL) {
            mapFCIError(perr,psess,szFCI_CREATE,&psess->erf);
            return FALSE;
        }
    }
    return TRUE;
} /* ensureCabinet() */


/***    ccabFromSession - Fill in a CCAB structure from a SESSION structure
 *
 *  Entry:
 *      pccab     - CCAB to fill in
 *      psess     - Session to use
 *      cbPrevCab - Size of previous cabinet; 0 if no previous cabinet
 *      perr      - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; pccab updated.
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in with error.
 */
BOOL ccabFromSession(PCCAB pccab, PSESSION psess, ULONG cbPrevCab, PERROR perr)
{
    HVARIABLE   hvar;

    AssertSess(psess);

#ifndef REMOVE_CHICAGO_M6_HACK
    //** Pass hack flag on to FCI
    pccab->fFailOnIncompressible = psess->fFailOnIncompressible;
#endif

    /*** Switch to new disk, if necessary
     *
     *  NOTE: cbPrevCab is an *estimate* from FCI.  We use it to decide
     *        if we need to switch to a new disk.  If we *don't* switch to
     *        a new disk, then our free space on disk value (psess->cbDiskLeft)
     *        will be slightly off until we get called back by FCI at
     *        our fciStatus() function with statusCabinet, at which point
     *        we can correct the amount of free space.
     *
     *        Also, we save this estimated size *before* we determine if
     *        we are switching to a new disk, since newDiskIfNecessary()
     *        will clear psess->cbCabinetEstimate if a new disk is needed,
     *        to prevent fciStatus() from messing with psess->cbDiskLeft.
     *
     *        See fciStatus() for more details.
     */
    psess->cbCabinetEstimate = cbPrevCab; // Save estimated size first
    if (!newDiskIfNecessary(psess,(long)cbPrevCab,FALSE,perr)) {
        return FALSE;
    }

    //** Construct new cabinet information
    psess->iCabinet++;                  // New cabinet number
    pccab->iCab  = psess->iCabinet;     // Set cabinet number for FCI
    pccab->iDisk = psess->iDisk;        // Set disk number for FCI
    pccab->setID = psess->setID;        // Carry over set ID

    //** Get cabinet file name
    if (!getVarWithOverride(psess,
                            pccab->szCab,
                            sizeof(pccab->szCab),
                            pszPATTERN_VAR_CAB_NAME,
                            pszVAR_CAB_NAME,
                            psess->iCabinet,
                            pszDIA_CABINET,
                            perr)) {
        return FALSE;                   // perr already filled in
    }

    //** Get current disk output directory
    Assert(sizeof(pccab->szCabPath) >= sizeof(psess->achCurrOutputDir));
    strcpy(pccab->szCabPath,psess->achCurrOutputDir);

    //** Set cabinet limits
    hvar = VarFind(psess->hvlist,pszVAR_MAX_CABINET_SIZE,perr);
    Assert(!perr->fError);              // Must be defined
    pccab->cb = VarGetLong(hvar);       // Get maximum cabinet size
    if ((pccab->cb == 0) ||             // Default is max disk size
        (pccab->cb > (ULONG)psess->cbDiskLeft)) { // Disk size is smaller
        pccab->cb = psess->cbDiskLeft;  // Use space on disk as cabinet limit
    }

    //** Set folder limits
    hvar = VarFind(psess->hvlist,pszVAR_FOLDER_SIZE_THRESHOLD,perr);
    Assert(!perr->fError);              // FolderSizeThreshold must be defined
    pccab->cbFolderThresh = VarGetLong(hvar); // Get disk size;
    if (pccab->cbFolderThresh == 0) {       // Use default value
        pccab->cbFolderThresh = pccab->cb;  // Use max cabinet size
    }

    //** Get user-readable disk label
    Assert(sizeof(pccab->szDisk) >= sizeof(psess->achCurrDiskLabel));
    strcpy(pccab->szDisk,psess->achCurrDiskLabel);

    //** Save away cabinet and disk info for INF
    if (!infAddCabinet(psess,
                       psess->iCabinet,
                       psess->iDisk,
                       pccab->szCab,
                       perr)) {
        return FALSE;
    }

    //** Success
    return TRUE;
} /* ccabFromSession() */


long roundUp(long value, long chunk)
{
    return ((value + chunk - 1)/chunk)*chunk;
}


/***    newDiskIfNecessary - Determine if new disk is necessary, update Session
 *
 *  This function is called in the following cases:
 *  (1) No files have been placed on any disks or in any cabinets, yet.
 *      ==> This function is used as the common place to initialize all
 *          the disk information; we increment the disk number (to 1),
 *          set the max disk size, etc.
 *  (2) FCI has called us back to get the next cabinet information.
 *      ==> FCI has limited the cabinet size to almost exactly the size
 *          we specified in the last CCAB structure we passed to FCI
 *          (it is not exact, because the cabinet file needs the cabinet
 *          file name and disk label name for this new cabinet in order
 *          to figure out the precise size!).  So we use the cbConsume
 *          value to figure out if the current disk is full (the directive
 *          file could permit multiple cabinets per disk, though that is
 *          kind of obscure).
 *  (3) We are placing a file on a disk *outside* of a cabinet.
 *      ==> In this case we need to check the limits on files per disk
 *          (the root directory size limitation) and the bytes per disk.
 *          If we cannot fit the file on the disk, then we increment to
 *          the next disk, leaving some free space.
 *
 *  Entry:
 *      psess         - Session to update
 *      cbConsume     - Size of cabinet/file to be placed on current disk
 *                      Pass -1 to force a new disk.
 *                      Pass 0 if no previous cabinet.
 *      fSubOnNewDisk - TRUE => subtract cbConsume from new disk (used
 *                          when copying a file to a disk outside a cabinet).
 *                      FALSE => don't subtract cbConsume from new disk (used
 *                          when copying a file to a cabinet).
 *      perr          - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; psess updated if necessary for new disk
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in
 */
BOOL newDiskIfNecessary(PSESSION psess,
                        long     cbConsume,
                        BOOL     fSubOnNewDisk,
                        PERROR   perr)
{
    long        cbCluster;
    int         cch;
    long        cMaxFiles;
    HVARIABLE   hvar;
    char       *pch;

    AssertSess(psess);

    //** Get cluster size to help decide if disk is full
    hvar = VarFind(psess->hvlist,pszVAR_CLUSTER_SIZE,perr);
    Assert(!perr->fError);              // Must be defined
    cbCluster = VarGetLong(hvar);       // Get cluster size
    psess->cbClusterCabEst = cbCluster; // Save cluster size for current disk

    //** Get limits on files per disk (outside of cabinets)
    hvar = VarFind(psess->hvlist,pszVAR_MAX_DISK_FILE_COUNT,perr);
    Assert(!perr->fError);              // Must be defined
    cMaxFiles = VarGetLong(hvar);       // Get file count limit

    //** Figure out if we need to go to a new disk
    if ((cbConsume == -1)                            || // Forced new disk
        psess->fForceNewDisk                         || // .New Disk directive
        (roundUp(cbConsume,cbCluster) > psess->cbDiskLeft) || // No more space
        (!inCabinet(psess,perr) && (psess->cFilesInDisk >= cMaxFiles))) {

        psess->fForceNewDisk = FALSE;   // Reset force flag
        psess->iDisk++;                 // New disk

        //** Get max disk size for this disk
        if (-1 == (psess->cbDiskLeft = getMaxDiskSize(psess,perr))) {
            return FALSE;
        }

        //** Update ClusterSize and MaxDiskFileCount (if standard disk size)
        if (!setDiskParameters(psess,NULL,psess->cbDiskLeft,perr)) {
            return FALSE;
        }

        if (fSubOnNewDisk) {           // Have to subtract from new disk
            psess->cbDiskLeft -= roundUp(cbConsume,cbCluster);
        }
        psess->cFilesInDisk = 1;        // Only one file on new disk so far

        //** Tell fciStatus() not to update psess->cbDiskLeft!
        psess->cbCabinetEstimate = 0;
    }
    else {                              // Update space left on current disk
        cbConsume = roundUp(cbConsume,cbCluster);
        psess->cbDiskLeft -= cbConsume; // Update remaining space on disk
        if (!inCabinet(psess,perr)) {   // Not in a cabinet
            psess->cFilesInDisk++;      // Update count of files on disk
        }
        return TRUE;                    // Nothing more to do!
    }

    //** OK, we have a new disk:
    //   1) Create output directory
    //   2) Get user-readable disk label
    //   3) Add disk to INF file

    //** Get disk output directory name
    if (!getVarWithOverride(psess,
                            psess->achCurrOutputDir,
                            sizeof(psess->achCurrOutputDir),
                            pszPATTERN_VAR_DISK_DIR,
                            pszVAR_DISK_DIR_NAME,
                            psess->iDisk,
                            pszDIA_DISK_DIR,
                            perr)) {
        return FALSE;                   // perr already filled in
    }

    //** Make sure destination directory exists
    if (!ensureDirectory(psess->achCurrOutputDir,FALSE,perr)) {
        return FALSE;                   // perr already filled in
    }

    //** Append path separator if necessary
    pch = psess->achCurrOutputDir;
    cch = strlen(pch);
    appendPathSeparator(&(pch[cch-1]));

    //**(2) Get the sticky, user-readable disk label
    if (!getVarWithOverride(psess,
                            psess->achCurrDiskLabel,
                            sizeof(psess->achCurrDiskLabel),
                            pszPATTERN_VAR_DISK_LABEL,
                            pszVAR_DISK_LABEL_NAME,
                            psess->iDisk,
                            pszDIA_DISK_LABEL,
                            perr)) {
        return FALSE;                   // perr already filled in
    }

    //**(3) Add new disk to INF file
    if (!infAddDisk(psess,psess->iDisk,psess->achCurrDiskLabel,perr)) {
        return FALSE;
    }

    return TRUE;
} /* newDiskIfNecessary() */


/***    setCabinetReserve - Set reserved size fields in CCAB
 *
 *  Entry:
 *      pccab     - CCAB to fill in
 *      psess     - Session to use
 *      perr      - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; pccab updated.
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in with error.
 */
BOOL setCabinetReserve(PCCAB pccab, PSESSION psess, PERROR perr)
{
    HVARIABLE   hvar;

    hvar = VarFind(psess->hvlist,pszVAR_RESERVE_PER_CABINET,perr);
    Assert(!perr->fError);              // Must be defined
    pccab->cbReserveCFHeader = (UINT)VarGetLong(hvar);

    hvar = VarFind(psess->hvlist,pszVAR_RESERVE_PER_FOLDER,perr);
    Assert(!perr->fError);              // Must be defined
    pccab->cbReserveCFFolder = (UINT)VarGetLong(hvar);

    hvar = VarFind(psess->hvlist,pszVAR_RESERVE_PER_DATA_BLOCK,perr);
    Assert(!perr->fError);              // Must be defined
    pccab->cbReserveCFData   = (UINT)VarGetLong(hvar);

    return TRUE;
} /* setCabinetReserve() */


/***    tcompFromSession - Get current compression setting
 *
 *  Entry:
 *      psess - Session to update
 *      perr  - ERROR structure
 *
 *  Exit-Success:
 *      Returns one of the tcompXXX equates
 *
 *  Exit-Failure:
 *      Returns tcompBAD; perr filled in
 */
int tcompFromSession(PSESSION psess, PERROR perr)
{
    HVARIABLE   hvar;
    int         typeC;
    int         level;                  // Quantum compression level
    int         memory;                 // Quantum compression memory

    AssertSess(psess);
    //** Get compression setting
    hvar = VarFind(psess->hvlist,pszVAR_COMPRESS,perr);
    Assert(!perr->fError);              // Must be defined
    if (VarGetBool(hvar)) {             // Compression is on
        //** Get the compression type
        hvar = VarFind(psess->hvlist,pszVAR_COMPRESSION_TYPE,perr);
        Assert(!ErrIsError(perr));      // Must be defined
        typeC = CompTypeFromPSZ(VarGetString(hvar),perr);
        Assert(!ErrIsError(perr));      // Checked when it was defined
        switch (typeC) {
            case tcompTYPE_MSZIP:
                return tcompTYPE_MSZIP;

            case tcompTYPE_QUANTUM:
#ifdef BIT16
                //** Quantum *compression* not supported in 16-bit mode.
                //   We should never get here because the dfparse.c
                //   validation should generate an error if Quantum is
                //   selected.
                Assert(0);
#endif
                //** Get the compression level setting
                hvar = VarFind(psess->hvlist,pszVAR_COMPRESSION_LEVEL,perr);
                Assert(!ErrIsError(perr));      // Must be defined
                level = CompLevelFromPSZ(VarGetString(hvar),perr);
                Assert(!ErrIsError(perr));      // Checked when it was defined

                //** Get the compression memory setting
                hvar = VarFind(psess->hvlist,pszVAR_COMPRESSION_MEMORY,perr);
                Assert(!ErrIsError(perr));      // Must be defined
                memory = CompMemoryFromPSZ(VarGetString(hvar),perr);
                Assert(!ErrIsError(perr));      // Checked when it was defined

                //** Construct TCOMP and return it
                return TCOMPfromTypeLevelMemory(typeC,level,memory);
        }
        //** Unknown compression type
        Assert(0);
    }
    else {
        return tcompTYPE_NONE;          // Compression is off
    }
    Assert(0);
} /* tcompFromSession() */


/***    checkDiskClusterSize - Check disk size and cluster size
 *
 *  Make sure disk size is valid for cluster size.
 *
 *  Entry:
 *      psess - Session to check
 *      perr  - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE;
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in
 */
BOOL checkDiskClusterSize(PSESSION psess, PERROR perr)
{
    long        cbCluster;
    long        cbDisk;
    long        cClusters;
    HVARIABLE   hvar;

    //** Get max disk size
    if (-1 == (cbDisk = getMaxDiskSize(psess,perr))) {
        return FALSE;
    }

    //** Get cluster size
    hvar = VarFind(psess->hvlist,pszVAR_CLUSTER_SIZE,perr);
    Assert(!perr->fError);              // Must be defined
    cbCluster = VarGetLong(hvar);

    //** Make sure disk size is an exact multiple of the cluster size
    cClusters = cbDisk / cbCluster;      // Gets truncated to integer

    //** Return result
    if (cbDisk == (cClusters*cbCluster)) {
        return TRUE;
    }
    else {
        //** Disk size is not a multiple of the cluster size
        ErrSet(perr,pszDIAERR_DISK_CLUSTER_SIZE,"%ld%ld",cbDisk,cbCluster);
        return FALSE;
    }
} /* checkDiskClusterSize() */


/***    getMaxDiskSize - Get current maximum disk size setting
 *
 *  Entry:
 *      psess - Session
 *      perr  - ERROR structure
 *
 *  Exit-Success:
 *      Returns maximum disk size
 *
 *  Exit-Failure:
 *      Returns -1; perr filled in
 */
long getMaxDiskSize(PSESSION psess, PERROR perr)
{
    long        cb;
    HVARIABLE   hvar;

    //** Build variable name that *may* exist for this disk
    //   NOTE: During pass 1, and before newDiskIfNeccessary() is called,
    //         psess->iDisk will be 0, so we'll always get the MaxDiskSize
    //         variable value (unless someone happens to define MaxDiskSize0!)
    //
    if (!nameFromTemplate(psess->achMsg,
                          sizeof(psess->achMsg),
                          pszPATTERN_VAR_MAX_DISK_SIZE,
                          psess->iDisk,
                          pszVAR_MAX_DISK_SIZE,
                          perr)) {
        Assert(0);                      // Should never fail
        return FALSE;                   // perr already filled in
    }

    //** See if this variable exists
    hvar = VarFind(psess->hvlist,psess->achMsg,perr);
    if (hvar != NULL) {                 // Yes, get its value
        cb = VarGetLong(hvar);
    }
    else {                              // NO, no MaxDiskSizeN variable
        ErrClear(perr);                 // Not an error
        //** Use default variable
        hvar = VarFind(psess->hvlist,pszVAR_MAX_DISK_SIZE,perr);
        Assert(!perr->fError);          // Must be defined
        cb = VarGetLong(hvar);
    }

    return cb;
} /* getMaxDiskSize() */


/***    setDiskParameters - Set ClusterSize/MaxDiskFileCount
 *
 *  If the specified disk size is on our predefined list, then set the
 *  standard values for ClusterSize and MaxDiskFileCount.
 *
 *  Entry:
 *      psess       - Session
 *      pszDiskSize - Disk size string (NULL if cbDisk should be used instead)
 *      cbDisk      - Disk size (only if pszDiskSize == NULL)
 *      perr        - ERROR structure
 *
 *  Exit-Success:
 *      TRUE; ClusterSize/MaxDiskFileCount adjusted if specified size is
 *            on our special list.
 *
 *  Exit-Failure:
 *      Returns -1; perr filled in
 */
BOOL setDiskParameters(PSESSION  psess,
                       char     *pszDiskSize,
                       long      cbDisk,
                       PERROR    perr)
{
    char    achSize[50];
    ERROR   errIgnore;
    char   *psz;

    //** Determine which parameter to use
    if (pszDiskSize == NULL) {
        _ltoa(cbDisk,achSize,10);       // Convert number to string
        psz = achSize;
    }
    else {
        psz = pszDiskSize;
    }

    //** Look up special list, get integer size for sure
    if (!(cbDisk = IsSpecialDiskSize(psess,psz))) {
        return TRUE;                    // Not a special size
    }

    //   NOTE: We have to be careful not to set these variables
    //         until they are defined!
    if (VarFind(psess->hvlist,pszVAR_CLUSTER_SIZE,&errIgnore)) {
        //** ClusterSize is defined
        if (!VarSetLong(psess->hvlist,pszVAR_CLUSTER_SIZE,cbDisk,perr)) {
            return FALSE;
        }
    }
    if (VarFind(psess->hvlist,pszVAR_MAX_DISK_FILE_COUNT,&errIgnore)) {
        //** MaxDiskFileCount is defined
        if (!VarSetLong(psess->hvlist,pszVAR_MAX_DISK_FILE_COUNT,cbDisk,perr)) {
            return FALSE;
        }
    }
    return TRUE;
} /* setDiskParameters() */


/***    fnofpDiamond - Override file date/time/attributes
 *
 *  NOTE: See fileutil.h for entry/exit conditions.
 */
FNOVERRIDEFILEPROPERTIES(fnofpDiamond)
{
    PFILEINFO   pfinfo;
    PSESSION    psess;

    psess = (PSESSION)pv;
    AssertSess(psess);

    //** Use override values and/or store real values
    if (psess->fGenerateInf) {          // Only valid if INF will be made
        pfinfo = GLGetValue(psess->hgenFileLast);
        AssertFinfo(pfinfo);
        if (pfinfo->flags & fifDATE_SET) {
            pfta->date = pfinfo->fta.date; // Override
        }
        else {
            pfinfo->fta.date = pfta->date; // Store for INF generation
        }

        if (pfinfo->flags & fifTIME_SET) {
            pfta->time = pfinfo->fta.time; // Override
        }
        else {
            pfinfo->fta.time = pfta->time; // Store for INF generation
        }

        if (pfinfo->flags & fifATTR_SET) {
            pfta->attr = pfinfo->fta.attr; // Override
        }
        else {
            pfinfo->fta.attr = pfta->attr; // Store for INF generation
        }
    }

    //** Success
    return TRUE;
} /* fnofpDiamond() */


/***    fciOpenInfo - Get file date, time, and attributes for FCI
 *
 *  Entry:
 *      pszName  - filespec
 *      pdate    - buffer to receive date
 *      ptime    - buffer to receive time
 *      pattribs - buffer to receive attributes
 *      pv       - our context pointer
 *
 *  Exit-Success:
 *      Returns file handle; *pdate, *ptime, *pattribs filled in.
 *
 *  Exit-Failure:
 *      Returns -1;
 */
FNFCIGETOPENINFO(fciOpenInfo)
{
    FILETIMEATTR    fta;
    int             hf;
    PERROR          perr  = ((PSESSIONANDERROR)pv)->perr;
    PSESSION        psess = ((PSESSIONANDERROR)pv)->psess;

    AssertSess(psess);

    //** Get real file date, time, and attributes
    if (!GetFileTimeAndAttr(&fta,pszName,perr)) {
        return FALSE;
    }

    //** Get/Override file date/time/attributes
    if (!fnofpDiamond(&fta,psess,perr)) {
        return FALSE;                   // perr already filled in
    }

    //** Tell FCI what to use
    *pdate    = fta.date;
    *ptime    = fta.time;
    *pattribs = fta.attr;

    //** Open the file
    hf = _open(pszName, _O_RDONLY | _O_BINARY);
    if (hf == -1) {
        ErrSet(perr,pszDIAERR_OPEN_FAILED,"%s",pszName);
        return -1;                      // abort on error
    }
    return hf;
}


/***  fciFilePlaced - FCI calls this when a file is commited to a cabinet
 *
 *   Entry:
 *      pccab         - Current cabinet structure
 *      pszFile       - Name of file placed in this cabinet
 *      cbFile        - File size
 *      fContinuation - TRUE => Continued from previous cabinet
 *      pv            - Really a psae
 *
 *   Exit-Success:
 *      returns anything but -1;
 *
 *   Exit-Failure:
 *      returns -1;
 */
FNFCIFILEPLACED(fciFilePlaced)
{
    PSESSION psess = ((PSESSIONANDERROR)pv)->psess;
    PERROR   perr  = ((PSESSIONANDERROR)pv)->perr;

    AssertSess(psess);

    if (psess->levelVerbose >= vbMORE) {
        if (fContinuation) {
            MsgSet(psess->achMsg,pszDIA_FILE_IN_CAB_CONT,"%s%s%d%s",
                pszFile, pccab->szCab, pccab->iCab, pccab->szDisk);
        }
        else {
            MsgSet(psess->achMsg,pszDIA_FILE_IN_CAB,"%s%s%d%s",
                pszFile, pccab->szCab, pccab->iCab, pccab->szDisk);
        }
        lineOut(psess,psess->achMsg,TRUE);
    }

    //** Add file entry to INF temp file
    if (psess->fGenerateInf) {          // Only if generating INF file
        if (!fContinuation) {           // Only if not a continuation
            if (!modeInfAddFile(psess,
                                pszFile,
                                pccab->iDisk,
                                pccab->iCab,
                                perr)) {
                return -1;              // Abort with error
            }
        }
    }

    return 0;                           // Success
} /* fciFilePlaced() */


/***  fciGetNextCabinet - FCI calls this to get new cabinet info
 *
 *  NOTE: See FCI.H for more details.
 *
 *  Entry:
 *      pccab     - Points to previous current-cabinet structure
 *      cbPrevCab - Size of previous cabinet - ESTIMATE!
 *      pv        - Really a psae
 *
 *  Exit-Success:
 *      Returns TRUE; pccab updated with info for new cabinet
 *
 *  Exit-Failure:
 *      returns FALSE; ((psae)pv)->perr filled in
 */
FNFCIGETNEXTCABINET(fciGetNextCabinet)
{
    PSESSION psess = ((PSESSIONANDERROR)pv)->psess;
    PERROR   perr  = ((PSESSIONANDERROR)pv)->perr;

    //** Set CCAB for new cabinet (and maybe disk)
    AssertSess(psess);
    if (!ccabFromSession(pccab,psess,cbPrevCab,perr)) {
        return FALSE;
    }

    //** Current folder and cabinet are empty
    psess->cFilesInFolder  = 0;
    psess->cFilesInCabinet = 0;

    //** Success
    return TRUE;
} /* fciGetNextCabinet() */


/***  fciStatus - FCI calls this periodically when adding files
 *
 *  NOTE: See FCI.H for more details.
 *
 *  Entry:
 *      typeStatus - Type of status call being made
 *      cb1        - Size of compressed data generated since last call
 *      cb2        - Size of uncompressed data processed since last call
 *      pv         - Really a psae
 *
 *  Exit-Success:
 *      returns anything but -1, continue building cabinets
 *
 *  Exit-Failure:
 *      returns -1, FCI should abort
 */
FNFCISTATUS(fciStatus)
{
    long        cbCabinetActual;
    double      percent;
    PERROR      perr  = ((PSESSIONANDERROR)pv)->perr;
    PSESSION    psess = ((PSESSIONANDERROR)pv)->psess;

    AssertSess(psess);
    switch (typeStatus) {

    case statusFolder:
        //** Adding folder to cabinet (i.e., folder flush)
        psess->cFilesInFolder = 0;      // reset count
        return TRUE;                    // Continue

    case statusFile:
        //** Compressing a file
        psess->cbFileBytes     += cb2;  // Update progress data
        psess->cbFileBytesComp += cb1;

        //** Compute percentage complete
        if (psess->cbTotalFileBytes > 0) {
            percent = psess->cbFileBytes/(float)psess->cbTotalFileBytes;
            percent *= 100.0;           // Make it 0..100
        }
        else {
            Assert(0);                  // Should never get here
            percent = 0.0;
        }

        //** Amount of status depends upon verbosity
        if (psess->levelVerbose >= vbFULL) {
            MsgSet(psess->achMsg,pszDIA_PERCENT_COMPLETE_DETAILS,
                "%6.2f%,ld%,ld",
                percent,psess->cbFileBytes,psess->cbFileBytesComp);
            lineOut(psess,psess->achMsg,TRUE);
        }
        else {
            MsgSet(psess->achMsg,pszDIA_PERCENT_COMPLETE_SOME,
                "%6.2f%s%,ld%,ld",
                percent, psess->achCurrFile, psess->iCurrFile, psess->cFiles);
            //** NOTE: No line-feed, so that we don't scroll
            lineOut(psess,psess->achMsg,FALSE);
        }
        return TRUE;                    // Continue

    case statusCabinet:
        /** Cabinet completed and written to disk
         *
         *  If this cabinet did not force a disk change, then we need to
         *  correct the amount of free space left on the current disk,
         *  since FCI only passed us an *estimated* cabinet size when it
         *  called our fciGetNextCabinet() function!
         *
         *  Why did FCI estimate the cabinet size, you ask?  Because there
         *  is a "chicken-and-egg" situation between FCI and Diamond!  Except
         *  for the last cabinet in a set of cabinets, each cabinet file
         *  has a *forward* reference to the next cabinet.  FCI calls our
         *  fciGetNextCabinet() function to get the cabinet file name and
         *  disk name for this forward reference, and passes the *estimated*
         *  cabinet size of the current cabinet, because Diamond needs this
         *  information to decide if it has to put the next cabinet on a new
         *  disk.
         *
         *  If Diamond decides to put the next cabinet on a new disk, then
         *  everything is fine, and the fact that Diamond had an estimate
         *  of the cabinet size is irrelevant.  BUT, if more cabinets or
         *  files are being placed on the same disk, then Diamond needs an
         *  *exact* cabinet size so that the disk free space can be precise.
         *
         *  So, FCI calls us back with the original estimate and the final
         *  size, and we adjust our disk free space amount as necessary.
         *
         *  We also return to FCI the *rounded* cabinet size, so that it
         *  can adjust its internal maximum cabinet size, too!
         */
        if (psess->cbCabinetEstimate != 0) {
            //** Round up to cabinet size on disk
            cbCabinetActual = roundUp(cb2,psess->cbClusterCabEst);

            //** Need to add back old estimate and subtract actual size
            Assert(psess->cbCabinetEstimate == (long)cb1);
            psess->cbDiskLeft += roundUp(cb1,psess->cbClusterCabEst);
            psess->cbDiskLeft -= roundUp(cb2,psess->cbClusterCabEst);
            return cbCabinetActual;     // Tell FCI size we actually used
        }
        return cb2;                     // Let FCI use size it had

    default:
        //** Unknown status callback
        Assert(0);
        return TRUE;
    }
} /* fciStatus() */


/***    fciAlloc - memory allocator for FCI
 *
 *  Entry:
 *      cb - size of block to allocate
 *
 *  Exit-Success:
 *      returns non-NULL pointer to block of size at least cb.
 *
 *  Exit-Failure:
 *      returns NULL
 */
FNALLOC(fciAlloc)
{
#ifdef  BIT16
    //** Use 16-bit function
    return _halloc(cb,1);
#else // !BIT16
    //** Use 32-bit function
    return malloc(cb);
#endif // !BIT16
} /* fciAlloc() */


/***    fciFree - memory free function for FCI
 *
 *  Entry:
 *      pv - memory allocated by fciAlloc to be freed
 *
 *  Exit:
 *      Frees memory
 */
FNFREE(fciFree)
{
#ifdef  BIT16
    //** Use 16-bit function
    _hfree(pv);
#else // !BIT16
    //** Use 32-bit function
    free(pv);
#endif // !BIT16
}


/***    fciTempFile - Construct tempfile name for FCI
 *
 *  Entry:
 *      pszTempName - Buffer to be filled in with tempfile name
 *      cbTempName  - Size of tempfile name buffer
 *
 *  Exit-Success:
 *      Returns TRUE; psz filled in
 *
 *  Exit-Failure:
 *      Returns FALSE;
 */
FNFCIGETTEMPFILE(fciTempFile)
{
    char    *psz;

    psz = _tempnam("","dc");            // Get a name
    if ((psz != NULL) && (strlen(psz) < (unsigned)cbTempName)) {
        strcpy(pszTempName,psz);        // Copy to caller's buffer
        free(psz);                      // Free temporary name buffer
        return TRUE;                    // Success
    }
    //** Failed
    if (psz) {
        free(psz);
    }
    return FALSE;
}


/***    getCompressedFileName - Generate compressed filename
 *
 *  Entry:
 *      psess     - Session (has variable list)
 *      pszResult - Buffer to receive concatentation
 *      cbResult  - Size of pszResult
 *      pszSrc    - Filespec to parse
 *      perr      - ERROR structure to fill in
 *
 *  Exit-Success:
 *      Returns TRUE; pszResult buffer has generated name
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in with error.
 *
 *  Notes:
 *  (1) Takes pszSrc filespec, trims off path, and replaces or appends
 *      the value of CompressedFileExtensionChar in the extension.
 *
 *  Examples:
 *  (1) "foo"    ,"_" => "foo._"
 *  (2) "foo.b"  ,"_" => "foo.b_"
 *  (3) "foo.ba" ,"_" => "foo.ba_"
 *  (4) "foo.bar","_" => "foo.ba_"
 */
BOOL getCompressedFileName(PSESSION psess,
                           char *   pszResult,
                           int      cbResult,
                           char *   pszSrc,
                           PERROR   perr)
{
    int         cch;
    char        chTrail;                // Trailing character to use
    HVARIABLE   hvar;
    int         i;
    char       *pch;
    char       *pchStart;

    AssertSess(psess);

    //** Get trailing character
    hvar = VarFind(psess->hvlist,pszVAR_COMP_FILE_EXT_CHAR,perr);
    Assert(!perr->fError);              // Must be defined
    pch = VarGetString(hvar);           // Get trailing string
    chTrail = *pch;                     // Take first char

    //** Find name.ext
    pchStart = getJustFileNameAndExt(pszSrc, perr);
    if (pchStart == NULL) {
        return FALSE;                   // perr already filled in
    }

    //** Make sure name is not too long
    cch = strlen(pchStart);             // Length of name.ext
    if (cch >= cbResult) {
        ErrSet(perr,pszDIAERR_PATH_TOO_LONG,"%s",pszSrc);
        return FALSE;
    }

    //** Find last extension separator
    strcpy(pszResult,pchStart);         // Copy name to output buffer
    pchStart = pszResult + cch;         // Assume there is no extension ("foo")
    for (pch=pszResult; *pch; pch++) {
        if ((*pch == chNAME_EXT_SEP)     && // Got one "."
            (*(pch+1) != chNAME_EXT_SEP) && // Ignore ".."
            (*(pch+1) != chPATH_SEP1) &&  // Ignore ".\"
            (*(pch+1) != chPATH_SEP2)) {  // Ignore "./"
            pchStart = pch+1;           // Ext starts after separator
        }
    }

    //** Increase length if not full extension (need room for '_' character)
    if (strlen(pchStart) < 3) {
        cch++;
    }

    //** Edit result buffer
    if (*pchStart == '\0') {            // Extension is empty or missing
        if (*(pchStart-1) != chNAME_EXT_SEP) { // Need to add "."
            *pchStart++ = chNAME_EXT_SEP;
            cch++;                      // Account for added "."
        }
        //** Add trail character below
    }
    else {                              // Extension has at least one character
        //** skip to location to place new character
        for (i=0; (i<2) && *pchStart; i++) {
            pchStart++;
        }
    }

    //** Check for buffer overflow
    if (cch >= cbResult) {
        ErrSet(perr,pszDIAERR_PATH_TOO_LONG,"%s",pszSrc);
        return FALSE;
    }

    //** Finally, store trailing character
    *pchStart++ = chTrail;              // Store trailing character
    *pchStart++ = '\0';                 // Terminate filename

    //** Success
    return TRUE;
} /* getJustFileNameAndExt() */


/***    resetSession - Reset SESSION members for start of a new pass
 *
 *  Entry:
 *      psess - Description of operation to perform
 *
 *  Exit:
 *      Initializes per-pass members.
 */
void resetSession(PSESSION psess)
{
    AssertSess(psess);
    psess->act                = actBAD;
    psess->iDisk              = 0;
    psess->iCabinet           = 0;
    psess->iFolder            = 0;
    psess->cbDiskLeft         = -1;     // Force new disk first time
    psess->cErrors            = 0;
    psess->cWarnings          = 0;
    psess->cbFileBytes        = 0;
    psess->cbFileBytesComp    = 0;
    psess->iCurrFile          = 0;
    psess->fRunSeen           = FALSE;

    psess->cFilesInFolder     = 0;
    psess->cFilesInCabinet    = 0;
    psess->cFilesInDisk       = 0;
    psess->cbCabinetEstimate  = 0;      // No estimated cabinet size
    psess->ddfmode            = ddfmodeUNKNOWN; // Don't know unified vs.
                                                // relational, yet
    psess->fExpectFileCommand = TRUE;   // Default is file copy commands
    psess->fCopyToInf         = FALSE;  // Not in .InfBegin/End
    psess->hgenFile           = (HGENERIC)-1; // Not valid
    psess->hgenFileLast       = (HGENERIC)-1; // Not valid
} /* resetSession() */


/***    parseCommandLine - Parse the command line arguments
 *
 *  Entry:
 *      cArg    - Count of arguments, including program name
 *      apszArg - Array of argument strings
 *      perr    - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; psess filled in.
 *
 *  Exit-Failure:
 *      Returns actBAD; perr filled in with error.
 */
BOOL parseCommandLine(PSESSION psess, int cArg, char *apszArg[], PERROR perr)
{
    ACTION      act=actBAD;     // No action determined, yet
    char       *apszFile[2];    // Non-directive file names
    int         cFile=0;        // Count of non-directive file names seen
    int         i;
    BOOL        fDefine=FALSE;  // TRUE => Last arg was /D
    BOOL        fFile=FALSE;    // TRUE => Last arg was /F
    BOOL	fLocation=FALSE;// TRUE => Last arg was /L
    HFILESPEC   hfspec;

    AssertSess(psess);

    //** Parse args, skipping program name
    for (i=1; i<cArg; i++) {
        if ((apszArg[i][0] == chSWITCH1) ||
            (apszArg[i][0] == chSWITCH2) ) {
            //** Have a switch to parse, make sure switch is OK here
            if (fDefine) {
                ErrSet(perr,pszDIAERR_MISSING_VAR_DEFINE);
                return FALSE;
            }
            if (fFile) {
                ErrSet(perr,pszDIAERR_MISSING_FILE_NAME);
                return FALSE;
            }
            if (fLocation) {
                ErrSet(perr,pszDIAERR_MISSING_LOCATION);
                return FALSE;
            }

            switch (toupper(apszArg[i][1])) {
                case chSWITCH_HELP:
                    if (apszArg[i][2] != '\0') {
                        ErrSet(perr,pszDIAERR_BAD_SWITCH,"%s",apszArg[i]);
                        return FALSE;
                    }
                    //** Ignore any remaining parameters
                    psess->act = actHELP;       // Show help
                    return TRUE;
                    break;

#ifndef REMOVE_CHICAGO_M6_HACK
                case chSWITCH_ANDY:
                    psess->fFailOnIncompressible = TRUE;
                    break;
#endif

                case chSWITCH_DEFINE:
                    if (apszArg[i][2] != '\0') {
                        ErrSet(perr,pszDIAERR_BAD_SWITCH,"%s",apszArg[i]);
                        return FALSE;
                    }
                    else if ((act != actBAD) && (act != actDIRECTIVE)) {
                        //* Got /D when we are not doing /F processing
                        ErrSet(perr,pszDIAERR_SWITCH_NOT_EXPECTED,"%s",apszArg[i]);
                        return FALSE;
                    }
                    fDefine = TRUE;
                    break;

                case chSWITCH_FILE:
                    if (apszArg[i][2] != '\0') {
                        ErrSet(perr,pszDIAERR_BAD_SWITCH,"%s",apszArg[i]);
                        return FALSE;
                    }
                    else if ((act != actBAD) && (act != actDIRECTIVE)) {
                        //* Got /F after we decided we're not doing /F
                        ErrSet(perr,pszDIAERR_SWITCH_NOT_EXPECTED,"%s",apszArg[i]);
                        return FALSE;
                    }
                    //* Next parm should be a directive file name
                    act = actDIRECTIVE; // The die is cast...
                    fFile = TRUE;
                    break;

                case chSWITCH_LOCATION:
                    if (apszArg[i][2] != '\0') {
                        ErrSet(perr,pszDIAERR_BAD_SWITCH,"%s",apszArg[i]);
                        return FALSE;
                    }
                    //* Next parm should be a location
                    fLocation = TRUE;
                    break;

#ifdef ASSERT
                case chSWITCH_MEMORY_DBG:
                    //** Turn on full memory heap checking (very slow!)
                    MemSetCheckHeap(TRUE);
                    break;
#endif

                case chSWITCH_VERBOSE:
                    if (apszArg[i][2] != '\0') {
                        psess->levelVerbose = atoi(&apszArg[i][2]);
                    }
                    else {
                        psess->levelVerbose = vbFULL; // Default to FULL
                    }
                    break;

                default:
                    ErrSet(perr,pszDIAERR_BAD_SWITCH,"%s",apszArg[i]);
                    return FALSE;
                    break;
            }
        }
        //** Not a command line switch
        else if (fFile) {
            //** Grab a directive file
            if (!addDirectiveFile(psess,apszArg[i],perr)) {
                //** Error adding directive file; perr already set
                return FALSE;           // Failure
            }
            fFile = FALSE;              // Done eating directive file
        }
        else if (fDefine) {
            //** Grab a define
            if (!addCmdLineVar(psess,apszArg[i],perr)) {
                //** Error adding definition; perr already set
                return FALSE;           // Failure
            }
            fDefine = FALSE;            // Done eating definition
        }
        else if (fLocation) {
            //** Grab the location
            if (strlen(apszArg[i]) >= sizeof(psess->achCurrOutputDir)) {
                ErrSet(perr,pszDIAERR_LOCATION_TOO_LONG,"%s",apszArg[i]);
                return FALSE;
            }
            strcpy(psess->achCurrOutputDir,apszArg[i]);
            //** Make sure destination directory exists
            if (!ensureDirectory(psess->achCurrOutputDir,FALSE,perr)) {
                return FALSE;           // perr already filled in
            }
            fLocation = FALSE;          // Done eating location
        }
        else {
            //** Should be a file name;
            //   Make sure we haven't made up our mind, yet!
            if ((act != actBAD) && (act != actFILE)) {
                //** Not doing single file compress, so this is a bad arg
                ErrSet(perr,pszDIAERR_BAD_PARAMETER,"%s",apszArg[i]);
                return FALSE;
            }
            act = actFILE;              // The die is cast...

            //** Make sure we haven't seen too many file names
            cFile++;                    // Count number of files we've seen
            if (cFile > 2) {
                //** Two many file names
                ErrSet(perr,pszDIAERR_TWO_MANY_PARMS,"%s",apszArg[i]);
                return FALSE;
            }

            //** Store file name
            apszFile[cFile-1] = apszArg[i];
        }
    }

    //** Make sure no trailing /D or /F
    if (fDefine) {
        ErrSet(perr,pszDIAERR_MISSING_VAR_DEFINE);
        return FALSE;
    }
    if (fFile) {
        ErrSet(perr,pszDIAERR_MISSING_FILE_NAME);
        return FALSE;
    }
    if (fLocation) {
        ErrSet(perr,pszDIAERR_MISSING_LOCATION);
        return FALSE;
    }

    //** Update Session
    switch (act) {
        case actBAD:
        case actHELP:
            psess->act = actHELP;       // If no args, show help
            break;

        case actDIRECTIVE:
            psess->act = act;
            break;

        case actFILE:
            psess->act = act;
            //** Add source file specification
            if (!(hfspec = addDirectiveFile(psess,apszFile[0],perr))) {
                //** Error adding source file; perr already set
                return FALSE;           // Failure
            }

            if (cFile == 2) {           // Destination filename specified
                if (!FLSetDestination(hfspec,apszFile[1],perr)) {
                    //** Error setting destination file; perr already set
                    return FALSE;       // Failure
                }
            }
            break;
    }

    //** Success
    return TRUE;
}


/***    addDirectiveFile - Add directive file to session list
 *
 *  Entry:
 *      psess  - Session to update
 *      pszArg - File name to add
 *      perr   - ERROR structure
 *
 *  Exit-Success:
 *      Returns HFILESPEC; psess updated.
 *
 *  Exit-Failure:
 *      Returns NULL; perr filled in with error.
 */
HFILESPEC addDirectiveFile(PSESSION psess, char *pszArg, PERROR perr)
{
    HFILESPEC   hfspec;

    //** Make sure file exists
    if (getFileSize(pszArg,perr) == -1) {
        return NULL;                    // perr already filled in
    }

    AssertSess(psess);
    //** Make sure a list exists
    if (psess->hflistDirectives == NULL) {
        if (!(psess->hflistDirectives = FLCreateList(perr))) {
            return FALSE;
        }
    }

    //** Add file to list
    if (!(hfspec = FLAddFile(psess->hflistDirectives, pszArg, NULL, perr))) {
        return NULL;
    }

    //** Success
    return hfspec;
} /* addDirectiveFile */


/***    addCmdLineVar - Add a command line variable to session list
 *
 *  Entry:
 *      psess  - Session to update
 *      pszArg - Variable name=value to add
 *      perr   - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; psess updated.
 *
 *  Exit-Failure:
 *      Returns actBAD; perr filled in with error.
 */
BOOL addCmdLineVar(PSESSION psess, char *pszArg, PERROR perr)
{
    COMMAND cmd;                        // For var name & value
    BOOL    f;

    //** Make sure asserts work
    SetAssertSignature((&cmd),sigCOMMAND);

    //** Parse assignment statment
    if (!DFPParseVarAssignment(&cmd,psess,pszArg,perr)) {
        return FALSE;
    }

    //** Assign variable
    f = setVariable(psess,
                    cmd.set.achVarName,
                    cmd.set.achValue,
                    perr);

    //** Clear signature
    ClearAssertSignature((&cmd));

    //** Return result
    return f;
} /* addCmdLineVar() */


#ifdef ASSERT
/***    fnafReport - Report assertion failure
 *
 *      NOTE: See asrt.h for entry/exit conditions.
 */
FNASSERTFAILURE(fnafReport)
{
        //** Make sure we don't overwrite a status line!
        printf("\n%s:(%d) Assertion Failed: %s\n",pszFile,iLine,pszMsg);
        exit(1);
}
#endif // ASSERT


/***    printError - Display error on stdout
 *
 *  Entry
 *      perr - ERROR structure to print
 *
 *  Exit-Success
 *      Writes error message to stdout.
 */
void printError(PSESSION psess, PERROR perr)
{
    //** Make sure error starts on a new line
    if (psess->fNoLineFeed) {
        printf("\n");
        psess->fNoLineFeed = FALSE;
    }

    //** Determine type of error
    if (perr->pszFile != NULL) {
        //** Error in a directive file
        printf("%s(%d): %s: %s\n",
                perr->pszFile,perr->iLine,pszDIAERR_ERROR,perr->ach);
    }
    else {
        //** General error
        printf("%s: %s\n",pszDIAERR_ERROR,perr->ach);
    }

    //** Count errors, to determine exit code and early out on MaxErrors
    psess->cErrors++;                   // Count this error
}


/***    mapFCIError - Create error message from FCI error codes
 *
 *  Entry:
 *      perr    - ERROR structure to recieve message
 *      psess   - Our context
 *      pszCall - FCI call that failed
 *      perf    - FCI error structure
 *
 *  Exit:
 *      perr filled in with formatted message
 */
void mapFCIError(PERROR perr, PSESSION psess, char *pszCall, PERF perf)
{
    char    *pszErrno;

    pszErrno = mapCRTerrno(perf->erfType);  // Get mapping, just in case

    switch (perf->erfOper) {

    case FCIERR_NONE:
        Assert(0);
        break;

    case FCIERR_ALLOC_FAIL:
        ErrSet(perr,pszFCIERR_ALLOC_FAIL,"%s",pszCall);
        break;

    case FCIERR_BAD_COMPR_TYPE:
        ErrSet(perr,pszFCIERR_BAD_COMPR_TYPE,"%s",pszCall);
        break;

    case FCIERR_MCI_FAIL:
        ErrSet(perr,pszFCIERR_MCI_FAIL,"%s%s",pszCall,psess->achCurrFile);
        break;

    case FCIERR_USER_ABORT:
        ErrSet(perr,pszFCIERR_USER_ABORT,"%s",pszCall);
        break;

    case FCIERR_OPEN_SRC:
        ErrSet(perr,pszFCIERR_OPEN_SRC,"%s%s%s",
                                        pszCall,psess->achCurrFile,pszErrno);
        break;

    case FCIERR_READ_SRC:
        ErrSet(perr,pszFCIERR_READ_SRC,"%s%s%s",
                                        pszCall,psess->achCurrFile,pszErrno);
        break;

    case FCIERR_TEMP_FILE:
        ErrSet(perr,pszFCIERR_TEMP_FILE,"%s%s",pszCall,pszErrno);
        break;

    case FCIERR_CAB_FILE:
        ErrSet(perr,pszFCIERR_CAB_FILE,"%s%s",pszCall,pszErrno);
        break;

    case FCIERR_M6_HACK_INCOMPRESSIBLE:
        ErrSet(perr,pszFCIERR_M6_HACK_INCOMPRESSIBLE,"%s%s",
                                        pszCall,psess->achCurrFile);
        break;

    default:
        ErrSet(perr,pszFCIERR_UNKNOWN_ERROR,"%s%d",pszCall,perf->erfOper);
        break;
    }
} /* mapFCIError() */


/***    mapCRTerrno - Get error string from C run-time library errno
 *
 *  Entry:
 *      errno - C run-time library errno value.
 *
 *  Exit:
 *      Returns pointer to appropriate causation string.
 */
char *mapCRTerrno(int errno)
{
    switch (errno) {
        case ECHILD:    return pszCRTERRNO_ECHILD;
        case EAGAIN:    return pszCRTERRNO_EAGAIN;
        case E2BIG:     return pszCRTERRNO_E2BIG;
        case EACCES:    return pszCRTERRNO_EACCES;
        case EBADF:     return pszCRTERRNO_EBADF;
        case EDEADLOCK: return pszCRTERRNO_EDEADLOCK;
        case EDOM:      return pszCRTERRNO_EDOM;
        case EEXIST:    return pszCRTERRNO_EEXIST;
        case EINVAL:    return pszCRTERRNO_EINVAL;
        case EMFILE:    return pszCRTERRNO_EMFILE;
        case ENOENT:    return pszCRTERRNO_ENOENT;
        case ENOEXEC:   return pszCRTERRNO_ENOEXEC;
        case ENOMEM:    return pszCRTERRNO_ENOMEM;
        case ENOSPC:    return pszCRTERRNO_ENOSPC;
        case ERANGE:    return pszCRTERRNO_ERANGE;
        case EXDEV:     return pszCRTERRNO_EXDEV;
        default:        return pszCRTERRNO_UNKNOWN;
    }
    Assert(0);
    return NULL;
} /* mapCRTerrno() */


/***    updateHgenLast - Set correct psess->hgenLast
 *
 *  Entry:
 *      psess  - Session
 *      pszDst - Destination file name
 *
 *  Exit:
 *      psess->hgenFileLast set to point to current file
 */
void updateHgenLast(PSESSION psess, char *pszDst)
{
    PFILEINFO   pfinfo;

    Assert(psess->hgenFileLast != NULL); // Catch read off end of list
    if (psess->hgenFileLast == (HGENERIC)-1) { // Get first file on list
        psess->hgenFileLast = GLFirstItem(psess->hglistFiles);
    }
    else {                          // Get next file
        psess->hgenFileLast = GLNextItem(psess->hgenFileLast);
    }

    //** Make sure we got the expected entry
    pfinfo = GLGetValue(psess->hgenFileLast);
    AssertFinfo(pfinfo);
    Assert(strcmp(pszDst,GLGetKey(psess->hgenFileLast)) == 0);
} /* updateHgenLast() */


/***    modeInfAddFile - Add a file line to INF, depending upon DDF mode
 *
 *  There are two cases to consider:
 *  (1) "relational" mode
 *      ==> Augment psess->hglistFiles with placement information
 *  (2) "unified" mode
 *      ==> Augment psess->hglistFiles with placement information and
 *          then format and write out file line to INF file.
 *  Entry:
 *      psess    - Session
 *      inf      - Area of INF file to receive line
 *      pszFile  - Destination file name
 *      iDisk    - Disk number (1-based)
 *      iCabinet - Cabinet number (1-based, 0=> not in cabinet)
 *      perr     - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; line added to INF file
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in with error.
 */
BOOL modeInfAddFile(PSESSION  psess,
                    char     *pszFile,
                    int       iDisk,
                    int       iCabinet,
                    PERROR    perr)

{
    HGENERIC    hgen;
    PFILEINFO   pfinfo;
    PFILEPARM   pfparm;

    AssertSess(psess);
    Assert(psess->ddfmode != ddfmodeBAD);
    Assert(psess->ddfmode != ddfmodeUNKNOWN);

    //** Get file item
    Assert(psess->hgenFile != NULL);    // Catch read off end of list
    if (psess->hgenFile == (HGENERIC)-1) { // Get first file on list
        psess->hgenFile = GLFirstItem(psess->hglistFiles);
    }
    hgen = psess->hgenFile;
    Assert(hgen != NULL);
    psess->hgenFile = GLNextItem(hgen); // Advance for next time

    //** Get fileinfo and augment it
    pfinfo = GLGetValue(hgen);
    AssertFinfo(pfinfo);
    Assert(strcmp(pszFile,GLGetKey(hgen)) == 0); // Verify destination name
    pfinfo->iDisk    = iDisk;           // Store placement info
    pfinfo->iCabinet = iCabinet;        // Store placement info

    //** Let /SIZE parm override true file size if former is *larger*
    //   NOTE: This is only for reporting in the INF file -- this has
    //         no affect on space used on the disk!
    pfparm = GLFindAndGetValue(pfinfo->hglistParm,pszPARM_FILESIZE);
    if (pfparm) {                       // /Size specfied
        AssertFparm(pfparm);
        pfinfo->cbFile = atol(pfparm->pszValue);
    }

    //** Write INF line if in unified mode
    if (psess->ddfmode == ddfmodeUNIFIED) {
        if (!infAddFile(psess,pszFile,pfinfo,pfinfo->hglistParm,perr)) {
            return FALSE;               // perr already filled in
        }
    }

    //** Success
    return TRUE;
} /* modeInfAddFile() */


/***    modeInfAddLine - Add line to INF, depending upon DDF mode
 *
 *  There are several cases to consider:
 *  (1) The disk or cabinet area is specified
 *      ==> Write line immediately to INF
 *  (2) "relational" mode AND GenerateINF is TRUE
 *      ==> Write line immediately to INF
 *  (3) "relational" mode AND GenerateINF is FALSE AND File area specified
 *      => ERROR -- it is unclear what the semantics of this should
 *         be, since the INF is not being generated in step with the
 *         file copy commands.  So writes to the FILE area are not
 *         supported in the "layout" section of the DDF.
 *  (4) "unified" mode AND File area specified
 *      (a) No file copy commands have been processed
 *          ==> Write line immediately to INF
 *      (b) One or more file copy commands already done
 *          ==> This is the tricky case, because we need to make sure that
 *              the line is written to the INF in correct synchronization
 *              with surrounding file copy commands.  If files are being
 *              placed in cabinets, then we may have passed file copy
 *              commands to FCI, but it may not yet have actually placed them
 *              in cabinets, so we won't have written their lines to the INF
 *              file!  So, if any "unmatched" files are queued up, we append
 *              this INF to the last file, and this line (or lines) will be
 *              written when this file is actually added to the INF.  If
 *              there are no queued files (we might not be in a cabinet, or
 *              the cabinet may have flushed), then we write to the INF
 *              immediately.
 *  (5) "unknown" mode (haven't decided between relational or unified)
 *      ==> Write line immediately to INF
 *
 *  Entry:
 *      psess   - Session
 *      inf     - Area of INF file to receive line
 *      pszLine - Line to add
 *      perr    - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; line added to INF file
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in with error.
 */
BOOL modeInfAddLine(PSESSION  psess,
                    INFAREA   inf,
                    char     *pszLine,
                    PERROR    perr)
{
    BOOL        fGenerateInf;
    HVARIABLE   hvar;
    PFILEINFO   pfinfo;
    PLINEINFO   plinfo;

    AssertSess(psess);
    hvar = VarFind(psess->hvlist,pszVAR_GENERATE_INF,perr);
    Assert(!perr->fError);              // Must be defined
    fGenerateInf = VarGetBool(hvar);    // Get INF generation setting

/*
 * Work common to Pass 1 & 2
 */

    //** Check for invalid location of InfWrite FILE
    if ((psess->ddfmode == ddfmodeRELATIONAL) &&
        !fGenerateInf                         &&
        (inf == infFILE)) {
        //** InfWrite to file area in relation layout section of DDF
        Assert(psess->fExpectFileCommand);
        ErrSet(perr,pszDIAERR_INF_IN_LAYOUT);
        return FALSE;
    }

    //** If this is pass 1, we're done
    if (!psess->fPass2) {
        return TRUE;
    }

/*
 * Pass 2
 */

    //** Check for InfWrite FILE that must be postponed
    if ((psess->ddfmode == ddfmodeUNIFIED) &&
        (inf == infFILE)                   &&
        (psess->iCurrFile > 0)) {
        //** InfWrite to file area while doing file copy commands
        Assert(fGenerateInf);           // Must be generating INF
        pfinfo = GLGetValue(psess->hgenFileLast); // Get last file added
        AssertFinfo(pfinfo);

        //** Do not have to postpone if preceding file written to INF already
        if (pfinfo->flags & fifWRITTEN_TO_INF) {
            return infAddLine(psess->hinf,inf,pszLine,perr);
        }

        //** Postpone write
        if (pfinfo->hglistInfLines == NULL) { // Create list
            pfinfo->hglistInfLines = GLCreateList(NULL,
                                                  DestroyLineInfo,
                                                  pszDIA_LINE_INFO,
                                                  perr);
            if (!pfinfo->hglistInfLines) {
                return FALSE;           // perr already filled in
            }
        }

        //** Create line info
        if (!(plinfo = MemAlloc(sizeof(LINEINFO)))) {
            ErrSet(perr,pszDIAERR_OUT_OF_MEMORY,"%s",pszDIAOOM_TRACKING_LINES);
            return FALSE;
        }
        if (!(plinfo->pszLine = MemStrDup(pszLine))) {
            ErrSet(perr,pszDIAERR_OUT_OF_MEMORY,"%s",pszDIAOOM_TRACKING_LINES);
            MemFree(plinfo);
            return FALSE;
        }

        //** Add line to list
        if (!GLAdd(pfinfo->hglistInfLines,  // List
                   NULL,                    // key name
                   plinfo,                  // file info
                   pszDIA_LINE_INFO,        // Description for error message
                   FALSE,                   // Uniqueness setting
                   perr)) {
            MemFree(plinfo->pszLine);
            MemFree(plinfo);
            return FALSE;
        }

        //** Initialize remainder of structure
        plinfo->inf = inf;
        SetAssertSignature(plinfo,sigLINEINFO);
        return TRUE;
    }

    //** We're OK to write the line right now!
    return infAddLine(psess->hinf,inf,pszLine,perr);
} /* modeInfAddLine() */


/***    addFileToSession - Add file to session list
 *
 *  Entry:
 *      psess  - Session to update
 *      pszSrc - Source file name
 *      pszDst - Destination file name (used as key in list)
 *      cbFile - Size of file
 *      pcmd   - Command to process (ct == ctFILE)
 *      perr   - ERROR structure
 *
 *  Exit-Success:
 *      Returns HGENERIC; file added to psess->hglistFiles; pcmd->file.hglist
 *          moved to newly added FILEINFO entry!
 *
 *  Exit-Failure:
 *      Returns NULL; perr filled in with error.
 */
HGENERIC addFileToSession(PSESSION  psess,
                          char     *pszSrc,
                          char     *pszDst,
                          long      cbFile,
                          PCOMMAND  pcmd,
                          PERROR    perr)
{
    BOOL        fUnique;                // True if file name has to be unique
    HGENERIC    hgen;
    HVARIABLE   hvar;
    PFILEINFO   pfinfo;
    PFILEINFO   pfinfoFirst;
    PFILEPARM   pfparm;

    AssertSess(psess);
    Assert(pcmd->ct == ctFILE);

    //** Get UniqueFiles setting
    hvar = VarFind(psess->hvlist,pszVAR_UNIQUE_FILES,perr);
    Assert(!perr->fError);              // Must be defined
    fUnique = VarGetBool(hvar);         // Get default setting

    //** See if default is overridden on command line
    pfparm = GLFindAndGetValue(pcmd->file.hglist,pszFILE_UNIQUE);
    if (pfparm) {                   // /Unique specfied
        AssertFparm(pfparm);
        if (-1 == (fUnique = BOOLfromPSZ(pfparm->pszValue,perr))) {
            return NULL;           // perr filled in already
        }
    }

    //** Relational mode requires unique destination file names
    if ((psess->ddfmode == ddfmodeRELATIONAL) && !fUnique) {
        ErrSet(perr,pszDIAERR_MUST_BE_UNIQUE2);
        return NULL;
    }

    //** Make sure file list exists
    if (!psess->hglistFiles) {
        psess->hglistFiles = GLCreateList(NULL,              // No default
                                          DestroyFileInfo,
                                          pszDIA_FILE_INFO,
                                          perr);
        if (!psess->hglistFiles) {
            return NULL;               // perr already filled in
        }
    }

    //** Create file info
    if (!(pfinfo = MemAlloc(sizeof(FILEINFO)))) {
        ErrSet(perr,pszDIAERR_OUT_OF_MEMORY,"%s",pszDIAOOM_TRACKING_FILES);
        return NULL;
    }
    if (!(pfinfo->pszDDF = MemStrDup(perr->pszFile))) {
        ErrSet(perr,pszDIAERR_OUT_OF_MEMORY,"%s",pszDIAOOM_TRACKING_FILES);
        MemFree(pfinfo);
        return NULL;
    }

    //** Add file to list
    if (!(hgen = GLAdd(psess->hglistFiles,  // List
                       pszDst,              // key name
                       pfinfo,              // file info
                       pszDIA_FILE,         // Description for error message
                       fUnique,             // Uniqueness setting
                       perr))) {
        //** See if we need to remap error
        if (perr->code == ERRGLIST_NOT_UNIQUE) {
            //** Give info on where first file name was found
            pfinfoFirst = GLGetValue(perr->pv);
            AssertFinfo(pfinfoFirst);
            ErrSet(perr,pszDIAERR_NOT_UNIQUE,"%s%s%d",
                            pszDst,pfinfoFirst->pszDDF,pfinfoFirst->ilineDDF);
        }
        MemFree(pfinfo->pszDDF);
        MemFree(pfinfo);
        return NULL;
    }

    //** Initialize remainder of structure
    pfinfo->ilineDDF       = perr->iLine;       // Set line number in DDF
    pfinfo->cbFile         = cbFile;            // File size
    pfinfo->iDisk          = idiskBAD;          // Not yet determined
    pfinfo->iCabinet       = icabBAD;           // Not yet determined
    pfinfo->iFile          = (int)psess->cFiles;// File index in layout
    pfinfo->fta.date       = 0;                 // Not yet determined
    pfinfo->fta.time       = 0;                 // Not yet determined
    pfinfo->fta.attr       = 0;                 // Not yet determined
    pfinfo->hglistInfLines = NULL;              // No lines to print
    pfinfo->flags          = 0;                 // Reset all flags
    pfinfo->hglistParm     = pcmd->file.hglist; // Save file parameters
    pfinfo->checksum       = 0;                 // Not yet determined
    pfinfo->verMS          = 0;                 // Not yet determined
    pfinfo->verLS          = 0;                 // Not yet determined
    pfinfo->pszVersion     = NULL;              // Not yet determined
    pfinfo->pszLang        = NULL;              // Not yet determined
    pcmd->file.hglist = NULL;           // Nothing to free!

    //** Set signature after we get structure fully initialized
    SetAssertSignature(pfinfo,sigFILEINFO);
    return hgen;                        //
} /* addFileToSession() */


/***    checkReferences - Make sure all files in layout section are referenced
 *
 *  Entry:
 *      psess  - Session
 *      perr   - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; everything is dandy.
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in with error.
 */
BOOL checkReferences(PSESSION psess, PERROR perr)
{
    BOOL        fInf;
    BOOL        fOK;
    HGENERIC    hgen;
    int         iLine;
    PFILEINFO   pfinfo;
    PFILEPARM   pfparm;
    char       *pszFile;

    AssertSess(psess);

    //** Only need to check if in relational mode
    if (psess->ddfmode != ddfmodeRELATIONAL) {
        return TRUE;
    }

    //** Check list of files in layout to make sure all were referenced
    fOK = TRUE;                         // Assume everything is OK
    for (hgen = GLFirstItem(psess->hglistFiles);
         hgen;
         hgen = GLNextItem(hgen)) {

        pfinfo = GLGetValue(hgen);
        AssertFinfo(pfinfo);
        if (!(pfinfo->flags & fifREFERENCED)) { // Not referenced
            fInf = TRUE;                // Assume INF reference is required
	    pfparm = GLFindAndGetValue(pfinfo->hglistParm,pszFILE_INF);
            if (pfparm) {               // /Inf specfied
                AssertFparm(pfparm);
                if (-1 == (fInf = BOOLfromPSZ(pfparm->pszValue,perr))) {
                    return FALSE;       // perr filled in already
                }
            }
            if (fInf) {                 // Should have been referenced
                ErrSet(perr,pszDIAERR_FILE_NOT_REFD,"%s",GLGetKey(hgen));

                //** Save current position
                pszFile = perr->pszFile;
                iLine   = perr->iLine;

                //** Set position of unreferenced file
                perr->pszFile = pfinfo->pszDDF;
                perr->iLine   = pfinfo->ilineDDF;

                //** Print error
                printError(psess,perr); // Show error

                //** Restore position
                perr->pszFile = pszFile;
                perr->iLine   = iLine;

                //** Reset error so we can continue
                ErrClear(perr);         // Catch all errors
                fOK = FALSE;            // Remember we had an error
            }
        }
    }
    //** Return status
    return fOK;
} /* checkReferences() */


/** apszVarTemplate - List of variable name templates
 *
 *  checkVariableDefinitions() uses this list to avoid complaining about
 *  "standard" variables that are of the form: Name<integer>.
 */
char *apszVarTemplate[] = {
    pszPATTERN_VAR_CAB_NAME,
    pszPATTERN_VAR_DISK_DIR,
    pszPATTERN_VAR_DISK_LABEL,
    pszPATTERN_VAR_INF_DISK_HEADER,
    pszPATTERN_VAR_INF_DISK_LINE_FMT,
    pszPATTERN_VAR_INF_CAB_HEADER,
    pszPATTERN_VAR_INF_CAB_LINE_FMT,
    pszPATTERN_VAR_INF_FILE_HEADER,
    pszPATTERN_VAR_INF_FILE_LINE_FMT,
    pszPATTERN_VAR_INF_HEADER,
    pszPATTERN_VAR_INF_FOOTER,
    pszPATTERN_VAR_MAX_DISK_SIZE,
    NULL,
};


/** apszParmNames - List of InfXxx suffixes for standard parameters
 *
 *  checkVariableDefinitions() uses this list to avoid complaining about
 *  "standard" parameters that are of the form: Inf<parm>.
 */
char *apszParmNames[] = {
    pszPARM_FILEATTR,
    pszPARM_CAB_NUMBER,
    pszPARM_CAB_FILE,
    pszPARM_CHECKSUM,
    pszPARM_FILEDATE,
    pszPARM_DISK_NUMBER,
    pszPARM_FILENAME,
    pszPARM_FILE_NUMBER,
    pszPARM_INF,
    pszPARM_LABEL,
    pszPARM_LANG,
    pszPARM_FILESIZE,
    pszPARM_FILETIME,
    pszPARM_UNIQUE,
    pszPARM_VERNUM,
    pszPARM_VERSTR,
};


/***    checkVariableDefinitions - Verify all variables are .Defined or PERM
 *
 *  Entry:
 *      psess  - Session
 *      perr   - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; everything is dandy.
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in with error.
 */
BOOL checkVariableDefinitions(PSESSION psess, PERROR perr)
{
    int         cb;
    BOOL        f;
    BOOL        fOK;
    HVARIABLE   hvar;
    VARFLAGS    vfl;
    char      **ppsz;
    char       *psz;
    char       *pszName;
    char       *pszSuffix;

    AssertSess(psess);

    //** Generate errors for any variables that are not permanent or were
    //   not .Defined.
    //   NOTE: We don't do this checking at .Set time, because user-defined
    //         variables may be specified on the command line, and we want
    //         to permit that *if* an INF file is specified.
    //
    fOK = TRUE;                         // Assume everything is OK
    for (hvar = VarFirstVar(psess->hvlist);
         hvar;
         hvar = VarNextVar(hvar)) {

        vfl = VarGetFlags(hvar);
        if (0 == (vfl & (vflPERM | vflDEFINE))) { //** Not defined
            f = FALSE;                  // Assume variable is OK
            pszName = VarGetName(hvar);

            //** See if this is one of the "template" variables
            for (ppsz=apszVarTemplate; *ppsz; ppsz++) {
                cb = strlen(*ppsz) - 1;
                Assert(cb > 0);
                Assert((*ppsz)[cb] == chDF_WILDCARD);
                if (_strnicmp(pszName,*ppsz,cb) == 0) {
                    //** The prefix is valid, make sure rest is an integer
                    for (psz = pszName + cb;   // Point at prefix
                         *psz && isdigit(*psz);
                         psz++) {
                        ;               // Check that rest of string is digits
                    }
                    f = (*psz == '\0'); // OK if we ran off end of string
                    break;              // No need to continue checking
                }
            }

            //** If no match so far, check for InfXxxx variable name
            if (!f) {
                //** See if this is one of the InfXxx parm default value vars
                cb = strlen(pszPREFIX_INF_VARS);
                if (_strnicmp(pszName,pszPREFIX_INF_VARS,cb) == 0) {
                    //** Starts with "Inf" -- check for parm name suffix
                    pszSuffix = pszName + cb;   // Point at suffix
                    for (ppsz=apszParmNames; *ppsz; ppsz++) {
                        if (_stricmp(pszSuffix,*ppsz) == 0) {
                            f = TRUE;   // Variable is OK
                            break;      // Stop checking
                        }
                    }
                }
            }

            //** Check result of comparisons
            if (!f) {
                ErrSet(perr,pszDIAERR_UNDEFINED_VAR,"%s%s%s",
                            pszCMD_OPTION,
                            pszOPTION_EXPLICIT,
                            pszName);

                //** Print error
                printError(psess,perr); // Show error

                //** Reset error so we can continue
                ErrClear(perr);         // Catch all errors
                fOK = FALSE;            // Remember we had an error
            }
        }
    }

    //** Return status
    return fOK;
} /* checkVariableDefinitions() */


/***    getVarWithOverride - Checks for override for variable, gets value
 *
 *  Entry:
 *      psess      - Session
 *      pchDst     - Buffer to receive constructed value
 *      cbDst      - Size of pchDst
 *      pszPattern - Variable name pattern (i.e., CabinetLabel*)
 *      pszVar     - Name of variable with default value (i.e., CabinetNameTemplate)
 *      i          - Index to be used for pszPattern and pszTemplate
 *      pszKind    - Description of variable (for error messages)
 *      perr       - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE; buffer filled in
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in with error.
 */
BOOL getVarWithOverride(PSESSION  psess,
                        char     *pchDst,
                        int       cbDst,
                        char     *pszPattern,
                        char     *pszVar,
                        int       i,
                        char     *pszKind,
                        PERROR    perr)
{
    HVARIABLE	hvar;
    char       *psz;

    AssertSess(psess);

    //** (1) Construct override variable name
    if (!nameFromTemplate(psess->achMsg,
                          sizeof(psess->achMsg),
                          pszPattern,
                          i,
                          pszKind,
                          perr)) {
        Assert(0);                      // Should never fail
        return FALSE;                   // perr already filled in
    }

    //** (2) See if override variable exists
    hvar = VarFind(psess->hvlist,psess->achMsg,perr);
    if (hvar != NULL) {                 // Yes, get its value
        psz = VarGetString(hvar);       // Get disk label
        if (strlen(psz) >= (size_t)cbDst) {
            ErrSet(perr,pszDIAERR_VALUE_TOO_LONG,"%s%d%s",pszKind,cbDst-1,psz);
            return FALSE;
        }
        strcpy(pchDst,psz);
    }
    else {                              // NO, no override for this *i*
        ErrClear(perr);                 // Not an error
        //** Construct default value
        hvar = VarFind(psess->hvlist,pszVar,perr);
        Assert(!perr->fError);          // Must be defined
        psz = VarGetString(hvar);       // Get template
        if (!nameFromTemplate(pchDst,
                              cbDst,
                              psz,
                              i,
                              pszKind,
                              perr)) {
            return FALSE;               // perr already filled in
        }
    }

    //** Success
    return TRUE;
} /* getVarWithOverride() */

//** Get CRC code
//BUGBUG 14-Dec-1994 bens Include code here to avoid makefile change
#include "crc32.c"


/***    getFileChecksum - Compute file checksum
 *
 *  Entry:
 *      pszFile     - Filespec
 *      pchecksum   - Receives 32-bit checksum of file
 *      perr        - ERROR structure
 *
 *  Exit-Success:
 *      Returns TRUE, *pchecksum filled in.
 *
 *  Exit-Failure:
 *      Returns FALSE; perr filled in with error.
 */
BOOL getFileChecksum(char *pszFile, ULONG *pchecksum, PERROR perr)
{
#define cbCSUM_BUFFER   4096            // File buffer size
    int     cb;                         // Amount of data in read buffer
    ULONG   csum=CRC32_INITIAL_VALUE;   // Initialize CRC
    char   *pb=NULL;                    // Read buffer
    int     hf=-1;                      // File handle
    int     rc;
    BOOL    result=FALSE;               // Assume failure

    //** Initialize returned checksum (assume failure)
    *pchecksum = csum;

    //** Allocate file buffer
    if (!(pb = MemAlloc(cbCSUM_BUFFER))) {
        ErrSet(perr,pszDIAERR_NO_MEMORY_CRC,"%s",pszFile);
        return FALSE;
    }

    //** Open file
    hf = _open(pszFile,_O_RDONLY | _O_BINARY,&rc);
    if (hf == -1) {
        ErrSet(perr,pszDIAERR_OPEN_FAILED,"%s",pszFile);
        goto Exit;
    }

    //** Compute checksum
    while (_eof(hf) == 0) {
        cb = _read(hf,pb,cbCSUM_BUFFER);
        if (cb == -1) {
            ErrSet(perr,pszDIAERR_READ_FAIL_CRC,"%s",pszFile);
            goto Exit;
        }
        if (cb != 0) {
            csum = CRC32Compute(pb,cb,csum); // Accumulate CRC
        }
    }

    //** Success
    result = TRUE;
    *pchecksum = csum;                  // Store checksum for caller

Exit:
    if (hf != -1) {
        _close(hf);
    }
    if (pb != NULL) {
        MemFree(pb);
    }
    return result;
} /* getFileChecksum() */