-- Copyright (C) 1985  by Xerox Corporation. All rights reserved. 
-- FloppyImplPublicD.mesa (last edited by: EKN   on:   1-Feb-85  8:02:07)

DIRECTORY
   Environment USING [wordsPerPage],
   File USING [nullFile, Type],
   FileTypes USING [tUnassigned],
   Floppy USING [
     Density, Error, ErrorType, FileID, maxCharactersInLabel, nullFileID, Sides],
   FloppyChannel USING [Attributes, GetDeviceAttributes, GetHandle, SetContext],
   FloppyExtras USING [ExtrasError, ExtrasErrorType],
   FloppyFormat USING [
     badSpotSector, BadSpotSectors, ConvertPageCount, dataContext, FileListEntry,
     FileListSeal, FileListVersion, FloppySeal, FloppyVersion, ImplementedFileSize,
     MarkerPage, MarkerPageEntryType, MarkerPageVersion, MarkerSeal, maxBadSectors,
     minTrackZeroSectors, nullSector, Sector, SectorNine, TrackZero,
     trackZeroAddress, TrackZeroSector],
   FloppyImplInterface USING [
     AccessFloppy, Allocation, allocationsPerPage, CreateBuffer, DiskChanged,
     FileListType, FirstDataSector, IOError, IsDriveWriteProtected, ReadFloppy,
     ValidDrive, VolumeDesc, VolumeChanging, VolumeStable, volumeTable,
     WordsToPages],
   Runtime USING [CallDebugger],
   Space USING [Error, Interval, Map, nullInterval, PageCount, Unmap];

