-- File: AltoFileOpsB.mesa -- Last edited by Levin: 30-Apr-81 14:24:14 DIRECTORY AltoDefs USING [PageSize], AltoFile USING [ AllocateDiskPage, ByteNumber, CFP, CloseDirectory, DeleteFP, DirHandle, DiskFull, FileAlreadyExists, FP, FreeDiskPage, IllegalFileName, InvalidFP, LDPtr, MapFileNameToFP, NewSN, NoSuchFile, OpenDirectory, PageNumber, VersionOption], AltoFileDefs USING [FA, TIME], AltoFilePrivate USING [ AltoPageNumber, DoDiskRequest, FileHandle, FileObject, FindEOF, GetvDAForPage, infinityPage, initialRuns, InsertFile, LastPageBytes, minUsefulRuns, openSeal, PurgeFile, ReleaseFile, RunIndex, RunTable, TruncatevDATable, VdaRun], DiskIODefs USING [ CompletionStatus, DiskError, DiskRequest, eofvDA, FID, fillInvDA, PageCount, RequestID, vDA, XferSpec], FileDefs USING [ ComparePositions, defaultTime, FileTime, FSInstance, OpenOptions, Position], MiscDefs USING [Zero], Mopcodes USING [zEXCH], StringDefs USING [MesaToBcplString], VMDefs USING [CantOpen, Error], VMStorage USING [AllocatePage, FreePage, shortTerm]; AltoFileOpsB: MONITOR LOCKS file.LOCK USING file: AltoFilePrivate.FileHandle IMPORTS AltoFile, AltoFilePrivate, DiskIODefs, FileDefs, MiscDefs, StringDefs, VMDefs, VMStorage EXPORTS AltoFile, AltoFilePrivate, FileDefs = BEGIN OPEN AltoFile, AltoFilePrivate, DiskIODefs, FileDefs; -- Miscellaneous Declarations -- unknownLengthFA: AltoFileDefs.FA = [da: eofvDA, page: 0, byte: 0]; IllegalExtend: ERROR = CODE; IllegalTruncate: ERROR = CODE; InvalidFile: ERROR = CODE; -- Types Exported to FileDefs -- FileObject: PUBLIC TYPE = AltoFilePrivate.FileObject; -- Operations (exported to AltoFilePrivate on behalf of FileDefs) -- Open: PUBLIC PROCEDURE [ instance: FileDefs.FSInstance, name: STRING, options: FileDefs.OpenOptions ← oldReadOnly] RETURNS [FileHandle] = BEGIN DoOpen: PROCEDURE [file: FileHandle, newlyOpened: BOOLEAN] RETURNS [worked: BOOLEAN] = {RETURN[OpenFromHandle[file, newlyOpened, options ~= oldReadOnly]]}; fv: VersionOption = SELECT options FROM oldReadOnly, old => old, new => new, ENDCASE => oldOrNew; fp: FP ← MapFileNameToFP[name, fv ! DiskFull => ERROR VMDefs.Error[resources]; NoSuchFile => ERROR VMDefs.CantOpen[notFound]; IllegalFileName => ERROR VMDefs.CantOpen[illegalFileName]; FileAlreadyExists => ERROR VMDefs.CantOpen[alreadyExists]]; RETURN[InsertFile[fp, DoOpen ! DiskError => ERROR VMDefs.Error[io]]] END; Close, CloseFile: PUBLIC PROCEDURE [file: FileHandle] = BEGIN DoClose: PROCEDURE [file: FileHandle] = BEGIN OPEN VMStorage; UpdateLengthHint[file ! DiskError => ERROR VMDefs.Error[io]]; shortTerm.FREE[@file.runTable]; END; ValidateFile[file]; ReleaseFile[file, DoClose]; END; Abandon: PUBLIC PROCEDURE [file: FileHandle] = BEGIN DoAbandon: PROCEDURE [file: FileHandle] = {VMStorage.shortTerm.FREE[@file.runTable]}; ValidateFile[file]; ReleaseFile[file, DoAbandon]; END; Destroy: PUBLIC PROCEDURE [file: FileHandle] = BEGIN DoDestroy: PROCEDURE [file: FileHandle] = BEGIN dirFP: FP; fileFP: FP ← FP[serial: file.fileID.serial, leaderDA: file.leadervDA]; ThrowAwayFilePages: PROCEDURE = BEGIN request: DiskRequest; leader: LDPtr ← ReadLeaderPage[file, @request]; dirFP ← FP[serial: leader.dirFP.serial, leaderDA: leader.dirFP.leaderDA]; VMStorage.FreePage[leader]; EnsureKnownVDAs[file, 0]; -- discover any unknown disk addresses FreeFileTail[file, 0]; END; ThrowAwayFilePages[ ! DiskError => ERROR VMDefs.Error[io]]; VMStorage.shortTerm.FREE[@file.runTable]; BEGIN OPEN AltoFile; dir: DirHandle = OpenDirectory[dirFP ! InvalidFP => GO TO SkipDirectoryDelete]; [] ← DeleteFP[dir, @fileFP]; CloseDirectory[dir]; EXITS SkipDirectoryDelete => NULL; END; END; ValidateFile[file]; PurgeFile[file, DoDestroy]; END; GetLength: PUBLIC ENTRY PROCEDURE [file: FileHandle] RETURNS [Position] = BEGIN page: AltoPageNumber; bytes: LastPageBytes; ValidateFile[file]; IF ~file.lengthKnown THEN FindEOF[file ! UNWIND => NULL]; page ← file.lastPage; bytes ← file.bytes; RETURN[MakeFileLength[page, bytes]] END; SetLength: PUBLIC PROCEDURE [file: FileHandle, length: Position] = BEGIN oldLength: Position; ValidateFile[file]; oldLength ← GetLength[file]; SELECT ComparePositions[oldLength, length] FROM less => BEGIN buffer: POINTER = VMStorage.AllocatePage[]; BEGIN ENABLE DiskError => {VMStorage.FreePage[buffer]; ERROR VMDefs.Error[io]}; tempLength: Position ← [page: oldLength.page, byte: 0]; IF oldLength.byte ~= 0 THEN -- read partial last page into buffer BEGIN xferSpec: ARRAY [0..1) OF XferSpec ← [[buffer, GetvDAForPage[file, file.lastPage], 0]]; request: DiskRequest ← [firstPage: file.lastPage, fileID:, firstPagevDA:, pagesToSkip: 0, nonXferID:, proc:, xfers: DESCRIPTOR[@xferSpec, 1], noRestore: FALSE, command: ReadD[]]; [] ← DoDiskRequest[@request, file]; END; -- not the most efficient algorithm... UNTIL tempLength.page = length.page DO tempLength.page ← tempLength.page + 1; Extend[file, tempLength, buffer]; ENDLOOP; IF length.byte ~= 0 THEN Extend[file, length, buffer]; END; VMStorage.FreePage[buffer]; END; equal => NULL; greater => Truncate[file, length]; ENDCASE; END; Extend: PUBLIC ENTRY PROCEDURE [ file: FileHandle, length: Position, buffer: POINTER] = BEGIN xferSpec: ARRAY [0..2) OF XferSpec; request: DiskRequest; lastvDA: vDA; oldLength: Position; needANewPage: BOOLEAN = (length.byte = 0); ValidateFile[file]; IF ~file.lengthKnown THEN FindEOF[file ! UNWIND => NULL]; oldLength ← MakeFileLength[file.lastPage, file.bytes]; IF ComparePositions[length, oldLength] ~= greater OR ~(length.page = oldLength.page OR (length.page = oldLength.page + 1 AND length.byte = 0)) THEN ERROR IllegalExtend; xferSpec[0] ← [buffer, (lastvDA ← GetvDAForPage[file, file.lastPage]), 0]; request ← DiskRequest[ firstPage: file.lastPage, fileID:, firstPagevDA:, pagesToSkip: 0, nonXferID:, proc:, xfers: DESCRIPTOR[@xferSpec, 1], noRestore: FALSE, command: WriteLD[ next: eofvDA, prev: GetvDAForPage[file, file.lastPage - 1], lastByteCount: AltoByteCount[length.byte] - 1]]; IF needANewPage THEN BEGIN -- the following hack writes the client's data in the shadow page. xferSpec[1] ← [buffer, AllocateDiskPage[lastvDA ! UNWIND => NULL], 0]; request.xfers ← DESCRIPTOR[@xferSpec, 2]; END; [] ← DoDiskRequest[@request, file]; IF needANewPage THEN {file.lastPage ← file.lastPage + 1; file.bytes ← 0} ELSE file.bytes ← AltoByteCount[length.byte] - 1; file.lengthChanged ← TRUE; END; Truncate: PUBLIC ENTRY PROCEDURE [file: FileHandle, length: Position] = BEGIN newLastPage: AltoPageNumber = AltoFromFilePageNumber[length.page]; pagesToFree: BOOLEAN; WriteNewLastPage: PROCEDURE = -- truncates the file at 'newLastPage', setting the appropriate byte count. BEGIN buffer: POINTER ← VMStorage.AllocatePage[]; xferSpec: ARRAY [0..1) OF XferSpec ← [[buffer, GetvDAForPage[file, newLastPage], 0]]; request: DiskRequest ← [firstPage: newLastPage, fileID: file.fileID, firstPagevDA:, pagesToSkip: 0, nonXferID:, proc:, xfers: DESCRIPTOR[@xferSpec, 1], noRestore: FALSE, command: ReadD[]]; [] ← DoDiskRequest[@request, file ! DiskError => {VMStorage.FreePage[buffer]; ERROR VMDefs.Error[io]}]; request.command ← WriteLD[ next: eofvDA, prev: GetvDAForPage[file, newLastPage - 1], lastByteCount: AltoByteCount[length.byte] - 1]; [] ← DoDiskRequest[@request, file ! DiskError => {VMStorage.FreePage[buffer]; ERROR VMDefs.Error[io]}]; VMStorage.FreePage[buffer]; END; ValidateFile[file]; IF ~file.lengthKnown THEN FindEOF[file ! UNWIND => NULL]; SELECT ComparePositions[length, MakeFileLength[file.lastPage, file.bytes]] FROM less => BEGIN IF (pagesToFree ← file.lastPage > newLastPage) THEN EnsureKnownVDAs[file, newLastPage - 1]; WriteNewLastPage[]; IF pagesToFree THEN {FreeFileTail[file, newLastPage + 1]; TruncatevDATable[file, newLastPage]}; file.lastPage ← newLastPage; file.bytes ← AltoByteCount[length.byte] - 1; file.lengthChanged ← TRUE; END; equal => NULL; greater => ERROR IllegalTruncate; ENDCASE; END; GetTimes: PUBLIC PROCEDURE [file: FileHandle] RETURNS [read, write, create: FileTime] = GetFileTimes; SetCreationTime: PUBLIC PROCEDURE [ file: FileHandle, create: FileTime ← defaultTime] = BEGIN request: DiskRequest; leader: LDPtr = ReadLeaderPage[file, @request]; IF create = defaultTime THEN create ← LeaderToMesaTime[DayTime[]]; leader.created ← MesaToLeaderTime[create]; RewriteLeaderPage[file, @request, leader]; END; -- Procedures Exported to AltoFile -- OpenFromFP: PUBLIC PROCEDURE [fp: FP, markWritten: BOOLEAN] RETURNS [FileHandle] = BEGIN DoOpen: PROCEDURE [file: FileHandle, newlyOpened: BOOLEAN] RETURNS [worked: BOOLEAN] = {RETURN[OpenFromHandle[file, newlyOpened, markWritten]]}; RETURN[InsertFile[fp, DoOpen ! DiskError => ERROR VMDefs.Error[io]]] END; CreateFile: PUBLIC PROCEDURE [ name: STRING, directory: FP, leadervDA: vDA ← fillInvDA] RETURNS [FP] = BEGIN leader: LDPtr; fp: FP; vDAtoTry: vDA ← IF leadervDA = fillInvDA THEN vDA[0] ELSE vDA[leadervDA - 1]; lastPagevDA: vDA; xferSpec: ARRAY [0..2) OF XferSpec; request: DiskRequest; xferSpec[0].diskAddress ← vDAtoTry ← AllocateDiskPage[vDAtoTry]; xferSpec[1].diskAddress ← lastPagevDA ← AllocateDiskPage[vDAtoTry]; xferSpec[0].buffer ← xferSpec[1].buffer ← leader ← VMStorage.AllocatePage[]; fp ← FP[NewSN[], vDAtoTry]; MiscDefs.Zero[leader, AltoDefs.PageSize]; leader.created ← DayTime[]; StringDefs.MesaToBcplString[name, LOOPHOLE[@leader.name]]; leader.propBegin ← @leader.props[0] - leader; leader.propLength ← LENGTH[leader.props]; leader.dirFP ← CFP[directory.serial, 1, 0, directory.leaderDA]; leader.eofFA ← AltoFileDefs.FA[da: lastPagevDA, page: 1, byte: 0]; request ← DiskRequest[ firstPage: 0, fileID: FID[1, fp.serial], firstPagevDA:, pagesToSkip: 0, nonXferID:, proc:, xfers: DESCRIPTOR[@xferSpec, 2], noRestore: FALSE, command: WriteLD[next: eofvDA, prev: eofvDA, lastByteCount: 0]]; [] ← DoDiskRequest[@request ! DiskError => VMStorage.FreePage[leader]]; VMStorage.FreePage[leader]; RETURN[fp] END; GetFileTimes: PUBLIC PROCEDURE [file: FileHandle] RETURNS [read, write, create: FileTime] = BEGIN request: DiskRequest; leader: LDPtr = ReadLeaderPage[file, @request]; read ← LeaderToMesaTime[leader.read]; write ← LeaderToMesaTime[leader.written]; create ← LeaderToMesaTime[leader.created]; VMStorage.FreePage[leader]; END; SetFileTimes: PUBLIC PROCEDURE [file: FileHandle, read, write, create: FileTime ← defaultTime] = BEGIN now: FileTime = LeaderToMesaTime[DayTime[]]; request: DiskRequest; leader: LDPtr = ReadLeaderPage[file, @request]; IF read = defaultTime THEN read ← now; IF write = defaultTime THEN write ← now; IF create = defaultTime THEN create ← now; leader.read ← MesaToLeaderTime[read]; leader.written ← MesaToLeaderTime[write]; leader.created ← MesaToLeaderTime[create]; RewriteLeaderPage[file, @request, leader]; END; ReadLeaderPage: PUBLIC PROCEDURE [file: FileHandle, request: POINTER TO DiskRequest] RETURNS [leader: LDPtr] = -- reads the leader page of 'file' into core, returning a pointer to it and leaving -- request↑ suitable for calling RewriteLeaderPage. BEGIN xferSpec: ARRAY [0..1) OF XferSpec; leader ← VMStorage.AllocatePage[]; xferSpec[0] ← [leader, file.leadervDA, 0]; request↑ ← [firstPage: 0, fileID:, firstPagevDA:, pagesToSkip: 0, nonXferID:, proc:, xfers: DESCRIPTOR[@xferSpec, 1], noRestore: FALSE, command: ReadD[]]; [] ← DoDiskRequest[request, file ! DiskError => VMStorage.FreePage[leader]]; END; RewriteLeaderPage: PUBLIC PROCEDURE [ file: FileHandle, request: POINTER TO DiskRequest, leader: LDPtr] = -- writes the leader page 'leader' onto 'file'. The storage associated with -- 'leader' is then released. BEGIN xferSpec: ARRAY [0..1) OF XferSpec ← [[leader, file.leadervDA, 0]]; request.xfers ← DESCRIPTOR[@xferSpec, 1]; request.command ← WriteD[]; [] ← DoDiskRequest[request, file ! DiskError => VMStorage.FreePage[leader]]; VMStorage.FreePage[leader]; END; -- Internal Procedures -- -- Open -- OpenFromHandle: PROCEDURE [file: FileHandle, checkLength, markWritten: BOOLEAN] RETURNS [worked: BOOLEAN] = BEGIN leader: LDPtr = VMStorage.AllocatePage[]; request: DiskRequest; xferSpec: ARRAY [0..1) OF XferSpec ← [[leader, file.leadervDA, 0]]; status: CompletionStatus; worked ← FALSE; request ← [firstPage: 0, fileID:, firstPagevDA:, pagesToSkip: 0, nonXferID:, proc:, xfers: DESCRIPTOR[@xferSpec, 1], noRestore: FALSE, command: ReadD[]]; file.runTable ← VMStorage.shortTerm.NEW[RunTable[initialRuns]]; file.nRuns ← minUsefulRuns; file.runTable[0] ← VdaRun[page: 0, vda: file.leadervDA]; file.runTable[1] ← VdaRun[page: 1, vda: fillInvDA]; file.runTable[2] ← VdaRun[page: infinityPage, vda: eofvDA]; [status, , ] ← DoDiskRequest[@request, file, FALSE]; -- disk error => InvalidFP IF status = ok THEN BEGIN leader.read ← DayTime[]; IF markWritten THEN leader.written ← leader.created ← leader.read; request.command ← WriteD[]; [] ← DoDiskRequest[@request, file ! DiskError => VMStorage.FreePage[leader]]; IF checkLength THEN -- validate length hint BEGIN eofFA: AltoFileDefs.FA = leader.eofFA; file.lengthKnown ← FALSE; IF eofFA.da ~= unknownLengthFA.da THEN BEGIN -- length hint is present - validate it. next: vDA; bytes: LastPageBytes; request.firstPage ← eofFA.page; xferSpec[0].diskAddress ← eofFA.da; request.command ← ReadD[]; request.noRestore ← TRUE; -- anticipate possible check error [status, next, bytes] ← DoDiskRequest[@request, file , FALSE]; IF status = ok AND next = eofvDA THEN BEGIN file.lastPage ← request.firstPage; file.lengthChanged ← (file.bytes ← bytes) ~= eofFA.byte; file.lengthKnown ← TRUE; END; END; END; worked ← TRUE; END; VMStorage.FreePage[leader]; END; -- Truncation Utilities -- EnsureKnownVDAs: PROCEDURE [file: FileHandle, tail: AltoPageNumber] = -- ensures that the vdas for all (Alto) pages >= 'tail' are known. BEGIN table: POINTER TO RunTable = file.runTable; IF file.lengthKnown THEN FOR i: RunIndex DECREASING IN [0..file.nRuns - 3] DO IF table[i].vda = fillInvDA THEN EXIT; IF table[i].page <= tail THEN RETURN; ENDLOOP; TruncatevDATable[file, tail]; FindEOF[file]; END; FreeFileTail: PROCEDURE [file: FileHandle, tail: AltoPageNumber] = -- releases all disk pages of 'file' beginning with 'tail'. It is assumed that -- EnsureKnownVDAs has previously been called to fill the vda table appropriately. BEGIN FOR page: AltoPageNumber IN [tail..file.lastPage] DO FreeDiskPage[GetvDAForPage[file, page] ! DiskError => CONTINUE]; ENDLOOP; END; UpdateLengthHint: PROCEDURE [file: FileHandle] = -- rewrites the length hint into the leader page of the specified file. Note: it -- is assumed that appropriate mutual exclusion on the file object has been -- performed by the caller. BEGIN leader: LDPtr; request: DiskRequest; lastPagevDA: vDA; IF ~(file.lengthKnown AND file.lengthChanged) THEN RETURN; leader ← ReadLeaderPage[file, @request]; lastPagevDA ← GetvDAForPage[file, file.lastPage]; leader.eofFA ← IF lastPagevDA = fillInvDA THEN unknownLengthFA ELSE AltoFileDefs.FA[da: lastPagevDA, page: file.lastPage, byte: file.bytes]; RewriteLeaderPage[file, @request, leader]; END; -- Miscellaneous Procedures -- ValidateFile: PROCEDURE [file: FileHandle] = {IF file.seal ~= openSeal THEN ERROR InvalidFile}; AltoFromFilePageNumber: PROCEDURE [fp: PageNumber] RETURNS [AltoPageNumber] = INLINE {RETURN[fp + 1]}; FileFromAltoPageNumber: PROCEDURE [ap: AltoPageNumber] RETURNS [PageNumber] = INLINE {RETURN[ap - 1]}; AltoByteCount: PROCEDURE [fb: ByteNumber] RETURNS [LastPageBytes] = INLINE -- ignore possibility that fb = LAST[ByteNumber] {RETURN[fb + 1]}; FileByteNumber: PROCEDURE [ab: LastPageBytes] RETURNS [ByteNumber] = INLINE {RETURN[ab - 1]}; MakeFileLength: PROCEDURE [page: AltoPageNumber, byte: LastPageBytes] RETURNS [Position] = INLINE {RETURN[[FileFromAltoPageNumber[page], FileByteNumber[byte] + 1]]}; DayTime: PROCEDURE RETURNS [AltoFileDefs.TIME] = INLINE BEGIN SecondsClock: POINTER TO AltoFileDefs.TIME = LOOPHOLE[572B]; RETURN[SecondsClock↑] END; LeaderToMesaTime: PROCEDURE [AltoFileDefs.TIME] RETURNS [FileTime] = MACHINE CODE BEGIN Mopcodes.zEXCH END; MesaToLeaderTime: PROCEDURE [FileTime] RETURNS [AltoFileDefs.TIME] = MACHINE CODE BEGIN Mopcodes.zEXCH END; END.