332 lines
8.5 KiB
C
332 lines
8.5 KiB
C
/* 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;
|
|
}
|