FloppyImplPublicD: MONITOR
   LOCKS volumeDesc USING volumeDesc: FloppyImplInterface.VolumeDesc
   IMPORTS Floppy, FloppyChannel, FloppyExtras, FloppyFormat, FloppyImplInterface,
     Runtime, Space
   EXPORTS FloppyExtras
   SHARES FloppyImplInterface =

  BEGIN

  -- PUBLIC ERRORs, SIGNALs and TYPEs

  -- Private constants, types, variables

  BugType: TYPE = {insufficientVM};
  Bug: PROCEDURE [bug: BugType] = {Runtime.CallDebugger["Insufficient VM"L]};
  --  When Bug moves from RuntimeInternal to SpecialRuntime, use the following
  --Bug: PROCEDURE [bug: BugType] = {RuntimeInternal.Bug[bug]};

  ScavengeProblemType: TYPE = {allocMapInconsistent, badPageTable, bootFile,
    duplicateFileID, duplicateFileList, fileList, fileListEntry,
    freeSpaceConflict, ioError, none, sectorNine};
  problem: ScavengeProblemType ← none;

  -- PUBLIC operations

  --FloppyExtras.--
  NewScavenge: PUBLIC PROCEDURE [drive: CARDINAL] RETURNS [okay: BOOLEAN] =
    BEGIN
    buffer: Space.Interval ← Space.nullInterval;
    type: Floppy.ErrorType;

    ScavengeInternal: ENTRY PROCEDURE [
      volumeDesc: FloppyImplInterface.VolumeDesc] =
      BEGIN

      Shutdown: PROCEDURE =
        BEGIN
        buffer.pointer ← Space.Unmap[buffer.pointer ! Space.Error => CONTINUE];
        IF volumeDesc.fileList ~= NIL THEN volumeDesc.fileList ←
	  Space.Unmap[volumeDesc.fileList ! Space.Error => CONTINUE];
        IF volumeDesc.allocationMap ~= NIL THEN volumeDesc.allocationMap ←
	  Space.Unmap[volumeDesc.allocationMap ! Space.Error => CONTINUE];
	END;

      BEGIN ENABLE
        BEGIN
        FloppyImplInterface.IOError => {problem ← ioError; GOTO notOK};
	Floppy.Error => {type ← error; GOTO floppyError}; --?????necessary????
	UNWIND => Shutdown[];
	END;
      trackZero: FloppyFormat.TrackZero;
      markerPage: LONG POINTER TO FloppyFormat.MarkerPage;
      requestedSize: Space.PageCount;
      sectorNine: LONG POINTER TO FloppyFormat.SectorNine;

      ValidMarkers: PROCEDURE [fileAddress: FloppyFormat.Sector,
        enclosee: FloppyFormat.MarkerPageEntryType,
        size: FloppyFormat.ImplementedFileSize,
	id: Floppy.FileID ← Floppy.nullFileID,
        type: File.Type ← FileTypes.tUnassigned]
        RETURNS [valid: BOOLEAN] =
        BEGIN
        FloppyImplInterface.ReadFloppy[
          volumeDesc: volumeDesc, buffer: markerPage, count: 1,
          address: fileAddress - 1];
        IF NOT ((markerPage.seal = FloppyFormat.MarkerSeal)
          AND (markerPage.version = FloppyFormat.MarkerPageVersion)
          AND (markerPage.next.length = size)) THEN GOTO failed;
        WITH mp: markerPage.next SELECT FROM
          free => IF enclosee ~= free THEN GOTO failed;
          file =>
            IF NOT ((enclosee = file) AND (mp.file = id) AND (mp.type = type))
	      THEN GOTO failed;
          fileList =>
            IF NOT
	      ((enclosee = fileList) AND (mp.file = id) AND (mp.type = type))
	      THEN GOTO failed;
          badSectors => IF enclosee ~= badSectors THEN GOTO failed;
	  ENDCASE => GOTO failed;
        FloppyImplInterface.ReadFloppy[
          volumeDesc: volumeDesc, buffer: markerPage, count: 1,
          address: fileAddress + size];
        IF NOT ((markerPage.seal = FloppyFormat.MarkerSeal)
          AND (markerPage.version = FloppyFormat.MarkerPageVersion)
          AND (markerPage.previous.length = size)) THEN GOTO failed;
        WITH mp: markerPage.previous SELECT FROM
          free => IF enclosee ~= free THEN GOTO failed;
          file =>
            IF ((enclosee ~= file) AND (mp.file = id) AND (mp.type = type))
	      THEN GOTO failed;
          fileList =>
            IF ((enclosee ~= fileList) AND (mp.file = id) AND (mp.type = type))
	      THEN GOTO failed;
          badSectors => IF enclosee ~= badSectors THEN GOTO failed;
          ENDCASE => GOTO failed;
        valid ← TRUE;
        EXITS failed => valid ← FALSE;
        END;  -- ValidMarkers

      --  Begin ScavengeInternal main code
      IF volumeDesc.open THEN
        RETURN WITH ERROR FloppyExtras.ExtrasError[volumeOpen];

      -- Get buffers and handle to access the floppy
      requestedSize ← 
        FloppyImplInterface.WordsToPages[(SIZE[FloppyFormat.TrackZeroSector] * 
	FloppyFormat.minTrackZeroSectors)] + 
        FloppyImplInterface.WordsToPages[SIZE[FloppyFormat.MarkerPage]];
      buffer ← FloppyImplInterface.CreateBuffer[requestedSize];
      IF requestedSize ~= buffer.count THEN Bug[insufficientVM];
      trackZero.BASE ← LOOPHOLE[buffer.pointer];
      markerPage ← LOOPHOLE[
        BASE[trackZero] +
        Environment.wordsPerPage * 
        (FloppyImplInterface.WordsToPages[FloppyFormat.minTrackZeroSectors *
	SIZE[FloppyFormat.TrackZeroSector]])];
      
      volumeDesc.fileList ← NIL;
      volumeDesc.allocationMap ← NIL;
      volumeDesc.handle ← FloppyChannel.GetHandle[drive];            
      IF (volumeDesc.writeProtected ←
        FloppyImplInterface.IsDriveWriteProtected[volumeDesc])
          THEN RETURN WITH ERROR Floppy.Error[writeInhibited];

      -- Read track zero.  Validate contents, fill in our local database.
      -- (We can access fixed-format track0 before volumeDesc is complete.)
      FloppyImplInterface.AccessFloppy[
        volumeDesc, BASE[trackZero], FloppyFormat.trackZeroAddress,
        FloppyFormat.minTrackZeroSectors, read];

      -- Check SectorNine, continue filling in volumeDesc
      volumeDesc.sectorNine↑ ← LOOPHOLE[trackZero[9]];
      sectorNine ← volumeDesc.sectorNine;
      IF FloppyChannel.SetContext[volumeDesc.handle, FloppyFormat.dataContext[single]] AND
        FloppyChannel.GetDeviceAttributes[volumeDesc.handle].maxSectorsPerTrack = sectorNine.sectorsPerTrack
	  THEN volumeDesc.density ← single
      ELSE IF FloppyChannel.SetContext[volumeDesc.handle, FloppyFormat.dataContext[double]] AND
        FloppyChannel.GetDeviceAttributes[volumeDesc.handle].maxSectorsPerTrack = sectorNine.sectorsPerTrack
	  THEN volumeDesc.density ← double
      ELSE {problem ← sectorNine; GOTO notOK};
      
      SELECT sectorNine.tracksPerCylinder FROM
        1 => volumeDesc.sides ← one;
        2 => volumeDesc.sides ← two;
        ENDCASE => {problem ← sectorNine; GOTO notOK};

      BEGIN
      attrib: FloppyChannel.Attributes ←
        FloppyChannel.GetDeviceAttributes[volumeDesc.handle];
      IF NOT ((sectorNine.seal = FloppyFormat.FloppySeal)
	AND (sectorNine.version = FloppyFormat.FloppyVersion)
        AND (sectorNine.cylinders > 0)
        AND (sectorNine.cylinders = attrib.numberOfCylinders)
	AND (sectorNine.tracksPerCylinder = attrib.numberOfHeads)
        AND (sectorNine.sectorsPerTrack = attrib.maxSectorsPerTrack))
	THEN {problem ← sectorNine; GOTO notOK};
      volumeDesc.numPages ← sectorNine.sectorsPerTrack*
        sectorNine.tracksPerCylinder * sectorNine.cylinders;
      END;

      -- Check sector nine file list entries.
      IF NOT ((sectorNine.fileList ~= FloppyFormat.nullSector)
	AND (sectorNine.fileList < volumeDesc.numPages)
	AND (sectorNine.fileListID ~= Floppy.nullFileID)
	AND (sectorNine.fileListSize > 0))
	THEN {problem ← sectorNine; GOTO notOK};

      IF ~ValidMarkers[fileAddress: sectorNine.fileList, enclosee: fileList,
        size: sectorNine.fileListSize, id: sectorNine.fileListID,
        type: FloppyImplInterface.FileListType]
	THEN {problem ← fileList; GOTO notOK};

      -- Get file list itself, check
      volumeDesc.fileListSpace ←
        FloppyImplInterface.CreateBuffer[sectorNine.fileListSize];
      volumeDesc.fileList ← volumeDesc.fileListSpace.pointer;
      FloppyImplInterface.ReadFloppy[
        volumeDesc, volumeDesc.fileList, sectorNine.fileList,
	sectorNine.fileListSize];
      IF NOT ((volumeDesc.fileList.seal = FloppyFormat.FileListSeal)
        AND (volumeDesc.fileList.version = FloppyFormat.FileListVersion)
        AND (volumeDesc.fileList.maxEntries > 0)
        AND (volumeDesc.fileList.maxEntries >= volumeDesc.fileList.count)
        AND (volumeDesc.fileList.count > 0))
        THEN {problem ← fileList; GOTO notOK};

      BEGIN
      fileListFound: BOOLEAN ← FALSE;
      highestID: Floppy.FileID ← Floppy.nullFileID;
      FOR i: CARDINAL IN [0..volumeDesc.fileList.count) DO
        file: FloppyFormat.FileListEntry = volumeDesc.fileList.files[i];
	IF NOT ((file.file ~= Floppy.nullFileID)
	  AND (file.location ~= FloppyFormat.nullSector)
	  AND (file.size > 0) AND (file.size <= volumeDesc.numPages))
	  THEN {problem ← fileListEntry; GOTO notOK};
	IF file.type = FloppyImplInterface.FileListType
	  THEN
	    {IF ~fileListFound THEN fileListFound ← TRUE
	      ELSE {problem ← duplicateFileList; GOTO notOK}}
	  ELSE IF ~ ValidMarkers[fileAddress: file.location, enclosee: file,
	    size: file.size, id: file.file, type: file.type]
	    THEN {problem ← fileListEntry; GOTO notOK};
	IF SmallerIDLargerID[highestID, file.file] THEN highestID ← file.file;
	FOR j: CARDINAL IN [0..volumeDesc.fileList.count - 1) DO
	  IF i = j THEN LOOP;
	  IF file.file = volumeDesc.fileList.files[j].file
	    THEN {problem ← duplicateFileID; GOTO notOK};
	  ENDLOOP;
	ENDLOOP;
      IF ~fileListFound THEN {problem ← fileList; GOTO notOK};
      IF sectorNine.nextUnusedFileID <= LOOPHOLE[highestID, LONG CARDINAL]
        THEN {problem ← sectorNine; GOTO notOK};
      -- For level 1 scavenger, nextUnusedFileID is guaranteed to be > last
      -- actual ID in file list, but it may be > last ID + 1.
      -- Level 2: reset nextUnusedFileID if ~= highestID + 1
      END;  -- check file list and related
      -- Level 2: if file list not sorted correctly (see PPM), rewrite sorted

      -- Check root file
      IF (~NullID[sectorNine.rootFile]
	AND (sectorNine.nextUnusedFileID <=
	  LOOPHOLE[sectorNine.rootFile, LONG CARDINAL]))
	THEN {problem ← fileList; GOTO notOK};
      -- ?? check at advertised loc only if ID > lastIDAllocated

      -- Check boot files: ensure advertised sector is in a file in the file list.
      -- Not necessary to check file itself because check file list did that.
      IF sectorNine.pilotMicrocode ~= FloppyFormat.nullSector THEN
        {IF ~BootFileInFileList[volumeDesc, sectorNine.pilotMicrocode]
	  THEN {problem ← bootFile; GOTO notOK}};
      IF sectorNine.diagnosticMicrocode ~= FloppyFormat.nullSector
        THEN {IF ~BootFileInFileList[
	  volumeDesc, sectorNine.diagnosticMicrocode]
	  THEN {problem ← bootFile; GOTO notOK}};
      IF sectorNine.germ ~= FloppyFormat.nullSector THEN
        {IF ~BootFileInFileList[volumeDesc, sectorNine.germ]
	  THEN {problem ← bootFile; GOTO notOK}};
      IF sectorNine.pilotBootFile ~= FloppyFormat.nullSector THEN
        {IF ~BootFileInFileList[volumeDesc, sectorNine.pilotBootFile]
	  THEN {problem ← bootFile; GOTO notOK}};

      IF NOT ((sectorNine.firstAlternateSector < volumeDesc.numPages)
        AND (sectorNine.countBadSectors < FloppyFormat.maxBadSectors)
        AND (sectorNine.labelSize <= Floppy.maxCharactersInLabel))
        THEN {problem ← sectorNine; GOTO notOK};
      -- End check sector nine

      -- Check bad spot table
      BEGIN
      track0Map: LONG POINTER TO FloppyFormat.BadSpotSectors ←
        LOOPHOLE[BASE[trackZero] +
	  (FloppyFormat.badSpotSector-1)*SIZE[FloppyFormat.TrackZeroSector]];
      FOR i: CARDINAL IN [0..FloppyFormat.maxBadSectors) DO
        volumeDesc.badPageMap[i] ← track0Map[i];
	ENDLOOP;
      END;
      FOR i: CARDINAL IN [0..sectorNine.countBadSectors) DO
        IF NOT ((volumeDesc.badPageMap[i].bad ~= FloppyFormat.nullSector)
	  AND (volumeDesc.badPageMap[i].bad < volumeDesc.numPages)
	  AND (volumeDesc.badPageMap[i].bad ~= volumeDesc.badPageMap[i].alternate)
	  AND (volumeDesc.badPageMap[i].alternate ~= FloppyFormat.nullSector)
	  AND (volumeDesc.badPageMap[i].alternate < volumeDesc.numPages))
	  THEN {problem ← badPageTable; GOTO notOK};
	-- check page has valid markers or other bad page adjacent
	-- check sorted?
	ENDLOOP;
      -- End check bad spot table

      -- Cross check file list, bad spot table, and standard overhead sectors
      -- for overlap by building an allocation map.
      BEGIN
      -- This calculation is flakey so allocate an extra page for safety
      size: CARDINAL ← 1 + FloppyImplInterface.WordsToPages[
        (volumeDesc.numPages + FloppyImplInterface.allocationsPerPage - 1)/
          FloppyImplInterface.allocationsPerPage];
      volumeDesc.allocationMapSpace ← Space.Map[[File.nullFile, 0, size]]; 
      -- We should really catch the signals and convert them into something else
      volumeDesc.allocationMap ← volumeDesc.allocationMapSpace.pointer;
      IF ~BuildAllocationMap[
	volumeDesc: volumeDesc,
	map: volumeDesc.allocationMapSpace.pointer].consistent
          THEN {problem ← allocMapInconsistent; GOTO notOK};
      END;  -- build and check allocation map

      -- Check that chunks the allocation map thinks are free, i.e., not used by
      -- files, bad pages, or file system structures, are well formed free spaces
      -- on the disk.
      BEGIN
      first: FloppyFormat.Sector;
      next: FloppyFormat.Sector ←
        FloppyImplInterface.FirstDataSector[volumeDesc];
      map: LONG POINTER TO PACKED ARRAY [0..0) OF FloppyImplInterface.Allocation
        ← volumeDesc.allocationMapSpace.pointer;
      DO
	FOR first ← next, first + 1 UNTIL first > volumeDesc.numPages DO
	  IF map[first] = free THEN EXIT;
	  REPEAT
	  FINISHED => GOTO allDone;
	  ENDLOOP;
	FOR next ← first + 1, next + 1 UNTIL next > volumeDesc.numPages DO
	  IF map[next] ~= free THEN EXIT;
	  REPEAT
	  FINISHED => {problem ← freeSpaceConflict; GOTO notOK};
	  ENDLOOP;
	IF ~ValidMarkers[fileAddress: first, enclosee: free, size: next-first]
	  THEN {problem ← freeSpaceConflict; GOTO notOK};
	REPEAT
	  allDone => NULL;
	ENDLOOP;
      END;  -- check free spaces

      -- Check, correct if wrong once file system verified (but not error)
      --   check track 0 sectors contents
      --   0..8, 10 specified in FloppyFormat
      --   11..26 are like 10
      --   check track 1 sectors contents

       FloppyImplInterface.VolumeStable[volumeDesc];
       Shutdown[];
       EXITS
         floppyError => {Shutdown[]; RETURN WITH ERROR Floppy.Error[type]};
         notOK =>
	   BEGIN
	   FloppyImplInterface.VolumeChanging[volumeDesc];
	   Shutdown[];
	   okay ← FALSE;
	   END;
       END;  -- scope of ENABLE
       END;  -- ScavengerInternal

     -- BEGIN Scavenge main code
     BEGIN ENABLE UNWIND => NULL;
     IF ~FloppyImplInterface.ValidDrive[drive] THEN
       ERROR Floppy.Error[noSuchDrive];
     okay ← TRUE;
     ScavengeInternal[FloppyImplInterface.volumeTable[drive]
       ! FloppyImplInterface.DiskChanged => RETRY];
     END;  -- scope of ENABLE
     END;  -- Scavenge

  BootFileInFileList: PROCEDURE [
    volumeDesc: FloppyImplInterface.VolumeDesc, page: FloppyFormat.Sector]
    RETURNS [BOOLEAN] =
    BEGIN
    FOR i: CARDINAL IN [0..volumeDesc.fileList.count) DO
      IF page IN [volumeDesc.fileList.files[i].location..
        volumeDesc.fileList.files[i].location+volumeDesc.fileList.files[i].size)
        THEN RETURN[TRUE];
      ENDLOOP;
    RETURN[FALSE];
    END;

  BuildAllocationMap: PROCEDURE [
    volumeDesc: FloppyImplInterface.VolumeDesc,
    map: LONG POINTER TO PACKED ARRAY [0..0) OF FloppyImplInterface.Allocation]
    RETURNS [consistent: BOOLEAN] =
    BEGIN
    -- This is almost exactly the same as
    -- FloppyImplPrivate.InitializeAllocationMap.  Volume is not open (thus not
    -- closed) and it doesn't quit as soon as it finds an inconsistency.
    -- InitializeEtc. could be modified to use this for stuff they have in common.

    -- Start clean
    FOR i: CARDINAL IN [0..FloppyFormat.ConvertPageCount[volumeDesc.numPages]] DO
      map[i] ← free;
      ENDLOOP;
    consistent ← TRUE;

    -- Mark first and last pages of disk as marker pages. If they are actually
    -- bad, loop over the bad page list below will take care of it.
    map[FloppyImplInterface.FirstDataSector[volumeDesc]] ← markerPage;
    map[FloppyFormat.ConvertPageCount[volumeDesc.numPages]] ← markerPage;

    -- Mark bad page list: bad pages and markers around them
    FOR i: CARDINAL IN [0..volumeDesc.sectorNine.countBadSectors) DO
      map[volumeDesc.badPageMap[i].bad] ← badPage;
      IF map[volumeDesc.badPageMap[i].bad-1] = free THEN
        map[volumeDesc.badPageMap[i].bad-1] ← markerPage;
      IF map[volumeDesc.badPageMap[i].bad+1] = free THEN
        map[volumeDesc.badPageMap[i].bad+1] ← markerPage;
      ENDLOOP;

    -- Mark Sector 0 and cylinder 0 allocated
    FOR i: CARDINAL IN [0..FloppyImplInterface.FirstDataSector[volumeDesc]) DO
      map[i] ← allocated;
      ENDLOOP;

    -- Mark marker pages and contents pages for all files in the File list
    FOR i: CARDINAL IN [0..volumeDesc.fileList.count) DO
      file: FloppyFormat.FileListEntry = volumeDesc.fileList.files[i];
      IF ~(map[file.location-1] = free OR map[file.location-1] = markerPage)
        THEN consistent ← FALSE ELSE map[file.location-1] ← markerPage;
      IF ~(map[file.location + file.size] = free OR
        map[file.location + file.size] = markerPage)
        THEN consistent ← FALSE ELSE map[file.location+file.size] ← markerPage;
      FOR j: CARDINAL IN [file.location..file.location + file.size) DO
	IF map[j] ~= free THEN consistent ← FALSE ELSE map[j] ← allocated;
	ENDLOOP;
      ENDLOOP;

    END;  -- BuildAllocationMap

  NullID: PROCEDURE [file: Floppy.FileID] RETURNS [BOOLEAN] = INLINE
    {RETURN[LOOPHOLE[file, LONG CARDINAL]
      = LOOPHOLE[Floppy.nullFileID, LONG CARDINAL]]};

  SmallerIDLargerID: PROCEDURE [idA, idB: Floppy.FileID] RETURNS [BOOLEAN] =
    INLINE
    {RETURN[LOOPHOLE[idA, LONG CARDINAL] < LOOPHOLE[idB, LONG CARDINAL]]};

  END.




LOG

13-Nov-84 15:42:58	CAJ 	Created file.
 1-Feb-85  8:02:15	EKN  	Removed uses of FloppyFormat.dataSectorsPerTrack. Fixed up uses of FloppyFormat.TrackZero. Fixed arrows.