<> <> <> <<>> <<>> <> <<>> <> <<1) Make sure the errors are right>> <<2) Make sure that file delete has the correct semantics (remove actually can destroy data; is that OK in hypertext land?)>> <<3) Check the arguments (file names OK, permissions)>> <<>> DIRECTORY Basics, BasicTime, PBasics, Process USING [Detach, MsecToTicks, Pause, SecondsToTicks, Ticks], RefText, Rope, SunMount, SunNFS, YggDID USING [DID, EqualDIDs, StabilizeDID, ValidateDID, VolatilizeDID], YggDIDPrivate USING [DIDForNFSRoot, DIDRep], YggDIDMap USING [CreateExplicitDID], YggFixedNames, YggEnvironment, YggFile, YggNaming, YggNav, YggNFS, YggRep, YggTransaction; YggNFSImpl: CEDAR MONITOR IMPORTS Basics, BasicTime, PBasics, Process, RefText, Rope, YggDID, YggDIDPrivate, YggDIDMap, YggFile, YggFixedNames, YggNaming, YggNav, YggRep, YggTransaction EXPORTS YggDID, YggNFS ~ BEGIN <> ErrorOnMistake: BOOL _ TRUE; -- TRUE for unit debugging ROPE: TYPE = Rope.ROPE; DID: PUBLIC TYPE ~ REF DIDRep; DIDRep: PUBLIC TYPE ~ YggDIDPrivate.DIDRep; AllNulls: PACKED ARRAY [0..Basics.charsPerWord) OF CHAR _ ALL[0C]; BadProps: PUBLIC ERROR [stat: SunNFS.Stat] = CODE; dawnOfHistory: SunNFS.TimeVal _ SunTimeFromGMT[sunEpoch]; EmptyCookie: SunNFS.Cookie; nullFAttr: SunNFS.FAttr _ [non, 0, 1, 0, 0, 0, 1024, 0, 0, 0, 0, dawnOfHistory, dawnOfHistory, dawnOfHistory]; sunEpoch: BasicTime.GMT _ BasicTime.Pack[ [year~1970, month~January, day~1, hour~0, minute~0, second~0, zone~0, dst~no] ]; CookieJarItem: TYPE = RECORD [ timeIssued: BasicTime.GMT, cookie: SunNFS.Cookie, nameToStart: REF TEXT ]; CookieJar: LIST OF CookieJarItem _ NIL; LastCookie: SunNFS.Cookie; SizeOfTimeCache: INT = 077B; -- all low bits on TimeCache: ARRAY [0..SizeOfTimeCache] OF LIST OF TimeCacheItem _ ALL[NIL]; TimeCacheItem: TYPE = RECORD[ did: DID, atime: SunNFS.TimeVal, mtime: SunNFS.TimeVal, itemCreateTime: BasicTime.GMT ]; <> Getattr: PUBLIC PROC [file: SunNFS.FHandle] RETURNS [reply: SunNFS.AttrStat] ~ { gotProps: BOOL _ TRUE; replyStat: SunNFS.Stat; reply _ InnerGetattr[file: file, fileDID: YggEnvironment.nullDID, trans: YggEnvironment.nullTransID ! BadProps => { gotProps _ FALSE; replyStat _ stat; CONTINUE; }; ]; IF ~gotProps THEN { reply _ [replyStat, nullFAttr]; }; }; Setattr: PUBLIC PROC [file: SunNFS.FHandle, attributes: SunNFS.SAttr] RETURNS [reply: SunNFS.AttrStat] ~ { gotProps: BOOL _ TRUE; replyStat: SunNFS.Stat; trans: YggEnvironment.TransID; outcome: YggTransaction.Outcome; currentAttrs: SunNFS.AttrStat; <> IF attributes.mode = CARD.LAST AND attributes.uid = CARD.LAST AND attributes.gid = CARD.LAST AND attributes.size = CARD.LAST THEN { -- only changing times currentAttrs _ InnerGetattr[file: file, fileDID: YggEnvironment.nullDID, trans: YggEnvironment.nullTransID ! BadProps => { gotProps _ FALSE; replyStat _ stat; CONTINUE; }; ]; IF gotProps THEN { IF attributes.atime = [CARD.LAST, CARD.LAST] AND attributes.mtime = [CARD.LAST, CARD.LAST] THEN RETURN [currentAttrs]; -- changing nothing IF attributes.atime # [CARD.LAST, CARD.LAST] THEN currentAttrs.attributes.atime _ attributes.atime; IF attributes.mtime # [CARD.LAST, CARD.LAST] THEN currentAttrs.attributes.mtime _ attributes.mtime; StickInTimeCache[file, currentAttrs.attributes.atime, currentAttrs.attributes.mtime]; reply _ currentAttrs; } ELSE { reply _ [replyStat, nullFAttr]; }; } ELSE { trans _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; currentAttrs _ InnerGetattr[file: file, fileDID: YggEnvironment.nullDID, trans: YggEnvironment.nullTransID ! BadProps => { gotProps _ FALSE; replyStat _ stat; CONTINUE; }; ]; IF gotProps THEN { foundC: BOOL _ FALSE; atimeC: SunNFS.TimeVal; mtimeC: SunNFS.TimeVal; reply.status _ ok; [foundC, atimeC, mtimeC] _ RemoveTimeFromCache[file]; IF foundC THEN { IF attributes.atime # [CARD.LAST, CARD.LAST] AND atimeC # [CARD.LAST, CARD.LAST] THEN attributes.atime _ atimeC; IF attributes.mtime # [CARD.LAST, CARD.LAST] AND mtimeC # [CARD.LAST, CARD.LAST] THEN attributes.mtime _ mtimeC; }; reply.attributes _ ChangeAttrsUnderTrans [trans: trans, fileDID: DIDFromFHandle[file], currentAttrs: currentAttrs.attributes, changeAttrs: attributes]; outcome _ YggTransaction.Finish[trans, commit]; IF outcome # commit THEN {RETURN [[status: acces, attributes: nullFAttr]]}; } ELSE { reply _ [replyStat, nullFAttr]; outcome _ YggTransaction.Finish[trans, abort]; }; }; }; Lookup: PUBLIC PROC [which: SunNFS.DirOpArgs] RETURNS [reply: SunNFS.DirOpRes] ~ { dirDID: YggDID.DID; lookUpName: ROPE; nameFound: BOOL; moreThanOneMatch: BOOL; didFound: YggDID.DID; dirDID _ DIDFromFHandle[which.dir]; IF ~YggDID.ValidateDID[dirDID] THEN RETURN[[status: noent, file: NIL, attributes: nullFAttr]]; IF ~YggNaming.HasDirectory[trans: YggEnvironment.nullTransID, directoryDid: dirDID] THEN RETURN[[status: notdir, file: NIL, attributes: nullFAttr]]; TRUSTED {lookUpName _ LOOPHOLE[which.name];}; [nameFound: nameFound, moreThanOneMatch: moreThanOneMatch, didFound: didFound] _ YggNaming.Lookup[trans: YggEnvironment.nullTransID, directoryDid: dirDID, namePattern: lookUpName, version: "h"]; IF ~nameFound OR moreThanOneMatch THEN RETURN[[status: noent, file: NIL, attributes: nullFAttr] ]; RETURN[[status: ok, file: FHandleFromDID[didFound], attributes: InnerGetattr[file: NIL, fileDID: didFound, trans: YggEnvironment.nullTransID ! BadProps => CONTINUE ].attributes]]; RETURN[[status: acces, file: NIL, attributes: nullFAttr]]; }; Readlink: PUBLIC PROC [file: SunNFS.FHandle] RETURNS [status: SunNFS.Stat, data: SunNFS.Path] ~ { fileDID: YggDID.DID; isDirectory: BOOL _ FALSE; fileDID _ DIDFromFHandle[file]; IF ~YggDID.ValidateDID[fileDID] THEN RETURN[noent, NIL]; isDirectory _ YggNaming.HasDirectory[trans: YggEnvironment.nullTransID, directoryDid: fileDID]; IF isDirectory THEN RETURN[isdir, NIL] ELSE { contentsType: YggRep.DocType; contentsType _ YggNav.GetTypeOfContents[trans: YggEnvironment.nullTransID, did: fileDID]; IF contentsType = YggRep.symbolicLink THEN { trouble: BOOL _ FALSE; bytesMoved: CARD _ 0; bytesStored: CARD _ 0; bytesStored _ YggNav.GetSize[trans: YggEnvironment.nullTransID, did: fileDID]; data _ NEW[TEXT[bytesStored]]; TRUSTED {bytesMoved _ YggNav.GetUninterpretedContents[trans: YggEnvironment.nullTransID, did: fileDID, firstByte: 0, byteCount: bytesStored, to: PointerFromRefText[data] ! YggNav.Error => {trouble _ TRUE; CONTINUE}]; }; IF trouble THEN RETURN[acces, NIL]; data.length _ bytesMoved; RETURN[ok, data]; } ELSE RETURN[acces, NIL]; }; }; Read: PUBLIC PROC [file: SunNFS.FHandle, offset, count: CARD, block: REF TEXT] RETURNS [reply: SunNFS.AttrStat] ~ { bytesMoved: CARD; fileDID: YggDID.DID; IF block.maxLength < count THEN RETURN[[status: acces, attributes: nullFAttr]]; block.length _ count; fileDID _ DIDFromFHandle[file]; IF ~YggDID.ValidateDID[fileDID] THEN RETURN[[status: noent, attributes: nullFAttr]]; TRUSTED {bytesMoved _ YggNav.GetUninterpretedContents [trans: YggEnvironment.nullTransID, did: fileDID, firstByte: offset, byteCount: count, to: PointerFromRefText[block]];}; block.length _ bytesMoved; reply _ Setattr[file, [mode: CARD.LAST, uid: CARD.LAST, gid: CARD.LAST, size: CARD.LAST, atime: SunTimeFromGMT[BasicTime.Now[]], mtime: [CARD.LAST, CARD.LAST]]]; }; <<>> Write: PUBLIC PROC [file: SunNFS.FHandle, offset, count: CARD, block: REF TEXT] RETURNS [reply: SunNFS.AttrStat] ~ { trans: YggEnvironment.TransID; outcome: YggTransaction.Outcome; bytesMoved: CARD; fileDID: YggDID.DID; currentAttrs: SunNFS.AttrStat; attrs: SunNFS.FAttr; gotProps: BOOL _ TRUE; replyStat: SunNFS.Stat; IF block.maxLength < count THEN RETURN[[status: acces, attributes: nullFAttr]]; block.length _ count; fileDID _ DIDFromFHandle[file]; IF ~YggDID.ValidateDID[fileDID] THEN RETURN[[status: noent, attributes: nullFAttr]]; trans _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; TRUSTED {bytesMoved _ YggNav.SetUninterpretedContents [trans: trans, did: fileDID, setDocType: FALSE, docType: YggRep.noValue, firstByte: offset, byteCount: count, from: PointerFromRefText[block]];}; currentAttrs _ InnerGetattr[file: NIL, fileDID: fileDID, trans: trans ! BadProps => { gotProps _ FALSE; replyStat _ stat; CONTINUE; }; ]; IF gotProps THEN { time: SunNFS.TimeVal; time _ SunTimeFromGMT[BasicTime.Now[]]; [] _ RemoveTimeFromCache[file]; attrs _ ChangeAttrsUnderTrans [trans: trans, fileDID: DIDFromFHandle[file], currentAttrs: currentAttrs.attributes, changeAttrs: [mode: CARD.LAST, uid: CARD.LAST, gid: CARD.LAST, size: CARD.LAST, atime: time, mtime: time]]; outcome _ YggTransaction.Finish[trans, commit]; IF outcome = commit THEN RETURN[[status: ok, attributes: attrs]] ELSE RETURN [[status: acces, attributes: nullFAttr]]; } ELSE { outcome _ YggTransaction.Finish[trans, abort]; RETURN [[replyStat, nullFAttr]]; }; }; Create: PUBLIC PROC [where: SunNFS.DirOpArgs, attributes: SunNFS.SAttr] RETURNS [reply: SunNFS.DirOpRes] ~ { trans: YggEnvironment.TransID; outcome: YggTransaction.Outcome; dirDID: YggDID.DID; newDID: YggDID.DID; isDirectory: BOOL _ FALSE; notADirectory: BOOL _ TRUE; newName: ROPE; nameFound: BOOL; moreThanOneMatch: BOOL; attrs: SunNFS.FAttr _ nullFAttr; dirDID _ DIDFromFHandle[where.dir]; IF ~YggDID.ValidateDID[dirDID] THEN RETURN[[status: noent, file: NIL, attributes: nullFAttr]]; isDirectory _ YggNaming.HasDirectory[trans: YggEnvironment.nullTransID, directoryDid: dirDID]; IF ~isDirectory THEN RETURN[[status: notdir, file: NIL, attributes: nullFAttr]]; <> newName _ Rope.FromRefText[where.name]; [nameFound: nameFound, moreThanOneMatch: moreThanOneMatch] _ YggNaming.Lookup[trans: YggEnvironment.nullTransID, directoryDid: dirDID, namePattern: newName, version: "h"]; IF nameFound OR moreThanOneMatch THEN RETURN[[status: exist, file: NIL, attributes: nullFAttr]]; trans _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; newDID _ YggNav.CreateObject[trans: trans, containerDID: dirDID, makeRoot: FALSE]; YggNav.SetContents[trans: trans, did: newDID, contents: [docType: YggRep.uninterpretedBytes, bits: NIL]]; [notADirectory, nameFound] _ YggNaming.UpdateItem[trans: trans, directoryDid: dirDID, name: newName, version: "h", did: newDID, updateType: insert]; IF notADirectory OR nameFound THEN { outcome _ YggTransaction.Finish[trans, abort]; RETURN[[status: IF nameFound THEN exist ELSE notdir, file: NIL, attributes: nullFAttr]]; }; attrs _ ChangeAttrsUnderTrans[trans: trans, fileDID: newDID, currentAttrs: nullFAttr, changeAttrs: attributes]; outcome _ YggTransaction.Finish[trans, commit]; IF outcome = commit THEN RETURN[[status: ok, file: FHandleFromDID[newDID], attributes: attrs]] ELSE RETURN [[status: acces, file: NIL, attributes: nullFAttr]]; }; Remove: PUBLIC PROC [which: SunNFS.DirOpArgs] RETURNS [status: SunNFS.Stat] ~ { trans: YggEnvironment.TransID; outcome: YggTransaction.Outcome; trans _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; status _ RemoveUnderTrans[trans: trans, which: which].status; IF status # ok THEN { outcome _ YggTransaction.Finish[trans, abort]; RETURN; }; outcome _ YggTransaction.Finish[trans, commit]; IF outcome = commit THEN RETURN[ok] ELSE RETURN [acces]; }; Rename: PUBLIC PROC [from, to: SunNFS.DirOpArgs] RETURNS [status: SunNFS.Stat] ~ { trans: YggEnvironment.TransID; outcome: YggTransaction.Outcome; rmDID: YggDID.DID; fromDirDID: YggDID.DID; toDirDID: YggDID.DID; rmFileName: ROPE; moreThanOneMatch: BOOL _ FALSE; nameFound: BOOL _ FALSE; isDirectory: BOOL _ FALSE; trans _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; fromDirDID _ DIDFromFHandle[from.dir]; toDirDID _ DIDFromFHandle[to.dir]; TRUSTED {rmFileName _ LOOPHOLE[from.name];}; IF ~YggDID.ValidateDID[fromDirDID] THEN { IF ErrorOnMistake THEN ERROR; RETURN[noent]; }; isDirectory _ YggNaming.HasDirectory[trans: trans, directoryDid: fromDirDID]; IF ~isDirectory THEN { IF ErrorOnMistake THEN ERROR; RETURN[notdir]; }; isDirectory _ YggNaming.HasDirectory[trans: trans, directoryDid: toDirDID]; IF ~isDirectory THEN { IF ErrorOnMistake THEN ERROR; RETURN[notdir]; }; <<[nameFound: nameFound, moreThanOneMatch: moreThanOneMatch, didFound: rmDID] _ YggNaming.Lookup[trans: trans, directoryDid: fromDirDID, namePattern: rmFileName, version: "h"];>> <> <> <> <<};>> [status, rmDID] _ RemoveUnderTrans[trans: trans, which: from, destroyIfLastDir: FALSE]; IF status # ok THEN { outcome _ YggTransaction.Finish[trans, abort]; RETURN; }; status _ LinkUnderTrans[trans: trans, toDID: rmDID, as: to]; IF status # ok THEN { outcome _ YggTransaction.Finish[trans, abort]; RETURN; }; outcome _ YggTransaction.Finish[trans, commit]; IF outcome = commit THEN RETURN[ok] ELSE RETURN [acces]; }; Link: PUBLIC PROC [to: SunNFS.FHandle, as: SunNFS.DirOpArgs] RETURNS [status: SunNFS.Stat] ~ { trans: YggEnvironment.TransID; outcome: YggTransaction.Outcome; trans _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; status _ LinkUnderTrans[trans: trans, toDID: DIDFromFHandle[to], as: as]; IF status # ok THEN { outcome _ YggTransaction.Finish[trans, abort]; RETURN; }; outcome _ YggTransaction.Finish[trans, commit]; IF outcome = commit THEN RETURN[ok] ELSE RETURN [acces]; }; Symlink: PUBLIC PROC [from: SunNFS.DirOpArgs, to: SunNFS.Path, attributes: SunNFS.SAttr] RETURNS [status: SunNFS.Stat] ~ { trans: YggEnvironment.TransID; outcome: YggTransaction.Outcome; attrs: SunNFS.FAttr _ nullFAttr; dirDID: YggDID.DID; linkDID: YggDID.DID; isDirectory: BOOL _ FALSE; lookUpName: ROPE; notADirectory: BOOL _ TRUE; nameFound: BOOL; bytesMoved: CARD; moreThanOneMatch: BOOL; dirDID _ DIDFromFHandle[from.dir]; IF ~YggDID.ValidateDID[dirDID] THEN RETURN[noent]; isDirectory _ YggNaming.HasDirectory[trans: YggEnvironment.nullTransID, directoryDid: dirDID]; IF ~isDirectory THEN RETURN[notdir]; TRUSTED {lookUpName _ LOOPHOLE[from.name];}; [nameFound: nameFound, moreThanOneMatch: moreThanOneMatch] _ YggNaming.Lookup[trans: YggEnvironment.nullTransID, directoryDid: dirDID, namePattern: lookUpName, version: "h"]; IF moreThanOneMatch THEN RETURN[status: noent]; IF nameFound THEN RETURN[status: exist]; trans _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; linkDID _ YggNav.CreateObject[trans: trans, containerDID: dirDID, makeRoot: FALSE]; TRUSTED {bytesMoved _ YggNav.SetUninterpretedContents[trans: trans, did: linkDID, setDocType: TRUE, docType: YggRep.symbolicLink, firstByte: 0, byteCount: to.length, from: PointerFromRefText[to]]; }; [notADirectory, nameFound] _ YggNaming.UpdateItem[trans: trans, directoryDid: dirDID, name: lookUpName, version: "h", did: linkDID, updateType: insert]; IF notADirectory OR nameFound THEN { outcome _ YggTransaction.Finish[trans, abort]; RETURN[IF nameFound THEN exist ELSE notdir]; }; attrs _ ChangeAttrsUnderTrans[trans: trans, fileDID: linkDID, currentAttrs: nullFAttr, changeAttrs: attributes]; outcome _ YggTransaction.Finish[trans, commit]; IF outcome = commit THEN RETURN[ok] ELSE RETURN [acces]; }; Mkdir: PUBLIC PROC [where: SunNFS.DirOpArgs, attributes: SunNFS.SAttr] RETURNS [reply: SunNFS.DirOpRes] ~ { trans: YggEnvironment.TransID; outcome: YggTransaction.Outcome; whereDirDID: YggDID.DID; newDirDID: YggDID.DID; newDirName: ROPE; isDirectory: BOOL; notADirectory: BOOL _ TRUE; nameFound: BOOL; moreThanOneMatch: BOOL; whereDirDID _ DIDFromFHandle[where.dir]; TRUSTED {newDirName _ LOOPHOLE[where.name];}; IF ~YggDID.ValidateDID[whereDirDID] THEN RETURN[[status: noent, file: NIL, attributes: nullFAttr]]; isDirectory _ YggNaming.HasDirectory[trans: YggEnvironment.nullTransID, directoryDid: whereDirDID]; IF ~isDirectory THEN RETURN[[status: notdir, file: NIL, attributes: nullFAttr]]; [nameFound: nameFound, moreThanOneMatch: moreThanOneMatch] _ YggNaming.Lookup[trans: YggEnvironment.nullTransID, directoryDid: whereDirDID, namePattern: newDirName, version: "h"]; IF moreThanOneMatch THEN RETURN[[status: noent, file: NIL, attributes: nullFAttr]]; IF nameFound THEN RETURN[[status: exist, file: NIL, attributes: nullFAttr]]; trans _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; newDirDID _ YggNav.CreateObject[trans: trans, containerDID: whereDirDID, makeRoot: FALSE]; YggNav.SetContents[trans: trans, did: newDirDID, contents: [docType: YggRep.uninterpretedBytes, bits: NIL]]; [notADirectory, nameFound] _ YggNaming.UpdateItem[trans: trans, directoryDid: whereDirDID, name: newDirName, version: "h", did: newDirDID, updateType: insert]; IF notADirectory OR nameFound THEN { outcome _ YggTransaction.Finish[trans, abort]; RETURN [[status: IF nameFound THEN exist ELSE notdir, file: NIL, attributes: nullFAttr]]; }; IF YggNaming.MkDir[trans: trans, did: newDirDID] THEN { outcome _ YggTransaction.Finish[trans, abort]; RETURN [[status: exist, file: NIL, attributes: nullFAttr]]; }; [notADirectory, nameFound] _ YggNaming.UpdateItem[trans: trans, directoryDid: newDirDID, name: "..", version: "h", did: whereDirDID, updateType: insert]; IF notADirectory OR nameFound THEN { outcome _ YggTransaction.Finish[trans, abort]; RETURN [[status: IF nameFound THEN exist ELSE notdir, file: NIL, attributes: nullFAttr]]; }; outcome _ YggTransaction.Finish[trans, commit]; IF outcome = commit THEN RETURN[[status: ok, file: FHandleFromDID[newDirDID], attributes: nullFAttr]] ELSE RETURN [[status: acces, file: NIL, attributes: nullFAttr]]; }; Rmdir: PUBLIC PROC [which: SunNFS.DirOpArgs] RETURNS [status: SunNFS.Stat] ~ { trans: YggEnvironment.TransID; outcome: YggTransaction.Outcome; whichDirDID: YggDID.DID; rmDirDID: YggDID.DID; rmDirName: ROPE; isDirectory: BOOL; nameFound: BOOL; moreThanOneMatch: BOOL; nameMatched: ROPE; versionMatched: ROPE; whichDirDID _ DIDFromFHandle[which.dir]; TRUSTED {rmDirName _ LOOPHOLE[which.name];}; IF ~YggDID.ValidateDID[whichDirDID] THEN RETURN[noent]; isDirectory _ YggNaming.HasDirectory[trans: YggEnvironment.nullTransID, directoryDid: whichDirDID]; IF ~isDirectory THEN RETURN[notdir]; [nameFound: nameFound, moreThanOneMatch: moreThanOneMatch, didFound: rmDirDID, nameMatched: nameMatched, versionMatched: versionMatched] _ YggNaming.Lookup[trans: YggEnvironment.nullTransID, directoryDid: whichDirDID, namePattern: rmDirName, version: "h"]; IF moreThanOneMatch THEN RETURN[status: noent]; IF ~nameFound THEN RETURN[status: noent]; trans _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; [nameFound: nameFound, moreThanOneMatch: moreThanOneMatch] _ YggNaming.Lookup[trans: trans, directoryDid: rmDirDID, namePattern: "*", version: "*"]; IF moreThanOneMatch THEN { outcome _ YggTransaction.Finish[trans, abort]; RETURN[status: notempty]; -- more than ".." in directory }; IF ~nameFound THEN { outcome _ YggTransaction.Finish[trans, abort]; RETURN[status: acces]; -- no ".." in directory }; [nameFound: nameFound, moreThanOneMatch: moreThanOneMatch] _ YggNaming.Lookup[trans: trans, directoryDid: rmDirDID, namePattern: "..", version: "*"]; IF ~nameFound THEN { outcome _ YggTransaction.Finish[trans, abort]; RETURN[status: notempty]; -- where's the ".."? something else is there! }; IF ~YggNaming.DeleteItem[trans: trans, directoryDid: whichDirDID, name: nameMatched, version: versionMatched] THEN { outcome _ YggTransaction.Finish[trans, abort]; RETURN [noent]; }; outcome _ YggTransaction.Finish[trans, commit]; IF outcome = commit THEN RETURN[ok] ELSE RETURN [acces]; }; Readdir: PUBLIC PROC [dir: SunNFS.FHandle, cookie: SunNFS.Cookie _ NIL, count: CARD, eachDirEntry: YggNFS.EachDirEntryProc] RETURNS [status: SunNFS.Stat _ ok, eof: BOOL _ FALSE, newCookie: SunNFS.Cookie] ~ { enum: YggNaming.EnumProc ~ { <> accept: BOOL; continue: BOOL; [accept, continue] _ eachDirEntry[fileid: did.didLow, filename: name]; IF accept THEN lastAcceptedName _ name; RETURN[~continue]; }; notADirectory: BOOL _ FALSE; zeroCookie: BOOL _ FALSE; cookieOK: BOOL _ TRUE; refTextNnameToStart: REF TEXT _ NIL; nameToStart: ROPE _ NIL; lastAcceptedName: REF TEXT _ NIL; dirDID: YggDID.DID; IF cookie = NIL THEN zeroCookie _ TRUE ELSE { FOR i: CARDINAL IN [0..SunNFS.cookieSize) DO IF cookie[i] = VAL[0] THEN EXIT; REPEAT FINISHED => zeroCookie _ TRUE; ENDLOOP; }; IF ~zeroCookie THEN [cookieOK, refTextNnameToStart] _ LookupCookie[cookie]; nameToStart _ RefText.TrustTextAsRope[refTextNnameToStart]; IF ~cookieOK THEN RETURN[status: stale, eof: TRUE, newCookie: EmptyCookie]; dirDID _ DIDFromFHandle[dir]; IF ~YggDID.ValidateDID[dirDID] THEN RETURN[noent, TRUE, EmptyCookie]; [notADirectory, eof] _ YggNaming.EnumerateEntries[trans: YggEnvironment.nullTransID, directoryDid: dirDID, namePattern: "*", version: "h", nameToStart: nameToStart, nameToStartVersion: "h", proc: enum]; IF notADirectory THEN RETURN[notdir, TRUE, EmptyCookie]; IF ~eof THEN { newCookie _ CookieForName[lastAcceptedName]; } ELSE newCookie _ EmptyCookie; }; Statfs: PUBLIC PROC [file: SunNFS.FHandle] RETURNS [reply: SunNFS.FSAttrStat] ~ { blockSize: CARD; secondaryBlocks: CARD; secondaryBlocksFree: CARD; tertiaryBlocks: CARD; tertiaryBlocksFree: CARD; [blockSize, secondaryBlocks, secondaryBlocksFree, tertiaryBlocksFree, tertiaryBlocksFree] _ YggFile.ServerInfo[]; RETURN[[status: ok, attributes: [tsize: 4096, bsize: blockSize, blocks: secondaryBlocks + tertiaryBlocks, bfree: secondaryBlocksFree + tertiaryBlocksFree, bavail: secondaryBlocks + tertiaryBlocks]]]; }; <> InnerGetattr: PROC [file: SunNFS.FHandle, fileDID: YggDID.DID, trans: YggEnvironment.TransID] RETURNS [attrs: SunNFS.AttrStat] ~ { <> now: SunNFS.TimeVal; isDirectory: BOOL _ FALSE; properties: LIST OF YggRep.Attribute; valueSet: LIST OF YggRep.TypedPrimitiveElement; now _ SunTimeFromGMT[BasicTime.Now[]]; IF file # NIL THEN fileDID _ DIDFromFHandle[file]; IF ~YggDID.ValidateDID[fileDID] THEN ERROR BadProps[noent]; attrs.status _ ok; attrs.attributes _ nullFAttr; isDirectory _ YggNaming.HasDirectory[trans: trans, directoryDid: fileDID]; IF isDirectory THEN attrs.attributes.type _ dir ELSE { contentsType: YggRep.DocType; contentsType _ YggNav.GetTypeOfContents[trans: trans, did: fileDID]; IF contentsType = YggRep.symbolicLink THEN attrs.attributes.type _ lnk ELSE attrs.attributes.type _ reg; }; properties _ YggNav.GetProperty[trans: trans, did: fileDID, propertyName: YggFixedNames.NFSPropertyName].property; IF properties = NIL OR properties.rest # NIL THEN ERROR BadProps[acces]; IF properties.first.value = NIL THEN ERROR BadProps[acces]; IF properties.first.value.rest # NIL THEN ERROR BadProps[acces]; valueSet _ properties.first.value.first.valueSet; <> IF valueSet.first.docType # YggRep.int THEN ERROR BadProps[acces]; IF ~ISTYPE[valueSet.first.bits, REF INT] THEN ERROR BadProps[acces]; attrs.attributes.mode _ NARROW[valueSet.first.bits, REF INT]^; valueSet _ valueSet.rest; <> IF valueSet = NIL THEN ERROR BadProps[acces]; IF valueSet.first.docType # YggRep.int THEN ERROR BadProps[acces]; IF ~ISTYPE[valueSet.first.bits, REF INT] THEN ERROR BadProps[acces]; attrs.attributes.uid _ NARROW[valueSet.first.bits, REF INT]^; valueSet _ valueSet.rest; <> IF valueSet = NIL THEN ERROR BadProps[acces]; IF valueSet.first.docType # YggRep.int THEN ERROR BadProps[acces]; IF ~ISTYPE[valueSet.first.bits, REF INT] THEN ERROR BadProps[acces]; attrs.attributes.gid _ NARROW[valueSet.first.bits, REF INT]^; valueSet _ valueSet.rest; <> attrs.attributes.size _ YggNav.GetSize[trans: trans, did: fileDID]; <> IF valueSet = NIL THEN ERROR BadProps[acces]; IF valueSet.first.docType # YggRep.int THEN ERROR BadProps[acces]; IF ~ISTYPE[valueSet.first.bits, REF INT] THEN ERROR BadProps[acces]; attrs.attributes.blocksize _ NARROW[valueSet.first.bits, REF INT]^; valueSet _ valueSet.rest; <> IF valueSet = NIL THEN ERROR BadProps[acces]; IF valueSet.first.docType # YggRep.int THEN ERROR BadProps[acces]; IF ~ISTYPE[valueSet.first.bits, REF INT] THEN ERROR BadProps[acces]; attrs.attributes.blocks _ NARROW[valueSet.first.bits, REF INT]^; valueSet _ valueSet.rest; <> IF valueSet = NIL THEN ERROR BadProps[acces]; IF valueSet.first.docType # YggRep.date THEN ERROR BadProps[acces]; IF ~ISTYPE[valueSet.first.bits, YggRep.AccurateGMT] THEN ERROR BadProps[acces]; attrs.attributes.atime _ SunTimeFromAccurateGMT[NARROW[valueSet.first.bits, YggRep.AccurateGMT]]; valueSet _ valueSet.rest; <> IF valueSet = NIL THEN ERROR BadProps[acces]; IF valueSet.first.docType # YggRep.date THEN ERROR BadProps[acces]; IF ~ISTYPE[valueSet.first.bits, YggRep.AccurateGMT] THEN ERROR BadProps[acces]; attrs.attributes.mtime _ SunTimeFromAccurateGMT[NARROW[valueSet.first.bits, YggRep.AccurateGMT]]; valueSet _ valueSet.rest; <> IF valueSet = NIL THEN ERROR BadProps[acces]; IF valueSet.first.docType # YggRep.date THEN ERROR BadProps[acces]; IF ~ISTYPE[valueSet.first.bits, YggRep.AccurateGMT] THEN ERROR BadProps[acces]; attrs.attributes.ctime _ SunTimeFromAccurateGMT[NARROW[valueSet.first.bits, YggRep.AccurateGMT]]; IF valueSet.rest # NIL THEN ERROR BadProps[acces]; }; ChangeAttrsUnderTrans: PROC [trans: YggEnvironment.TransID, fileDID: YggDID.DID, currentAttrs: SunNFS.FAttr, changeAttrs: SunNFS.SAttr] RETURNS [newAttributes: SunNFS.FAttr] ~ { newAttributes _ currentAttrs; IF changeAttrs.mode # CARD.LAST THEN newAttributes.mode _ changeAttrs.mode; IF changeAttrs.uid # CARD.LAST THEN newAttributes.uid _ changeAttrs.uid; IF changeAttrs.gid # CARD.LAST THEN newAttributes.gid _ changeAttrs.gid; IF changeAttrs.size # CARD.LAST THEN { YggNav.SetSize[trans: trans, did: fileDID, size: changeAttrs.size]; newAttributes.size _ changeAttrs.size; }; IF changeAttrs.atime # [CARD.LAST, CARD.LAST] THEN newAttributes.atime _ changeAttrs.atime; IF changeAttrs.mtime # [CARD.LAST, CARD.LAST] THEN newAttributes.mtime _ changeAttrs.mtime; InnerSetattr[file: NIL, fileDID: fileDID, trans: trans, attrs: newAttributes]; }; <<>> InnerSetattr: PROC [file: SunNFS.FHandle, fileDID: YggDID.DID, trans: YggEnvironment.TransID, attrs: SunNFS.FAttr] ~ { <> valueSet: LIST OF YggRep.TypedPrimitiveElement _ NIL; IF file # NIL THEN fileDID _ DIDFromFHandle[file]; IF ~YggDID.ValidateDID[fileDID] THEN ERROR BadProps[noent]; valueSet _ LIST[ [YggRep.int, NEW[INT _ attrs.mode]], -- mode [YggRep.int, NEW[INT _ attrs.uid]], -- uid [YggRep.int, NEW[INT _ attrs.gid]], -- gid [YggRep.int, NEW[INT _ attrs.blocksize]], -- blocksize [YggRep.int, NEW[INT _ attrs.blocks]], -- blocks [YggRep.date, AccurateGMTFromSunTime[attrs.atime]], -- atime [YggRep.date, AccurateGMTFromSunTime[attrs.mtime]], -- mtime [YggRep.date, AccurateGMTFromSunTime[attrs.ctime]]]; -- ctime YggNav.SetProperty[trans: trans, did: fileDID, propertyName: YggFixedNames.NFSPropertyName, property: [attributeName: YggFixedNames.NFSPropertyName, ordered: FALSE, value: LIST[[NIL, valueSet]]], appendProperty: FALSE]; }; StickInTimeCache: ENTRY PROC [file: SunNFS.FHandle, atime: SunNFS.TimeVal, mtime: SunNFS.TimeVal] ~ { bucket: INT; fileDid: DID; fileDid _ DIDFromFHandle[file]; bucket _ PBasics.BITAND[SizeOfTimeCache, fileDid.didLow]; FOR tcl: LIST OF TimeCacheItem _ TimeCache[bucket], tcl.rest UNTIL tcl = NIL DO IF tcl.first.did.didLow = fileDid.didLow AND tcl.first.did.didHigh = fileDid.didHigh THEN { tcl.first.atime _ atime; tcl.first.mtime _ mtime; EXIT; }; REPEAT FINISHED => { TimeCache[bucket] _ CONS [[fileDid, atime, mtime, BasicTime.Now[]], TimeCache[bucket]]; }; ENDLOOP; }; RemoveTimeFromCache: ENTRY PROC [file: SunNFS.FHandle] RETURNS [ found: BOOL _ FALSE, atime: SunNFS.TimeVal, mtime: SunNFS.TimeVal] ~ { bucket: INT; fileDid: DID; prev: LIST OF TimeCacheItem _ NIL; fileDid _ DIDFromFHandle[file]; bucket _ PBasics.BITAND[SizeOfTimeCache, fileDid.didLow]; FOR tcl: LIST OF TimeCacheItem _ TimeCache[bucket], tcl.rest UNTIL tcl = NIL DO IF tcl.first.did.didLow = fileDid.didLow AND tcl.first.did.didHigh = fileDid.didHigh THEN { IF prev = NIL THEN TimeCache[bucket] _ tcl.rest ELSE prev.rest _ tcl.rest; RETURN[TRUE, tcl.first.atime, tcl.first.mtime]; }; prev _ tcl; ENDLOOP; RETURN[FALSE, [CARD.LAST, CARD.LAST], [CARD.LAST, CARD.LAST]]; }; PruneTimeCache: PROC ~ { ticksToWait: Process.Ticks; ticksToWait _ Process.MsecToTicks[1713]; DO now: BasicTime.GMT _ BasicTime.Now[]; FOR hval: INT IN [0..SizeOfTimeCache) DO pruneInternal: ENTRY PROC ~ { prev: LIST OF TimeCacheItem _ NIL; FOR tcl: LIST OF TimeCacheItem _ TimeCache[hval], tcl.rest UNTIL tcl = NIL DO IF BasicTime.Period[from: tcl.first.itemCreateTime, to: now] > 30 THEN { trans: YggEnvironment.TransID; gotProps: BOOL _ TRUE; replyStat: SunNFS.Stat; currentAttrs: SunNFS.AttrStat; trans _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; currentAttrs _ InnerGetattr[file: NIL, fileDID: tcl.first.did, trans: YggEnvironment.nullTransID ! BadProps => { gotProps _ FALSE; replyStat _ stat; CONTINUE; }; ]; IF gotProps THEN { [] _ ChangeAttrsUnderTrans [trans: trans, fileDID: tcl.first.did, currentAttrs: currentAttrs.attributes, changeAttrs: [CARD.LAST, CARD.LAST, CARD.LAST, CARD.LAST, tcl.first.atime, tcl.first.mtime]]; [] _ YggTransaction.Finish[trans, commit]; <> } ELSE { <> [] _ YggTransaction.Finish[trans, abort]; }; IF prev = NIL THEN TimeCache[hval] _ tcl.rest ELSE prev.rest _ tcl.rest; LOOP; }; prev _ tcl; ENDLOOP; }; pruneInternal[]; Process.Pause[5]; ENDLOOP; Process.Pause[ticksToWait]; ENDLOOP; }; RemoveUnderTrans: PROC [trans: YggEnvironment.TransID, which: SunNFS.DirOpArgs, destroyIfLastDir: BOOL _ TRUE] RETURNS [status: SunNFS.Stat, rmDID: YggDID.DID] ~ { whichDirDID: YggDID.DID; rmFileName: ROPE; isDirectory: BOOL; nameFound: BOOL; moreThanOneMatch: BOOL; nameMatched: ROPE; versionMatched: ROPE; success: BOOL _ TRUE; parentsDids: LIST OF YggDID.DID; whichDirDID _ DIDFromFHandle[which.dir]; TRUSTED {rmFileName _ LOOPHOLE[which.name];}; IF ~YggDID.ValidateDID[whichDirDID] THEN { IF ErrorOnMistake THEN ERROR; RETURN[noent, YggEnvironment.nullDID]; }; isDirectory _ YggNaming.HasDirectory[trans: trans, directoryDid: whichDirDID]; IF ~isDirectory THEN { IF ErrorOnMistake THEN ERROR; RETURN[notdir, YggEnvironment.nullDID]; }; [nameFound: nameFound, moreThanOneMatch: moreThanOneMatch, didFound: rmDID, nameMatched: nameMatched, versionMatched: versionMatched] _ YggNaming.Lookup[trans: trans, directoryDid: whichDirDID, namePattern: rmFileName, version: "h"]; IF moreThanOneMatch OR ~nameFound THEN { IF ErrorOnMistake THEN ERROR; RETURN[status: noent, rmDID: YggEnvironment.nullDID]; }; IF ~YggNaming.DeleteItem[trans: trans, directoryDid: whichDirDID, name: nameMatched, version: versionMatched] THEN { IF ErrorOnMistake THEN ERROR; RETURN [noent, YggEnvironment.nullDID]; }; [dids: parentsDids, success: success] _ YggNav.GetParents[trans: trans, did: rmDID, dontWait: FALSE]; FOR didList: LIST OF YggDID.DID _ parentsDids, didList.rest UNTIL didList = NIL DO IF YggDID.EqualDIDs[didList.first, whichDirDID] THEN EXIT; REPEAT FINISHED => { IF ErrorOnMistake THEN ERROR; RETURN [noent, YggEnvironment.nullDID]; }; ENDLOOP; success _ YggNav.RemoveFromContainer[trans: trans, did: rmDID, containerDID: whichDirDID]; IF ~success THEN { IF ErrorOnMistake THEN ERROR; RETURN [noent, YggEnvironment.nullDID]; }; IF parentsDids.rest = NIL AND destroyIfLastDir THEN { YggNav.RemoveObject[trans: trans, did: rmDID]; [] _ RemoveTimeFromCache[file: FHandleFromDID[rmDID]]; }; RETURN [ok, rmDID]; }; LinkUnderTrans: PROC [trans: YggEnvironment.TransID, toDID: YggDID.DID, as: SunNFS.DirOpArgs] RETURNS [status: SunNFS.Stat] ~ { dirDID: YggDID.DID; isDirectory: BOOL _ FALSE; notADirectory: BOOL _ FALSE; lookUpName: ROPE; nameFound: BOOL; moreThanOneMatch: BOOL; dirDID _ DIDFromFHandle[as.dir]; IF ~YggDID.ValidateDID[dirDID] THEN RETURN[noent]; isDirectory _ YggNaming.HasDirectory[trans: YggEnvironment.nullTransID, directoryDid: dirDID]; IF ~isDirectory THEN RETURN[notdir]; TRUSTED {lookUpName _ LOOPHOLE[as.name];}; [nameFound: nameFound, moreThanOneMatch: moreThanOneMatch] _ YggNaming.Lookup[trans: YggEnvironment.nullTransID, directoryDid: dirDID, namePattern: lookUpName, version: "h"]; IF moreThanOneMatch THEN RETURN[status: noent]; IF nameFound THEN RETURN[status: exist]; [notADirectory, nameFound] _ YggNaming.UpdateItem[trans: trans, directoryDid: dirDID, name: lookUpName, version: "h", did: toDID, updateType: insert]; YggNav.AddToContainer[trans: trans, did: toDID, containerDID: dirDID]; IF notADirectory OR nameFound THEN { RETURN [IF nameFound THEN exist ELSE notdir]; }; RETURN[ok]; }; PointerFromRefText: UNSAFE PROC [block: REF READONLY TEXT] RETURNS [LONG POINTER] ~ TRUSTED INLINE { RETURN[ LOOPHOLE[block, LONG POINTER] + UNITS[TEXT[0]] ] }; DIDFromFHandle: PROC [file: SunNFS.FHandle] RETURNS [did: YggDID.DID] ~ TRUSTED { did _ YggDID.VolatilizeDID[PointerFromRefText[file]]; }; FHandleFromDID: PROC [did: YggDID.DID] RETURNS [file: SunNFS.FHandle] ~ TRUSTED { file _ NEW[TEXT[SunNFS.fhSize]]; YggDID.StabilizeDID[did, PointerFromRefText[file]]; }; GMTFromSunTime: PROC [sunTime: SunNFS.TimeVal] RETURNS [gmt: BasicTime.GMT] ~ { RETURN [BasicTime.Update[sunEpoch, INT[sunTime.seconds]]]; }; AccurateGMTFromSunTime: PROC [sunTime: SunNFS.TimeVal] RETURNS [agmt: YggRep.AccurateGMT] ~ { agmt _ NEW[YggRep.AccurateGMTRep]; agmt.gmt _ BasicTime.Update[sunEpoch, INT[sunTime.seconds]]; agmt.usecs _ sunTime.useconds; }; SunTimeFromGMT: PROC [gmt: BasicTime.GMT] RETURNS [sunTime: SunNFS.TimeVal] ~ { RETURN [ [seconds~BasicTime.Period[from~sunEpoch, to~gmt], useconds~0] ]; }; SunTimeFromAccurateGMT: PROC [agmt: YggRep.AccurateGMT] RETURNS [sunTime: SunNFS.TimeVal] ~ { RETURN[ [seconds~BasicTime.Period[from~sunEpoch, to~agmt.gmt], useconds~agmt.usecs] ]; }; CompareSunTimes: PROC [t1, t2: SunNFS.TimeVal] RETURNS [Basics.Comparison] ~ { RETURN [SELECT t1.seconds FROM < t2.seconds => less, > t2.seconds => greater, ENDCASE => Basics.CompareCard[t1.useconds, t2.useconds]]; }; <> LookupCookie: ENTRY PROC [cookie: SunNFS.Cookie] RETURNS [cookieOK: BOOL _ FALSE, nameToStart: REF TEXT _ NIL] ~ { FOR loc: LIST OF CookieJarItem _ CookieJar, loc.rest UNTIL loc = NIL DO IF loc.first.cookie = cookie THEN RETURN[cookieOK: TRUE, nameToStart: loc.first.nameToStart]; FOR i: CARDINAL IN [0..SunNFS.cookieSize) DO IF loc.first.cookie[i] # cookie[i] THEN EXIT; REPEAT FINISHED => RETURN[cookieOK: TRUE, nameToStart: loc.first.nameToStart]; ENDLOOP; ENDLOOP; }; CookieForName: ENTRY PROC [lastAcceptedName: REF TEXT] RETURNS [cookie: SunNFS.Cookie _ EmptyCookie] ~ { cookie _ NextCookie[]; CookieJar _ CONS[[BasicTime.Now[], cookie, lastAcceptedName], CookieJar]; }; NextCookie: INTERNAL PROC RETURNS [cookie: SunNFS.Cookie _ EmptyCookie] ~ { FOR i: CARDINAL IN [0..SunNFS.cookieSize) DO IF LastCookie[i].ORD = CHAR.LAST.ORD THEN {LastCookie[i] _ VAL[0]; LOOP}; LastCookie[i] _ VAL[LastCookie[i].ORD.SUCC]; EXIT; REPEAT FINISHED => ERROR; ENDLOOP; cookie _ LastCookie; RETURN; }; CookieMonster: PROC ~ { <> ticksToWait: Process.Ticks; ticksToWait _ Process.SecondsToTicks[5]; DO innerTrim: ENTRY PROC = { now: BasicTime.GMT; prevCookie: LIST OF CookieJarItem _ NIL; now _ BasicTime.Now[]; FOR loc: LIST OF CookieJarItem _ CookieJar, loc.rest UNTIL loc = NIL DO IF BasicTime.Period[from: loc.first.timeIssued, to: now] > 6 THEN { IF prevCookie = NIL THEN CookieJar _ loc.rest ELSE prevCookie.rest _ loc.rest; LOOP; }; prevCookie _ loc; ENDLOOP; }; Process.Pause[ticksToWait]; innerTrim[]; ENDLOOP; }; <> MakeFileSystem: PUBLIC ENTRY PROC RETURNS [alreadyExists: BOOL _ FALSE] ~ { ENABLE UNWIND => NULL; trans: YggEnvironment.TransID; outcome: YggTransaction.Outcome; IF YggDID.ValidateDID[YggDIDPrivate.DIDForNFSRoot] THEN RETURN [TRUE]; trans _ YggTransaction.CreateTrans[YggEnvironment.nullTransID]; IF ~YggDIDMap.CreateExplicitDID[trans, YggDIDPrivate.DIDForNFSRoot] THEN RETURN [TRUE]; YggNav.SetContents[trans: trans, did: YggDIDPrivate.DIDForNFSRoot, contents: [docType: YggRep.uninterpretedBytes, bits: YggRep.SetSizeOfBits[NIL, 0].newBits]]; IF YggNaming.MkDir[trans: trans, did: YggDIDPrivate.DIDForNFSRoot] THEN { outcome _ YggTransaction.Finish[trans, abort]; RETURN [TRUE]; }; outcome _ YggTransaction.Finish[trans, commit]; IF outcome = commit THEN RETURN[FALSE] ELSE RETURN [TRUE]; }; <> Mnt: PUBLIC PROC [directory: SunMount.Path] RETURNS [reply: SunMount.FHStatus] ~ { badStat: CARD ; badStat _ ORD[SunNFS.Stat.notdir]; SELECT RefText.Length[directory] FROM = 0 => {}; = 1 => { IF RefText.Fetch[directory, 0] # '/ THEN RETURN[[status: ORD[SunNFS.Stat.notdir], directory: NIL]] }; >= 2 => { RETURN[[status: ORD[SunNFS.Stat.notdir], directory: NIL]] }; ENDCASE => ERROR; RETURN[[status: 0, directory: FHandleFromDID[YggDIDPrivate.DIDForNFSRoot]]]; }; Dump: PUBLIC PROC [eachMount: SunMount.EachMountProc] ~ { }; Umnt: PUBLIC PROC [directory: SunMount.Path] ~ { }; Umntall: PUBLIC PROC [] ~ { }; Export: PUBLIC PROC [eachExport: SunMount.EachExportProc, eachGroup: SunMount.EachGroupProc] ~ { }; <> TRUSTED {Process.Detach[FORK CookieMonster[] ];}; TRUSTED {Process.Detach[FORK PruneTimeCache[] ];}; EmptyCookie _ RefText.New[SunNFS.cookieSize]; LastCookie _ RefText.New[SunNFS.cookieSize]; FOR i: CARDINAL IN [0..SunNFS.cookieSize) DO EmptyCookie[i] _ VAL[0]; LastCookie[i] _ VAL[0]; ENDLOOP; LastCookie[0] _ VAL[100]; -- should this be stable and count up forever? END.