-- 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: BOOLEANTRUE;
create, count, needed: LONG CARDINAL;
entry: LSD.Entry;
wasLocked: BOOLEAN;
server, nameOnServer: Rope.ROPE;
exclusive: BOOLEANIF 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[];


}..