-- FTImpl.mesa -- Implementation of the File Transfer component of the -- Cedar Interim File System. -- Changed by Schroeder, November 11, 1982 11:43 am -- Changed by MBrown, November 22, 1982 10:59 pm DIRECTORY CIFS: TYPE USING [ConnectionErrors, Error, ErrorCode], CedarSnapshot: TYPE USING [Register, CheckpointProc, RollbackProc], ConvertUnsafe: TYPE USING [AppendRope, ToRope], Directory: TYPE USING [ignore, Lookup, Error, CreateFile, PutProperty, PropertyType, LookupUnlimited, RemoveFile, GetProps], DCSFileTypes: TYPE USING [tLeaderPage], Rope: TYPE USING [ROPE, Index, Size, Concat, Substr, Fetch, Equal, Compare, Cat], Environment: TYPE USING [bytesPerPage], File: TYPE USING [Capability, Create, GetSize, nullCapability, Delete, Unknown, MakePermanent, read, write, grow, shrink, delete, LimitPermissions], FileStream: TYPE USING [GetLeaderPropertiesForCapability], FT, FtpMan: TYPE USING [Retrieve, Login, GetFileInfo, Delete, Connect, Store], KernelFile: TYPE USING [MakeTemporary], Inline: TYPE USING [BITAND], LSD: TYPE USING [Entry, Lock, Unlock, Lookup, Create, GetVictim, Delete, SetDirty, ClearDirty], PropertyTypes: TYPE USING [tByteLength], Runtime: TYPE USING [BoundsFault], RTFiles: TYPE USING [IsFileInUse], System: TYPE USING [GetGreenwichMeanTime], Volume: TYPE USING [GetAttributes, SystemID]; FTImpl: MONITOR IMPORTS CIFS, CedarSnapshot, ConvertUnsafe, Directory, File, FileStream, FtpMan, KernelFile, Inline, LSD, Rope, RTFiles, System, Volume, Runtime EXPORTS FT SHARES File = { -- Default value for SetFreeSpace LocalDiskFreeSpace: LONG CARDINAL = 1000; -- pages -- Remote Name Property from Eric's code RemoteNameProperty: Directory.PropertyType = LOOPHOLE[213B]; -- Directory delimiter is / at this level dirDelim: CHARACTER = '/; dirD: Rope.ROPE = "/"; -- Initial File Size for Create initialSize: LONG CARDINAL = 5; -- globals. DEATH TO THE MDS! global: REF Global _ NIL; Global: TYPE = RECORD [ -- start time of the session sessionStart: LONG CARDINAL, -- reserved is the number of free pages that are committed -- while we FTP files to the local disk. reserved: LONG CARDINAL, -- number of pages always kept free baseFreeSpace: LONG CARDINAL ]; -- Public Procedures Close: PUBLIC PROC [fh: FT.OpenFile] = { -- Close a file IF fh=NIL THEN RETURN; -- See if we already did this IF fh.name=NIL THEN RETURN; -- if file not in LSD then return -- (must be a com soft file system file) IF fh.entry=NIL THEN RETURN; -- Reset the dirty hint if it's on but not true IF fh.entry.dirtyF AND fh.oldFile AND FileStream.GetLeaderPropertiesForCapability[cap: fh.fc].create = fh.entry.create THEN LSD.ClearDirty[fh.entry]; LSD.Unlock[fh.entry]; -- just for safety, zero out the open file object fh.name _ NIL; fh.entry _ NIL; fh.fc _ File.nullCapability; fh.mode _ 0; }; Connect: PUBLIC PROC[name: Rope.ROPE, password: Rope.ROPE] = { -- Set credentials FtpMan.Connect[name, password]; }; Delete: PUBLIC PROC[name: Rope.ROPE] = { -- Delete a file server, nameOnServer: Rope.ROPE; entry: LSD.Entry; wasLocked: BOOLEAN; [server, nameOnServer] _ BreakName[name]; -- HACK HACK IF Rope.Compare[server, "local", FALSE] = equal THEN { -- it is a local file localName: STRING _ [100]; fc: File.Capability; localName.length _ 0; {ENABLE Directory.Error => ERROR CIFS.Error[CIFS.ErrorCode[noSuchFile], Rope.Concat[name, " not found."]]; ConvertUnsafe.AppendRope[to: localName, from: nameOnServer ! Runtime.BoundsFault => CONTINUE]; fc _ Directory.LookupUnlimited[localName]; Directory.RemoveFile[localName, fc]; FileDelete[fc]; RETURN; }}; -- END HACK HACK [wasLocked, entry] _ LookupAndLock[name, TRUE]; IF entry#NIL THEN { -- file is on local disk IF NOT wasLocked THEN LockError[name]; FileDelete[entry.fc]; LSD.Delete[entry]; }; {ENABLE CIFS.Error => TRUSTED { IF code=CIFS.ErrorCode[noSuchFile] THEN CONTINUE ELSE REJECT }; -- Delete the file on the server FtpMan.Delete[server, nameOnServer]; }}; ExplicitBackup: PUBLIC PROC [name: Rope.ROPE] RETURNS[version: INT] = { -- Explict backup of a file -- Returns 0 if the file is current on the server leaderPageCreate, serverCreate: LONG CARDINAL; entry: LSD.Entry; wasLocked: BOOLEAN; server, nameOnServer: Rope.ROPE; [server, nameOnServer] _ BreakName[name]; -- HACK HACK for com soft dir system IF Rope.Compare[server, "local", FALSE] = equal THEN { -- file from com soft dir system of: FT.OpenFile _ Open[name, FT.read]; RETURN[FtpMan.Store[server, nameOnServer, of.fc]]; }; [wasLocked, entry] _ LookupAndLock[name, TRUE]; IF entry=NIL THEN ERROR CIFS.Error[CIFS.ErrorCode[noSuchFile], Rope.Cat[name, " not found"]]; IF NOT wasLocked THEN ERROR CIFS.Error[CIFS.ErrorCode[fileBusy], Rope.Cat[name, " is busy"]]; -- only store back if create date is earlier on server leaderPageCreate _ FileStream.GetLeaderPropertiesForCapability[cap: entry.fc ! File.Unknown => { LSD.Delete[entry]; ERROR CIFS.Error[CIFS.ErrorCode[undefinedError], Rope.Cat[name, " is malformed and was deleted"]]; }].create; serverCreate _ 0; serverCreate _ FtpMan.GetFileInfo[server, nameOnServer ! CIFS.Error => CHECKED {IF code = CIFS.ErrorCode[noSuchFile] THEN CONTINUE ELSE REJECT}].create; SELECT serverCreate FROM < leaderPageCreate => { -- local copy more recent version _ FtpMan.Store[server, nameOnServer, entry.fc]; entry.create _ leaderPageCreate; LSD.ClearDirty[entry] }; = leaderPageCreate => {-- no need to backup version _ 0; LSD.ClearDirty[entry] }; > leaderPageCreate => -- newer version out there version _ -1; ENDCASE; LSD.Unlock[entry]; RETURN[version]; }; GetBaseFreeSpace: PUBLIC ENTRY PROC RETURNS[pages: INT] = { -- Return the amount of free space we should keep -- on the local disk RETURN[LOOPHOLE[global.baseFreeSpace, INT]]; }; Login: PUBLIC PROC[name: Rope.ROPE, password: Rope.ROPE] = { -- Set credentials FtpMan.Login[name, password]; }; Rename: PUBLIC PROC[from: Rope.ROPE, to: Rope.ROPE] = { -- Rename file fileName: STRING _ [125]; of: FT.OpenFile; newEntry: LSD.Entry; server, nameOnServer: Rope.ROPE; [server, nameOnServer] _ BreakName[from]; -- first, open the "from" file of _ Open[from, FT.read + FT.write]; {ENABLE UNWIND => Close[of]; -- now create the "to" file newEntry _ LSD.Lookup[to]; IF newEntry#NIL THEN -- to file already exists ERROR CIFS.Error[CIFS.ErrorCode[fileAlreadyExists], Rope.Concat[to, " already exists"]]; newEntry _ LSDCreate[to, of.fc, of.entry.create]; -- now reset the name property fileName.length _ 0; ConvertUnsafe.AppendRope[to: fileName, from: to ! Runtime.BoundsFault => CONTINUE]; Directory.PutProperty[of.fc, RemoteNameProperty, DESCRIPTOR[fileName, SIZE[StringBody[fileName.maxlength]]], TRUE]; -- say the "to" file has new contents LSD.SetDirty[newEntry]; LSD.Unlock[newEntry]; -- and delete the "from" file LSD.Delete[of.entry]; {ENABLE CIFS.Error => TRUSTED { IF code=CIFS.ErrorCode[noSuchFile] THEN CONTINUE ELSE REJECT }; FtpMan.Delete[server, nameOnServer]; }}}; Open: PUBLIC PROC[name: Rope.ROPE, mode: FT.Mode] RETURNS[fh: FT.OpenFile] = { -- Open a file. fc: File.Capability; -- this string has to be 125 chars long for Eric's stuff -- to work fileName: STRING _ [125]; createFile: BOOLEAN _ (Inline.BITAND[mode, FT.create + FT.replace]#0); found: BOOLEAN _ TRUE; create, count, needed: LONG CARDINAL; entry: LSD.Entry; wasLocked: BOOLEAN; server, nameOnServer: Rope.ROPE; exclusive: BOOLEAN _ IF Inline.BITAND[mode, FT.write]#0 THEN TRUE ELSE FALSE; -- allocate open file object fh _ NEW[FT.FileObject]; fh.mode _ mode; fh.entry _ NIL; fh.name _ name; -- break path into server and file name [server, nameOnServer] _ BreakName[name]; -- HACK: if server is "local" use Common Software Dir System IF Rope.Equal[server, "local"] THEN { ConvertUnsafe.AppendRope[to: fileName, from: nameOnServer ! Runtime.BoundsFault => CONTINUE]; fc _ Directory.Lookup[fileName: fileName, permissions: IF exclusive THEN File.read+File.write+File.grow+File.shrink+File.delete ELSE Directory.ignore ! Directory.Error => {found _ FALSE; CONTINUE}]; IF NOT exclusive THEN fc.permissions _ File.read; -- we have a problem if: -- (1) The file exists already -- (2) The user wants to write on it -- (3) The run time system is using it IF found AND exclusive AND RTFiles.IsFileInUse[fc] THEN { -- if we are in replace mode, then create a new -- empty file IF Inline.BITAND[mode, FT.replace]#0 THEN { -- first, take old file out of the directory system Directory.RemoveFile[fileName: fileName, file: fc]; -- make the old file temporary KernelFile.MakeTemporary[fc]; -- now say we must create a new file found _ FALSE; } ELSE -- we are not in replace mode. Give an error LockError[name]; }; -- create a new file if necessary IF NOT found THEN { -- file not found. -- if not mode does not include create, it's an error IF Inline.BITAND[mode, FT.create + FT.replace]=0 THEN ERROR CIFS.Error[CIFS.ErrorCode[noSuchFile], Rope.Concat[name, " not found"]]; -- create the file ReserveFreeSpace[initialSize]; fc _ Directory.CreateFile[fileName: fileName, size: initialSize, fileType: DCSFileTypes.tLeaderPage]; UsedSpace[initialSize]; } ELSE { -- file was found in com soft dir system fileName.length _ 0; [] _ Directory.GetProps[file: fc, name: fileName]; fh.name _ Rope.Cat["/local/", ConvertUnsafe.ToRope[fileName]]; }; fh.fc _ fc; RETURN[fh]; }; -- END OF HACK -- not a com soft dir system file -- see if the file is on the local disk and locl for reading [wasLocked, entry] _ LookupAndLock[name, FALSE]; IF entry#NIL THEN { IF NOT wasLocked THEN LockError[name] ELSE IF entry.fc=File.nullCapability THEN { LSD.Delete[entry]; entry _ NIL }; }; IF entry#NIL THEN { -- file is on the local disk ENABLE File.Unknown => { -- Internal error. Flush the LSD Entry LSD.Delete[entry]; ERROR CIFS.Error[CIFS.ErrorCode[noSuchFile], Rope.Concat[name, " caused an internal error. Try again"]]; }; -- file is already locked for reading -- Reset the dirty hint if it's on but not true IF entry.dirtyF AND FileStream.GetLeaderPropertiesForCapability[entry.fc].create = entry.create THEN LSD.ClearDirty[entry]; -- see if the local copy is current if -- (1) we have not already checked the file this session -- (2) the local file is not dirty -- (3) the mode is not create -- (4) the mode is not dontCheck -- (5) the mode is not replace IF (Inline.BITAND[mode, FT.dontCheck+FT.create+FT.replace]=0) AND (entry.checked < global.sessionStart) AND (NOT entry.dirtyF) THEN { -- need to check to see if the file is still current IF NOT ChangeLock[entry, TRUE] THEN LockError[name]; entry.checked _ System.GetGreenwichMeanTime[]; [create, count] _ FtpMan.GetFileInfo[server, nameOnServer ! UNWIND => LSD.Unlock[entry]; CIFS.Error => TRUSTED { SELECT code FROM CIFS.ErrorCode[noSuchFile] => {found _ FALSE; CONTINUE}; IN CIFS.ConnectionErrors => {create _ entry.create; CONTINUE}; ENDCASE => REJECT; } ]; IF NOT found THEN { -- Not found, and mode is not create. Error! LSD.Delete[entry]; ERROR CIFS.Error[CIFS.ErrorCode[noSuchFile], Rope.Concat[name, " not found."]]; }; IF create > entry.create THEN { -- local copy is obsolete and we need to bring over the remote copy -- first, make sure we have enough room needed _ MAX[LOOPHOLE[Pages[count] - File.GetSize[entry.fc], LONG INTEGER], 0]; ReserveFreeSpace[needed]; FtpMan.Retrieve[server, nameOnServer, entry.fc]; entry.create _ create; -- we used the space UsedSpace[needed]; }; }; -- copy on local disk is current -- ensure that the runtime is not using the file if the client wants -- to write on it IF exclusive AND RTFiles.IsFileInUse[fh.fc] THEN { -- file is in use IF Inline.BITAND[mode, FT.replace]#0 THEN { -- user does not wish previous file contents. go ahead and get rid of the file KernelFile.MakeTemporary[entry.fc]; -- now do in the fc in the LSD entry entry.fc _ File.nullCapability; -- now get rid of the LSD entry LSD.Delete[entry]; -- now say we should create a new file entry _ NIL; createFile _ TRUE; } ELSE { -- paul is using the file and the user wants to write -- on it. give a lock error LSD.Unlock[entry]; LockError[name]; }; } ELSE { -- runtime is not using the file IF NOT ChangeLock[entry, exclusive] THEN LockError[name]; fh.fc _ entry.fc; }; }; -- now handle the following cases -- (1) the file was not already on the local disk -- (2) the file was on the local disk, but a new file must be created because: -- (2.1) the user wanted to write on the file -- (2.2) the runtime was using the file -- (2.3) the user opened the file in replace mode IF entry=NIL THEN { -- File is not on the local disk. -- Reserve space for LSD Entry entry _ LSDCreate[name, File.nullCapability, 0]; {ENABLE UNWIND => LSD.Delete[entry]; -- if not create mode, prepare to fetch remote file IF (NOT createFile) THEN { -- not create mode. get remote file size [create, count] _ FtpMan.GetFileInfo[server, nameOnServer]; }; IF createFile THEN create _ System.GetGreenwichMeanTime[]; -- make sure we have enough room -- in Pages case add one for leader page needed _ IF createFile THEN initialSize ELSE Pages[count]+1; ReserveFreeSpace[needed]; fc _ File.nullCapability; -- HACK: Put in common software dir system -- However, don't put dir.dir in local fs -- turn off for the time being IF FALSE THEN {entryN: Rope.ROPE _ EntryName[name]; IF Rope.Compare[entryN, "dir.dir!h", FALSE] # equal THEN { ConvertUnsafe.AppendRope[to: fileName, from: entryN ! Runtime.BoundsFault => CONTINUE]; fc _ Directory.CreateFile[fileName: fileName, size: needed, fileType: DCSFileTypes.tLeaderPage ! Directory.Error => CONTINUE]; }; }; -- END OF HACK -- If not created in dir system, then create an anonymous file IF fc=File.nullCapability THEN fc _ File.Create[volume: Volume.SystemID[], initialSize: needed, type: DCSFileTypes.tLeaderPage]; UsedSpace[needed]; -- now get the file IF (NOT createFile) THEN FtpMan.Retrieve[server, nameOnServer, fc] ELSE { -- not found. zero byte count of new file count _ 0; Directory.PutProperty[file: fc, property: PropertyTypes.tByteLength, propertyValue: DESCRIPTOR[@count, SIZE[LONG CARDINAL]]]; }; -- update LSD entry to point to file and have correct create date entry _ LSDCreate[name, fc, create]; -- put file name in property list of file fileName.length _ 0; ConvertUnsafe.AppendRope[to: fileName, from: name ! Runtime.BoundsFault => CONTINUE]; Directory.PutProperty[fc, RemoteNameProperty, DESCRIPTOR[fileName, SIZE[StringBody[fileName.maxlength]]], TRUE]; -- now make file permanent File.MakePermanent[file: fc]; }; -- ENABLE UNWIND IF NOT exclusive THEN { -- change lock mode IF NOT ChangeLock[entry, FALSE] THEN LockError[name]; }; fh.fc _ fc; -- now have file on local disk }; -- return open file fh.oldFile _ found; fh.entry _ entry; -- if the file is open for write, then mark it dirty to be conservative in case -- of a crash. if the file is just open for reading, then limit permissions to -- reflect this. IF exclusive THEN LSD.SetDirty[entry] ELSE fh.fc _ File.LimitPermissions[fh.fc, File.read]; RETURN[fh]; }; Reset: PUBLIC PROC[file: Rope.ROPE] = { -- Resets a file's contents from the backing store wasLocked: BOOLEAN; entry: LSD.Entry; [wasLocked, entry] _ LookupAndLock[file, TRUE]; IF entry=NIL THEN ERROR CIFS.Error[CIFS.ErrorCode[noSuchFile], Rope.Concat[file, " not found."]]; IF NOT wasLocked THEN LockError[file]; -- eliminate the local copy FileDelete[entry.fc ! File.Unknown => CONTINUE]; LSD.Delete[entry]; }; SetBaseFreeSpace: PUBLIC PROC [pages: INT _ 0] = { -- sets the number of pages we would like to keep free IF pages#0 THEN global.baseFreeSpace _ LOOPHOLE[pages]; -- now force deletion till we get to this level [] _ XReserveFreeSpace[0]; }; Swap: PUBLIC PROC[filea: Rope.ROPE, fileb: Rope.ROPE] = { -- Swaps the contents of filea and fileb fileName: STRING _ [125]; ofa: FT.OpenFile _ Open[filea, FT.read+FT.write]; ofb: FT.OpenFile; tfc: File.Capability; tcd: LONG CARDINAL; {ENABLE UNWIND => Close[ofa]; ofb _ Open[fileb, FT.read+FT.write]; -- Swap the file capabilities tfc _ ofa.entry.fc; tcd _ ofa.entry.create; ofa.entry.fc _ ofb.entry.fc; ofa.entry.create _ ofb.entry.create; ofb.entry.fc _ tfc; ofb.entry.create _ tcd; -- Now set file name properties fileName.length _ 0; ConvertUnsafe.AppendRope[to: fileName, from: filea ! Runtime.BoundsFault => CONTINUE]; Directory.PutProperty[ofa.entry.fc, RemoteNameProperty, DESCRIPTOR[fileName, SIZE[StringBody[fileName.maxlength]]], TRUE]; fileName.length _ 0; ConvertUnsafe.AppendRope[to: fileName, from: fileb ! Runtime.BoundsFault => CONTINUE]; Directory.PutProperty[ofb.entry.fc, RemoteNameProperty, DESCRIPTOR[fileName, SIZE[StringBody[fileName.maxlength]]], TRUE]; -- Say that the contents have changed LSD.SetDirty[ofa.entry]; LSD.SetDirty[ofb.entry]; Close[ofa]; Close[ofb]; }}; -- Internal Procedures BreakName: PROC[name: Rope.ROPE] RETURNS[server, file: Rope.ROPE] = { -- returns a server name and file name from a path name fs: LONG INTEGER _ Rope.Index[name, 1, dirD]; IF ((fs+1)>=Rope.Size[name]) OR (Rope.Fetch[name, 0]#dirDelim) THEN ERROR CIFS.Error[ CIFS.ErrorCode[illegalFileName], Rope.Concat[name, " is an illegal name"]] ELSE { server _ Rope.Substr[name, 1, fs-1]; file _ Rope.Substr[name, fs + 1, Rope.Size[name]-fs-1]}; }; FileDelete: PROC [fc: File.Capability] = { -- Should be used instead of File.Delete -- Deletes a file if Paul does not have his mitts on it IF RTFiles.IsFileInUse[fc] THEN -- file is in use -- make it temporary KernelFile.MakeTemporary[fc] ELSE -- okay to delete file File.Delete[fc]; }; MakeSpace: PROC RETURNS [failed: BOOLEAN] = { -- make space by deleting a file victim: LSD.Entry _ LSD.GetVictim[]; IF victim=NIL THEN RETURN[TRUE]; FileDelete[victim.fc ! File.Unknown => CONTINUE]; LSD.Delete[victim]; RETURN[FALSE]; }; LockError: PROC[file: Rope.ROPE] = { -- called when a file can not be locked ERROR CIFS.Error[ CIFS.ErrorCode[fileBusy], Rope.Concat[file, " is busy."]]; }; LSDCreate: PROC[name: Rope.ROPE, fc: File.Capability, create: LONG CARDINAL] RETURNS [entry: LSD.Entry] = { -- get an LSD entry. If the LSD is full, delete files until there is enough -- room entry _ LSD.Create[name, fc, create]; UNTIL entry#NIL DO IF MakeSpace[] THEN ERROR CIFS.Error[ CIFS.ErrorCode[localDiskFull], "The LSD is full. Try typing Backup. If that fails, call 4478"]; entry _ LSD.Create[name, fc, create]; ENDLOOP; }; EntryName: PROC[name: Rope.ROPE] RETURNS[entry: Rope.ROPE] = { -- returns the entry name component of a path length: LONG INTEGER _ 0; start: LONG INTEGER; FOR start DECREASING IN [0..Rope.Size[name]) DO IF Rope.Fetch[name, start]=dirDelim THEN EXIT; length _ length + 1; ENDLOOP; RETURN[Rope.Substr[name, start+1, length]]; }; Pages: PROC[count: LONG CARDINAL] RETURNS[pages: LONG CARDINAL] = { -- computes page count from byte count RETURN[(count+Environment.bytesPerPage-1)/Environment.bytesPerPage] }; ReserveFreeSpace: PROC[pages: LONG CARDINAL] = { -- ensures that there is enough free space on the local disk IF XReserveFreeSpace[pages] THEN ERROR CIFS.Error[ CIFS.ErrorCode[localDiskFull], "The local disk is full. Type Backup"]; }; XReserveFreeSpace: ENTRY PROC[pages: LONG CARDINAL] RETURNS [failed: BOOLEAN] = { -- reserve pages of space temporarily -- pages are returned by UsedSpace freePages: LONG CARDINAL _ Volume.GetAttributes[Volume.SystemID[]].freePageCount; desiredFree: LONG CARDINAL; failed _ FALSE; -- increase the number of pages temp. reserved global.reserved _ global.reserved + pages; -- the total number of free pages that we would like is desiredFree _ global.reserved + global.baseFreeSpace; -- now try to get to this number of free pages {ENABLE UNWIND => NULL; UNTIL freePages >= desiredFree DO IF MakeSpace[] THEN EXIT; freePages _ Volume.GetAttributes[Volume.SystemID[]].freePageCount; ENDLOOP; -- if we have enough for our temporary needs, then we are ok IF global.reserved > freePages THEN RETURN[TRUE]; RETURN[FALSE]; }}; UsedSpace: ENTRY PROC[pages: LONG CARDINAL] = { -- used free space that was reserved global.reserved _ IF pages > global.reserved THEN 0 ELSE global.reserved - pages; }; LookupAndLock: ENTRY PROC[name: Rope.ROPE, exculsive: BOOLEAN] RETURNS [wasLocked: BOOLEAN, e: LSD.Entry] = { wasLocked _ FALSE; e _ LSD.Lookup[name]; IF e # NIL THEN wasLocked _ LSD.Lock[e, exculsive]; }; ChangeLock: ENTRY PROC[e: LSD.Entry, exculsive: BOOLEAN] RETURNS [BOOLEAN] = { LSD.Unlock[e]; RETURN [LSD.Lock[e, exculsive]]; }; -- Checkpoint and Rollback CheckpointP: CedarSnapshot.CheckpointProc = { -- currently FTImpl does not have to do anything at checkpoint time }; RollbackP: CedarSnapshot.RollbackProc = { -- At rollback time, mark a session boundary global.sessionStart _ System.GetGreenwichMeanTime[]; }; Init: PROC = { global _ NEW[Global]; global.sessionStart _ System.GetGreenwichMeanTime[]; global.reserved _ 0; SetBaseFreeSpace[LocalDiskFreeSpace]; CedarSnapshot.Register[CheckpointP, RollbackP]; }; -- Start trap intializes Init[]; }..