<> <> <> <> DIRECTORY Disk USING[ Channel, DoIO, invalid, Label, labelCheck, ok, PageNumber, PageCount, Request, SameDrive, Status ], DiskFace USING[ DontCare, RelID, wordsPerPage ], File USING[ Create, Error, FileID, FP, nullFP, Handle, NextVolume, Open, PageCount, PageNumber, PagesForWords, RC, Read, SetRoot, VolumeFile, VolumeFlusher, VolumeID, Write ], FileInternal, Process USING[ Pause, SecondsToTicks, Ticks ], Rope USING[ Equal, FromRefText, ROPE ], VolumeFormat USING[ AbsID, Attributes, LogicalPage, LogicalPageCount, LogicalRoot, LogicalRun, LRCurrentVersion, LRSeal, LVBootFile, nullDiskFileID, RunPageCount, VAMObject, VAMChunk, vamChunkPos, volumeLabelLength ], PhysicalVolume USING[ SubVolumeDetails ], VM USING[ AddressToPageNumber, Allocate, Interval, MakeUnchanged, PageCount, PageNumber, PageNumberToAddress, State, SwapIn, Unpin, WordsToPages], VMSideDoor USING[ Run ]; LogicalVolumeImpl: CEDAR MONITOR LOCKS volume USING volume: Volume IMPORTS Disk, File, Process, FileInternal, Rope, VM EXPORTS DiskFace--RelID,AbsID,Attributes--, File, FileInternal SHARES File = BEGIN -- ******** Data Types and minor subroutines ******** -- --DiskFace.--Attributes: PUBLIC TYPE = VolumeFormat.Attributes; --DiskFace.--AbsID: PUBLIC TYPE = VolumeFormat.AbsID; --File.--FileID: PUBLIC TYPE = FileInternal.FileID; Volume: TYPE = REF VolumeObject; --File.--VolumeObject: PUBLIC TYPE = FileInternal.VolumeObject; PhysicalRun: TYPE = VMSideDoor.Run; DoPinnedIO: UNSAFE PROC[channel: Disk.Channel, label: LONG POINTER TO 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; -- ******** Access to volume root page and volume's list of sub-volumes ******** -- volRoot: VolumeFormat.LogicalPage = [0]; -- logical page number of volume root page -- --File.--GetVolumeID: PUBLIC PROC[volume: Volume] RETURNS[id: File.VolumeID] = { RETURN[volume.id] }; LogRootLabel: PROC[id: File.VolumeID] RETURNS[Disk.Label] = BEGIN RETURN[ [ fileID: [abs[AbsID[id]]], filePage: 0, attributes: Attributes[logicalRoot], dontCare: LOOPHOLE[LONG[0]] ] ] END; RootTransfer: INTERNAL PROC[volume: Volume, direction: {read, write} ] RETURNS[why: File.RC] = TRUSTED BEGIN status: Disk.Status; countDone: Disk.PageCount; label: Disk.Label _ LogRootLabel[volume.id]; req: Disk.Request _ [ diskPage: volume.subVolumes.first.address, data: volume.root, incrementDataPtr: TRUE, command: [header: verify, label: verify, data: IF direction = read THEN read ELSE write], count: 1 ]; [status, countDone] _ DoPinnedIO[volume.subVolumes.first.channel, @label, @req]; RETURN[ FileInternal.TranslateStatus[status] ] END; --FileInternal.--ReadRootPage: PUBLIC ENTRY PROC[volume: Volume] = TRUSTED BEGIN ENABLE UNWIND => NULL; volume.root _ VM.PageNumberToAddress[VM.Allocate[ VM.WordsToPages[DiskFace.wordsPerPage]].page]; volume.rootStatus _ RootTransfer[volume, read]; IF volume.rootStatus = ok AND (volume.root.seal # VolumeFormat.LRSeal OR volume.root.version # VolumeFormat.LRCurrentVersion) THEN volume.rootStatus _ inconsistent; IF volume.rootStatus = ok THEN TRUSTED{ volume.name _ GetName[@volume.root.label, volume.root.labelLength] }; IF volume.rootStatus = ok AND volume.root.type # cedar THEN volume.rootStatus _ nonCedarVolume; IF volume.rootStatus = ok THEN BEGIN ENABLE File.Error => BEGIN volume.vamStatus _ SELECT why FROM unknownFile, unknownPage => inconsistent, ENDCASE => why; CONTINUE END; volume.lastFileID _ volume.root.lastFileID; ReadVAM[volume]; volume.vamStatus _ ok; END; END; GetName: PROC[ chars: LONG POINTER TO PACKED ARRAY [0..VolumeFormat.volumeLabelLength) OF CHAR, length: INT] RETURNS[Rope.ROPE] = TRUSTED BEGIN text: REF TEXT = NEW[TEXT[length]]; FOR i: INT IN [0..length) DO text[i] _ chars[i] ENDLOOP; text.length _ length; RETURN[Rope.FromRefText[text]] END; --File.--GetVolumeName: PUBLIC ENTRY PROC[volume: Volume] RETURNS[Rope.ROPE] = BEGIN ENABLE UNWIND => NULL; IF volume.rootStatus # ok AND volume.rootStatus # nonCedarVolume THEN RETURN WITH ERROR File.Error[volume.rootStatus]; RETURN[volume.name] END; --File.--FindVolumeFromID: PUBLIC PROC[id: File.VolumeID] RETURNS[volume: Volume] = BEGIN volume _ NIL; DO volume _ File.NextVolume[volume]; IF volume = NIL THEN EXIT; IF GetVolumeID[volume] = id THEN EXIT; ENDLOOP; END; --File.--FindVolumeFromName: PUBLIC PROC[name: Rope.ROPE] RETURNS[volume: Volume] = BEGIN volume _ NIL; DO volume _ File.NextVolume[volume]; IF volume = NIL THEN EXIT; IF Rope.Equal[GetVolumeName[volume ! File.Error => CONTINUE], name, FALSE] THEN EXIT; ENDLOOP; END; --FileInternal.--TranslateLogicalRun: PUBLIC --EXTERNAL-- PROC[logicalRun: VolumeFormat.LogicalRun, volume: Volume] RETURNS[channel: Disk.Channel, diskPage: Disk.PageNumber] = <> BEGIN FOR sv: LIST OF PhysicalVolume.SubVolumeDetails _ volume.subVolumes, sv.rest UNTIL sv = NIL DO IF sv.first.start <= logicalRun.first AND sv.first.start + sv.first.size >= logicalRun.first + logicalRun.size THEN RETURN[channel: sv.first.channel, diskPage: [sv.first.address + (logicalRun.first - sv.first.start)] ]; REPEAT FINISHED => RETURN WITH ERROR File.Error[inconsistent] ENDLOOP; END; --FileInternal.--RecordRootFile: PUBLIC ENTRY PROC[volume: Volume, root: File.VolumeFile, fp: File.FP, page: File.PageNumber, id: DiskFace.RelID, link: DiskFace.DontCare, channel: Disk.Channel] = TRUSTED BEGIN ENABLE UNWIND => NULL; why: File.RC; IF volume.rootStatus # ok THEN RETURN WITH ERROR File.Error[volume.rootStatus]; IF NOT Disk.SameDrive[channel, volume.subVolumes.first.channel] THEN RETURN WITH ERROR File.Error[mixedDevices]; <> volume.root.rootFile[root] _ [fp: fp, page: page]; IF root IN [FIRST[VolumeFormat.LVBootFile]..LAST[VolumeFormat.LVBootFile]] THEN volume.root.bootingInfo[root] _ [fID: [rel[id]], firstPage: page, firstLink: link]; why _ RootTransfer[volume, write]; IF why # ok THEN RETURN WITH ERROR File.Error[why]; END; --File.--GetRoot: PUBLIC ENTRY PROC[volume: Volume, root: File.VolumeFile] RETURNS[fp: File.FP, page: File.PageNumber _ [0]] = TRUSTED BEGIN ENABLE UNWIND => NULL; IF volume.rootStatus # ok THEN RETURN WITH ERROR File.Error[volume.rootStatus]; [fp: fp, page: page] _ volume.root.rootFile[root]; END; --File.--MarkDebugger: PUBLIC ENTRY PROC[volume: Volume, isDebugger: BOOL] = TRUSTED BEGIN ENABLE UNWIND => NULL; why: File.RC; IF volume.rootStatus # ok THEN RETURN WITH ERROR File.Error[volume.rootStatus]; volume.root.coCedar _ isDebugger; why _ RootTransfer[volume, write]; IF why # ok THEN RETURN WITH ERROR File.Error[why]; END; --File.--IsDebugger: PUBLIC ENTRY PROC[volume: Volume] RETURNS[BOOL] = TRUSTED BEGIN ENABLE UNWIND => NULL; IF volume.rootStatus # ok THEN RETURN WITH ERROR File.Error[volume.rootStatus]; RETURN[volume.root.coCedar] END; EraseRoot: INTERNAL PROC[volume: Volume] = TRUSTED BEGIN volume.root.bootingInfo _ ALL[VolumeFormat.nullDiskFileID]; volume.root.rootFile _ ALL[]; END; idGroupSize: INT = 100; -- number if id's to allocate at a time. --FileInternal.--NewID: PUBLIC ENTRY PROC[volume: Volume] RETURNS [FileID] = TRUSTED BEGIN ENABLE UNWIND => NULL; IF volume.rootStatus # ok THEN RETURN WITH ERROR File.Error[volume.rootStatus]; DO lastRecorded: FileID = volume.root.lastFileID; why: File.RC; IF volume.lastFileID < lastRecorded THEN EXIT; volume.root.lastFileID _ FileID[lastRecorded + idGroupSize]; why _ RootTransfer[volume, write]; IF why # ok THEN RETURN WITH ERROR File.Error[why]; ENDLOOP -- This is a loop only to recover from garbage ID's --; RETURN[ volume.lastFileID _ [volume.lastFileID + 1] ] END; -- ******** Volume Allocation Map ******** -- VAM: TYPE = FileInternal.VAM; PageBits: TYPE = MACHINE DEPENDENT RECORD[ls(0): CARDINAL, ms(1): CARDINAL]; VAMChunks: PROC[pages: VolumeFormat.LogicalPageCount] RETURNS[CARDINAL] = TRUSTED { OPEN p: LOOPHOLE[pages+LAST[CARDINAL]-1, PageBits]; RETURN[p.ms] }; VAMWords: PROC[pages: VolumeFormat.LogicalPageCount] RETURNS[INT] = -- SIZE[VAMObject[VAMChunks[pages]]], but compiler can't handle it -- { RETURN[VolumeFormat.vamChunkPos + VAMChunks[pages]*4096] }; VAMFilePages: PROC[pages: VolumeFormat.LogicalPageCount] RETURNS[File.PageCount] = INLINE { RETURN[File.PagesForWords[VAMWords[pages]]] }; VAMVMPages: PROC[pages: VolumeFormat.LogicalPageCount] RETURNS[VM.PageCount] = INLINE { RETURN[VM.WordsToPages[VAMWords[pages]]] }; Used: PROC[vam: VAM, page: VolumeFormat.LogicalPage] RETURNS[BOOL] = TRUSTED INLINE -- vam.used[p.ms][p.ls]], but the compiler can't handle it! -- { OPEN p: LOOPHOLE[page, PageBits]; RETURN[LOOPHOLE[@vam.used + p.ms*SIZE[VolumeFormat.VAMChunk], LONG POINTER TO VolumeFormat.VAMChunk][p.ls]] }; SetUsed: PROC[vam: VAM, page: VolumeFormat.LogicalPage, inUse: BOOL] = TRUSTED INLINE -- vam.used[p.ms][p.ls]], but the compiler can't handle it! -- { OPEN p: LOOPHOLE[page, PageBits]; LOOPHOLE[@vam.used + p.ms*SIZE[VolumeFormat.VAMChunk], LONG POINTER TO VolumeFormat.VAMChunk][p.ls] _ inUse }; AllocVAM: INTERNAL PROC[volume: Volume] = TRUSTED BEGIN IF volume.vam = NIL THEN BEGIN volume.vam _ VM.PageNumberToAddress[VM.Allocate[VAMVMPages[volume.size]].page]; volume.vam.size _ volume.size; END; SetUsed[volume.vam, volRoot, TRUE]; volume.vam.rover _ [0]; volume.free _ 0; END; ReadVAM: INTERNAL PROC[volume: Volume] = TRUSTED BEGIN this: VolumeFormat.LogicalPage _ [0]; IF volume.rootStatus # ok THEN ERROR; -- checked by our caller volume.vamFile _ File.Open[volume, volume.root.rootFile[VAM].fp --File.Error is caught by our caller--]; AllocVAM[volume]; File.Read[volume.vamFile, [0], VAMFilePages[volume.size], volume.vam]; volume.free _ 0; DO IF this = volume.vam.size THEN EXIT; IF NOT Used[volume.vam, this] THEN volume.free _ volume.free+1; this _ [this+1]; ENDLOOP; END; --File.--GetVolumePages: PUBLIC ENTRY PROC[volume: Volume] RETURNS[size, free, freeboard: INT] = BEGIN ENABLE UNWIND => NULL; IF volume.rootStatus # ok THEN RETURN WITH ERROR File.Error[volume.rootStatus]; RETURN[size: volume.size, free: volume.free, freeboard: volume.freeboard] END; --File.--SetFlusher: PUBLIC ENTRY PROC[volume: Volume, flusher: File.VolumeFlusher, data: REF ANY] = BEGIN ENABLE UNWIND => NULL; volume.flusherData _ data; volume.flusher _ flusher; IF volume.freeboard = 0 THEN volume.freeboard _ MIN[volume.size/5, 1000]; END; --File.--GetFlusher: PUBLIC ENTRY PROC[volume: Volume] RETURNS[File.VolumeFlusher, REF ANY] = BEGIN ENABLE UNWIND => NULL; RETURN[volume.flusher, volume.flusherData] END; --File.--SetFreeboard: PUBLIC ENTRY PROC[volume: Volume, freeboard: INT] = BEGIN ENABLE UNWIND => NULL; volume.freeboard _ freeboard; END; --FileInternal.--Alloc: PUBLIC ENTRY PROC[volume: Volume, first: VolumeFormat.LogicalPage, size: VolumeFormat.LogicalPageCount] RETURNS[given: VolumeFormat.LogicalRun] = TRUSTED BEGIN ENABLE UNWIND => NULL; <> reqSize: VolumeFormat.RunPageCount = MIN[size,LAST[VolumeFormat.RunPageCount]]; vam: VAM = volume.vam; IF volume.vamStatus # ok THEN RETURN WITH ERROR File.Error[volume.vamStatus]; IF volume.free < size THEN RETURN WITH ERROR File.Error[volumeFull]; volume.vamChanged _ TRUE; -- Restrict run length to LAST[CARDINAL], to maximize runs per run-table page -- -- TEMP! Returns first free run found -- WHILE Used[vam, vam.rover] DO vam.rover _ [vam.rover+1]; IF vam.rover = vam.size THEN vam.rover _ [0] ENDLOOP; given _ [first: vam.rover, size: 0]; UNTIL Used[vam, vam.rover] OR vam.rover = vam.size OR given.size = reqSize DO given.size _ given.size+1; vam.rover _ [vam.rover+1] ENDLOOP; FOR i: INT IN [0..given.size) DO SetUsed[vam, [given.first+i], TRUE] ENDLOOP; volume.free _ volume.free - given.size; END; --FileInternal.--Free: PUBLIC ENTRY PROC[volume: Volume, logicalRun: VolumeFormat.LogicalRun] = BEGIN ENABLE UNWIND => NULL; IF volume.vamStatus # ok THEN RETURN WITH ERROR File.Error[volume.vamStatus]; volume.vamChanged _ TRUE; FOR i: VolumeFormat.RunPageCount IN [0..logicalRun.size) DO SetUsed[volume.vam, [logicalRun.first + i], FALSE] ENDLOOP; volume.free _ volume.free + logicalRun.size; END; --FileInternal.--Flush: PUBLIC PROC[volume: Volume, lack: VolumeFormat.LogicalPageCount] RETURNS [BOOL] = BEGIN flusher: File.VolumeFlusher; data: REF ANY; [flusher, data] _ GetFlusher[volume]; IF flusher # NIL AND flusher[volume, lack, data] THEN RETURN[TRUE]; IF GetVolumePages[volume].free >= lack THEN RETURN[TRUE]; RETURN[FALSE] END; <<-- ******** Creation and writing of the VAM file ******** -->> <<>> <> vamFileRelease: CONDITION; GrabVAMFile: ENTRY PROC[volume: Volume] RETURNS [file: File.Handle] = TRUSTED BEGIN ENABLE UNWIND => NULL; DO IF volume.vamStatus # ok THEN RETURN WITH ERROR File.Error[volume.vamStatus]; IF (file _ volume.vamFile) # NIL THEN { volume.vamFile _ NIL; EXIT }; WAIT vamFileRelease; -- because we must be in the process of erasing the volume ENDLOOP; volume.vamChanged _ FALSE; -- because our caller is about to write it to disk END; ReleaseVAMFile: ENTRY PROC[volume: Volume, file: File.Handle, changed: BOOL] = TRUSTED BEGIN ENABLE UNWIND => NULL; volume.vamChanged _ volume.vamChanged OR changed--indicates if commit failed--; IF (volume.vamFile _ file) = NIL THEN volume.vamStatus _ inconsistent; BROADCAST vamFileRelease; END; --FileInternal.--Commit: PUBLIC PROC[volume: Volume] = TRUSTED BEGIN -- Ensure VAM is up to date on disk -- -- Assumes volume.vam is vm-page-aligned. -- vamFile: File.Handle _ GrabVAMFile[volume]; -- raises File.Error if vamFile isn't ok. InnerCommit[volume, vamFile ! File.Error => { ReleaseVAMFile[volume, vamFile, TRUE]; vamFile _ NIL }; -- release our lock UNWIND => IF vamFile # NIL THEN ReleaseVAMFile[volume, vamFile, TRUE] ]; ReleaseVAMFile[volume, vamFile, FALSE]; END; InnerCommit: PROC[volume: Volume, vamFile: File.Handle] = TRUSTED BEGIN filePageCount: File.PageCount = VAMFilePages[volume.size]; vmPagesPerFilePage: INT = VM.WordsToPages[DiskFace.wordsPerPage]; FOR filePage: File.PageNumber _ [0], [filePage+1] UNTIL filePage = filePageCount DO fileBaseAddr: LONG POINTER = LOOPHOLE[volume.vam + filePage * DiskFace.wordsPerPage]; baseVMPage: VM.PageNumber = VM.AddressToPageNumber[fileBaseAddr]; FOR i: INT IN [0..vmPagesPerFilePage) DO IF VM.State[baseVMPage+i].dataState = changed THEN BEGIN VM.MakeUnchanged[[page: baseVMPage, count: vmPagesPerFilePage]]; File.Write[vamFile, filePage, 1, fileBaseAddr]; EXIT END; ENDLOOP; ENDLOOP; END; --File.--EraseVolume: PUBLIC PROC[volume: Volume] = TRUSTED BEGIN EntryErase[volume]; <> BEGIN ENABLE UNWIND => ReleaseVAMFile[volume, NIL, TRUE];-- vamState _ inconsistent vamFile: File.Handle _ NIL; FOR sv: LIST OF PhysicalVolume.SubVolumeDetails _ volume.subVolumes, sv.rest UNTIL sv = NIL DO IF volRoot >= sv.first.start AND volRoot < sv.first.start + sv.first.size THEN BEGIN <> FileInternal.FreeRun[[first: sv.first.start, size: volRoot-sv.first.start], volume]; FileInternal.FreeRun[[first: [volRoot+1], size: sv.first.size-(volRoot-sv.first.start)-1], volume]; END ELSE FileInternal.FreeRun[[first: sv.first.start, size: sv.first.size], volume]; ENDLOOP; vamFile _ File.Create[volume, VAMFilePages[volume.size]]; InnerCommit[volume, vamFile]; File.SetRoot[VAM, vamFile]; ReleaseVAMFile[volume, vamFile, FALSE]; END; END; EntryErase: ENTRY PROC[volume: Volume] = TRUSTED BEGIN ENABLE UNWIND => NULL; WHILE volume.vamStatus = ok AND volume.vamFile = NIL DO WAIT vamFileRelease ENDLOOP; IF volume.rootStatus # ok THEN RETURN WITH ERROR File.Error[volume.rootStatus]; volume.vamStatus _ inconsistent; volume.vamFile _ NIL; EraseRoot[volume]; AllocVAM[volume]; volume.vamStatus _ ok; -- but we still own the (not-yet-created) volume.vamFile END; CommitVAMFiles: PROC = BEGIN commitPause: Process.Ticks _ Process.SecondsToTicks[1]; DO FOR this: Volume _ File.NextVolume[NIL,FALSE], File.NextVolume[this,FALSE] UNTIL this = NIL DO IF this.vamChanged--in monitor!-- THEN Commit[this ! File.Error => CONTINUE] ENDLOOP; Process.Pause[commitPause]; ENDLOOP; END; vamCommiter: PROCESS = FORK CommitVAMFiles[]; END.