<> <> <> <> DIRECTORY Disk USING[ Channel, DoIO, GetBootChainLink, invalid, Label, labelCheck, ok, PageNumber, PageCount, Request, SameDrive, Status ], DiskFace USING[ wordsPerPage ], NewFile USING[ Error, FileID, FP, GetVolumeID, PageCount, PageNumber, PagesForWords, PropertyStorage, propertyWords, RC, Volume, VolumeFile, VolumeID ], FileInternal, VolumeFormat USING[ AbsID, Attributes, lastLogicalRun, LogicalPage, LogicalPageCount, LogicalRun, LogicalRunObject, RelID, RunPageCount ], VM USING[ AddressToPageNumber, Allocate, Free, Interval, PageCount, PageNumber, PageNumberToAddress, SwapIn, Unpin, WordsToPages], VMSideDoor USING[ Run, RunTableIndex, RunTableObject, RunTablePageNumber ]; FileHeaderImpl: CEDAR MONITOR LOCKS file USING file: Handle IMPORTS Disk, NewFile, FileInternal, VM EXPORTS DiskFace--RelID,AbsID,Attributes--, NewFile, FileInternal SHARES NewFile = BEGIN OPEN File: NewFile; -- ******** Data Types and minor subroutines ******** -- --DiskFace.--Attributes: PUBLIC TYPE = VolumeFormat.Attributes; --DiskFace.--AbsID: PUBLIC TYPE = VolumeFormat.AbsID; --DiskFace.--RelID: PUBLIC TYPE = VolumeFormat.RelID; --File.--DA: PUBLIC TYPE = VolumeFormat.LogicalPage; Handle: TYPE = REF Object; --File.--Object: PUBLIC TYPE = FileInternal.Object; RunTable: TYPE = FileInternal.RunTable; RunTableObject: TYPE = VMSideDoor.RunTableObject; RunTableIndex: TYPE = VMSideDoor.RunTableIndex; PhysicalRun: TYPE = FileInternal.PhysicalRun; lastRun: VMSideDoor.RunTablePageNumber = LAST[INT]; -- end marker in runTable -- initRuns: CARDINAL = (DiskFace.wordsPerPage-SIZE[VolumeFormat.LogicalRunObject[0]]) / SIZE[VolumeFormat.LogicalRun]; DoPinnedIO: PROC[channel: Disk.Channel, label: REF Disk.Label, req: POINTER TO Disk.Request] RETURNS[ status: Disk.Status, countDone: Disk.PageCount] = TRUSTED BEGIN interval: VM.Interval = [ page: VM.AddressToPageNumber[req.data], count: VM.WordsToPages[ (IF req.incrementDataPtr THEN req.count ELSE 1)*DiskFace.wordsPerPage] ]; VM.SwapIn[interval: interval, kill: req.command.data=read, pin: TRUE]; [status, countDone] _ Disk.DoIO[channel, label, req ! UNWIND => VM.Unpin[interval] ]; VM.Unpin[interval]; END; CheckStatus: PROC[status: Disk.Status] = BEGIN why: File.RC = FileInternal.TranslateStatus[status]; IF why # ok THEN ERROR File.Error[why]; END; -- ******** Header and Run-table management ******** -- --FileInternal.--GetHeaderVM: PUBLIC PROC[file: Handle, runs: CARDINAL] = TRUSTED BEGIN words: INT = File.propertyWords + SIZE[VolumeFormat.LogicalRunObject[runs]]; file.headerVM _ VM.PageNumberToAddress[VM.Allocate[VM.WordsToPages[words]].page]; file.logicalRunTable _ LOOPHOLE[file.headerVM + File.propertyWords]; file.logicalRunTable.headerPages _ File.PagesForWords[words]; file.logicalRunTable.maxRuns _ runs; END; initHeaderPages: File.PageCount = File.PagesForWords[File.propertyWords + SIZE[VolumeFormat.LogicalRunObject[initRuns]]]; --FileInternal.--TranslateLogicalRunTable: PUBLIC PROC[file: Handle] RETURNS[ File.PageCount ] = TRUSTED BEGIN nRuns: CARDINAL; filePage: File.PageNumber _ [-file.logicalRunTable.headerPages]; file.runTable _ NEW[RunTableObject[file.logicalRunTable.maxRuns]]; FOR nRuns IN [0..file.logicalRunTable.maxRuns) DO IF file.logicalRunTable[nRuns].first = VolumeFormat.lastLogicalRun THEN EXIT; file.runTable[nRuns] _ FileInternal.TranslateLogicalRun[file.logicalRunTable[nRuns], file.volume, filePage]; filePage _ [filePage + file.logicalRunTable[nRuns].size]; REPEAT FINISHED => ERROR File.Error[inconsistent] ENDLOOP; file.runTable.nDataPages _ filePage; file.runTable.nRuns _ nRuns; file.runTable[file.runTable.nRuns].filePage _ lastRun; RETURN[ file.runTable.nDataPages ] END; --FileInternal.--CreateEmptyRunTable: PUBLIC PROC[file: Handle] RETURNS[ File.PageCount ] = TRUSTED BEGIN GetHeaderVM[file, initRuns]; file.logicalRunTable[0].first _ VolumeFormat.lastLogicalRun; RETURN[ TranslateLogicalRunTable[file] ]; END; --FileInternal.--AddRun: PUBLIC PROC[file: Handle, run: POINTER TO PhysicalRun, logicalPage: VolumeFormat.LogicalPage, okPages: INT] = TRUSTED BEGIN file.runTable.nDataPages _ file.size + okPages; file.runTable[file.runTable.nRuns] _ run^; file.logicalRunTable[file.runTable.nRuns].first _ logicalPage; file.logicalRunTable[file.runTable.nRuns].size _ okPages; file.runTable.nRuns _ file.runTable.nRuns + 1; file.runTable[file.runTable.nRuns].filePage _ lastRun; file.logicalRunTable[file.runTable.nRuns].first _ VolumeFormat.lastLogicalRun; END; --FileInternal.--LastLogicalPage: PUBLIC PROC[file: Handle] RETURNS [VolumeFormat.LogicalPage] = TRUSTED BEGIN < 0>> lastRun: VolumeFormat.LogicalRun = file.logicalRunTable[file.runTable.nRuns-1]; RETURN[ [lastRun.first + lastRun.size-1] ] END; --FileInternal.--RemoveFromRunTable: PUBLIC PROC[file: Handle, remove: INT] = TRUSTED BEGIN file.runTable.nDataPages _ file.size; WHILE remove > 0 DO runSize: INT = file.logicalRunTable[file.runTable.nRuns-1].size; amount: INT = IF remove > runSize THEN runSize ELSE remove; file.logicalRunTable[file.runTable.nRuns-1].size _ runSize - amount; IF runSize - amount = 0 THEN BEGIN -- Remove the new run table entry entirely! -- IF file.runTable.nRuns = 1 THEN ERROR File.Error[inconsistent]; file.runTable.nRuns _ file.runTable.nRuns - 1; file.runTable[file.runTable.nRuns].filePage _ lastRun; file.logicalRunTable[file.runTable.nRuns].first _ VolumeFormat.lastLogicalRun; END; remove _ remove - amount; ENDLOOP; END; --FileInternal.--FindRun: PUBLIC PROC[start: File.PageNumber, nPages: File.PageCount, runTable: RunTable] RETURNS[diskPage: Disk.PageNumber, size: Disk.PageCount, channel: Disk.Channel] = BEGIN probe: RunTableIndex _ runTable.nRuns / 2; -- NB: round down -- increment: CARDINAL _ probe; IF runTable.nRuns = 0 THEN ERROR File.Error[inconsistent]; IF start + nPages > runTable.nDataPages THEN ERROR File.Error[unknownPage]; IF start < runTable[0].filePage THEN ERROR File.Error[unknownPage]; DO increment _ (increment+1)/2; SELECT TRUE FROM runTable[probe].filePage > start => probe _ IF probe < increment THEN 0 ELSE probe-increment; runTable[probe+1].filePage <= start => probe _ MIN[probe + increment, runTable.nRuns-1]; ENDCASE => RETURN[ diskPage: [runTable[probe].diskPage + (start-runTable[probe].filePage)], size: IF start + nPages <= runTable[probe+1].filePage THEN nPages ELSE runTable[probe+1].filePage - start, channel: runTable[probe].channel ] ENDLOOP; END; -- ******** Subroutines for access to file pages ******** -- HeaderLabel: PROC[id: File.FileID] RETURNS[REF Disk.Label] = BEGIN RETURN[NEW[Disk.Label _ [ fileID: [rel[RelID[id]]], filePage: 0, attributes: Attributes[header], dontCare: LOOPHOLE[LONG[0]] ] ] ] END; DataLabel: PROC[id: File.FileID] RETURNS[REF Disk.Label] = BEGIN RETURN[NEW[Disk.Label _ [ fileID: [rel[RelID[id]]], filePage: 0, attributes: Attributes[data], dontCare: LOOPHOLE[LONG[0]] ] ] ] END; FreeLabel: PROC[volume: File.Volume] RETURNS[REF Disk.Label] = BEGIN RETURN[NEW[Disk.Label _ [ fileID: [abs[AbsID[File.GetVolumeID[volume]]]], filePage: 0, attributes: Attributes[freePage], dontCare: LOOPHOLE[LONG[0]] ] ] ] END; WriteLabels: PROC[channel: Disk.Channel, diskPage: Disk.PageNumber, count: Disk.PageCount, data: LONG POINTER, label: REF Disk.Label] RETURNS[ status: Disk.Status, countDone: Disk.PageCount] = TRUSTED BEGIN req: Disk.Request _ [ diskPage: diskPage, data: IF data = NIL THEN freePageData ELSE data, incrementDataPtr: data # NIL, command: [header: verify, label: write, data: write], count: count ]; [status, countDone] _ DoPinnedIO[channel, label, @req]; END; GetScratchPage: PROC RETURNS[data: LONG POINTER] = TRUSTED BEGIN temp: LONG POINTER TO ARRAY [0..DiskFace.wordsPerPage) OF WORD; data _ VM.PageNumberToAddress[VM.Allocate[VM.WordsToPages[DiskFace.wordsPerPage]].page]; temp _ LOOPHOLE[data]; temp^ _ ALL[0]; END; FreeScratchPage: PROC[data: LONG POINTER] = TRUSTED BEGIN VM.Free[ [ page: VM.AddressToPageNumber[data], count: VM.WordsToPages[DiskFace.wordsPerPage] ] ]; END; freePageData: LONG POINTER = GetScratchPage[]; -- buffer for reading/writing freepages --FileInternal.--FreeRun: PUBLIC PROC[logicalRun: VolumeFormat.LogicalRun, volume: File.Volume] = BEGIN -- Write page labels as "free" and mark as unused in VAM -- label: REF Disk.Label _ FreeLabel[volume]; FileInternal.Free[volume, logicalRun]; WHILE logicalRun.size > 0 DO run: PhysicalRun _ FileInternal.TranslateLogicalRun[logicalRun, volume, [0]]; status: Disk.Status; countDone: Disk.PageCount; label.filePage _ logicalRun.first; TRUSTED{[status, countDone] _ WriteLabels[run.channel, run.diskPage, logicalRun.size, NIL, label]}; SELECT status FROM Disk.ok => NULL; Disk.invalid => ERROR File.Error[wentOffline]; ENDCASE => BEGIN -- Couldn't write label, so assume page is bad -- countDone _ countDone + 1; END; logicalRun.first _ [logicalRun.first + countDone]; logicalRun.size _ logicalRun.size - countDone; ENDLOOP; END--FreeRun--; Extend: PROC[file: Handle, amount: INT, create: BOOL] = BEGIN volume: File.Volume = file.volume; prev: VolumeFormat.LogicalPage _ IF create THEN [0] ELSE FileInternal.LastLogicalPage[file]; freeLabel: REF Disk.Label = FreeLabel[volume]; freeReq: Disk.Request _ [ diskPage:, data: freePageData, incrementDataPtr: FALSE, command: [header: verify, label: verify, data: read], count: ]; label: REF Disk.Label; IF NOT create THEN { label _ DataLabel[file.fp.id]; label.filePage _ file.size }; <> <> <> <> <> <> WHILE amount > 0 -- Loop for each allocated disk run -- DO logicalRun: VolumeFormat.LogicalRun _ FileInternal.Alloc[volume: volume, first: prev, size: amount]; IF create AND logicalRun.size < initHeaderPages THEN ERROR File.Error[volumeFull]; WHILE logicalRun.size > 0 -- Loop for each available fragment of disk run -- DO run: PhysicalRun _ FileInternal.TranslateLogicalRun[logicalRun, volume, IF create THEN [-initHeaderPages] ELSE [file.size]]; labelsOK: Disk.PageCount; -- count of labels that are genuinely free pages -- status: Disk.Status; freeReq.diskPage _ run.diskPage; freeReq.count _ logicalRun.size; freeLabel.filePage _ logicalRun.first; TRUSTED{ [status, labelsOK] _ DoPinnedIO[run.channel, freeLabel, @freeReq] -- verify free --}; IF create AND labelsOK < initHeaderPages THEN labelsOK _ 0; -- may lose a page -- IF labelsOK > 0 THEN BEGIN labelsWritten: Disk.PageCount _ 0; -- total labels actually written -- labelsThisTime: Disk.PageCount _ 0; -- labels written in this transfer -- Consume: PROC = BEGIN labelsWritten _ labelsWritten + labelsThisTime; amount _ amount - labelsThisTime; logicalRun.first _ [logicalRun.first+labelsThisTime]; logicalRun.size _ logicalRun.size - labelsThisTime; END; -- ensure run-table is superset of allocated pages -- TRUSTED{FileInternal.AddRun[file, @run, logicalRun.first, labelsOK]}; IF create THEN BEGIN -- write labels and data for header area -- file.fp _ [id: FileInternal.NewID[volume], da: logicalRun.first]; TRUSTED{[status, labelsThisTime] _ WriteLabels[run.channel, run.diskPage, initHeaderPages, file.headerVM, HeaderLabel[file.fp.id]]}; IF labelsThisTime < initHeaderPages THEN labelsThisTime _ 0 -- incorrigable page; may lose preceding page -- ELSE BEGIN create _ FALSE; file.size _ file.size + labelsThisTime; label _ DataLabel[file.fp.id]; label.filePage _ file.size; END; Consume[]; END ELSE WriteHeader[file]; IF labelsOK > labelsWritten AND NOT create -- i.e. if there's still pages free and creation succeeded THEN BEGIN TRUSTED{[status, labelsThisTime] _ WriteLabels[run.channel, [run.diskPage+labelsThisTime], labelsOK-labelsWritten, NIL, label]}; file.size _ file.size + labelsThisTime; Consume[]; END; -- correct run-table to number of pages successfully written -- IF labelsOK > labelsWritten THEN FileInternal.RemoveFromRunTable[file, labelsOK-labelsWritten]; END; SELECT status FROM Disk.ok => NULL; Disk.invalid => ERROR File.Error[wentOffline]; ENDCASE => -- skip bad/in-use page -- { logicalRun.first _ [logicalRun.first+1]; logicalRun.size _ logicalRun.size-1 }; ENDLOOP-- Loop for each available fragment of disk run --; ENDLOOP-- Loop for each allocated disk run --; END--Extend--; maxTransferRun: File.PageCount _ 100; <> Transfer: PROC[file: Handle, data: LONG POINTER, filePage: File.PageNumber, nPages: File.PageCount, action: {write, read}, where: {header, data} ] = BEGIN label: REF Disk.Label _ IF where = header THEN HeaderLabel[file.fp.id] ELSE DataLabel[file.fp.id]; IF where = data THEN label.filePage _ filePage; WHILE nPages > 0 DO status: Disk.Status; countDone: Disk.PageCount; channel: Disk.Channel; req: Disk.Request; req.data _ data; req.incrementDataPtr _ TRUE; req.command _ IF action = read THEN [header: verify, label: verify, data: read] ELSE [header: verify, label: verify, data: write]; [diskPage: req.diskPage, size: req.count, channel: channel] _ FileInternal.FindRun[start: filePage, nPages: MIN[nPages,maxTransferRun], runTable: file.runTable]; TRUSTED{[status, countDone] _ DoPinnedIO[channel, label, @req]}; CheckStatus[status]; TRUSTED{data _ data + countDone * DiskFace.wordsPerPage}; nPages _ nPages - countDone; filePage _ [filePage + countDone]; ENDLOOP; END; MakeBootable: PROC[file: Handle, firstPage: File.PageNumber] = BEGIN data: LONG POINTER; status: Disk.Status; label: REF Disk.Label _ DataLabel[file.fp.id]; countDone: Disk.PageCount; filePage: File.PageNumber _ firstPage; thisDiskPage: Disk.PageNumber; thisSize: Disk.PageCount; channel: Disk.Channel; IF file.size > firstPage THEN [diskPage: thisDiskPage, size: thisSize, channel: channel] _ FileInternal.FindRun[start: filePage, nPages: file.size-filePage, runTable: file.runTable]; data _ GetScratchPage[]; WHILE filePage + thisSize < file.size DO BEGIN ENABLE UNWIND => FreeScratchPage[data]; nextDiskPage: Disk.PageNumber; nextSize: Disk.PageCount; nextChannel: Disk.Channel; req: Disk.Request; filePage _ [filePage+thisSize]; -- file page number of start of next run thisDiskPage _ [thisDiskPage + thisSize - 1]; -- disk page number of last page of this run [diskPage: nextDiskPage, size: nextSize, channel: nextChannel] _ FileInternal.FindRun[start: filePage, nPages: file.size-filePage, runTable: file.runTable]; IF NOT Disk.SameDrive[nextChannel, channel] THEN ERROR File.Error[mixedDevices]; label.filePage _ filePage-1; req _ [ diskPage: thisDiskPage, data: data, incrementDataPtr: TRUE, command: [header: verify, label: verify, data: read], count: 1 ]; TRUSTED{[status, countDone] _ DoPinnedIO[channel, label, @req]}; CheckStatus[status]; label.dontCare _ Disk.GetBootChainLink[channel, nextDiskPage]; label.filePage _ filePage-1; TRUSTED{[status, countDone] _ WriteLabels[channel, thisDiskPage, 1, data, label]}; CheckStatus[status]; thisDiskPage _ nextDiskPage; thisSize _ nextSize; END ENDLOOP; FreeScratchPage[data]; END; LockMode: TYPE = { shared, exclusive }; unlocked: CONDITION; Lock: ENTRY PROC[file: Handle, mode: LockMode] = BEGIN ENABLE UNWIND => NULL; IF file = NIL THEN RETURN WITH ERROR File.Error[unknownFile]; IF mode = shared THEN BEGIN WHILE file.users < 0 --writers-- DO WAIT unlocked ENDLOOP; file.users _ file.users + 1; END ELSE BEGIN WHILE file.users # 0 --anyone-- DO WAIT unlocked ENDLOOP; file.users _ file.users - 1; END; END; Unlock: ENTRY PROC[file: Handle] = BEGIN SELECT file.users FROM < 0 => file.users _ file.users + 1; > 0 => file.users _ file.users - 1; ENDCASE => NULL; BROADCAST unlocked; END; -- ******** Top-level procedures ******** -- --File.--Create: PUBLIC PROC[volume: File.Volume, size: File.PageCount] RETURNS[file: Handle] = BEGIN file _ NEW[Object _ [volume: volume] ]; file.size _ FileInternal.CreateEmptyRunTable[file]; Extend[file, initHeaderPages+size, TRUE]; END; --File.--Open: PUBLIC PROC[volume: File.Volume, fp: File.FP] RETURNS[Handle] = BEGIN file: Handle = NEW[Object _ [volume: volume, fp: fp] ]; logicalRun: VolumeFormat.LogicalRun _ [first: fp.da, size: initHeaderPages]; run: PhysicalRun _ FileInternal.TranslateLogicalRun[logicalRun, volume, [-initHeaderPages]]; label: REF Disk.Label _ HeaderLabel[fp.id]; req: Disk.Request; status: Disk.Status; countDone: Disk.PageCount; FileInternal.GetHeaderVM[file, initRuns]; req _ [ diskPage: run.diskPage, data: file.headerVM, incrementDataPtr: TRUE, command: [header: verify, label: verify, data: read], count: initHeaderPages ]; TRUSTED{[status, countDone] _ DoPinnedIO[run.channel, label, @req]}; IF countDone = 0 AND status = Disk.labelCheck THEN ERROR File.Error[unknownFile]; CheckStatus[status]; TRUSTED{ IF file.logicalRunTable.headerPages # initHeaderPages THEN ERROR File.Error[inconsistent] }; --Run table has been extended; tedious-- file.size _ FileInternal.TranslateLogicalRunTable[file]; RETURN[file] END; --File.--Delete: PUBLIC PROC[file: Handle] = BEGIN Lock[file, exclusive]; BEGIN ENABLE File.Error => Unlock[file]; NULL; END; Unlock[file]; END; --File.--SetSize: PUBLIC PROC[file: Handle, size: File.PageCount] = BEGIN Lock[file, exclusive]; BEGIN ENABLE File.Error => Unlock[file]; SELECT TRUE FROM size > file.size => Extend[file, size-file.size, FALSE]; size < file.size => ERROR; ENDCASE => NULL; END; Unlock[file]; END; --File.--Info: PUBLIC PROC[file: Handle] RETURNS[volume: File.Volume, fp: File.FP, size: File.PageCount] = BEGIN Lock[file, shared]; BEGIN ENABLE File.Error => Unlock[file]; volume _ file.volume; fp _ file.fp; size _ file.size; END; Unlock[file]; END; --File.--SetRoot: PUBLIC PROC[root: File.VolumeFile, file: Handle, page: File.PageNumber _ [0]] = TRUSTED BEGIN diskPage: Disk.PageNumber; channel: Disk.Channel; Lock[file, shared]; BEGIN ENABLE File.Error => Unlock[file]; MakeBootable[file, page]; [diskPage: diskPage, channel: channel] _ FileInternal.FindRun[start: page, nPages: 1, runTable: file.runTable]; FileInternal.RecordRootFile[file.volume, root, file.fp, RelID[file.fp.id], Disk.GetBootChainLink[channel, diskPage], channel]; END; Unlock[file]; END; WriteHeader: PROC[file: Handle] = BEGIN hPages: File.PageCount = -file.runTable[0].filePage; Transfer[file: file, data: file.headerVM, filePage: [-hPages], nPages: hPages, action: write, where: header] END; --File.--Read: PUBLIC UNSAFE PROC[file: Handle, from: File.PageNumber, nPages: File.PageCount, to: LONG POINTER] = BEGIN IF from < 0 THEN ERROR File.Error[unknownPage]; Lock[file, shared]; BEGIN ENABLE File.Error => Unlock[file]; Transfer[file: file, data: to, filePage: from, nPages: nPages, action: read, where: data]; END; Unlock[file]; END; --File.--Write: PUBLIC PROC[file: Handle, to: File.PageNumber, nPages: File.PageCount, from: LONG POINTER] = BEGIN IF to < 0 THEN ERROR File.Error[unknownPage]; Lock[file, shared]; BEGIN ENABLE File.Error => Unlock[file]; Transfer[file: file, data: from, filePage: to, nPages: nPages, action: write, where: data]; END; Unlock[file]; END; --File.--GetProperties: PUBLIC PROC[file: Handle] RETURNS[File.PropertyStorage] = BEGIN RETURN[LOOPHOLE[file.headerVM]] END; --File.--WriteProperties: PUBLIC PROC[file: Handle] = BEGIN Lock[file, shared]; BEGIN ENABLE File.Error => Unlock[file]; hPages: File.PageCount = -file.runTable[0].filePage; Transfer[file: file, data: file.headerVM, filePage: [-hPages], nPages: File.PagesForWords[File.propertyWords], action: write, where: header] END; Unlock[file]; END; END.