DIRECTORY
BasicTime USING [GMT, nullGMT],
FS USING [Error],
FSBackdoor USING [noVersion, Version],
FSName USING [BangVersionFile],
FSRemoteFileBackdoor USING [EnumerateForInfoProc, EnumerateForNamesProc, InfoProc],
Rope USING [Compare, Concat, Equal, IsEmpty, IsPrefix, Match, ROPE, Substr],
SunNFS USING [DirOpRes],
SunNFSRemoteFile USING [caseFileNamePrefix, caseFileNamePrefixLen, CreateNameReader, CreateNameWriter, DecodeVersionFromNFSName, EachDirEntryProc, EnumerateDirectory, GetRemoteDirChild, GetRemoteDirRoot, GMTFromSunTime, LookupThruSymLinks, NameReaderObject, NameWriterObject, ReadBaseComponent, ReadDirComponent, RemoteDirObject, ReportFSError, ReportNFSError, RetractComponent, RopeFromNameWriter, ServerHandle, UnPinRemoteDir, UnPinRemoteDirPath, VersionInfo, WriteBaseComponent, WriteDirComponent]
;
SunNFSRemoteEnumImpl:
CEDAR
MONITOR
IMPORTS FS, FSName, Rope, SunNFSRemoteFile
EXPORTS SunNFSRemoteFile
~ {
OPEN SunNFSRemoteFile;
Copied Types
GMT: TYPE ~ BasicTime.GMT;
NameReader: TYPE ~ REF NameReaderObject;
NameWriter: TYPE ~ REF NameWriterObject;
RemoteDirHandle: TYPE ~ REF RemoteDirObject;
ROPE: TYPE ~ Rope.ROPE;
Version: TYPE ~ FSBackdoor.Version;
Parameters
initialInfoSetSize: CARDINAL ← 16;
Sets of Info
InfoSet: TYPE ~ REF InfoSetObject;
InfoSetObject:
TYPE ~
RECORD [
size: CARDINAL,
info: SEQUENCE maxSize: CARDINAL OF Info
];
maxMaxSize:
CARDINAL ← (
CARDINAL.
LAST -
SIZE[InfoSetObject[0]] - 10) /
SIZE[Info];
-- the '10' is just for luck
InfoKind: TYPE ~ { caseHint, file, subdirectory };
Info:
TYPE ~
RECORD [
name: ROPE, -- not including version part
kind: InfoKind,
versionNum: CARDINAL,
created: GMT,
bytes: INT
];
AddInfoToSet:
PROC [set: InfoSet, info: Info]
RETURNS [newSet: InfoSet] ~ {
IF set = NIL THEN { set ← NEW[InfoSetObject[initialInfoSetSize]]; set.size ← 0 };
IF set.size < set.maxSize
THEN { newSet ← set }
ELSE {
newSize: CARDINAL ~ MIN[2*set.maxSize, maxMaxSize];
newSet ← NEW[InfoSetObject[newSize]];
FOR i:
CARDINAL
IN [0 .. set.size)
DO
newSet.info[i] ← set.info[i];
ENDLOOP;
newSet.size ← set.size;
};
newSet.info[newSet.size] ← info;
newSet.size ← newSet.size + 1;
};
SortInfoSet:
PROC [set: InfoSet] ~ {
Heapsort using <nameWithoutVersion, versionNum> as key ...
left, right, p, c, cRight: CARDINAL;
temp: Info;
Less:
PROC [i, j:
CARDINAL]
RETURNS [
BOOL] ~
INLINE {
RETURN [
SELECT Rope.Compare[set.info[i].name, set.info[j].name,
FALSE]
FROM
less => TRUE,
greater => FALSE,
ENDCASE => (set.info[i].versionNum < set.info[j].versionNum)
];
};
Swap:
PROC [i, j:
CARDINAL] ~
INLINE {
temp ← set.info[i];
set.info[i] ← set.info[j];
set.info[j] ← temp;
};
IF set.size < 2 THEN RETURN;
left ← set.size/2; right ← set.size - 1;
DO
SELECT
TRUE
FROM
(left > 0) => { left ← left - 1 };
(right > 0) => { Swap[0, right]; right ← right - 1 };
ENDCASE => EXIT;
FOR p ← left, c
DO
IF (c ← 2*p+1) > right THEN EXIT;
IF ((cRight ← c+1) <= right) AND Less[c, cRight] THEN c ← cRight;
IF Less[c, p] THEN EXIT;
Swap[p, c];
ENDLOOP;
ENDLOOP;
};
Exported to SunNFSRemoteFile
EnumerationAborted: PRIVATE ERROR ~ CODE;
SunNFSEnumerateForInfo:
PUBLIC FSRemoteFileBackdoor.EnumerateForInfoProc
-- [h: ServerHandle, pattern: ROPE, proc: InfoProc] -- ~ {
dH: RemoteDirHandle;
nR: NameReader;
nWPath, nWSuffix: NameWriter;
patternPrefix, patternUpToVersionPart: ROPE;
isPattern: BOOL;
versionFromPattern: Version;
vI: VersionInfo;
callDepth: CARDINAL ← 0;
EnumerateInner:
PROC ~ {
set: InfoSet ← NIL;
EachName: EachDirEntryProc
-- [entryName: ROPE] RETURNS [continue: BOOL ← TRUE] -- ~ {
Select names to be included, add them to set.
dirOpRes: SunNFS.DirOpRes;
IF Rope.Equal[entryName, "."] OR Rope.Equal[entryName, ".."] THEN RETURN [TRUE];
IF Rope.IsPrefix[caseFileNamePrefix, entryName,
FALSE]
THEN {
casedComponent, nameSuffix: ROPE;
casedComponent ← Rope.Substr[entryName, caseFileNamePrefixLen];
WriteDirComponent[nWSuffix, casedComponent]; -- Ugh! Logically, this ought to be WriteBaseComponent[...], but I don't want leading '> characters for the pattern match!
nameSuffix ← RopeFromNameWriter[nWSuffix];
IF Rope.Match[patternUpToVersionPart, nameSuffix,
FALSE]
THEN {
set ← AddInfoToSet[set, [name~casedComponent, kind~caseHint, versionNum~0, created~BasicTime.nullGMT, bytes~0]];
};
RetractComponent[nWSuffix];
RETURN[TRUE];
};
dirOpRes ← LookupThruSymLinks[h, dH, entryName];
SELECT dirOpRes.status
FROM
ok => NULL;
perm, acces, noent => RETURN [TRUE];
ENDCASE => ReportNFSError[dirOpRes.status, h];
SELECT dirOpRes.attributes.type
FROM
reg => {
nameWithoutVersion, nameSuffix: ROPE;
thisVersion: Version;
caseOK: BOOL;
[nameWithoutVersion, thisVersion, caseOK] ← DecodeVersionFromNFSName[entryName, TRUE];
IF (thisVersion = FSBackdoor.noVersion) OR (NOT caseOK) THEN RETURN[TRUE];
IF (vI # bangNumber)
OR (thisVersion = versionFromPattern)
THEN {
WriteDirComponent[nWSuffix, nameWithoutVersion]; -- Ugh! Logically, this ought to be WriteBaseComponent[...], but I don't want leading '> characters for the pattern match!
nameSuffix ← RopeFromNameWriter[nWSuffix];
IF Rope.Match[patternUpToVersionPart, nameSuffix,
FALSE]
THEN {
set ← AddInfoToSet[set, [name~nameWithoutVersion, kind~file, versionNum~thisVersion, created~GMTFromSunTime[dirOpRes.attributes.mtime], bytes~dirOpRes.attributes.size]];
};
RetractComponent[nWSuffix];
};
};
dir => {
addIt: BOOL;
thisVersion: Version;
caseOK: BOOL;
[, thisVersion, caseOK] ← DecodeVersionFromNFSName[entryName, TRUE];
SELECT
TRUE
FROM
((thisVersion # FSBackdoor.noVersion) OR (NOT caseOK)) => addIt ← FALSE;
(callDepth > 0) => addIt ← TRUE;
(NOT isPattern) => addIt ← FALSE;
ENDCASE => addIt ← Rope.IsPrefix[patternPrefix, entryName, FALSE];
IF addIt
THEN {
set ← AddInfoToSet[set, [name~entryName, kind~subdirectory, versionNum~CARDINAL.LAST, created~BasicTime.nullGMT, bytes~0]];
};
};
ENDCASE;
};
EnumerateDirectory[h, dH, EachName,
FALSE
!
FS.Error => {
IF error.code = $accessDenied THEN { set ← NIL; CONTINUE } ELSE REJECT
}
];
IF set #
NIL
THEN {
i: CARDINAL ← 0;
latestCaseHint, thisName, casedName: ROPE ← NIL;
SortInfoSet[set];
WHILE i < set.size
DO
thisName ← set.info[i].name;
casedName ← thisName;
SELECT set.info[i].kind
FROM
caseHint => {
latestCaseHint ← thisName;
i ← i + 1;
};
subdirectory => {
callDepth ← callDepth + 1;
IF latestCaseHint #
NIL
THEN {
SELECT Rope.Compare[latestCaseHint, thisName,
FALSE]
FROM
less => latestCaseHint ← NIL;
equal => casedName ← latestCaseHint;
ENDCASE;
};
WriteDirComponent[nWPath, casedName];
WriteDirComponent[nWSuffix, casedName];
[dHChild~dH] ← GetRemoteDirChild[h, dH, thisName, FALSE];
EnumerateInner[];
dH ← UnPinRemoteDir[dH];
RetractComponent[nWSuffix];
RetractComponent[nWPath];
callDepth ← callDepth - 1;
i ← i + 1;
};
file => {
it: CARDINAL;
temp: ROPE;
IF latestCaseHint #
NIL
THEN {
SELECT Rope.Compare[latestCaseHint, thisName,
FALSE]
FROM
less => latestCaseHint ← NIL;
equal => casedName ← latestCaseHint;
ENDCASE;
};
SELECT vI
FROM
bangStar, bangNumber => {
If vI = number the selection by version number was done before sorting.
it ← i; i ← i + 1;
};
bangH, bangL => {
start: CARDINAL ← i;
i ← i + 1;
WHILE (i < set.size)
AND (set.info[i].kind = file)
AND Rope.Equal[thisName, set.info[i].name,
FALSE]
DO
i ← i + 1;
ENDLOOP;
it ← (IF vI = bangH THEN i-1 ELSE start);
};
ENDCASE;
temp ← FSName.BangVersionFile[casedName, [set.info[it].versionNum]];
WriteBaseComponent[nWPath, temp];
temp ← Rope.Concat["<", RopeFromNameWriter[nWPath]];
IF
NOT proc[temp, set.info[it].bytes, set.info[it].created]
THEN ERROR EnumerationAborted[];
RetractComponent[nWPath];
};
ENDCASE;
ENDLOOP;
};
};
Main line code of EnumerateForInfo ...
{
ENABLE {
UNWIND => {
IF dH # NIL THEN UnPinRemoteDirPath[dH];
};
EnumerationAborted => {
CONTINUE;
};
};
nR ← CreateNameReader[pattern];
nWPath ← CreateNameWriter[];
dH ← FollowDirPathForEnumeration[h, nR, nWPath];
[base~patternUpToVersionPart, vI~vI, version~versionFromPattern, isPattern~isPattern, patternHead~patternPrefix] ← ReadBaseComponent[nR~nR, case~FALSE, stripVersion~TRUE];
SELECT vI
FROM
missing => ReportFSError[illegalName, h, patternUpToVersionPart, "Enumerate, version part required"];
ENDCASE;
nWSuffix ← CreateNameWriter[];
EnumerateInner[];
};
IF dH # NIL THEN UnPinRemoteDirPath[dH];
};
FollowDirPathForEnumeration:
PROC [sH: ServerHandle, nR: NameReader, nW: NameWriter]
RETURNS [dH: RemoteDirHandle] ~ {
ENABLE
UNWIND => {
IF dH # NIL THEN UnPinRemoteDirPath[dH];
};
caseFileName: ROPE;
component, casedComponent: ROPE;
EachDirEntry: EachDirEntryProc
-- [entryName: ROPE] RETURNS [continue: BOOL ← TRUE] -- ~ {
IF Rope.Equal[entryName, caseFileName,
FALSE]
THEN {
casedComponent ← Rope.Substr[entryName, caseFileNamePrefixLen];
continue ← FALSE;
};
};
dH ← GetRemoteDirRoot[sH];
DO
dHParent: RemoteDirHandle ← dH;
component ← casedComponent ← ReadDirComponent[nR, FALSE];
IF Rope.IsEmpty[component] THEN EXIT;
[dHChild~dH] ← GetRemoteDirChild[sH~sH, dH~dH, childName~component, create~FALSE];
caseFileName ← Rope.Concat[caseFileNamePrefix, component];
EnumerateDirectory[sH, dHParent, EachDirEntry];
WriteDirComponent[nW, casedComponent];
ENDLOOP;
};
SunNFSEnumerateForNames:
PUBLIC FSRemoteFileBackdoor.EnumerateForNamesProc
-- [h: ServerHandle, pattern: ROPE, proc: NameProc] -- ~ {
This looks inefficient, but for NFS I have to Stat every file to find out whether it's a directory anyway, so ...
EachFile: FSRemoteFileBackdoor.InfoProc
-- [file: ROPE, ... ] RETURNS [continue: BOOL] -- ~ {
RETURN [proc[file]];
};
SunNFSEnumerateForInfo[h, pattern, EachFile];
};
}...