-- ScavengeImplExtras.mesa (edited 28-Oct-81 18:24:02 by Fay) DIRECTORY Environment USING [wordsPerPage], File USING [Capability, Error, GetAttributes, read, SetSize, shrink, Unknown, write], Scavenger USING [Error, ErrorType, FileEntry, Header, Problem, ReadBadPage, RewritePage, Scavenge], Space USING [Create, defaultWindow, GetWindow, Handle, LongPointer, Map, PageNumber, Unmap, virtualMemory, VMPageNumber, WindowOrigin], Transaction USING [nullHandle], Volume USING [Close, ID, Open, Unknown]; ScavengeImplExtras: MONITOR IMPORTS File, Scavenger, Space, Transaction, Volume -- Transaction is implicitly imported because of Space, but is explicitly -- listed in the IMPORTS to allow positional notation to specify the -- imports of ScavengeImplExtras in an enclosing configuration (i.e. to -- allow binder magic). EXPORTS Scavenger SHARES File = BEGIN -- Global spaces (create them only once to avoid running out of region -- cache in UtilityPilot) bufferSpace: Space.Handle = Space.Create[1, Space.virtualMemory]; pilotLogSpace: Space.Handle = Space.Create[pilotLogSpaceSize, Space.virtualMemory]; pilotLogSpaceSize: CARDINAL = 1; -- debugging stuff BugType: TYPE = {impossibleSelectError, pilotLogFileUnknown}; Bug: ERROR [BugType] = CODE; --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- Scavenger implementation (exported procedure) --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Scavenge: PUBLIC ENTRY PROC [volume, logDestination: Volume.ID, repair: BOOLEAN] RETURNS [logFile: File.Capability] = -- Does a regular logical volume scavenge and then takes care of any -- problems in temporary files. -- Note: this must be called from a UtilityPilot client ONLY. If it is -- called from a Pilot client, and there are unreadable or missing pages -- in temporary files, we will crash on the automatic temporary file -- deletion attempted during Volume.Open. This would defeat the whole -- purpose of this special version of the scavenger. BEGIN ENABLE UNWIND => NULL; -- for Bug or errors from FixTempFiles theError: Scavenger.ErrorType; BEGIN holeFound: BOOLEAN _ TRUE; -- needed for Rubicon workaround for -- missing pages. We have to scavenge again after truncating files -- with missing pages in order to clean up the VFM. If n is the -- number of holes in the file with the most holes, we will have to -- call the scavenger n + 1 times to get rid of the holes, since we -- can only truncate down to the last hole each time. Ugh! WHILE holeFound DO logFile _ Scavenger.Scavenge[volume: volume, logDestination: logDestination, repair: repair ! Scavenger.Error => {theError _ error; GOTO ScavengerError}]; -- repair must always be TRUE in Rubicon, so we can always fix -- temporary files. Scavenger.Scavenge will catch the repair=FALSE -- error for us. Volume.Open[volume]; holeFound _ FixTempFiles[volume, logFile]; Volume.Close[volume]; ENDLOOP; EXITS ScavengerError => RETURN WITH ERROR Scavenger.Error[theError]; END; -- ScavengerError scope END; -- Scavenge --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- Internal procedures --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Backup: PUBLIC PROC [bufferPointer: LONG POINTER TO UNSPECIFIED, currentWord, count: CARDINAL] RETURNS [nextWord: CARDINAL] = -- Backs up count words in the Pilot scavenger log. BEGIN pilotLogWindow: Space.WindowOrigin; THROUGH [0..count) DO IF currentWord = 0 THEN {pilotLogWindow _ Space.GetWindow[pilotLogSpace]; pilotLogWindow.base _ pilotLogWindow.base - pilotLogSpaceSize; currentWord _ (Environment.wordsPerPage*pilotLogSpaceSize) - 1; Space.Unmap[pilotLogSpace]; Space.Map[pilotLogSpace, pilotLogWindow]} ELSE currentWord _ currentWord - 1; ENDLOOP; nextWord _ currentWord; END; -- Backup ClosePilotLogFile: PROC = BEGIN Space.Unmap[pilotLogSpace]; END; -- ClosePilotLogFile FixOneProblem: PROC [ fileEntry: POINTER TO Scavenger.FileEntry, problem: POINTER TO Scavenger.Problem] RETURNS [holeFound: BOOLEAN] = -- Fixes one file problem reported in a Problem entry in the Pilot -- scavenger log. (Note that MakePage0Readable has already fixed any -- unreadable page group which starts with page 0, so those are skipped -- over here.) BEGIN WITH problem^ SELECT FROM missing => {holeFound _ TRUE; MissingPages[fileEntry, problem]}; unreadable => {holeFound _ FALSE; IF first # 0 THEN UnreadablePages[fileEntry, problem]}; ENDCASE => ERROR Bug[impossibleSelectError]; -- duplicate page detection is not implemented in Pilot 6.0. -- orphan pages have no fileID, so we shouldn't get here with one. END; -- FixOneProblem FixProblems: PROC [currentProblem: CARDINAL, fileEntry: POINTER TO Scavenger.FileEntry, pilotLogPointer: LONG POINTER TO UNSPECIFIED, currentWord: CARDINAL, holeAlreadyFound: BOOLEAN] RETURNS [nextWord: CARDINAL, holeFound: BOOLEAN] = -- Fixes no more than one missing page and all other Problems for one -- file. Uses recursion to allow it to deal with the last missing page -- first, due to the algorithm used (truncation of the file). It also -- makes sure that all problems other than missing pages are fixed before -- fixing a missing page to avoid tripping over them when trying to -- truncate the file. Depends on missing pages being reported in order -- from first to last. BEGIN lastProblem: BOOLEAN = currentProblem=fileEntry.numberOfProblems; problem: Scavenger.Problem; nextWord _ GetWords[@problem, pilotLogPointer, currentWord, SIZE[Scavenger.Problem]]; IF problem.entryType # missing OR lastProblem THEN {holeFound _ FixOneProblem[fileEntry, @problem] OR holeAlreadyFound; IF ~lastProblem THEN [nextWord, holeFound] _ FixProblems[currentProblem + 1, fileEntry, pilotLogPointer, nextWord, holeFound]} ELSE -- this is a missing page but not the last problem, so process -- all other problems first {[nextWord, holeFound] _ FixProblems[currentProblem + 1, fileEntry, pilotLogPointer, nextWord, holeAlreadyFound]; -- now fix this missing page if there were no following missing pages IF ~holeFound THEN holeFound _ FixOneProblem[fileEntry, @problem]}; END; -- FixProblems FixTempFiles: PUBLIC PROC [volume: Volume.ID, logFile: File.Capability] RETURNS [holeFound: BOOLEAN] = -- Scans the Pilot Scavenger log for Problem entries, and repairs any -- damaged temporary files. BEGIN newHoleFound: BOOLEAN; fileEntry: Scavenger.FileEntry; nextWord: CARDINAL _ SIZE[Scavenger.Header]; numberOfFiles: LONG CARDINAL; pilotLogPointer: LONG POINTER TO UNSPECIFIED; holeFound _ FALSE; pilotLogPointer _ OpenPilotLogFile[logFile]; numberOfFiles _ LOOPHOLE[ pilotLogPointer, LONG POINTER TO Scavenger.Header].numberOfFiles; FOR counter: LONG CARDINAL _ 1, counter + 1 WHILE counter <= numberOfFiles DO nextWord _ GetWords[@fileEntry, pilotLogPointer, nextWord, SIZE[Scavenger.FileEntry]]; IF fileEntry.numberOfProblems # 0 THEN {[nextWord, newHoleFound] _ ProcessProblems[@fileEntry, pilotLogPointer, nextWord ! Scavenger.Error, File.Error, File.Unknown, Volume.Unknown => {ClosePilotLogFile[]; REJECT}]; holeFound _ holeFound OR newHoleFound}; -- must be cumulative ENDLOOP; ClosePilotLogFile[]; END; -- FixTempFiles GetWords: PUBLIC PROC [ toPointer, bufferPointer: LONG POINTER TO UNSPECIFIED, currentWord, count: CARDINAL] RETURNS [nextWord: CARDINAL] = -- Gets the next count words from the Pilot scavenger log. BEGIN pilotLogWindow: Space.WindowOrigin; THROUGH [0..count) DO IF currentWord >= (Environment.wordsPerPage*pilotLogSpaceSize) THEN {pilotLogWindow _ Space.GetWindow[pilotLogSpace]; pilotLogWindow.base _ pilotLogWindow.base + pilotLogSpaceSize; currentWord _ 0; Space.Unmap[pilotLogSpace]; Space.Map[pilotLogSpace, pilotLogWindow]}; toPointer^ _ (bufferPointer + currentWord)^; toPointer _ toPointer + 1; currentWord _ currentWord + 1; ENDLOOP; nextWord _ currentWord; END; -- GetWords MakePage0Readable: PROC [fileEntry: POINTER TO Scavenger.FileEntry, pilotLogPointer: LONG POINTER TO UNSPECIFIED, currentWord: CARDINAL] = BEGIN problem: Scavenger.Problem; -- scan through this file's problems to see if page 0 is unreadable and -- fix it if so. THROUGH [0..fileEntry.numberOfProblems) DO currentWord _ GetWords[@problem, pilotLogPointer, currentWord, SIZE[Scavenger.Problem]]; WITH problem SELECT FROM missing => NULL; orphan => NULL; unreadable => IF first = 0 THEN UnreadablePages[fileEntry, @problem]; ENDCASE => ERROR Bug[impossibleSelectError]; ENDLOOP; -- now backup the log file "reader" so the problems can be read again THROUGH [0..fileEntry.numberOfProblems) DO currentWord _ Backup[pilotLogPointer, currentWord, SIZE[Scavenger.Problem]]; ENDLOOP; END; -- MakePage0Readable MissingPages: PROC [fileEntry: POINTER TO Scavenger.FileEntry, problem: POINTER TO Scavenger.Problem] = BEGIN -- Takes care of one missing Problem entry from the Pilot scavenger log. -- Truncates the file to n + 1 pages long assuming the last page of the -- hole is at page n. (We can't just fill in the hole with a new page, -- because of a Rubicon VFM bug. Replacing a page missing in the middle -- of a page group with another page causes an invalid VFM to be built. -- The workaround is to truncate the file so that the last page is the -- missing page, then close the volume and scavenge again. This causes -- the VFM to be rebuilt. Since there are now no file pages after the -- missing page, the missing page just falls off the end and disappears -- when we rebuild the VFM. Repeat this process until all the holes -- have disappeared, and then the file no longer contains any holes and -- can be safely deleted.) WITH problem^ SELECT FROM missing => File.SetSize[[fileEntry.file, File.write + File.shrink], first + count]; -- let FixTempFiles catch any errors from SetSize ENDCASE => ERROR Bug[impossibleSelectError]; END; -- MissingPages OpenPilotLogFile: PUBLIC PROC [logFile: File.Capability] RETURNS [bufferPointer: LONG POINTER TO UNSPECIFIED] = -- Maps the Pilot scavenger log into a space and returns a pointer to the -- header information. BEGIN Space.Map[ pilotLogSpace, [logFile, 0] ! File.Unknown, Volume.Unknown => ERROR Bug[pilotLogFileUnknown]]; bufferPointer _ Space.LongPointer[pilotLogSpace]; END; -- OpenPilotLogFile ProcessProblems: PROC [fileEntry: POINTER TO Scavenger.FileEntry, pilotLogPointer: LONG POINTER TO UNSPECIFIED, currentWord: CARDINAL] RETURNS [nextWord: CARDINAL, holeFound: BOOLEAN] = -- Either fixes problems or skips over Problem entries in log, depending -- on whether the file is known and temporary or not. Will also fix page -- 0 in any file in which it is unreadable. (If page 0 of a file is -- unreadable, File.GetAttributes gets an unrecoverable disk error, so we -- cannot call GetAttributes in this case. Thus we are unable to -- determine if the file is temporary or not. Page 0 must be fixed -- first so that the File.GetAttributes works. It also insures that -- DeleteTempsInternalOld and the client scavenger won't trip over the -- unreadable page. If page 0 is missing, File.GetAttributes raises -- File.Unknown, and we will just skip the file.) BEGIN knownAndTemporary: BOOLEAN _ FALSE; holeFound _ FALSE; MakePage0Readable[fileEntry, pilotLogPointer, currentWord]; [temporary: knownAndTemporary] _ File.GetAttributes[[fileEntry.file, File.read] ! File.Unknown, Volume.Unknown => {knownAndTemporary _ FALSE; CONTINUE}]; IF knownAndTemporary THEN [nextWord, holeFound] _ FixProblems[1, fileEntry, pilotLogPointer, currentWord, holeFound] ELSE -- just skip over the Problem entries to get to the next FileEntry {problem: Scavenger.Problem; nextWord _ currentWord; THROUGH [0..fileEntry.numberOfProblems) DO nextWord _ GetWords[ @problem, pilotLogPointer, nextWord, SIZE[Scavenger.Problem]]; ENDLOOP}; END; -- ProcessProblems UnreadablePages: PROC [fileEntry: POINTER TO Scavenger.FileEntry, problem: POINTER TO Scavenger.Problem] = -- Takes care of one unreadable Problem entry from the Pilot scavenger -- log. Tries to read the page and then rewrites the page with whatever -- the read produces, if anything. Getting the contents right is -- less important than clearing the CRC error, because the chief goal is -- to make the file deletable. If the first unreadable page is page 0 of -- the file, it is not guaranteed that the file exists, since we are -- unable to call File.GetAttributes in this case. Thus File.Unknown is -- treated differently (not as an error) if page 0 is unreadable. BEGIN Space.Map[bufferSpace, Space.defaultWindow]; WITH problem^ SELECT FROM unreadable => FOR i: LONG CARDINAL _ 0, i + 1 WHILE i < count DO Scavenger.ReadBadPage[fileEntry.file, first + i, Space.VMPageNumber[bufferSpace] ! File.Unknown => IF first = 0 THEN EXIT ELSE CONTINUE; Scavenger.Error => CONTINUE]; Scavenger.RewritePage[fileEntry.file, first + i, Space.VMPageNumber[bufferSpace] ! Scavenger.Error, File.Unknown, File.Error => {Space.Unmap[bufferSpace]; REJECT}]; ENDLOOP; ENDCASE => ERROR Bug[impossibleSelectError]; Space.Unmap[bufferSpace]; END; -- UnreadablePages END. LOG 28-Oct-81 18:23:57 Fay Created file.