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.
586 lines
16 KiB
586 lines
16 KiB
/*
|
|
* GlobArgs
|
|
*
|
|
* This module processes all recursive and wildcard pathname arguments.
|
|
*/
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
EnableAssert
|
|
|
|
char szStar[] = "*.*";
|
|
|
|
private F FRecurseFiles(P1(AD *));
|
|
private F FRecDir(P2(AD *pad, NE *pneFiles));
|
|
private F FDoNe(P2(AD *, NE *));
|
|
private F FCallCmd(P1(AD *));
|
|
private F FMatched(P0());
|
|
private F FDoFile(AD *, NE *, TD *, BOOL);
|
|
private F FDoPending(P1(AD *));
|
|
private F FWild(P3(AD *, char *, TD *));
|
|
private NE *PneMatch(P5(AD *, char *, F, F, F));
|
|
private F FGetStatus(P1(AD *));
|
|
private void FlushCachedStatus(P1(AD *));
|
|
|
|
F fLocal; /* Match wildcards against local files? */
|
|
|
|
/* Recurse or glob all arguments. */
|
|
void
|
|
GlobArgs(
|
|
AD * pad)
|
|
{
|
|
NE *pne;
|
|
F fOk = fTrue;
|
|
|
|
fLocal = (pad->pecmd->gl&fglLocal) != 0;
|
|
|
|
/* Initialize file accumulation. */
|
|
pad->pneFiles = 0;
|
|
PthCopy(pad->pthFiles, "");
|
|
pad->pthGlobSubDir = pad->pthUSubDir + CchOfPth(pad->pthUSubDir);
|
|
|
|
if (pad->flags&flagAll|| pad->pecmd->gl&fglAll)
|
|
CreatePeekThread(pad);
|
|
|
|
/* If we are matching wildcard files, directories do not apply.
|
|
* For instance, consider delfile -r sub vs. delfile -r foo* sub.
|
|
* In the latter case, we are not indicating to recursively remove
|
|
* everything in sub, just matching patterns below it. It is a
|
|
* real pity that we didn't use a different flag for patterns
|
|
* than for -a/-r.
|
|
*/
|
|
if (pad->szPattern != 0)
|
|
pad->pecmd->gl &= ~fglDirsToo;
|
|
|
|
if (pad->flags&flagAll || pad->pecmd->gl&fglAll) {
|
|
ChngDir(pad, "/");
|
|
pad->pthGlobSubDir = pad->pthUSubDir + 1;
|
|
fOk = FRecurseFiles(pad);
|
|
}
|
|
else if (pad->pneArgs == 0) {
|
|
if (pad->flags&flagRecursive)
|
|
fOk = FRecurseFiles(pad);
|
|
else if (!(pad->pecmd->gl&fglFiles))
|
|
fOk = FCallCmd(pad);
|
|
else {
|
|
Warn("no files specified\n");
|
|
Usage(pad);
|
|
}
|
|
}
|
|
|
|
/* Perform wildcard matching on every pathname argument. */
|
|
else ForEachNeWhileF(pne, pad->pneArgs, fOk) {
|
|
F fTLocal;
|
|
TD *ptd;
|
|
|
|
CheckForBreak();
|
|
|
|
/* Set fLocal if argument begins with "./". */
|
|
fTLocal = fLocal;
|
|
fLocal |= strncmp(SzOfNe(pne), "./", 2) == 0;
|
|
|
|
/* Do wildcard matching on this pathname.
|
|
* Use tdMin if no time was specified with "@time".
|
|
*/
|
|
ptd = pne->u.tdNe.tdt != tdtNone ? &pne->u.tdNe : &pad->tdMin;
|
|
fOk &= FWild(pad, SzOfNe(pne), ptd);
|
|
|
|
if (!FMatched())
|
|
Error("%s: not found\n", SzOfNe(pne));
|
|
|
|
fLocal = fTLocal;
|
|
}
|
|
|
|
if (fOk)
|
|
FDoPending(pad); /* Flush last files. */
|
|
|
|
FlushCachedStatus(pad);
|
|
|
|
}
|
|
|
|
|
|
/* Recursively process all files in and under the current directory.
|
|
* If pad->pecmd->gl&fglDirsToo, FDoFile the directory too;
|
|
* pad->pecmd->gl&fglTopDown determines whether we do the directory
|
|
* before or after its contents.
|
|
*
|
|
* Regardless of gl&fglFiles, we match against files if a pattern is specified.
|
|
*/
|
|
private F
|
|
FRecurseFiles(
|
|
AD *pad)
|
|
{
|
|
NE *pneDirs = 0; /* Subdirectories */
|
|
NE *pneFiles = 0; /* Files (+ dirs?) in this directory */
|
|
NE *pne;
|
|
F fOk = fTrue;
|
|
|
|
CheckForBreak();
|
|
|
|
pneDirs = PneMatch(pad, szStar, fLocal, fTrue, fFalse);
|
|
if (pad->pecmd->gl&fglFiles || pad->szPattern != 0)
|
|
pneFiles = PneMatch(pad, pad->szPattern ? pad->szPattern:szStar,
|
|
fLocal, !!(pad->pecmd->gl&fglDirsToo), fTrue);
|
|
|
|
if (pad->pecmd->gl&fglTopDown)
|
|
fOk = FRecDir(pad, pneFiles);
|
|
|
|
ForEachNeWhileF(pne, pneDirs, fOk) {
|
|
ChngDir(pad, SzOfNe(pne));
|
|
fOk = FRecurseFiles(pad);
|
|
ChngDir(pad, "..");
|
|
}
|
|
|
|
if (fOk && !(pad->pecmd->gl&fglTopDown))
|
|
fOk = FRecDir(pad, pneFiles);
|
|
|
|
FreeNe(pneDirs);
|
|
if (pneFiles)
|
|
FreeNe(pneFiles);
|
|
|
|
return fOk;
|
|
}
|
|
|
|
|
|
private F
|
|
FRecDir(
|
|
AD *pad,
|
|
NE *pneFiles) /* Files (+ dirs?) in this directory */
|
|
{
|
|
if (pad->pecmd->gl&fglFiles || pneFiles != 0) {
|
|
F fOk = FDoNe(pad, pneFiles);
|
|
return fOk;
|
|
}
|
|
else if (!pad->szPattern) {
|
|
/* Call the command for this directory, with no file list.
|
|
* First we call FDoPending, because there may be files
|
|
* pending, i.e. "addfile -r file dir"; file is pending, so
|
|
* we call it; then call dir recursively. FDoPending does
|
|
* nothing if nothing is pending.
|
|
*/
|
|
return FDoPending(pad) && FCallCmd(pad);
|
|
}
|
|
|
|
/* else !fglFiles && pad->szPattern && pneFiles == 0 (no matches) */
|
|
}
|
|
|
|
|
|
/* Process all files in the list */
|
|
private F
|
|
FDoNe(
|
|
AD *pad,
|
|
NE *pneList)
|
|
{
|
|
NE *pne;
|
|
|
|
ForEachNe(pne, pneList) {
|
|
if (!FDoFile(pad, pne, &pad->tdMin, FALSE))
|
|
return fFalse;
|
|
}
|
|
return fTrue;
|
|
}
|
|
|
|
|
|
/* Call the command's dir function */
|
|
private F
|
|
FCallCmd(
|
|
AD *pad)
|
|
{
|
|
PTH pth[cchPthMax];
|
|
|
|
// compute maximum path length allowance
|
|
// magic number 18 = 12 + 5 + 1
|
|
// 12 = 8.3 file name
|
|
// 5 = "diff/" diff is larger than src or etc
|
|
// 1 = "/" (after project name)
|
|
|
|
if ((strlen(pad->pthSRoot)+
|
|
strlen(pad->pthSSubDir)+
|
|
strlen(pad->nmProj) + 18) >= cchPthMax-1 ||
|
|
(strlen(pad->pthURoot)+strlen(pad->pthUSubDir)+12)>=cchPthMax-1) {
|
|
|
|
Error("<SLM PATH-LIMIT EXCEEDED! STOP.>\n%&C\n", pad);
|
|
return fFalse;
|
|
}
|
|
|
|
if (!FPthExists(PthForStatus(pad, pth), fFalse)) {
|
|
UINT ulSleepTime = 10;
|
|
|
|
Warn("%&/C is not a directory of SLM installation %&/S, project %&P\n", pad, pad, pad);
|
|
SleepCsecs(ulSleepTime);
|
|
if (!FPthExists(PthForStatus(pad, pth), fFalse)) {
|
|
Warn("%&/C is not a directory of SLM installation %&/S, project %&P - skipping\n", pad, pad, pad);
|
|
return fTrue;
|
|
}
|
|
PrErr("Proceeding\n");
|
|
}
|
|
|
|
FlushCachedStatus(pad);
|
|
return (*pad->pecmd->pfncFDir)(pad);
|
|
}
|
|
|
|
|
|
/* Process a single file. If it is in a new directory, call the command
|
|
* with the collection of files that we have previously accumulated, then
|
|
* begin accumulating again with the new file.
|
|
*/
|
|
private F
|
|
FDoFile(
|
|
AD *pad,
|
|
NE *pneFile,
|
|
TD *ptd,
|
|
BOOL fWild
|
|
)
|
|
{
|
|
F fOk = fTrue;
|
|
NE *pne;
|
|
static NE **ppneLast = 0;
|
|
|
|
CheckForBreak();
|
|
|
|
if (ppneLast == 0 || PthCmp(pad->pthFiles, pad->pthUSubDir) != 0) {
|
|
fOk = FDoPending(pad);
|
|
InitAppendNe(&ppneLast, &pad->pneFiles);
|
|
|
|
if (!fOk)
|
|
return fFalse;
|
|
|
|
PthCopy(pad->pthFiles, pad->pthUSubDir);
|
|
}
|
|
|
|
/* Append copy of NE to end of pad->pneFiles. */
|
|
pne = PneCopy(pneFile);
|
|
pne->u.tdNe = *ptd;
|
|
pne->fWild = fWild;
|
|
AppendNe(&ppneLast, pne);
|
|
|
|
return fOk;
|
|
}
|
|
|
|
|
|
/* Call the command with those files which have been accumulated by calls
|
|
* to FDoFile.
|
|
*/
|
|
private F
|
|
FDoPending(
|
|
AD *pad)
|
|
{
|
|
F fOk = fTrue;
|
|
|
|
if (pad->pneFiles) {
|
|
PushDir(pad, pad->pthFiles);
|
|
|
|
fOk = FCallCmd(pad);
|
|
FreeNe(pad->pneFiles);
|
|
pad->pneFiles = 0;
|
|
|
|
PopDir(pad);
|
|
}
|
|
return fOk;
|
|
}
|
|
|
|
|
|
static F fMatch = fFalse; /* has this wildcard matched yet? */
|
|
|
|
/* Return fTrue if any files matched since last call. */
|
|
private F
|
|
FMatched()
|
|
{
|
|
F f = fMatch;
|
|
|
|
fMatch = fFalse;
|
|
return f;
|
|
}
|
|
|
|
|
|
/* Call FDoFile() on all pathnames matching the wildcard pathname,
|
|
* or (if flagRecursive), call FRecurseFiles() on all matching directories.
|
|
*
|
|
* Each recursive invocation consumes some number of directories and one
|
|
* wildcard component of the path.
|
|
*/
|
|
private F
|
|
FWild(
|
|
AD *pad,
|
|
char *szToDo, /* Wildcard path remaining to match. */
|
|
TD *ptd) /* Optional time for this file. */
|
|
{
|
|
char *pchSlash; /* Pointer to '/' in szToDo. */
|
|
char *pchPrevSlash; /* The preceding '/' in szToDo. */
|
|
char *szWild; /* Wildcard component to match. */
|
|
NE *pneDirs; /* Names matching wildcard directory. */
|
|
NE *pneFiles; /* Names matching wildcard filename. */
|
|
NE *pne;
|
|
F fOk = fTrue;
|
|
|
|
/* EXAMPLE: "a/b/wild*dir/file"
|
|
* szToDo ---^
|
|
*/
|
|
|
|
/* Search for first wildcard component of szToDo. */
|
|
for (pchPrevSlash = 0, pchSlash = index(szToDo, '/'); pchSlash != 0;
|
|
pchPrevSlash = pchSlash, pchSlash = index(pchSlash + 1, '/')) {
|
|
*pchSlash = 0; /* Temporarily terminate component. */
|
|
if (FWildSz(szToDo)) {
|
|
/* Found a wildcard directory component of szToDo.
|
|
*
|
|
* Search for directories matching the pattern, then
|
|
* recursively call FWild on each, with what remains
|
|
* of szToDo.
|
|
*/
|
|
if (pchPrevSlash) {
|
|
*pchPrevSlash = 0;
|
|
|
|
/* EXAMPLE: "a/b\0wild*dir\0file"
|
|
* szToDo ----^ ^ ^
|
|
* pchPrevSlash--+ +-- pchSlash
|
|
*/
|
|
szWild = pchPrevSlash + 1;
|
|
|
|
PushDir(pad, szToDo);
|
|
}
|
|
else
|
|
szWild = szToDo;
|
|
|
|
/* Search for every matching directory, and recurse. */
|
|
pneDirs = PneMatch(pad, szWild, fLocal, fTrue, fFalse);
|
|
fOk = fTrue;
|
|
ForEachNeWhileF(pne, pneDirs, fOk) {
|
|
ChngDir(pad, SzOfNe(pne));
|
|
fOk &= FWild(pad, pchSlash + 1, ptd);
|
|
ChngDir(pad, "..");
|
|
}
|
|
|
|
/* Clean up. */
|
|
FreeNe(pneDirs);
|
|
if (pchPrevSlash) {
|
|
*pchPrevSlash = '/';
|
|
PopDir(pad);
|
|
}
|
|
*pchSlash = '/';
|
|
return fOk;
|
|
}
|
|
*pchSlash = '/';
|
|
}
|
|
|
|
/* If the preceding loop didn't find anything, then we have a simple
|
|
* pathname or a pathname whose last component is a wildcard.
|
|
* The recursion stops here.
|
|
*/
|
|
|
|
/* Extract directory component(s) of path (if any) and chdir there. */
|
|
pchSlash = rindex(szToDo, '/');
|
|
if (pchSlash) {
|
|
*pchSlash = 0;
|
|
|
|
PushDir(pad, szToDo);
|
|
|
|
szWild = pchSlash + 1;
|
|
}
|
|
else
|
|
szWild = szToDo;
|
|
|
|
/* If the '/' is the last character, (i.e. "status dir/"):
|
|
* * if the command requires filenames (i.e. "out dir/"),
|
|
* match against "*.*"
|
|
* * if the command doesn't require filenames (i.e. "log dir/",
|
|
* directly call the command function with NO filenames.
|
|
*/
|
|
if (*szWild == 0) {
|
|
if (pad->pecmd->gl&fglFiles)
|
|
szWild = szStar;
|
|
else {
|
|
fMatch = fTrue;
|
|
return FDoPending(pad) && FCallCmd(pad);
|
|
}
|
|
}
|
|
|
|
if (!strcmp(szWild, "*"))
|
|
szWild = szStar;
|
|
|
|
pneFiles = PneMatch(pad, szWild, fLocal, fTrue, fTrue);
|
|
|
|
/* If the file doesn't necessarily exist (i.e. a log argument, etc.),
|
|
* and if indeed it turns out not to exist, we add it anyway, so
|
|
* long as it doesn't contain any wildcards.
|
|
*/
|
|
if (pneFiles == 0 && pad->pecmd->gl&fglNoExist &&
|
|
!FWildSz(szWild))
|
|
pneFiles = PneNewNm((NM far *)szWild, strlen(szWild), faNormal);
|
|
|
|
fMatch |= (pneFiles != 0);
|
|
|
|
/* Process each filename. If it is a directory and the -r flag was
|
|
* specified (i.e. in -r dir/sub*), then recurse from that directory.
|
|
* Otherwise FDoFile it.
|
|
*/
|
|
fOk = fTrue;
|
|
ForEachNeWhileF(pne, pneFiles, fOk) {
|
|
if (!FDirNe(pne)) {
|
|
fOk = FDoFile(pad, pne, ptd, TRUE);
|
|
continue;
|
|
}
|
|
|
|
/* Issue an error if the user directly specified an unexpected
|
|
* directory name (not the result of a wildcard match).
|
|
*/
|
|
if (!FWildSz(szWild) && !(pad->pecmd->gl&fglDirsToo) &&
|
|
!(pad->flags&flagRecursive)) {
|
|
AssertF(FDirNe(pne));
|
|
Warn("ignoring directory %s\n", SzOfNe(pneFiles));
|
|
continue;
|
|
}
|
|
|
|
if ((pad->pecmd->gl&(fglTopDown|fglDirsToo)) == (fglTopDown|fglDirsToo))
|
|
fOk = FDoFile(pad, pne, ptd, TRUE);
|
|
|
|
if (fOk && pad->flags&flagRecursive) {
|
|
ChngDir(pad, SzOfNe(pne));
|
|
fOk = FRecurseFiles(pad);
|
|
ChngDir(pad, "..");
|
|
}
|
|
|
|
if (fOk && (pad->pecmd->gl&(fglTopDown|fglDirsToo)) == fglDirsToo)
|
|
fOk = FDoFile(pad, pne, ptd, TRUE);
|
|
}
|
|
|
|
/* Clean up. */
|
|
FreeNe(pneFiles);
|
|
if (pchSlash) {
|
|
*pchSlash = '/';
|
|
PopDir(pad);
|
|
}
|
|
return fOk;
|
|
}
|
|
|
|
|
|
/* Return a list of all files in current directory which match this pattern
|
|
* and are files or dirs (or either)
|
|
*/
|
|
private NE *
|
|
PneMatch(
|
|
AD *pad,
|
|
char *szPattern,
|
|
F fLocal,
|
|
F fDirs,
|
|
F fFiles)
|
|
{
|
|
NE *pneList = 0;
|
|
NE **ppneLast;
|
|
|
|
InitAppendNe(&ppneLast, &pneList);
|
|
|
|
if (fLocal) {
|
|
DE de;
|
|
FA fa;
|
|
PTH pth[cchPthMax]; /* current directory */
|
|
char sz[cchFileMax+1]; /* one match */
|
|
|
|
PthForUDir(pad, pth);
|
|
OpenPatDir(&de, pth, szPattern,
|
|
(FA)(fDirs ? (fFiles ? (faDir|faFiles) : faDir)
|
|
: faFiles));
|
|
|
|
while (FGetDirSz(&de, sz, &fa)) {
|
|
/* Lowercase, FGetDirSz always returns all uppercase. */
|
|
LowerLsz((char far *)sz);
|
|
AppendNe(&ppneLast, PneNewNm((NM far *)sz, strlen(sz), fa));
|
|
}
|
|
|
|
CloseDir(&de);
|
|
}
|
|
else {
|
|
FI far *pfi;
|
|
FI far *pfiMac;
|
|
char sz[cchFileMax+1];
|
|
|
|
//
|
|
// If using IED Caching (log, status and ssync) and not in root
|
|
// directory (so lckNil works) try to load status file
|
|
// quickly using IED cache, which justed reads the SH, FI, 1 ED and
|
|
// 1 FS array. If that does not succeed go the slow way.
|
|
//
|
|
if (pad->pneArgs == 0 &&
|
|
pad->flags&flagCacheIed &&
|
|
!FTopUDir(pad) &&
|
|
(FlushCachedStatus(pad), FLoadStatus(pad, lckNil, flsNone))) {
|
|
//
|
|
// Status file is loaded for both PneMatch and log/status/ssync routines.
|
|
//
|
|
pad->fStatusAlreadyLoaded = fTrue;
|
|
}
|
|
else
|
|
if (!FGetStatus(pad))
|
|
return (NE *)0;
|
|
|
|
for (pfi = pad->rgfi, pfiMac = pfi + pad->psh->ifiMac;
|
|
pfi < pfiMac; pfi++) {
|
|
F fDir = pfi->fk == fkDir;
|
|
|
|
SzCopyNm(sz, pfi->nmFile, cchFileMax);
|
|
/* Use the file if the name matches and it's the type
|
|
* of file we're looking for, unless its a deleted dir.
|
|
*/
|
|
if (FMatch(sz, szPattern) &&
|
|
(fDir && fDirs || !fDir && fFiles) &&
|
|
((pad->pecmd->cmd == cmdLog && pad->flags&flagLogDelDirToo) ||
|
|
!(pfi->fDeleted && fDir)))
|
|
AppendNe(&ppneLast,
|
|
PneNewNm(pfi->nmFile, cchFileMax,
|
|
(FA)(fDir ? faDir
|
|
: faNormal)));
|
|
}
|
|
}
|
|
|
|
return pneList;
|
|
}
|
|
|
|
/*
|
|
* Status file caching, keeps the current status file loaded as long as
|
|
* possible.
|
|
*/
|
|
|
|
static PTH pthTag[cchPthMax] = ""; /* empty string - invalid tag */
|
|
|
|
/* If the status file isn't already loaded into the cache, load it readonly.
|
|
* Return fTrue if the status file is now loaded.
|
|
*/
|
|
private F
|
|
FGetStatus(
|
|
AD *pad)
|
|
{
|
|
PTH pth[cchPthMax];
|
|
|
|
if (!FPthExists(PthForStatus(pad, pth), fFalse))
|
|
return fFalse;
|
|
|
|
/* Return if the status file for this directory is still loaded. */
|
|
if (PthCmp(pthTag, pad->pthUSubDir) == 0)
|
|
return fTrue;
|
|
|
|
/* Flush the current status file. */
|
|
FlushCachedStatus(pad);
|
|
|
|
/* Open the status file for reading just the FI information. Set the
|
|
* cache tag if successful.
|
|
*/
|
|
if (FLoadStatus(pad, lckNil, flsJustFi)) {
|
|
PthCopy(pthTag, pad->pthUSubDir);
|
|
return fTrue;
|
|
}
|
|
|
|
/* Status file wasn't cached and couldn't be loaded. */
|
|
return fFalse;
|
|
}
|
|
|
|
|
|
/* Flush the cached status file (if any) */
|
|
private void
|
|
FlushCachedStatus(
|
|
AD *pad)
|
|
{
|
|
if (PthCmp(pthTag, "") != 0) {
|
|
FlushStatus(pad);
|
|
PthCopy(pthTag, "");
|
|
}
|
|
}
|