Copied Types
GMT: TYPE ~ BasicTime.GMT;
ROPE: TYPE ~ Rope.ROPE;
Mode: TYPE ~ UnixTypes.Mode;
Stat: TYPE ~ UnixTypes.Stat;
RES: TYPE ~ UnixTypes.RES;
PATH: TYPE ~ PFSNames.PATH;
CString: TYPE ~ CStrings.CString;
Version: TYPE ~ PFSNames.Version;
Parameters
initialRemoteDirTTL: CARDINAL ¬ 1200;
initialCreateModeTTL: CARDINAL ¬ 1200;
defaultCreateMode: Mode ¬ [owner~[true, true, false], group~[true, false, false]];
fsModeFileName: ROPE ¬ ".~fsmode~";
dirSearchBlocksize: CARD ¬ 8 * 1024;
initialDirContentTTL: CARDINAL ¬ 900;
maxDirEntryHeight: CARDINAL ~ 16;
tooBig: INT ¬ LAST[INT] - 1;
FHandle: TYPE = ROPE;
File mode stuff
FixModeForRegularFile:
PUBLIC
PROC [mode: Mode]
RETURNS [Mode] ~ {
mode.fmt ¬ reg;
mode.owner.write ¬ false;
mode.group.write ¬ false;
mode.others.write ¬ false;
RETURN [mode];
};
FixModeForDirectory:
PUBLIC
PROC [mode: Mode]
RETURNS [Mode] ~ {
mode.fmt ¬ dir;
mode.sgid ¬ true;
mode.owner ¬ [true, true, true];
IF mode.group.write=true THEN mode.group.read ¬ true;
IF mode.group.read=true THEN mode.group.exec ¬ true;
IF mode.others.write=true THEN mode.others.read ¬ true;
IF mode.others.read=true THEN mode.others.exec ¬ true;
RETURN [mode];
};
GetModeAccessBits:
PUBLIC
PROC [mode: Mode]
RETURNS [Mode] ~ {
mode.fmt ¬ LOOPHOLE[0]; -- invalid format
RETURN[mode];
};
UpdateModeAccessBits:
PUBLIC
PROC [mode: Mode, newAccessBits: Mode]
RETURNS [Mode] ~ {
newAccessBits.fmt ¬ mode.fmt;
RETURN[newAccessBits];
};
Cache of directory handles
FindDirChild:
ENTRY
PROC [dH: DirHandle, childName:
ROPE]
RETURNS [dHChild: DirHandle] ~ {
ENABLE UNWIND => NULL;
FOR dHChild ¬ dH.child, dHChild.sibling
WHILE dHChild #
NIL
DO
IF Rope.Equal[dHChild.nameComponent, childName,
TRUE]
THEN {
dHChild.useCount ¬ dHChild.useCount.SUCC;
RETURN;
};
ENDLOOP;
};
InsertDirChild:
PUBLIC
ENTRY
PROC [dH: DirHandle, childName:
ROPE, fHandle: FHandle]
RETURNS [dHChild: DirHandle] ~ {
ENABLE UNWIND => NULL;
dHChild ¬ NEW[DirObject ¬ [parent~dH, sibling~dH.child, nameComponent~childName, fHandle~fHandle, ownerUID~0, ownerGID~0, createMode~[], createModeTTL~0, contentMTime~0, contentTTL~0, contentKnownStale~TRUE, contentLocked~FALSE, useCount~1, ttl~initialRemoteDirTTL]];
dH.child ¬ dHChild;
};
PinDirPath:
PUBLIC
ENTRY
PROC [dH: DirHandle] ~ {
ENABLE UNWIND => NULL;
WHILE dH #
NIL
DO
dH.useCount ¬ dH.useCount.SUCC;
dH ¬ dH.parent;
ENDLOOP;
};
UnPinDir:
PUBLIC
ENTRY
PROC [dH: DirHandle]
RETURNS [dHParent: DirHandle] ~ {
ENABLE UNWIND => NULL;
dH.useCount ¬ dH.useCount.PRED;
dH.ttl ¬ initialRemoteDirTTL;
RETURN[dH.parent];
};
UnPinDirPath:
PUBLIC
ENTRY
PROC [dH: DirHandle] ~ {
ENABLE UNWIND => NULL;
WHILE dH #
NIL
DO
dH.useCount ¬ dH.useCount.PRED;
dH.ttl ¬ initialRemoteDirTTL;
dH ¬ dH.parent;
ENDLOOP;
};
dirRoot: DirHandle ¬ NEW[DirObject ¬ [parent~NIL, sibling~NIL, nameComponent~NIL, fHandle~"/", ownerUID~0, ownerGID~0, createMode~[], createModeTTL~0, contentMTime~0, contentTTL~0, contentKnownStale~TRUE, contentLocked~FALSE, useCount~1, ttl~initialRemoteDirTTL]];
GetDirRoot:
PUBLIC
ENTRY
PROC []
RETURNS [dH: DirHandle] ~ {
ENABLE UNWIND => NULL;
dH ¬ dirRoot;
dH.useCount ¬ dH.useCount.SUCC;
};
VerifySubdirectory:
PROC [dH: DirHandle, name:
ROPE]
RETURNS [fH: FHandle] ~ {
Return fHandle for subdirectory if it exists; return NIL if subdirectory doesn't exist, else raise appropriate PFS.Error.
reply: DirOpRes;
reply ¬ DirLookup[dH, name];
SELECT reply.status
FROM
ok => {
IF reply.attributes.mode.fmt # dir
AND reply.attributes.mode.fmt # lnk
THEN
PFSBackdoor.ProduceError[code: $unknownFile, explanation: "Child is not a directory", info: NIL];
fH ¬ reply.file;
};
ENDCASE => {
PFSBackdoor.ProduceError[code: $unknownFile, explanation: "Child is not a directory", info: NIL];
};
};
CreateSubdirectory:
PUBLIC
PROC [dH: DirHandle, name:
ROPE, desiredMode: Mode]
RETURNS [fH: FHandle] ~ {
Create subdirectory in the directory specified by dH.
reply: VUXFiles.DirOpRes;
fullName: ROPE ~ Rope.Cat[dH.fHandle, IF dH.fHandle.Fetch[dH.fHandle.Length[]-1]#'/ THEN "/" ELSE NIL, name];
dirMode: Mode ~ FixModeForDirectory[desiredMode];
res: RES;
{
ENABLE
UNWIND =>
NULL;
uName: CString ~ UXStrings.Create[fullName];
res ¬ UnixSysCalls.MkDir[uName, dirMode];
SELECT res
FROM
success => {
fH ¬ fullName;
};
ENDCASE => {
errno: UnixErrno.Errno ~ UnixErrno.GetErrno[];
UnixFSPrivate.ReportFailure[errno, Rope.Concat["VUXDirImpl.CreateSubdirectory: Couldn't create directory ", fullName]];
};
To force the mode to what is required, in the presence of unix umasks, requires an explicit chmod.
IF UnixSysCalls.ChMod[uName, dirMode] # success
THEN {
errno: UnixErrno.Errno ~ UnixErrno.GetErrno[];
UnixFSPrivate.ReportFailure[errno, Rope.Concat["VUXDirImpl.CreateSubdirectory: ChMod failed for ", fullName]];
};
dH.contentKnownStale ¬ TRUE;
};
};
Pugh Tables for Directory Contents
DirEntries: TYPE ~ VUXFiles.DirEntries;
DirEntriesObject:
TYPE ~ VUXFiles.DirEntriesObject;
DirNode: TYPE ~ VUXFiles.DirNode;
DirNodeObject:
TYPE ~ VUXFiles.DirNodeObject;
DirEntry:
TYPE ~ VUXFiles.DirEntry;
AllocateDirNode:
PROC [maxHeight:
CARDINAL]
RETURNS [node: DirNode] ~ {
bits: INT ¬ Random.ChooseInt[NIL, 0, INT[INTEGER.LAST]];
height: CARDINAL ¬ 1;
WHILE (height < maxHeight)
AND ((bits
MOD 2) = 0)
DO
height ¬ height + 1;
bits ¬ bits / 2;
ENDLOOP;
node ¬ NEW[DirNodeObject[height]];
};
IsCaseVersion:
PROC [v: Version]
RETURNS [
BOOL] ~
INLINE {
RETURN[ v.versionKind=numeric AND v.version=0 ];
};
Less:
PROC [n1, n2: DirEntry]
RETURNS [
BOOL] ~ {
RETURN [
SELECT Rope.Compare[n1.nameWithoutVersion, n2.nameWithoutVersion,
FALSE]
FROM
less => TRUE,
greater => FALSE,
ENDCASE =>
SELECT
TRUE
FROM
IsCaseVersion[n1.version] AND NOT IsCaseVersion[n2.version] => TRUE,
IsCaseVersion[n2.version] => FALSE,
n1.version.versionKind=none AND n2.version.versionKind#none=> TRUE,
n2.version.versionKind=none => FALSE,
ENDCASE => (n1.version.version < n2.version.version)
];
};
InsertDirEntry: PROC [newEntry: DirEntry, dirEntries: DirEntries] RETURNS [newNode: DirNode] ~ {
This implementation must be careful about the order of operations so concurrent enumerators see a legal data structure.
leftNode: DirNode;
path: ARRAY [0 .. maxDirEntryHeight) OF DirNode;
leftNode ¬ dirEntries.leftNode;
newNode ¬ AllocateDirNode[1+leftNode.height];
newNode.entry ¬ newEntry;
IF newNode.height > leftNode.height
THEN {
temp: DirNode ~ NEW[DirNodeObject[newNode.height]];
FOR i:
CARDINAL
IN [0 .. leftNode.height)
DO
temp.link[i] ¬ leftNode.link[i];
ENDLOOP;
leftNode ¬ dirEntries.leftNode ¬ temp;
};
FOR level:
CARDINAL
DECREASING
IN [0 .. dirEntries.leftNode.height)
DO
rightNode: DirNode ¬ leftNode.link[level];
WHILE (rightNode #
NIL)
AND Less[rightNode.entry, newEntry]
DO
leftNode ¬ rightNode;
rightNode ¬ leftNode.link[level];
ENDLOOP;
IF level < newNode.height THEN path[level] ¬ leftNode;
ENDLOOP;
FOR level:
CARDINAL
IN [0 .. newNode.height)
DO
notice that lower levels are filled before upper levels so a concurrent enumerator will not have trouble
leftNode ¬ path[level];
newNode.link[level] ¬ leftNode.link[level];
leftNode.link[level] ¬ newNode;
ENDLOOP;
};
CNameFromDirEntP:
PROC [dp: UnixDirectory.DirEntPtr]
RETURNS [CString] ~
TRUSTED
INLINE {
RETURN[LOOPHOLE[@(dp.name)]];
};
NullTerminatedFlatRopeFromCString:
PROC [s: CString]
RETURNS [Rope.Text] ~
TRUSTED {
len: INT ¬ 0;
newText: Rope.Text;
newTextChar: POINTER TO Basics.RawChars;
IF s = NIL THEN RETURN [NIL];
WHILE s[len]#'\000 DO len ¬ len + 1 ENDLOOP;
IF len = 0 THEN RETURN [NIL];
newText ¬ Rope.NewText[len+1];
newTextChar ¬ LOOPHOLE[RefText.BaseFromTextRope[newText]];
FOR i:
NAT
IN [0..len]
DO
newTextChar[i] ¬ s[i];
ENDLOOP;
newText.length ¬ len;
RETURN [newText];
};
InsertOrUpdateDirEntry:
PROC [dp: UnixDirectory.DirEntPtr, generation:
INT, prevNode: DirNode, dirEntries: DirEntries]
RETURNS [currNode: DirNode] ~ {
finger: DirNode ¬ IF prevNode # NIL THEN prevNode.nextPhysical ELSE NIL;
stringName: CString ¬ CNameFromDirEntP[dp];
IF finger#
NIL
THEN
TRUSTED {
entryName: REF TEXT ~ LOOPHOLE [RefText.TrustTextRopeAsText[finger.entry.unixName]];
IF strcmp[stringName, UXStrings.ViewRefText[entryName]]=0
THEN {
finger.entry.generation ¬ generation;
RETURN[finger];
};
};
{
unixName: Rope.Text ~ NullTerminatedFlatRopeFromCString[stringName];
nameWithoutVersion: Rope.ROPE;
version: VUXFiles.Version;
newEntry: DirEntry;
[nameWithoutVersion, version] ¬ SplitUName[unixName];
newEntry ¬ [nameWithoutVersion, version, TRUE, generation, unixName];
finger ¬ FindFirstNode[nameWithoutVersion, dirEntries];
Assert: NOT Less[finger.entry, newEntry];
WHILE (finger#
NIL)
AND
NOT Less[newEntry, finger.entry]
DO
-- Less is case insensitive...
but for the next comparison we require an exact cased match
IF Rope.Equal[nameWithoutVersion, finger.entry.nameWithoutVersion,
TRUE]
AND version = finger.entry.version
THEN {
finger.entry.generation ¬ newEntry.generation;
IF prevNode#NIL THEN prevNode.nextPhysical ¬ finger;
RETURN[finger];
};
finger ¬ finger.link[0];
ENDLOOP;
currNode ¬ InsertDirEntry[newEntry, dirEntries];
IF prevNode#
NIL
THEN {
currNode.nextPhysical ¬ prevNode.nextPhysical; -- best guess; confirmed on next iteration
prevNode.nextPhysical ¬ currNode; -- correct
};
};
};
FindFirstNode:
PROC [nameWithoutVersion:
ROPE, dirEntries: DirEntries]
RETURNS [firstNode: DirNode] ~ {
target: DirEntry ~ [ nameWithoutVersion, [numeric, 0], FALSE, 0];
firstNode ¬ dirEntries.leftNode;
FOR level:
CARDINAL
DECREASING
IN [0 .. dirEntries.leftNode.height)
DO
rightNode: DirNode ¬ firstNode.link[level];
WHILE (rightNode #
NIL)
AND Less[rightNode.entry, target]
DO
firstNode ¬ rightNode;
rightNode ¬ firstNode.link[level];
ENDLOOP;
ENDLOOP;
RETURN[firstNode.link[0]];
};
NewDirEntries:
PROC
RETURNS [dirEntries: DirEntries] ~ {
leftNode: DirNode ¬ NEW[DirNodeObject[1]];
dirEntries ¬ NEW[DirEntriesObject ¬ [leftNode~leftNode]];
};
RefreshDirContent:
PROC [dH: DirHandle, staleOK:
BOOL]
RETURNS [content: DirEntries] ~ {
Read directory content from server if it has changed since oldMTime.
mTime, oldMTime: CARD ¬ 0;
knownStale: BOOL;
CheckOutContent:
ENTRY
PROC
RETURNS [locked:
BOOL] ~ {
ENABLE UNWIND => NULL;
WHILE dH.contentLocked DO WAIT dH.contentAvailable ENDLOOP;
content ¬ dH.content;
oldMTime ¬ dH.contentMTime;
locked ¬ dH.contentLocked ¬ ((NOT staleOK) OR (dH.contentTTL = 0));
};
NewContentGeneration:
ENTRY
PROC
RETURNS [generation:
INT] ~ {
ENABLE UNWIND => NULL;
RETURN[ dH.contentGenStarted ¬ dH.contentGenStarted+1 ];
};
CheckInContent:
ENTRY
PROC ~ {
ENABLE UNWIND => NULL;
dH.content ¬ content;
dH.contentMTime ¬ mTime;
dH.contentTTL ¬ initialDirContentTTL;
dH.contentKnownStale ¬ FALSE;
dH.contentGenFinished ¬ dH.contentGenStarted;
dH.contentLocked ¬ FALSE;
BROADCAST dH.contentAvailable;
};
UnlockContent:
ENTRY
PROC ~ {
ENABLE UNWIND => NULL;
dH.contentLocked ¬ FALSE;
BROADCAST dH.contentAvailable;
};
IF NOT CheckOutContent[].locked THEN RETURN;
{
ENABLE
UNWIND => {
UnlockContent[];
};
{
res: DirOpRes ~ DirLookup[dH, NIL];
IF res.status # ok THEN ERROR VUXFiles.CantStat[res.status];
knownStale ¬ ((mTime ¬ res.attributes.mtime) # oldMTime) OR dH.contentKnownStale;
};
WHILE knownStale
DO
dirString: CString ~ UXStrings.Create[dH.fHandle];
dirp: UnixDirectory.DirPtr ~ UnixDirectory.OpenDir[dirString];
prevNode: DirNode ¬ NIL;
generation: INT;
IF dirp =
NIL
THEN {
content ¬ NIL;
CheckInContent[];
RETURN;
};
IF content=NIL THEN content ¬ NewDirEntries[];
generation ¬ NewContentGeneration[];
FOR dp: UnixDirectory.DirEntPtr ¬ UnixDirectory.ReadDir[dirp], UnixDirectory.ReadDir[dirp]
WHILE dp #
NIL
DO
prevNode ¬ InsertOrUpdateDirEntry[
dp~dp,
generation~generation,
prevNode~prevNode,
dirEntries~content
];
ENDLOOP;
[] ¬ UnixDirectory.CloseDir[dirp];
{
res: DirOpRes ~ DirLookup[dH, NIL];
IF res.status # ok THEN ERROR VUXFiles.CantStat[res.status];
oldMTime ¬ mTime;
knownStale ¬ (mTime ¬ res.attributes.mtime) # oldMTime;
};
ENDLOOP;
};
CheckInContent[];
};
caseFileNamePrefix: ROPE ~ ".~case~";
caseFileNamePrefixLen: INT ~ Rope.Length[caseFileNamePrefix];
SplitUName:
PROC [uName: Rope.
ROPE]
RETURNS [namePart: Rope.
ROPE, version: Version] = {
--given a (short) file name, splits it into its name part and version part
baseLength: INT;
versionPart: INT;
[baseLength, versionPart] ¬ SplitBaseAndVersion[uName];
SELECT
TRUE
FROM
versionPart>0 =>
RETURN [Rope.Substr[uName, 0, baseLength], [numeric, versionPart]]; -- versioned file encoding
Rope.IsPrefix[caseFileNamePrefix, uName,
TRUE] =>
RETURN [Rope.Substr[uName, caseFileNamePrefixLen], [numeric, 0]]; -- case file encoding
ENDCASE =>
RETURN [uName, [none]]; -- versionless file or directory encoding
};
SplitBaseAndVersion:
PROC [uName: Rope.
ROPE]
RETURNS [
--baseLength:--
INT,
--version:--
INT] = {
--given a (short) file name, returns length of base part and version
--returns -1 for version part if uName doesn't look right
ch: CHAR;
length: INT ¬ Rope.Length[uName];
idx: INT ¬ length-1;
version: INT ¬ 0;
power: INT ¬ 1;
IF idx<4 OR Rope.Fetch[uName, idx]#'~ THEN RETURN [length, -1];
DO
idx ¬ idx-1;
IF idx<2 OR version>=tooBig THEN RETURN [length, -1];
ch ¬ Rope.Fetch[uName, idx];
SELECT
TRUE
FROM
Ascii.Digit[ch] => {
version ¬ version + (ORD[ch]-ORD['0])*power;
power ¬ power*10;
};
ch='~ => {
IF version>0
AND Rope.Fetch[uName, idx-1]='.
THEN RETURN [idx-1, version]
ELSE RETURN [length, -1];
};
ENDCASE => RETURN [length, -1];
ENDLOOP;
};
EnumerateDirectory:
PUBLIC
PROC [dH: DirHandle, eachDirEntry: VUXFiles.EachDirEntryProc, lowNameWithoutVersion:
ROPE, staleOK:
BOOL] ~ {
content: DirEntries;
finger: DirNode;
generation: INT;
content ¬ RefreshDirContent[dH, staleOK];
generation ¬ dH.contentGenFinished;
IF content = NIL THEN RETURN; -- e.g., the directory was unreadable
finger ¬ (
IF lowNameWithoutVersion =
NIL
THEN content.leftNode.link[0]
ELSE FindFirstNode[lowNameWithoutVersion, content].firstNode);
WHILE finger #
NIL
DO
IF finger.entry.generation >= generation
THEN {
IF NOT eachDirEntry[finger.entry].continue THEN RETURN;
};
finger ¬ finger.link[0];
ENDLOOP;
};
Follow Paths
ToLower: Rope.TranslatorType ~ {
Ascii.Lower is an inline, but I need to pass it as a translator
RETURN [Ascii.Lower[ch: old]];
};
GetDirComponent:
PUBLIC
PROC [path:
PATH, pos:
INT, smashCase:
BOOL]
RETURNS [
ROPE] ~ {
r: ROPE ¬ IF pos >= PFSNames.ComponentCount[path] THEN NIL ELSE PFSNames.ComponentRope[PFSNames.Fetch[path, pos]];
IF smashCase THEN r ¬ Rope.Translate[base: r, translator: ToLower];
RETURN[r];
};
FollowDirPath:
PUBLIC
PROC [path:
PATH, case:
BOOL, create:
BOOL]
RETURNS [dH: DirHandle ¬
NIL] ~ {
ENABLE
UNWIND => {
IF dH # NIL THEN UnPinDirPath[dH];
};
pos: INT ¬ 0; -- skip the server component; increment before use.
dir: PATH ¬ PFSNames.Directory[path];
dH ¬ GetDirRoot[];
DO
dHParent: DirHandle ~ dH;
created: BOOL;
component: ROPE ~ GetDirComponent[path~dir, pos~pos ¬ pos+1, smashCase~TRUE];
IF Rope.IsEmpty[component] THEN EXIT;
[dH, created] ¬ GetDirChild[dH~dH, childName~component, create~create];
IF created
THEN {
componentWithCase: ROPE ~ GetDirComponent[path~dir, pos~pos, smashCase~FALSE];
VUXFiles.CreateCaseFile[dHParent, componentWithCase ! PFS.Error => CONTINUE];
};
ENDLOOP;
};
GetDirChild:
PUBLIC
PROC [dH: DirHandle, childName:
ROPE, create:
BOOL]
RETURNS [dHChild: DirHandle, created:
BOOL ¬
FALSE] ~ {
Given a (pinned) DirHandle, return a (pinned) DirHandle for the named child, if that child exists and is a directory. Otherwise raise PFS.Error[...].
fH: FHandle;
fH ¬ VerifySubdirectory[dH, childName];
IF (fH#NIL) AND ((dHChild ¬ FindDirChild[dH, childName]) # NIL) THEN RETURN;
IF fH =
NIL
THEN {
desiredMode: Mode;
IF
NOT create
THEN
PFSBackdoor.ProduceError[code: $unknownFile, explanation: "Child is not a directory", info: NIL];
desiredMode ¬ GetCreateMode[dH, TRUE];
fH ¬ CreateSubdirectory[dH, childName, desiredMode];
created ¬ TRUE;
};
IF dHChild=NIL THEN dHChild ¬ InsertDirChild[dH, childName, fH];
};
Lookup
UXO:
PROC[r:
REF]
RETURNS [CString] ~
INLINE{
RETURN[UXStrings.CreateScratch[r]];
};
UXR:
PROC[s: CString] ~
INLINE{
UXStrings.ReleaseScratch[s];
};
maxAttachmentLength: CARD ~ 1000;
GetAttachment:
PROC [fH:
ROPE]
RETURNS [attachedTo:
PATH] ~
TRUSTED {
fHandle: CString ~ UXO[fH];
buf: CString ~ UXStrings.ObtainScratch[maxAttachmentLength];
linkSize: INT ~ UnixSysCalls.ReadLink[fHandle, buf, maxAttachmentLength];
IF linkSize<0
THEN {
errno: UnixErrno.Errno ~ UnixErrno.GetErrno[];
message: ROPE ~ Rope.Concat["VUXDirImpl.GetAttachment: ReadLink failed for ", UXStrings.ToRope[fHandle]];
UXR[fHandle];
UXR[buf];
UnixFSPrivate.ReportFailure[errno, message];
};
attachedTo ¬ PFS.PathFromRope[UXStrings.ToRope[buf, linkSize]];
UXR[buf];
UXR[fHandle];
};
Lookup:
PUBLIC
PROC [dH: DirHandle, name:
ROPE]
RETURNS [dirOpRes: DirOpRes, attachedTo:
PATH] ~
TRUSTED {
fullPathName: ROPE ~ Rope.Cat[dH.fHandle, IF dH.fHandle.Fetch[dH.fHandle.Length[]-1]#'/ THEN "/" ELSE NIL, name];
unixName: CString ¬ UXO[fullPathName];
res: RES ¬ UnixSysCalls.LStat[unixName, @dirOpRes.attributes ];
IF res=success
AND dirOpRes.attributes.mode.fmt=lnk
THEN {
res2: RES ¬ UnixSysCalls.Stat[unixName, @dirOpRes.attributes ];
attachedTo ¬ GetAttachment[fullPathName];
IF res2#success THEN dirOpRes.attributes.size ¬ LOOPHOLE[-1];
};
UXR[unixName];
dirOpRes.file ¬ fullPathName;
dirOpRes.status ¬ IF res=success THEN ok ELSE UnixErrno.GetErrno[];
};
DirLookup:
PUBLIC
PROC [dH: DirHandle, name:
ROPE]
RETURNS [dirOpRes: DirOpRes] ~
TRUSTED {
fullPathName: ROPE ~ Rope.Cat[dH.fHandle, IF dH.fHandle.Fetch[dH.fHandle.Length[]-1]#'/ THEN "/" ELSE NIL, name];
unixName: CString ¬ UXO[fullPathName];
res: RES ~ UnixSysCalls.Stat[unixName, @dirOpRes.attributes ];
UXR[unixName];
dirOpRes.file ¬ fullPathName;
dirOpRes.status ¬ IF res=success THEN ok ELSE UnixErrno.GetErrno[];
};
}...