mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1543 lines
35 KiB
1543 lines
35 KiB
#include "precomp.h"
|
|
#pragma hdrstop
|
|
#include "messages.h"
|
|
EnableAssert
|
|
|
|
const PTH pthEtc[] = "/etc";
|
|
const PTH pthSrc[] = "/src";
|
|
const PTH pthDiff[] = "/diff";
|
|
|
|
const char szEtcPZ[] = "%&/S/etc/P/C/Z";
|
|
const char szSrcPZ[] = "%&/S/src/P/C/Z";
|
|
const char szDifPZ[] = "%&/S/diff/P/C/Z";
|
|
const char szDifPF[] = "%&/S/diff/P/C/F";
|
|
|
|
const char szEtcPT[] = "%&/S/etc/P/C/T";
|
|
const char szEtcPA[] = "%&/S/etc/P/C/A";
|
|
const char szEtcPL[] = "%&/S/etc/P/C/L";
|
|
const char szSrcPF[] = "%&/S/src/P/C/F";
|
|
const char szBasPB[] = "%&/S/etc/P/C/B";
|
|
|
|
const char szUQ[] = "%&/U/Q";
|
|
const char szUQF[] = "%&/U/Q/F";
|
|
const char szUQFR[] = "%&/U/Q/F/R";
|
|
const char szUQZCache[] = "%&/U/Q/slm.dif/Z";
|
|
const char szUQFCached[] = "%&/U/Q/slm.dif/F";
|
|
|
|
const char szYEtcPC[] = "%&/Y/etc/P/C";
|
|
const char szYEtcPCT[] = "%&/Y/etc/P/C/T";
|
|
const char szYSrcPC[] = "%&/Y/src/P/C";
|
|
const char szYSrcPCF[] = "%&/Y/src/P/C/F";
|
|
|
|
const char szCD[] = "%c%d";
|
|
|
|
/* replace backward slashes by forward ones */
|
|
void ConvToSlash(
|
|
char *sz)
|
|
{
|
|
for ( ;*sz != '\0'; sz++)
|
|
if (*sz == '\\')
|
|
*sz = '/';
|
|
}
|
|
|
|
|
|
/* replace forward slashes by backward ones */
|
|
void ConvFromSlash(
|
|
char *sz)
|
|
{
|
|
for (;*sz != '\0'; sz++)
|
|
if (*sz == '/')
|
|
*sz = '\\';
|
|
}
|
|
|
|
|
|
/* returns a duplicate sz */
|
|
char *SzDup(
|
|
char *sz)
|
|
{
|
|
return strcpy(PbAllocCb((unsigned)strlen(sz)+1,fFalse), sz);
|
|
}
|
|
|
|
|
|
/* converts lsz to lower case */
|
|
void LowerLsz(
|
|
char far *lsz)
|
|
{
|
|
while (*lsz)
|
|
{
|
|
if (isupper(*lsz))
|
|
*lsz = (char)tolower((int)*lsz);
|
|
lsz++;
|
|
}
|
|
}
|
|
|
|
|
|
/* sets *pw to the integer at pch and return pointer after integer. */
|
|
char *PchGetW(
|
|
char *pch,
|
|
int *pw)
|
|
{
|
|
F fNeg;
|
|
|
|
if ((fNeg = *pch == '-') == fTrue)
|
|
pch++;
|
|
|
|
*pw=0;
|
|
while (isdigit(*pch))
|
|
*pw = *pw * 10 + (*pch++ - '0');
|
|
|
|
if (fNeg)
|
|
*pw = -*pw;
|
|
|
|
return pch;
|
|
}
|
|
|
|
|
|
void InitAppendNe(
|
|
NE ***pppneLast,
|
|
NE **ppneHead)
|
|
{
|
|
*pppneLast = ppneHead;
|
|
}
|
|
|
|
void AppendNe(
|
|
NE ***pppneLast,
|
|
NE *pne)
|
|
{
|
|
**pppneLast = pne;
|
|
*pppneLast = &(**pppneLast)->pneNext;
|
|
**pppneLast = 0;
|
|
}
|
|
|
|
void InsertNe(
|
|
NE **ppneList,
|
|
NE *pne)
|
|
{
|
|
pne->pneNext = *ppneList;
|
|
*ppneList = pne;
|
|
}
|
|
|
|
void
|
|
RemoveNe(
|
|
NE **ppneList,
|
|
NE *pne
|
|
)
|
|
{
|
|
NE *pneT;
|
|
pneT = *ppneList;
|
|
|
|
if (pne == pneT) {
|
|
// First node in the list
|
|
if (pneT->pneNext == pne) {
|
|
// Only node in the list
|
|
*ppneList = NULL;
|
|
} else {
|
|
*ppneList = pne->pneNext;
|
|
}
|
|
} else {
|
|
while (pneT && pneT->pneNext != pne) {
|
|
pneT= pneT->pneNext;
|
|
}
|
|
|
|
// Remove it from the list
|
|
pneT->pneNext = pne->pneNext;
|
|
}
|
|
|
|
free((char *)pne);
|
|
}
|
|
|
|
/* Return count of names in NE list */
|
|
int Cne(
|
|
NE *pneList)
|
|
{
|
|
int cne = 0;
|
|
NE *pne;
|
|
|
|
ForEachNe(pne, pneList)
|
|
++cne;
|
|
|
|
return cne;
|
|
}
|
|
|
|
|
|
/* for list of files in current dir by reading directory; returns 0 if there
|
|
were none.
|
|
*/
|
|
NE *PneLstInDir(
|
|
AD *pad)
|
|
{
|
|
FA fa;
|
|
NE *pneList = 0;
|
|
NE **ppneLast; /* place to hook next element */
|
|
char pth[cchPthMax];
|
|
char sz[cchFileMax+1];
|
|
DE de;
|
|
|
|
InitAppendNe(&ppneLast, &pneList);
|
|
|
|
OpenDir(&de, PthForUDir(pad, pth), faFiles | faDir);
|
|
while (FGetDirSz(&de, sz, &fa))
|
|
AppendNe(&ppneLast, PneNewNm((NM far *)sz, strlen(sz), fa));
|
|
CloseDir(&de);
|
|
|
|
return pneList; /* may be 0 */
|
|
}
|
|
|
|
|
|
/* forms list of all files which match function; pfnFAdd is called:
|
|
|
|
(*pfnFAdd)(pfi)
|
|
*/
|
|
NE *PneLstFiles(
|
|
AD *pad,
|
|
F (*pfnFAdd)(FI far *))
|
|
{
|
|
register FI far *pfi;
|
|
FI far *pfiMac;
|
|
NE *pneList = 0;
|
|
NE **ppneLast; /* place to hook next element */
|
|
|
|
InitAppendNe(&ppneLast, &pneList);
|
|
|
|
AssertLoaded(pad);
|
|
|
|
for (pfi=pad->rgfi, pfiMac=pfi+pad->psh->ifiMac; pfi < pfiMac; pfi++)
|
|
{
|
|
if ((*pfnFAdd)(pfi))
|
|
AppendNe(&ppneLast, PneNewNm(pfi->nmFile, cchFileMax, (FA)((pfi->fk == fkDir) ? faDir : faNormal)));
|
|
}
|
|
|
|
return pneList; /* may be 0 */
|
|
}
|
|
|
|
|
|
/* These three functions are used with PneLstFiles() above */
|
|
F FAddMDir(
|
|
FI far *pfi)
|
|
{
|
|
return !pfi->fDeleted && pfi->fMarked && pfi->fk == fkDir;
|
|
}
|
|
|
|
F FAddADir(
|
|
FI far *pfi)
|
|
{
|
|
return !pfi->fDeleted && pfi->fk == fkDir;
|
|
}
|
|
|
|
F FAddAFi(
|
|
FI far *pfi)
|
|
{
|
|
return !pfi->fDeleted;
|
|
}
|
|
|
|
NE *PneLstBroken(
|
|
AD *pad)
|
|
{
|
|
FI far *pfi;
|
|
FI far *pfiMac;
|
|
FS far *pfs;
|
|
NE *pneList = 0;
|
|
NE **ppneLast; /* place to hook next element */
|
|
|
|
InitAppendNe(&ppneLast, &pneList);
|
|
|
|
AssertLoaded(pad);
|
|
|
|
for (pfi=pad->rgfi, pfiMac=pfi+pad->psh->ifiMac; pfi < pfiMac; pfi++)
|
|
{
|
|
pfs = PfsForPfi(pad, pad->iedCur, pfi);
|
|
if (FBroken(pad, pfi, pfs, fTrue))
|
|
AppendNe(&ppneLast, PneNewNm(pfi->nmFile, cchFileMax, (FA)((pfi->fk == fkDir) ? faDir : faNormal)));
|
|
}
|
|
|
|
return pneList; /* may be 0 */
|
|
}
|
|
|
|
|
|
/* Return a copy of the given pne, but with zeroed pneNext field. */
|
|
NE *PneCopy(
|
|
NE *pne)
|
|
{
|
|
NE *pneNew = PneNewNm(SzOfNe(pne), strlen(SzOfNe(pne)), pne->faNe);
|
|
pneNew->u = pne->u;
|
|
pneNew->pneNext = 0;
|
|
return pneNew;
|
|
}
|
|
|
|
|
|
/* return a new ne holding the name */
|
|
NE *PneNewNm(
|
|
NM *nm,
|
|
int cchMac,
|
|
FA fa)
|
|
{
|
|
register NE *pne;
|
|
|
|
AssertF(cchMac >= 0);
|
|
|
|
/* allocate a zero-filled block; + 1 for '\0' */
|
|
pne = (NE *)PbAllocCb((unsigned)(sizeof(NE) + cchMac + 1), fTrue);
|
|
pne->faNe = fa;
|
|
SzCopyNm(SzOfNe(pne), nm, cchMac);
|
|
|
|
return pne;
|
|
}
|
|
|
|
|
|
/* free list passed */
|
|
void FreeNe(
|
|
NE *pne)
|
|
{
|
|
register NE *pneT;
|
|
|
|
while (pne != 0)
|
|
{
|
|
pneT = pne->pneNext;
|
|
free((char *)pne);
|
|
pne = pneT;
|
|
}
|
|
}
|
|
|
|
|
|
/* Reverse a list. */
|
|
NE *PneReverse(
|
|
NE *pne)
|
|
{
|
|
NE *pnePrev, *pneNext;
|
|
|
|
pnePrev = 0;
|
|
while (pne)
|
|
{
|
|
pneNext = pne->pneNext;
|
|
pne->pneNext = pnePrev;
|
|
pnePrev = pne;
|
|
pne = pneNext;
|
|
}
|
|
|
|
return pnePrev;
|
|
}
|
|
|
|
|
|
/* unmark all fi */
|
|
void UnMarkAll(
|
|
AD *pad)
|
|
{
|
|
register FI far *pfi;
|
|
register FI far *pfiMac;
|
|
|
|
AssertLoaded(pad);
|
|
|
|
/* unmark all first */
|
|
for (pfi=pad->rgfi, pfiMac=pfi+pad->psh->ifiMac; pfi < pfiMac; pfi++)
|
|
pfi->fMarked = fFalse;
|
|
}
|
|
|
|
|
|
/* marks the files given in lst; loop through rgfi */
|
|
void MarkList(
|
|
AD *pad,
|
|
NE *pneList,
|
|
int fDelOk) /* true -> deleted files are ok */
|
|
{
|
|
FI far *pfi;
|
|
F fExists;
|
|
int cErr = 0;
|
|
NE *pne;
|
|
|
|
AssertLoaded(pad);
|
|
|
|
UnMarkAll(pad);
|
|
|
|
ForEachNe(pne, pneList)
|
|
{
|
|
CheckForBreak();
|
|
|
|
/* mark if ok or recently deleted */
|
|
if (FLookupSz(pad, SzOfNe(pne), &pfi, &fExists) || fDelOk && fExists)
|
|
pfi->fMarked = fTrue;
|
|
else
|
|
{
|
|
Error("%s is not a file of %&P/C\n", SzOfNe(pne), pad);
|
|
cErr++;
|
|
}
|
|
}
|
|
|
|
if (cErr != 0 && (!FCanQuery((char *)0) || !FQueryUser("continue ? ")))
|
|
ExitSlm();
|
|
}
|
|
|
|
|
|
/* Mark the marked files in pne list. All names have already been shown to
|
|
* exist (or are fDeleted) by glob.
|
|
*/
|
|
void MarkFiForMarkedNeList(
|
|
AD *pad,
|
|
NE *pneList)
|
|
{
|
|
FI far *pfi;
|
|
F fExists;
|
|
NE *pne;
|
|
|
|
AssertLoaded(pad);
|
|
|
|
UnMarkAll(pad);
|
|
|
|
ForEachNe(pne, pneList)
|
|
{
|
|
if (!FMarkedNe(pne))
|
|
continue;
|
|
if (FLookupSz(pad, SzOfNe(pne), &pfi, &fExists))
|
|
pfi->fMarked = fTrue;
|
|
else
|
|
Warn("%s is not a file of %&P/C\n", SzOfNe(pne), pad);
|
|
}
|
|
}
|
|
|
|
|
|
/* binary searchs the names looking for sz;
|
|
NOTE: all names, even those deleted, are sorted.
|
|
Returns fTrue if found and not deleted (*pfExists is true in this case);
|
|
*ppfi is points to the matching name if *pfExists is fTrue; otherwise,
|
|
*ppfi points to the name before which the new name should be placed.
|
|
*/
|
|
F FLookupSz(
|
|
AD *pad,
|
|
char *sz,
|
|
FI far **ppfi,
|
|
F *pfExists)
|
|
{
|
|
register IFI ifi, ifiLim;
|
|
IFI ifiMin;
|
|
int w;
|
|
SH far *psh;
|
|
FI far *rgfi;
|
|
|
|
AssertLoaded(pad);
|
|
|
|
psh = pad->psh;
|
|
rgfi = pad->rgfi;
|
|
ifiLim = psh->ifiMac;
|
|
ifiMin = 0;
|
|
|
|
/* invariant at top of loop: name is >= ifiMin and < ifiLim */
|
|
while(ifiLim > ifiMin)
|
|
{
|
|
ifi = (IFI)((ifiMin + ifiLim) / 2);
|
|
if ((w = SzCmpiNm(sz, rgfi[ifi].nmFile, cchFileMax)) == 0)
|
|
{
|
|
*ppfi = &rgfi[ifi];
|
|
*pfExists = fTrue;
|
|
return !rgfi[ifi].fDeleted;
|
|
}
|
|
else if (w < 0)
|
|
/* name is less than ifi */
|
|
ifiLim = ifi;
|
|
else
|
|
/* name is greater than ifi; set min to one after */
|
|
ifiMin = (IFI)(ifi + 1);
|
|
}
|
|
|
|
/* name goes before ifiLim (which may be equal to ifiMac) */
|
|
*ppfi = &rgfi[ifiLim];
|
|
*pfExists = fFalse;
|
|
return fFalse;
|
|
}
|
|
|
|
|
|
/* Unmark files which aren't in pneFiles. Complain about files in pneFiles
|
|
* which weren't marked or which don't exist.
|
|
*/
|
|
void ReMarkList(
|
|
AD *pad,
|
|
NE *pneFiles,
|
|
char *szWarnNotMarked) /* Warning if file wasn't marked. */
|
|
{
|
|
FI far *pfi;
|
|
FI far *pfiMac = pad->rgfi + pad->psh->ifiMac;
|
|
NE *pne;
|
|
|
|
AssertLoaded(pad);
|
|
|
|
/* Complain about the files which weren't marked or don't exist. */
|
|
ForEachNe(pne, pneFiles)
|
|
{
|
|
F fExists;
|
|
|
|
if (FLookupSz(pad, SzOfNe(pne), &pfi, &fExists) || fExists)
|
|
{
|
|
if (!pfi->fMarked && fVerbose)
|
|
{
|
|
AssertF(szWarnNotMarked != 0);
|
|
Warn(szWarnNotMarked, pad, pfi);
|
|
}
|
|
}
|
|
else
|
|
Error("%s is not a file of %&P/C\n", SzOfNe(pne), pad);
|
|
}
|
|
|
|
/* Unmark those marked files which aren't in the list. */
|
|
for (pfi = pad->rgfi; pfi < pfiMac; pfi++)
|
|
{
|
|
char sz[cchFileMax + 1];
|
|
|
|
if (pfi->fMarked)
|
|
{
|
|
SzCopyNm(sz, pfi->nmFile, cchFileMax);
|
|
if (!PneLookup(pneFiles, sz))
|
|
pfi->fMarked = fFalse;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Return a pointer to the first ne matching sz. */
|
|
NE *PneLookup(
|
|
NE *pneList,
|
|
char *sz)
|
|
{
|
|
NE *pne;
|
|
|
|
ForEachNe(pne, pneList)
|
|
if (SzCmp(SzOfNe(pne), sz) == 0)
|
|
return pne;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* marks all of those files checked out to a particular directory */
|
|
void MarkOut(
|
|
AD *pad,
|
|
IED ied)
|
|
{
|
|
register FI far *pfi;
|
|
FI far *pfiMac;
|
|
|
|
AssertLoaded(pad);
|
|
AssertF(ied != iedNil);
|
|
|
|
for (pfi=pad->rgfi, pfiMac=pfi+pad->psh->ifiMac; pfi < pfiMac; pfi++)
|
|
pfi->fMarked = (BIT)FCheckedOut(pad, ied, pfi);
|
|
}
|
|
|
|
|
|
/* marks all of those files out or out of sync for a particular directory */
|
|
void MarkOSync(
|
|
AD *pad,
|
|
IED ied,
|
|
F fChkBroken,
|
|
F fMarkGhosted)
|
|
{
|
|
register FI far *pfi;
|
|
FI far *pfiMac;
|
|
|
|
AssertLoaded(pad);
|
|
AssertF(ied != iedNil);
|
|
|
|
for (pfi=pad->rgfi, pfiMac=pfi+pad->psh->ifiMac; pfi < pfiMac; pfi++)
|
|
{
|
|
register FS far *pfs = PfsForPfi(pad, ied, pfi);
|
|
|
|
switch(pfs->fm)
|
|
{
|
|
default: FatalError(szBadFileFormat, pad, pfi);
|
|
|
|
case fmGhost:
|
|
pfi->fMarked = fMarkGhosted;
|
|
break;
|
|
|
|
case fmNonExistent:
|
|
pfi->fMarked = fFalse;
|
|
break;
|
|
|
|
case fmIn:
|
|
pfi->fMarked = (BIT)(fChkBroken &&
|
|
ied == pad->iedCur &&
|
|
FBroken(pad, pfi, pfs, fFalse));
|
|
break;
|
|
|
|
case fmAdd:
|
|
case fmDelIn:
|
|
case fmDelOut:
|
|
case fmCopyIn:
|
|
case fmOut:
|
|
case fmVerify:
|
|
case fmConflict:
|
|
case fmMerge:
|
|
pfi->fMarked = fTrue;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* marks all of those files checked out to any directory */
|
|
void MarkAOut(
|
|
AD *pad)
|
|
{
|
|
register FI far *pfi;
|
|
register IED ied;
|
|
FI far *pfiMac;
|
|
|
|
AssertLoaded(pad);
|
|
|
|
for (pfi=pad->rgfi, pfiMac=pfi+pad->psh->ifiMac; pfi < pfiMac; pfi++)
|
|
{
|
|
pfi->fMarked = fFalse;
|
|
for (ied = 0; ied < pad->psh->iedMac; ied++)
|
|
{
|
|
if ((!FIsFreeEdValid(pad->psh) || !pad->rged[ied].fFreeEd) &&
|
|
FCheckedOut(pad, ied, pfi))
|
|
{
|
|
pfi->fMarked = fTrue;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* marks all of the files and directories */
|
|
void MarkAll(
|
|
AD *pad)
|
|
{
|
|
register FI far *pfi;
|
|
FI far *pfiMac;
|
|
|
|
AssertLoaded(pad);
|
|
|
|
for (pfi=pad->rgfi, pfiMac=pfi+pad->psh->ifiMac; pfi < pfiMac; pfi++)
|
|
pfi->fMarked = fTrue;
|
|
}
|
|
|
|
|
|
/* marks all of the directories */
|
|
void MarkAllDir(
|
|
AD *pad)
|
|
{
|
|
register FI far *pfi;
|
|
FI far *pfiMac;
|
|
|
|
AssertLoaded(pad);
|
|
|
|
for (pfi=pad->rgfi, pfiMac=pfi+pad->psh->ifiMac; pfi < pfiMac; pfi++)
|
|
if (pfi->fk == fkDir)
|
|
pfi->fMarked = fTrue;
|
|
}
|
|
|
|
/* marks all of the directories */
|
|
void MarkAllDirOnly(
|
|
AD *pad)
|
|
{
|
|
register FI far *pfi;
|
|
FI far *pfiMac;
|
|
|
|
AssertLoaded(pad);
|
|
|
|
for (pfi=pad->rgfi, pfiMac=pfi+pad->psh->ifiMac; pfi < pfiMac; pfi++)
|
|
if (pfi->fk == fkDir)
|
|
pfi->fMarked = fTrue;
|
|
else
|
|
pfi->fMarked = fFalse;
|
|
}
|
|
|
|
/* marks all of the non-deleted files and directories */
|
|
void MarkNonDel(
|
|
AD *pad)
|
|
{
|
|
register FI far *pfi;
|
|
FI far *pfiMac;
|
|
|
|
AssertLoaded(pad);
|
|
|
|
for (pfi=pad->rgfi, pfiMac=pfi+pad->psh->ifiMac; pfi < pfiMac; pfi++)
|
|
pfi->fMarked = (BIT)(!pfi->fDeleted);
|
|
}
|
|
|
|
|
|
void MarkDelDir(pad)
|
|
/* marks all of the deleted directories */
|
|
AD *pad;
|
|
{
|
|
register FI far *pfi;
|
|
FI far *pfiMac;
|
|
|
|
AssertLoaded(pad);
|
|
|
|
for (pfi=pad->rgfi, pfiMac=pfi+pad->psh->ifiMac; pfi < pfiMac; pfi++)
|
|
{
|
|
if (pfi->fk == fkDir && PfsForPfi(pad, pad->iedCur, pfi)->fm == fmDelIn)
|
|
{
|
|
pfi->fMarked = fTrue;
|
|
}
|
|
else
|
|
{
|
|
pfi->fMarked = fFalse;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* marks all broken linked files in the given ed */
|
|
void MarkBroken(
|
|
AD *pad)
|
|
{
|
|
register FI far *pfi;
|
|
FI far *pfiMac;
|
|
|
|
AssertLoaded(pad);
|
|
|
|
for (pfi=pad->rgfi, pfiMac=pfi+pad->psh->ifiMac; pfi < pfiMac; pfi++)
|
|
{
|
|
register FS far *pfs = PfsForPfi(pad, pad->iedCur, pfi);
|
|
|
|
pfi->fMarked = (BIT)((pfs->fm == fmIn ||
|
|
pfs->fm == fmCopyIn) &&
|
|
FBroken(pad, pfi, pfs, fFalse));
|
|
}
|
|
}
|
|
|
|
|
|
/* returns true if all FI are deleted */
|
|
F FAllFiDel(
|
|
AD *pad)
|
|
{
|
|
register FI far *pfi;
|
|
FI far *pfiMac;
|
|
|
|
for (pfi=pad->rgfi, pfiMac=pfi+pad->psh->ifiMac; pfi < pfiMac; pfi++)
|
|
if (!pfi->fDeleted)
|
|
return fFalse;
|
|
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/* returns true if all FS for the file pfi are fmNonExistent. Must have
|
|
loaded all the ed.
|
|
*/
|
|
F FAllFsDel(
|
|
AD *pad,
|
|
FI far *pfi)
|
|
{
|
|
IED ied, iedMac;
|
|
|
|
AssertLoaded(pad);
|
|
|
|
iedMac = pad->psh->iedMac;
|
|
for (ied = 0; ied < iedMac; ied++)
|
|
if (PfsForPfi(pad, ied, pfi)->fm != fmNonExistent)
|
|
return fFalse;
|
|
|
|
AssertF(iedMac == 0 || pfi->fDeleted);
|
|
return pfi->fDeleted;
|
|
}
|
|
|
|
|
|
/* assert that there is a current directory; print a message and return false
|
|
if none. If fFalse is returned, we flush the status file.
|
|
*/
|
|
F FHaveCurDir(
|
|
AD *pad)
|
|
{
|
|
AssertLoaded(pad);
|
|
if (pad->iedCur != iedNil)
|
|
{
|
|
ED *rged;
|
|
|
|
if (pad->fQuickIO) {
|
|
rged = pad->rged1;
|
|
}
|
|
else {
|
|
rged = &pad->rged[pad->iedCur];
|
|
}
|
|
if (NmCmp(pad->nmInvoker, rged->nmOwner, cchUserMax) != 0)
|
|
Warn("invoker is not owner of directory\n");
|
|
return fTrue;
|
|
}
|
|
else
|
|
{
|
|
Error(szNotEnlisted, pad, pad, pad, pad);
|
|
FlushStatus(pad);
|
|
return fFalse;
|
|
}
|
|
}
|
|
|
|
|
|
/* We simulate a directory stack with a linked list of (pthSSubDir,pthUSubDir)
|
|
* NE pairs.
|
|
*/
|
|
NE *pneDirStack = 0;
|
|
|
|
void PushDir(
|
|
AD *pad,
|
|
char *sz)
|
|
{
|
|
NE *pneSSubDir = PneNewNm(pad->pthSSubDir, CchOfPth(pad->pthSSubDir),
|
|
faNormal);
|
|
NE *pneUSubDir = PneNewNm(pad->pthUSubDir, CchOfPth(pad->pthUSubDir),
|
|
faNormal);
|
|
|
|
/* Insert pneSSubDir and pneUSubDir at the front of pneDirStack. */
|
|
pneSSubDir->pneNext = pneUSubDir;
|
|
pneUSubDir->pneNext = pneDirStack;
|
|
pneDirStack = pneSSubDir;
|
|
|
|
ChngDir(pad, sz);
|
|
}
|
|
|
|
|
|
void PopDir(
|
|
AD *pad)
|
|
{
|
|
NE *pneSSubDir;
|
|
NE *pneUSubDir;
|
|
|
|
/* Retrieve pneSSubDir and pneUSubDir. */
|
|
pneSSubDir = pneDirStack;
|
|
AssertF(pneSSubDir != 0);
|
|
pneUSubDir = pneSSubDir->pneNext;
|
|
AssertF(pneUSubDir != 0);
|
|
|
|
/* Restore current subdirectories. */
|
|
PthCopySz(pad->pthSSubDir, SzOfNe(pneSSubDir));
|
|
PthCopySz(pad->pthUSubDir, SzOfNe(pneUSubDir));
|
|
|
|
/* Unlink them from the pneDirStack and free them. */
|
|
pneDirStack = pneUSubDir->pneNext;
|
|
pneUSubDir->pneNext = 0;
|
|
FreeNe(pneSSubDir);
|
|
}
|
|
|
|
|
|
/* Change the notion of current directory in both the system and user domains.
|
|
* Sz can be an absolute or relative path, including . and .., but not
|
|
* wildcards.
|
|
*/
|
|
void ChngDir(
|
|
AD *pad,
|
|
char *sz)
|
|
{
|
|
if (*sz == '/')
|
|
{
|
|
int cchUSub = CchOfPth(pad->pthUSubDir);
|
|
|
|
if (cchUSub > 1)
|
|
{
|
|
int cchDiff;
|
|
|
|
/* Remove pthUSubDir from both pthUSubDir and
|
|
* pthSSubDir. If pthUSubDir is a proper suffix of
|
|
* pthSSubDir, then we will leave pthSSubDir with more
|
|
* than just "/". Example:
|
|
* pthSSubDir = "/a/b/c/d"
|
|
* pthUSubDir = "/c/d"
|
|
* ChngDir(pad, "/");
|
|
* pthSSubDir = "/a/b"
|
|
* pthUSubDir = "/"
|
|
*/
|
|
cchDiff = CchOfPth(pad->pthSSubDir) - cchUSub;
|
|
AssertF(cchDiff >= 0 &&
|
|
PthCmpCb(pad->pthSSubDir + cchDiff, pad->pthUSubDir, cchUSub) == 0 &&
|
|
pad->pthSSubDir[cchDiff] == '/');
|
|
|
|
if (cchDiff > 0)
|
|
pad->pthSSubDir[cchDiff] = 0;
|
|
else
|
|
PthCopySz(pad->pthSSubDir, "/");
|
|
|
|
PthCopySz(pad->pthUSubDir, "/");
|
|
}
|
|
else
|
|
AssertF(PthCmp(pad->pthUSubDir, "/") == 0);
|
|
|
|
++sz;
|
|
}
|
|
|
|
/* Process each component of the pathname. */
|
|
for (;;)
|
|
{
|
|
char *pchSl = index(sz, '/');
|
|
|
|
/* Null terminate this component. */
|
|
if (pchSl)
|
|
*pchSl = 0;
|
|
|
|
if (strcmp(sz, ".") == 0)
|
|
/* Ignore . */
|
|
;
|
|
|
|
else if (strcmp(sz, "..") == 0)
|
|
{
|
|
/* Remove last component of both pthSSubDir and
|
|
* pthUSubDir. Note that since pthUSubDir is a
|
|
* suffix of pthSSubDir, we only need to check that
|
|
* pthUSubDir will remain in the tree.
|
|
*/
|
|
char *pchS;
|
|
char *pchU;
|
|
|
|
if (PthCmp(pad->pthUSubDir, "/") == 0)
|
|
FatalError(".. would leave the project tree\n");
|
|
|
|
/* Find last component of path */
|
|
pchS = rindex(pad->pthSSubDir, '/');
|
|
pchU = rindex(pad->pthUSubDir, '/');
|
|
|
|
/* System and user's last components should be
|
|
* identical.
|
|
*/
|
|
AssertF(pchS && pchU && *pchU && PthCmp(pchS, pchU)==0);
|
|
|
|
/* If just "/component", advance pchX past '/' */
|
|
pchS += (pchS == pad->pthSSubDir);
|
|
pchU += (pchU == pad->pthUSubDir);
|
|
|
|
AssertF(*pchU && *pchS);
|
|
|
|
ClearLpbCb((char far *)pchS,
|
|
cchPthMax - (pchS - pad->pthSSubDir));
|
|
ClearLpbCb((char far *)pchU,
|
|
cchPthMax - (pchU - pad->pthUSubDir));
|
|
}
|
|
else if (*sz)
|
|
{
|
|
/* Append component to pthSSubDir and pthUSubDir.
|
|
* Since pthUSubDir is a suffix of pthSSubDir we need
|
|
* only check pthSSubDir for overflow.
|
|
*/
|
|
if (CchOfPth(pad->pthSSubDir) + strlen(sz) + 2 > sizeof pad->pthSSubDir)
|
|
FatalError("subdirectory path too long at \"%s\"\n", sz);
|
|
|
|
if (CchOfPth(pad->pthSSubDir) > 1)
|
|
PthCatSz(pad->pthSSubDir, "/");
|
|
PthCatSz(pad->pthSSubDir, sz);
|
|
|
|
if (CchOfPth(pad->pthUSubDir) > 1)
|
|
PthCatSz(pad->pthUSubDir, "/");
|
|
PthCatSz(pad->pthUSubDir, sz);
|
|
}
|
|
|
|
/* Restore slash at end of component, and advance to next
|
|
* component. */
|
|
if (pchSl)
|
|
{
|
|
*pchSl = '/';
|
|
sz = pchSl + 1;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/* calls malloc and aborts if we ran out of memory; zeros the block before
|
|
returning a pointer to it. REVIEW.
|
|
*/
|
|
char *PbAllocCb(
|
|
unsigned cb,
|
|
int fClear)
|
|
{
|
|
char *pb;
|
|
|
|
if ((pb = malloc(cb)) == 0)
|
|
FatalError("out of memory\n");
|
|
|
|
if (fClear)
|
|
ClearPbCb(pb, cb);
|
|
|
|
return pb;
|
|
}
|
|
|
|
/* Calls malloc (if pb == 0) or remalloc and aborts if we ran out of memory.
|
|
* To free the memory, call free() (ug).
|
|
*/
|
|
char *PbReallocPbCb(
|
|
char * pb,
|
|
unsigned cbNewSize)
|
|
{
|
|
if (!(pb = (!pb ? malloc(cbNewSize) : realloc(pb, cbNewSize))))
|
|
FatalError("out of memory\n");
|
|
|
|
return pb;
|
|
}
|
|
|
|
#define cdayWeek 7
|
|
static char *rgszDay[cdayWeek] = { "Sun",
|
|
"Mon",
|
|
"Tue",
|
|
"Wed",
|
|
"Thu",
|
|
"Fri",
|
|
"Sat" };
|
|
|
|
/* return short or long version of time;
|
|
* 23 for long, 14 for short.
|
|
*
|
|
* short - 11-06-85@19:57
|
|
* long - 11-06-85@19:57:16 (Tue)
|
|
*/
|
|
char *SzTime(
|
|
TIME time)
|
|
{
|
|
static char szUnknown[] = "(unknown)";
|
|
static char szTTime[26];
|
|
struct tm *tmT;
|
|
|
|
if (timeNil == time)
|
|
return (szUnknown);
|
|
|
|
if ((tmT = localtime(&time)) == NULL)
|
|
return (szUnknown);
|
|
|
|
if (fVerbose)
|
|
{
|
|
SzPrint(szTTime,"%02d-%02d-%02d@%02d:%02d:%02d (%s)",tmT->tm_mon+1,tmT->tm_mday,
|
|
tmT->tm_year,tmT->tm_hour,tmT->tm_min,tmT->tm_sec,
|
|
rgszDay[tmT->tm_wday]);
|
|
}
|
|
else
|
|
{
|
|
SzPrint(szTTime,"%02d-%02d-%02d@%02d:%02d",tmT->tm_mon+1,tmT->tm_mday,
|
|
tmT->tm_year,tmT->tm_hour,tmT->tm_min);
|
|
}
|
|
|
|
return szTTime;
|
|
}
|
|
|
|
|
|
/* return short or long version of time;
|
|
* 23 for long, 14 for short.
|
|
* But year is first so it is sortable.
|
|
*
|
|
* short - 85-11-06@19:57
|
|
* long - 85-11-06@19:57:16 (Tue)
|
|
*/
|
|
char *SzTimeSortable(
|
|
TIME time)
|
|
{
|
|
static char szUnknown[] = "(unknown)";
|
|
static char szTTime[26];
|
|
struct tm *tmT;
|
|
|
|
if (timeNil == time)
|
|
return (szUnknown);
|
|
|
|
if ((tmT = localtime(&time)) == NULL)
|
|
return (szUnknown);
|
|
|
|
if (fVerbose)
|
|
{
|
|
SzPrint(szTTime,"%02d-%02d-%02d@%02d:%02d:%02d (%s)",tmT->tm_year,tmT->tm_mon+1,tmT->tm_mday,
|
|
tmT->tm_hour,tmT->tm_min,tmT->tm_sec,
|
|
rgszDay[tmT->tm_wday]);
|
|
}
|
|
else
|
|
{
|
|
SzPrint(szTTime,"%02d-%02d-%02d@%02d:%02d",tmT->tm_year,tmT->tm_mon+1,tmT->tm_mday,
|
|
tmT->tm_hour,tmT->tm_min);
|
|
}
|
|
|
|
return szTTime;
|
|
}
|
|
|
|
|
|
/* This predicate tests the first 50 characters in a file to see if the
|
|
* file is binary or not. It returns fTrue if any nonASCII characters
|
|
* or unusual control codes are found.
|
|
*/
|
|
F FBinaryPth(
|
|
PTH pth[cchPthMax])
|
|
{
|
|
#define cchBinMax 50
|
|
#define BYTE_ORDER_MARK 0xFEFF
|
|
char rgb[cchBinMax];
|
|
register char *pbMac;
|
|
register char *pb;
|
|
MF *pmf;
|
|
struct _stat st;
|
|
|
|
if (!FStatPth(pth, &st) || (st.st_mode&S_IFREG) == 0)
|
|
/* not present, or not regular */
|
|
return fFalse;
|
|
|
|
if ((pmf = PmfOpen(pth, omReadOnly, fxNil)) == 0)
|
|
return fFalse;
|
|
|
|
pbMac = CbReadMf(pmf, (char far *)rgb, cchBinMax) + rgb;
|
|
CloseMf(pmf);
|
|
|
|
if ((*TestForUnicode) (rgb, pbMac - rgb, NULL))
|
|
return fUnicode;
|
|
|
|
for (pb = rgb; pb < pbMac; pb++)
|
|
{
|
|
switch(*pb)
|
|
{
|
|
default:
|
|
/* special case ^Z at end of file...*/
|
|
if (!(*pb == '\032' && pb == pbMac - 1) &&
|
|
(*pb < ' ' || *pb > '~'))
|
|
{
|
|
return fTrue;
|
|
}
|
|
|
|
/* else fall through */
|
|
case '\n':
|
|
case '\r':
|
|
case '\b':
|
|
case '\f':
|
|
case '\t':
|
|
/* printable chars plus allowable control chars */
|
|
break;
|
|
}
|
|
}
|
|
// It looks like text so far. Add a final test for the COFF
|
|
// library signature.
|
|
|
|
if (!strncmp(rgb, IMAGE_ARCHIVE_START, IMAGE_ARCHIVE_START_SIZE))
|
|
return(fTrue);
|
|
{
|
|
PIMAGE_DOS_HEADER pHdr = (PIMAGE_DOS_HEADER)rgb;
|
|
|
|
if ((pHdr->e_magic == IMAGE_NT_SIGNATURE) ||
|
|
(pHdr->e_magic == IMAGE_DOS_SIGNATURE) ||
|
|
(pHdr->e_magic == IMAGE_OS2_SIGNATURE) ||
|
|
(pHdr->e_magic == IMAGE_VXD_SIGNATURE)
|
|
|
|
)
|
|
return(fTrue);
|
|
}
|
|
|
|
return fText;
|
|
}
|
|
|
|
/* Fills the given buffer with the names of those who have the file checked
|
|
* out. If the names don't all fit, tries to put an ellipsis in.
|
|
*/
|
|
F FOutUsers(
|
|
char *sz,
|
|
int cchSz,
|
|
AD *pad,
|
|
FI far *pfi)
|
|
{
|
|
IED ied, iedMac;
|
|
int cchUsed;
|
|
register char *pch;
|
|
F fFound = fFalse;
|
|
|
|
for (ied = 0, iedMac = pad->psh->iedMac; ied < iedMac; ied++)
|
|
{
|
|
if ((!FIsFreeEdValid(pad->psh) || !pad->rged[ied].fFreeEd) &&
|
|
FCheckedOut(pad,ied,pfi))
|
|
{
|
|
cchUsed = strlen(sz);
|
|
pch = sz + cchUsed;
|
|
|
|
if (cchSz - cchUsed < cchUserMax + 2)
|
|
{
|
|
if (cchSz - cchUsed > 3)
|
|
SzPrint(pch, "...");
|
|
fFound = fTrue;
|
|
break;
|
|
}
|
|
SzPrint(pch, fFound ? ", %&O" : "%&O", pad, ied);
|
|
fFound = fTrue;
|
|
}
|
|
}
|
|
|
|
return fFound;
|
|
}
|
|
|
|
|
|
/* return fTrue if any names in the list have associated TDs. */
|
|
F FAnyFileTimes(
|
|
NE *pneList)
|
|
{
|
|
register NE *pne;
|
|
|
|
for (pne = pneList; pne; pne = pne->pneNext)
|
|
if (pne->u.tdNe.tdt != tdtNone)
|
|
return fTrue;
|
|
return fFalse;
|
|
}
|
|
|
|
|
|
private F FMatchPart(
|
|
char *,
|
|
char *,
|
|
int);
|
|
|
|
/* DOS style pattern match. The DOS strategy is to match the file parts and
|
|
* the extensions separately. * and ? are wildcards, * fills the rest of the
|
|
* compiled pattern with ?s. ? even matches nulls, thus "????" matches "foo".
|
|
* Case insensitive too.
|
|
*/
|
|
F FMatch(
|
|
char *sz,
|
|
char *szPat)
|
|
{
|
|
char *szExt = "";
|
|
char *szPatExt = "";
|
|
char *pchDot = index(sz, '.');
|
|
char *pchPatDot = index(szPat, '.');
|
|
F fMatch;
|
|
|
|
if (pchDot)
|
|
{
|
|
*pchDot = 0;
|
|
szExt = pchDot + 1;
|
|
}
|
|
if (pchPatDot)
|
|
{
|
|
*pchPatDot = 0;
|
|
szPatExt = pchPatDot + 1;
|
|
}
|
|
|
|
fMatch = FMatchPart(sz, szPat, cchDosName) &&
|
|
FMatchPart(szExt, szPatExt, cchDosExt);
|
|
|
|
if (pchDot)
|
|
*pchDot = '.';
|
|
if (pchPatDot)
|
|
*pchPatDot = '.';
|
|
|
|
return fMatch;
|
|
}
|
|
|
|
|
|
#define ChLower(ch) (isupper(ch) ? tolower(ch) : (ch))
|
|
|
|
private F FMatchPart(
|
|
char *sz,
|
|
char *szPat,
|
|
int ichMac)
|
|
{
|
|
char rgch[cchDosName+1];
|
|
char rgchPat[cchDosName+1];
|
|
int ich;
|
|
|
|
/* Copy sz into rgch, compile szPat into rgchPat. Yes, we could
|
|
* compile the pattern just once for any given run of matchings, but
|
|
* then we'd have to have a different interface for DOS vs UNIX code.
|
|
*/
|
|
LszCopyCb((char far *)rgch, sz, ichMac); /* zeroes remainder */
|
|
ClearLpbCb((char far *)rgchPat, ichMac);
|
|
for (ich = 0; ich < ichMac && szPat[ich]; ich++)
|
|
{
|
|
if (szPat[ich] == '*')
|
|
{
|
|
for ( ; ich < ichMac; ich++)
|
|
rgchPat[ich] = '?';
|
|
break;
|
|
}
|
|
rgchPat[ich] = szPat[ich];
|
|
}
|
|
rgchPat[ich] = '\0';
|
|
|
|
for (ich = 0; ich < ichMac; ich++)
|
|
{
|
|
if ((rgchPat[ich] == '\0') && (rgch[ich] == '\0')) return fTrue;
|
|
if (ChLower(rgch[ich]) == ChLower(rgchPat[ich])) continue ;
|
|
if (rgchPat[ich] == '?') continue;
|
|
return fFalse;
|
|
}
|
|
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
F FWildSz(
|
|
char *sz)
|
|
{
|
|
return index(sz, '*') || index(sz, '?');
|
|
}
|
|
|
|
|
|
PV PvGlobal(
|
|
AD *pad)
|
|
{
|
|
return pad->psh->pv;
|
|
}
|
|
|
|
|
|
PV PvLocal(
|
|
AD *pad,
|
|
IED ied)
|
|
{
|
|
ED *rged;
|
|
|
|
AssertLoaded(pad);
|
|
|
|
AssertF(ied != iedNil);
|
|
if (pad->fQuickIO) {
|
|
rged = pad->rged1;
|
|
}
|
|
else {
|
|
rged = &pad->rged[ied];
|
|
}
|
|
return (rged->fNewVer) ? PvIncr(PvGlobal(pad)) : PvGlobal(pad);
|
|
}
|
|
|
|
|
|
PV PvIncr(
|
|
PV pv)
|
|
{
|
|
/* Increment project version number. 1.1.1 -> 1.1.2; 1.2.0 -> 1.3.1. */
|
|
if (pv.rup == 0)
|
|
pv.rmm++;
|
|
pv.rup++;
|
|
|
|
/* Clear the project version name. */
|
|
ClearPbCb(pv.szName, cchPvNameMax + 1);
|
|
|
|
return pv;
|
|
}
|
|
|
|
|
|
char *SzForPv(
|
|
char *sz,
|
|
PV pv,
|
|
F fNameToo)
|
|
{
|
|
SzPrint(sz, pv.rup ? "%d.%02d.%02d" : "%d.%02d", pv.rmj, pv.rmm, pv.rup);
|
|
if (fNameToo && pv.szName[0])
|
|
{
|
|
strcat(sz, " ");
|
|
strcat(sz, pv.szName);
|
|
}
|
|
return sz;
|
|
}
|
|
|
|
|
|
/* Return -1, 0, 1 if pv1 is <, =, or > pv2. */
|
|
int CmpPv(
|
|
PV pv1,
|
|
PV pv2)
|
|
{
|
|
if (pv1.rmj < pv2.rmj)
|
|
return -1;
|
|
|
|
if (pv1.rmj > pv2.rmj)
|
|
return 1;
|
|
|
|
if (pv1.rmm < pv2.rmm)
|
|
return -1;
|
|
|
|
if (pv1.rmm > pv2.rmm)
|
|
return 1;
|
|
|
|
if (pv1.rup == pv2.rup)
|
|
return 0;
|
|
|
|
/* special case for no revision number (i.e. 2.28 > 2.28.01) */
|
|
if (pv1.rup == 0)
|
|
return 1;
|
|
|
|
if (pv2.rup == 0 || pv1.rup < pv2.rup)
|
|
return -1;
|
|
|
|
AssertF(pv1.rup > pv2.rup);
|
|
return 1;
|
|
}
|
|
|
|
|
|
F FIsF(
|
|
int w)
|
|
{
|
|
return w == (int)fTrue || w == (int)fFalse;
|
|
}
|
|
|
|
|
|
/* return T if:
|
|
file is a version.h and doesn't exist or
|
|
file is not a version.h and:
|
|
on Xenix: the file is not linked to the src dir
|
|
on DOS: the file is r/w
|
|
*/
|
|
F FBroken(
|
|
AD *pad,
|
|
FI far *pfi,
|
|
FS far *pfs,
|
|
int fDelOk)
|
|
{
|
|
struct _stat stUFile;
|
|
PTH pthUFile[cchPthMax];
|
|
|
|
AssertLoaded(pad);
|
|
AssertF(pad->iedCur != iedNil && pfi != 0 && pfs != 0);
|
|
|
|
switch(pfs->fm)
|
|
{
|
|
default: FatalError(szBadFileFormat, pad, pfi);
|
|
|
|
case fmGhost:
|
|
case fmOut:
|
|
case fmMerge:
|
|
case fmVerify:
|
|
case fmConflict:
|
|
AssertF(!pfi->fDeleted);
|
|
case fmNonExistent: /* not necc delete because of defect */
|
|
case fmDelOut: /* not necc delete because of defect */
|
|
return fFalse;
|
|
|
|
case fmDelIn: /* not necc delete because of defect */
|
|
case fmIn:
|
|
case fmCopyIn:
|
|
if (!FStatPth(PthForUFile(pad, pfi, pthUFile), &stUFile))
|
|
/* gone but we are not going to destroy anything */
|
|
return !fDelOk;
|
|
break;
|
|
|
|
case fmAdd:
|
|
/* broken if non-directory exists */
|
|
return FStatPth(PthForUFile(pad, pfi, pthUFile), &stUFile) &&
|
|
((stUFile.st_mode&S_IFDIR) == 0 || pfi->fk != fkDir);
|
|
}
|
|
|
|
/* file exists for fm...In; test for write permission */
|
|
|
|
if (pfi->fk == fkDir && (stUFile.st_mode&S_IFDIR) == 0)
|
|
{
|
|
Error("directory %s is now a file?\n", pthUFile);
|
|
return fTrue;
|
|
}
|
|
|
|
if (pfi->fk != fkDir && (stUFile.st_mode&S_IFDIR) != 0)
|
|
{
|
|
Error("regular file %s is now a directory?\n", pthUFile);
|
|
return fTrue;
|
|
}
|
|
|
|
if ((stUFile.st_mode&S_IFDIR) != 0)
|
|
/* directories are never broken */
|
|
return fFalse;
|
|
|
|
if (pfi->fk == fkVersion)
|
|
/* neither are version.h files */
|
|
return fFalse;
|
|
|
|
return (stUFile.st_mode&S_IWRITE) != 0;/* return true if writable */
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns fTrue if the given name contains only valid filename characters.
|
|
*/
|
|
F FIsValidFileNm(
|
|
NM *nm)
|
|
{
|
|
register char *pch;
|
|
|
|
while (*nm != '\0' && (isalnum(*nm) || ((pch = index("`~!@#$%^&()-_'.}{", *nm)) != 0)))
|
|
{
|
|
nm++;
|
|
}
|
|
|
|
return *nm == '\0';
|
|
}
|
|
|
|
|
|
/*
|
|
* Make sure the given name is a valid file or dir, not a protected
|
|
* SLM file name and not a DOS device. "." and ".." are also disallowed.
|
|
*
|
|
* This checking is a superset of FIsValidFileNm().
|
|
*
|
|
*/
|
|
BOOL
|
|
ValidateFileName(
|
|
char *szFile,
|
|
BOOL fAbortOnSystemFile
|
|
)
|
|
{
|
|
char *pch;
|
|
unsigned cchName;
|
|
unsigned isz;
|
|
F fDev;
|
|
static char *rgszSLMNames[] = { "slm.ini",
|
|
"local.scr",
|
|
"status.slm",
|
|
"iedcache.slm",
|
|
"cookie" };
|
|
static char *rgszDOSNames[] = { "AUX",
|
|
"CLOCK$",
|
|
"CON",
|
|
"NUL",
|
|
"PRN" };
|
|
|
|
#define cSLMNames (sizeof(rgszSLMNames)/sizeof(char *))
|
|
#define cDOSNames (sizeof(rgszDOSNames)/sizeof(char *))
|
|
|
|
/* Check for szName == "<dev><n>". Any device name with a
|
|
* colon will have been filtered by FIsValidFileNm, so no
|
|
* checking is done here.
|
|
*/
|
|
#define FIsDevN(szDev, szName) (strlen(szName) == 4 && \
|
|
_strnicmp(szDev, szName, 3) == 0 && \
|
|
isdigit(szName[3]))
|
|
|
|
pch = index(szFile, '.');
|
|
|
|
/* Make sure file name conforms to cchDosName.cchDosExt.
|
|
* These checks disallow "." and "..".
|
|
*/
|
|
if (pch != NULL) {
|
|
if (index(pch + 1, '.') != NULL)
|
|
FatalError("\"%s\": names cannot contain more"
|
|
" than one '.'\n", szFile);
|
|
|
|
/* Check length of extension */
|
|
if (strlen(pch + 1) > cchDosExt)
|
|
FatalError("\"%s\": extension must have %u or fewer"
|
|
" characters\n", szFile, cchDosExt);
|
|
|
|
cchName = pch - szFile;
|
|
}
|
|
else
|
|
cchName = strlen(szFile);
|
|
|
|
/* Check length of base name */
|
|
if (cchName < 1 || cchName > cchDosName)
|
|
FatalError("\"%s\": base name must have between 1 and %u"
|
|
" characters\n", szFile, cchDosName);
|
|
|
|
if (!FIsValidFileNm(szFile))
|
|
FatalError("\"%s\" is not a valid file or directory name.\n",
|
|
szFile);
|
|
|
|
/* Check for protected SLM names */
|
|
for (isz = 0; isz < cSLMNames; isz++) {
|
|
if (_stricmp(szFile, rgszSLMNames[isz]) == 0)
|
|
if (fAbortOnSystemFile)
|
|
FatalError("the name \"%s\" would conflict with"
|
|
" SLM system files.\n", szFile);
|
|
else
|
|
return(FALSE);
|
|
}
|
|
|
|
/* Check for DOS devices, with or w/o extension (relies on
|
|
* pch already being set).
|
|
*/
|
|
if (pch != NULL)
|
|
*pch = '\0';
|
|
|
|
for (isz = 0, fDev = fFalse; !fDev && isz < cDOSNames; isz++)
|
|
fDev = _stricmp(szFile, rgszDOSNames[isz]) == 0;
|
|
|
|
fDev |= FIsDevN("LPT", szFile) || FIsDevN("COM", szFile);
|
|
|
|
/* Restore the extension if there was one */
|
|
if (pch != NULL)
|
|
*pch = '.';
|
|
|
|
if (fDev)
|
|
if (fAbortOnSystemFile)
|
|
FatalError("the name \"%s\" would conflict with"
|
|
" a DOS device.\n", szFile);
|
|
else
|
|
return(FALSE);
|
|
|
|
return(TRUE);
|
|
|
|
#undef cSLMNames
|
|
#undef cDOSNames
|
|
#undef FIsDevN
|
|
}
|