/*** 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 #include #include #include #include #include #include #include #include #include #include #ifdef BIT16 #include #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 #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; iact = 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. */ 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. */ 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() */