/* This module checks the SLM system at the file level. We check that all * the files SLM requires are intact and can be examined. The status file * is put into a buffer, and the log file is checked for proper sequence. */ #include "precomp.h" #pragma hdrstop EnableAssert #define iszDirMax 3 /* number of master directories */ #define cbRdWrMax (unsigned)65520 /* can't use 65535 because offset may not be 0 */ private F FCheckSequenceLog(AD*, LE *, F, F); F FCkSRoot( AD *pad) { PTH pth[cchPthMax]; struct _stat st; /* does the Slm root exist and can we write to it */ if (FStatPth(SzPrint(pth, "%&/S", pad), &st)) return FCkWritePth(pth, &st); else { Error("SLM root %&S does not exist\n", pad); return fFalse; } } /* Check for existence of the project and for master directories. * If master directories don't exist, create them. */ F FCkMaster( AD *pad) { PTH pth[cchPthMax]; static char *rgszDir[iszDirMax] = {"diff", "etc", "src"}; char **pszDir; /* points to element of rgszDir */ /* project exists iff there is a status file */ if (!FPthExists(PthForStatus(pad, pth), fFalse)) { Error("status file for %&P/C does not exist\n", pad); return fFalse; } /* check for existence of basic subdirectories */ for (pszDir = rgszDir; pszDir - rgszDir < iszDirMax; pszDir++) { /* a project main directory does not exist */ SzPrint(pth, "%&/S/Z/P/C", pad, *pszDir); if (!FMkPth(pth, (void *)0, fTrue)) return fFalse; } CkSrcPrms(pad); /* check src for readonly files on DOS */ return fTrue; } void CkSrcPrms( AD *pad) { char szFile[cchFileMax]; FA fa; DE de; struct _stat st; PTH pthDir[cchPthMax]; PTH pthT[cchPthMax]; OpenDir(&de, SzPrint(pthDir, szSrcPZ, pad, (char *)NULL), faFiles); while (FGetDirSz(&de, szFile, &fa)) { /* mode not stored in DE for dos so must do stat */ /* stat can fail if a bad directory */ if (!FStatPth(SzPrint(pthT, szSrcPZ, pad, szFile), &st)) Error("cannot access %&P/C/%s\n", pad, szFile); else if (!FReadOnly(&st)) Error("%&P/C/%s is writeable; should be readonly\n", pad, szFile); } CloseDir(&de); } /* load status and lock whole file; must init script later! */ F FLoadSd( AD *pad, SD *psd) { struct _stat st; long cb; unsigned cbT; char *hpbT; ClearPbCb((char *)psd, sizeof(SD)); if (!FStatPth(PthForStatus(pad, psd->pthSd), &st)) { Error("status file for %&P/C does not exist\n", pad); return fFalse; } /* do some bookkeeping before opening the file since want file locked for shortest amount of time. */ cb = (long)st.st_size; if ((psd->hpbStatus = HpbResStat(cb)) == 0) { FlushSd(pad, psd, fTrue); return fFalse; } psd->hpbStatMac = psd->hpbStatus + cb; if ((psd->pmfStat = PmfOpen(psd->pthSd, omReadWrite, fxNil)) == 0) { Error("cannot open status file for %&P/C\n", pad); FlushSd(pad, psd, fTrue); return fFalse; } /* must lock the status file; it will remain locked until FlushSd() */ if (!FLockMf(psd->pmfStat)) { Error("status file for %&P/C in use\n", pad); FlushSd(pad, psd, fTrue); return fFalse; } /* read file into a buffer, cbRdWrMax bytes at a time */ for (hpbT = psd->hpbStatus; cb > 0; hpbT += cbT, cb -= cbT) { cbT = cb > cbRdWrMax ? cbRdWrMax : (unsigned)cb; if (CbReadMf(psd->pmfStat, LpbFromHpb(hpbT), cbT) != cbT) { Error("error reading status file for %&P/C\n", pad); FlushSd(pad, psd, fTrue); return fFalse; } } return fTrue; } /* This function cleans up an SD and closes open files, etc. */ void FlushSd( AD *pad, SD *psd, F fAbort) { /* should be current version by now */ SH *psh = (SH *)psd->hpbStatus; unsigned cbT; long cb; char *hpbT; char *szComment; if (fAbort) { if (psd->pmfStat != 0) CloseMf(psd->pmfStat); /* unlock and close the file */ AbortScript(); /* abort script before unlock */ if (psd->hpbStatus != 0) FreeHResStat(psd->hpbStatus); /* need to free buffer */ return; } AssertF(psd->pmfStat != 0 && psd->hpbStatus != 0); /* Write out a new status file if any changes made or file needs to be * truncated. */ cb = CbStatusFromPsh(psh); AssertF(cb <= CbHugeDiff(psd->hpbStatMac, psd->hpbStatus)); if (cb < CbHugeDiff(psd->hpbStatMac, psd->hpbStatus)) { if (FQueryPsd(psd, "status file should be truncated")) /* If query returns yes, changes flag is set * if not, we do not do any truncation */ psd->hpbStatMac = psd->hpbStatus + cb; else cb = CbHugeDiff(psd->hpbStatMac, psd->hpbStatus); } if (!psd->fAnyChanges || !FCanQuery("status file not rewritten\n") || !FQueryUser("write out new status file? ")) { CloseMf(psd->pmfStat); /* unlock and close the file */ AbortScript(); /* abort script before unlock */ FreeHResStat(psd->hpbStatus); /* need to free buffer */ } else { MF *pmf; PTH pthFrom[cchPthMax]; AssertF(psd->pmfStat != 0); PthForStatusBak(pad, pthFrom); PrErr("Saving old status file as %s\n", pthFrom); PthForStatus(pad, pthFrom); pmf = PmfCreate(pthFrom, permSysFiles, fTrue, fxGlobal); pmf->mm = mmInstall; /* Write status file out cbRdWrMax bytes at a time */ for (hpbT = psd->hpbStatus; cb > 0; hpbT += cbT, cb -= cbT) { cbT = cb > cbRdWrMax ? cbRdWrMax : (unsigned)cb; WriteMf(pmf, LpbFromHpb(hpbT), cbT); } CloseMf(pmf); /* Write to log to notify that changes were made */ if ((szComment = pad->szComment) == 0 && FCanQuery("no log comment given\n")) szComment = SzQuery("Comment for log: "); OpenLog(pad, fTrue); AppendLog(pad, (FI *)0, (char *)0, szComment); CloseLog(); CloseMf(psd->pmfStat); FreeHResStat(psd->hpbStatus); /* need to free buffer */ /* REVIEW: there is a small chance that another user may grab the status file before we have a chance to rename it. */ RunScript(); } } static TIME timePrev = 0L; static long cleChecked = -1L; /* This function will examine the log file. The only check made on the log is * to determine whether the times (i.e. the numbers berfore the first * semi-colon) of the entries are in increasing numerical order. Since the log * file is not crucial for the rest of SLMCK (at this stage anyway) we may * continue even if bad log file. Hence this function does not return a * boolean. It informs the user of any problems, and fixes any broken entries. */ void CkLog( AD *pad) { PTH pth[cchPthMax]; LE le; if (fVerbose) PrErr("Checking log file\n"); if (!FPthExists(PthForLog(pad, pth), fFalse)) { Error("log file for %&P/C does not exist\n", pad); return; } OpenLog(pad, fFalse); /* open log readonly */ SetLogPos((POS)0, fTrue); /* go to start and read forward */ if (!FGetLe(&le)) { Error("log file for %&P/C has no entries\n", pad); CloseLog(); return; } CloseLog(); /* required by FCopyLog */ timePrev = 0L; cleChecked = -1L; /* copy all entries to new file, fixing broken ones */ (void)FCopyLog(pad, 0, FCheckSequenceLog, fsmUseAll); } /* * check that the given log entry has a greater time than the previous entry. */ private F FCheckSequenceLog( AD *pad, LE *ple, F fFirst, /* unused */ F fUse) /* unused */ { Unreferenced(fUse); Unreferenced(fFirst); cleChecked++; if (ple->timeLog < timePrev && FQueryApp("log file for %&P/C out of sequence at line %d", "fix", pad, cleChecked)) { ple->timeLog = timePrev; } else timePrev = ple->timeLog; PrMf(pmfNewLog, /* copy log entry to new file */ szFileLog, ple->timeLog, ple->szUser, ple->szLogOp, ple->szURoot, ple->szSubDir, ple->szFile, ple->fv, ple->szDiFile, ple->szComLog); return fTrue; }