summaryrefslogtreecommitdiffstats
path: root/private/windows/diamond/diamond.c
diff options
context:
space:
mode:
Diffstat (limited to 'private/windows/diamond/diamond.c')
-rw-r--r--private/windows/diamond/diamond.c4159
1 files changed, 4159 insertions, 0 deletions
diff --git a/private/windows/diamond/diamond.c b/private/windows/diamond/diamond.c
new file mode 100644
index 000000000..1ccaac044
--- /dev/null
+++ b/private/windows/diamond/diamond.c
@@ -0,0 +1,4159 @@
+/*** 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() */