-- Transactions>TransactionImpl.mesa
-- Last edited by Levin on August 26, 1982 11:04 am
-- This module implements high-level transaction operations:
-- Commit, Abort, Crash Recovery.  Serial access to individual transactions
-- is accomplished by checking-out a transaction from TransactionStateImpl.
-- This module runs at a level within Pilot above the VMMgr.

DIRECTORY
  Environment USING [wordsPerPage],
  File USING [
    Capability, Create, Delete, Error, GetSize, MakeImmutable, MakePermanent,
    Move, nullCapability, nullID, PageCount, SetSize, Unknown],
  Inline USING [LongCOPY],
  KernelFile USING [
    CreateWithID, ExistingFile, GetRootFile, MakeMutable, MakeTemporary],
  KernelSpace USING [ReleaseFromTransaction],
  PilotFileTypes USING [tTransactionStateFile],
  PilotMP USING [cTransactionCrashRecovery],
  PilotSwitches USING [switches --.x--],
  ProcessorFace USING [SetMP],
  Runtime USING [CallDebugger, IsBound],
  SimpleSpace USING [Kill, Map, Unmap],
  Space USING [
    CopyOut, Create, Delete, defaultBase, defaultWindow, Error, ForceOut, GetAttributes,
    GetWindow, Handle, Kill, LongPointer, LongPointerFromPage, Map, PageCount,
    PageNumber, PageOffset, Unmap, virtualMemory, VMPageNumber, WindowOrigin],
  SpecialTransaction USING [ClientStart],
  StoragePrograms USING [],
  TemporaryTransaction USING [],
  Transaction USING [Handle],
  TransactionExtras USING [],
  TransactionInternal USING [
    Checksum, countLogEntry, disabled, FilePageCount, FilePageNumber, Handle,
    LogFileEntry, LogFileEntryData, LogFileEntryPtr, logFileFormatVersion,
    MaybeCrash, permissions, ShortenPageCount, standardLogFileCount, state,
    State, state1Window, state2Window, stateCount, StateData, StateEdition,
    stateFormatVersion, stateSpace, TxDescPtr, TxIndex, ValidTxEdition,
    ValidTxIndex],
  TransactionState USING [
    InitializeStateA, InitializeStateB, LogOp, NoMoreOperations, RecordCommit,
    ReleaseTransaction, SpaceNodePtr],
  Utilities USING [LongPointerFromPage],
  Volume USING [systemID, Unknown];

TransactionImpl: MONITOR
  -- This module's monitor is only used to control access to the two utility
  -- spaces used by Abort and crash recovery.  Access to individual
  -- transactions is controlled by the monitor of TransactionStateImpl.

  IMPORTS
    File, Inline, KernelFile, KernelSpace, PilotSwitches, ProcessorFace,
    Runtime, SimpleSpace, Space, SpecialTransaction, TransactionInternal,
    TransactionState, Utilities, Volume
  EXPORTS StoragePrograms, TemporaryTransaction, Transaction, TransactionExtras
  SHARES File --USING [permissions]-- =
BEGIN OPEN TransactionInternal;

--Transaction.--Handle: PUBLIC TYPE = TransactionInternal.Handle;

logSpaceSize: Space.PageCount ← 500;
  -- arbitrary number, big enough to avoid doing lots of Maps and Unmaps
  -- on logSpace

entrySpace, logSpace, clientSpace: Space.Handle; -- for Abort processing.
logZeroSpace: Space.Handle; -- for zeroing out log files
logZeroData: LONG POINTER; -- to logZeroSpace

BugType: TYPE = {
  xxx, abortFileError, abortSpaceError, illegalLogOperation,
  inactiveTransaction, invalidLogEntry, stateDataNotEqualToState,
  unexpectedErrorRestoringSpaces, unexpectedErrorValidateLogFile};

-- BugType: TYPE = {commitSpaceError, crashRecoveryFileError, crashRecoverySpaceError, illegalLogOperation, impossibleStateInconsistency, invalidLogHandle, invalidVersionSeal, logAlreadyAllocated, logFileDisappeared, logFileVolumeError, missingPinnedPageGroup, remoteLogFile, spaceAlreadyInTransaction, tooManyTransactions};

Bug: PRIVATE --PROGRAMMING--ERROR [BugType] = CODE;


--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- Monitor External Procedures
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

--TemporaryTransaction.--DisableTransactions: PUBLIC PROCEDURE =
  {disabled ← TRUE};

Abort: PUBLIC SAFE PROCEDURE [transaction: TransactionInternal.Handle] = TRUSTED
  BEGIN
  -- Save the windows that the spaces are currently mapped to, and unmap them:
  spaceList: TransactionState.SpaceNodePtr;
  IF disabled THEN RETURN;
  spaceList ← TransactionState.NoMoreOperations[transaction];
    -- may raise InvalidHandle
  FOR space: TransactionState.SpaceNodePtr ← spaceList, space.nextSpace UNTIL
    space = NIL DO
    space.window ← Space.GetWindow[space.handle !
      -- (note that GetWindow will return defaultWindow if the space is
      -- mapped as a data space.  We will not do anything to that space
      -- during Abort processing.)
      Space.Error, File.Unknown, Volume.Unknown =>
      {space.window ← Space.defaultWindow; CONTINUE}];
         -- we assume client deleted the space or the file.
    IF space.window # Space.defaultWindow THEN
      Space.Unmap[space.handle !
        Space.Error => {space.window ← Space.defaultWindow; CONTINUE}];
    ENDLOOP;

  -- Back out the changes that have been made to the client's files:
  RestoreFilesInTransaction[transaction.index];

  -- Map spaces back to their windows and deallocate the space list elements:
  FOR space: TransactionState.SpaceNodePtr ← spaceList, space.nextSpace UNTIL
    space = NIL DO
    IF space.window # Space.defaultWindow THEN
      Space.Map[space.handle, space.window !
        Space.Error, File.Unknown, Volume.Unknown => CONTINUE;
          -- we assume client deleted the space
          -- or the file was deleted in the course of Abort processing.
        ANY => ERROR Bug[unexpectedErrorRestoringSpaces]];
          -- client messing with files during transaction!
    KernelSpace.ReleaseFromTransaction[space.handle, transaction !
      Space.Error => CONTINUE];
    ENDLOOP;
  ReleaseLog[transaction.index];
  TransactionState.ReleaseTransaction[transaction];
  END;

-- Note:  Begin[] is implemented in TransactionStateImpl.

Commit: PUBLIC SAFE PROCEDURE [transaction: TransactionInternal.Handle] = TRUSTED
  BEGIN
  -- At present, all transaction space and file operations use an "undo log",
  -- so Commit is very simple: force out all mapped spaces, record the commit
  -- in the Transaction State file, then clear the log file, then free the tx
  -- entry in the State.
  spaceList: TransactionState.SpaceNodePtr;
  IF disabled THEN RETURN;
  spaceList ← TransactionState.NoMoreOperations[transaction];
    -- may raise InvalidHandle
  FOR space: TransactionState.SpaceNodePtr ← spaceList, space.nextSpace UNTIL
    space = NIL DO
    Space.ForceOut[space.handle ! Space.Error => CONTINUE];
      -- If error, we assume client deleted the space.
    KernelSpace.ReleaseFromTransaction[
      space.handle, transaction ! Space.Error => CONTINUE];
    ENDLOOP;
  TransactionState.RecordCommit[transaction];
    -- now the log file will be ignored if we crash.
  ReleaseLog[transaction.index];
  TransactionState.ReleaseTransaction[transaction];
  END;


--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- Initialization and Crash Recovery
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


--StoragePrograms.--InitializeTransactionData: PUBLIC PROCEDURE[] =
-- Initializes nullHandle exported variable, creates SimpleSpaces for logging.
  {TransactionState.InitializeStateA[]}; -- creates stateSpace.


--StoragePrograms.--RecoverTransactions: PUBLIC --EXTERNAL--PROCEDURE[] =
-- Initializes Transaction modules, performs Crash Recovery.
-- This procedure is called during Pilot initialization after the FileMgr and
-- VMMgr are started, but before anyone is using transactions, so (1) we can
-- use high-level File and Space operations, and (2) no one will call Log
-- from inside FileImpl, which would trigger a deadlock since we are using
-- File operations ourself.
  BEGIN

  ProcessorFace.SetMP[PilotMP.cTransactionCrashRecovery];
  IF PilotSwitches.switches.x = down THEN Runtime.CallDebugger["Key Stop X"L];
  IF Runtime.IsBound[SpecialTransaction.ClientStart] THEN
    SpecialTransaction.ClientStart[];
  logSpace ← Space.Create[logSpaceSize, Space.virtualMemory];
  clientSpace ← Space.Create[logSpaceSize, Space.virtualMemory];
  entrySpace ← Space.Create[countLogEntry, Space.virtualMemory];
    -- Create real swap units to avoid UniformSwapUnit deadlock bug:
  logZeroSpace ←
    Space.Create[standardLogFileCount-countLogEntry, Space.virtualMemory];
  logZeroData ← Space.LongPointer[logZeroSpace];
  Space.Map[logZeroSpace]; -- always mapped
  FOR base: Space.PageOffset ← 0, base + 20 WHILE base < logSpaceSize DO
    [] ← Space.Create[MIN[20, logSpaceSize - base], logSpace, base];
    [] ← Space.Create[MIN[20, logSpaceSize - base], clientSpace, base];
    ENDLOOP;
  DoCrashRecovery[];
  END;

--TransactionExtras.--DoCrashRecovery: PUBLIC PROCEDURE =
  BEGIN
  RecoverState[]; -- performs Crash Recovery on the State Table.
  TransactionState.InitializeStateB[];
  FOR txi: ValidTxIndex IN ValidTxIndex DO
    ValidateLogFile[txi];
    WITH trans: state[txi] SELECT FROM
      active =>
        BEGIN
        trans.noMoreOperations ← TRUE; -- (keeps ReleaseTransaction happy.)
        trans.spaceList ← NIL;
        TransactionInternal.MaybeCrash[1];
        IF ~trans.committed THEN RestoreFilesInTransaction[txi, TRUE];
        TransactionInternal.MaybeCrash[5];
        ReleaseLog[txi];
        TransactionState.ReleaseTransaction[
          TransactionInternal.Handle[trans.txEdition, txi]]
        END;
      free => NULL;
      ENDCASE;
    ENDLOOP;
  END;

--TransactionExtras.--TransactionsInProgress: PUBLIC PROCEDURE RETURNS [BOOLEAN] =
  BEGIN
  FOR txi: ValidTxIndex IN ValidTxIndex DO
    WITH trans: state[txi] SELECT FROM
      active => IF ~trans.committed THEN RETURN[TRUE];
      ENDCASE;
    ENDLOOP;
  RETURN[FALSE];
  END;



--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- Support routines
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

RestoreFilesInTransaction: ENTRY PROCEDURE [
  txi: ValidTxIndex, crashRecovery: BOOLEAN ← FALSE] =
  -- Restores client files involved in the transaction.
  -- Notice that this routine may be (possibly partially) executed an
  -- arbitrary number of times if we crash during crash recovery.
  BEGIN
  pTrans: TxDescPtr = @state[txi];
  entry: LogFileEntry;
  logPage: Space.PageNumber ← Space.VMPageNumber[logSpace];
  clientPage: Space.PageNumber ← Space.VMPageNumber[clientSpace];
  pageBase, pageEntry: FilePageNumber;
  WITH trans: pTrans SELECT FROM
    active =>
      IF trans.pageFree > 0 THEN -- there's something in the log..
        BEGIN
        pageEntry ← trans.pageFree - trans.countPrevLogEntry;
          -- start with the last entry, process file back to front
        entry.countPrevEntry ← trans.countPrevLogEntry;
          -- so maybe we have to do some adjusting for the first one, but this
          -- saves keeping this number in the state file...

        DO -- UNTIL all entries processed

-- first find the minimum possible pageBase value that would include the
-- header page for the next entry to be processed...
          pageBase ←
            IF pageEntry < logSpaceSize THEN 0
            ELSE (pageEntry + countLogEntry) - logSpaceSize;

-- then, if not all of the entry about to be mapped would be in the space
-- with that pageBase, add to it to get as much as possible of that entry
-- (all of it, if it will fit) into the space, remembering not to make
-- pageBase greater than pageEntry.
          IF pageEntry+entry.countPrevEntry > pageBase+logSpaceSize THEN
            pageBase ← MIN[
              pageEntry+(entry.countPrevEntry-countLogEntry), pageEntry];
          Space.Map[logSpace, [[trans.logFile, permissions], pageBase]];
          WHILE pageEntry >= pageBase DO
            -- scope of IgnorableError --
            BEGIN
            ENABLE
              BEGIN
              File.Unknown, Volume.Unknown => GOTO IgnorableError;
              File.Error =>
                SELECT type FROM
                  immutable, notImmutable =>
                    GOTO IgnorableError;
                  ENDCASE => ERROR Bug[abortFileError];
              END;
            entry ← LOOPHOLE[Utilities.LongPointerFromPage[
              logPage + (pageEntry - pageBase)], LogFileEntryPtr]↑;
            IF entry.seal # logFileFormatVersion OR
              entry.transactionHandle.index # txi OR
              entry.transactionHandle.txEdition # trans.txEdition OR
              entry.transactionHandle2.index # txi OR
              entry.transactionHandle2.txEdition # trans.txEdition THEN
              ERROR Bug[invalidLogEntry];
            WITH entry.op SELECT FROM
              create =>
                KernelFile.CreateWithID[volume, size, type, id !
                  KernelFile.ExistingFile => CONTINUE];
                  -- (we already created it during previous incomplete
                  -- crash Recovery.)
              delete => File.Delete[[id, permissions]];
              makeMutable => KernelFile.MakeMutable[[id, permissions]];
              makeTemporary =>
                IF crashRecovery THEN File.Delete[[id, permissions]]
                ELSE KernelFile.MakeTemporary[[id, permissions]];
              move => File.Move[[id, permissions], volume];
              setContents =>
                BEGIN
                -- Note: must do things in the proper order below!
                -- (FIRST set contents of file, THEN MakePermanent,
                -- THEN MakeImmutable)
                clientFile: File.Capability = [id, permissions];
                fileSize: File.PageCount = File.GetSize[clientFile];
                mapSize: Space.PageCount ← MIN[
                  ShortenPageCount[size], logSpaceSize];
                logData: LONG POINTER TO UNSPECIFIED ←
                  LOOPHOLE[Utilities.LongPointerFromPage[
                    logPage + (pageEntry - pageBase) + countLogEntry]];
                clientData: LONG POINTER TO UNSPECIFIED ←
                  LOOPHOLE[Utilities.LongPointerFromPage[clientPage]];
                originalWindow: Space.WindowOrigin =
                  [clientFile, base];
                clientWindow: Space.WindowOrigin ← originalWindow;

                IF fileSize<base+size THEN
                  File.SetSize[clientFile, base+size];
                IF pageEntry+countLogEntry+size > pageBase+logSpaceSize THEN
                  BEGIN -- get logSpace and clientWindow in sync
                  pageBase ← pageEntry + countLogEntry;
                  Space.Unmap[logSpace];
                  Space.Map[logSpace,
                    [[trans.logFile, permissions], pageBase]];
                  logData ← LOOPHOLE[Utilities.LongPointerFromPage[logPage]];
                  END;
                DO
                  Space.Map[clientSpace, clientWindow];
                  Inline.LongCOPY[from: logData, to: clientData,
                    nwords: MIN[mapSize, ShortenPageCount[(base+size)-
                      clientWindow.base]]*Environment.wordsPerPage];
                  IF clientWindow.base+mapSize>=originalWindow.base+size
                    THEN EXIT;
                  clientWindow.base ← clientWindow.base + mapSize;
                  pageBase ← pageBase + mapSize;
                  Space.Unmap[clientSpace];
                  Space.Unmap[logSpace];
                  Space.Map[logSpace,
                    [[trans.logFile, permissions], pageBase]];
                  ENDLOOP;
                Space.Unmap[clientSpace];
                IF makePermanent THEN File.MakePermanent[clientFile];
                IF makeImmutable THEN File.MakeImmutable[clientFile];
                TransactionInternal.MaybeCrash[6];
                END;
              shrink => File.SetSize[[id, permissions], size];
              ENDCASE => ERROR Bug[illegalLogOperation];
            EXITS IgnorableError => NULL;
            -- come here on all errors that may result from trying to execute
            -- a log entry that has already been executed (e.g., trying to
            -- delete a file that already been deleted).  This can occur if
            -- there was a crash during a previous attempt to process the log
            -- for this transaction.

            END;
            IF pageEntry = 0 THEN GOTO Done;
            pageEntry ← pageEntry - entry.countPrevEntry;
            ENDLOOP;
          Space.Unmap[logSpace];
          IF pageBase = 0 THEN EXIT;
          REPEAT Done => Space.Unmap[logSpace];
          ENDLOOP;
        END;
    ENDCASE => ERROR Bug[inactiveTransaction];
  -- the transaction should have been active.

  END;

ReleaseLog: ENTRY PROCEDURE [txi: ValidTxIndex] = {ReleaseLogInternal[txi]};

ReleaseLogInternal: INTERNAL PROCEDURE [txi: ValidTxIndex] =
  -- Resets the transaction's log file to the standard size, zeroes its
  -- contents, then releases it for general use.
  BEGIN
  entryData: LONG POINTER TO UNSPECIFIED ← Space.LongPointer[entrySpace];
  trans: TxDescPtr = @state[txi];
  IF trans.fileInUse THEN
    BEGIN
    Space.Map[entrySpace, [[trans.logFile, permissions], 0]];
    Space.Kill[entrySpace];

-- first clear page 0 to avoid confusing crash recovery if we crash while
-- releasing the file...
    entryData↑ ← 0;
    Inline.LongCOPY[from: entryData, to: entryData+1,
      nwords: (countLogEntry*Environment.wordsPerPage)-1];

    Space.Unmap[entrySpace];
    IF trans.logFileSize # standardLogFileCount THEN
      File.SetSize[[trans.logFile, permissions],
        trans.logFileSize ← standardLogFileCount];
    Space.Kill[logZeroSpace];

-- clear all of the pages so that we will not re-execute any entries of
-- this log file due to any future crashes..
    logZeroData↑ ← 0;
    Inline.LongCOPY[from: logZeroData, to: logZeroData+1,
      nwords:
        ((standardLogFileCount-countLogEntry)*Environment.wordsPerPage)-1];
    Space.CopyOut[logZeroSpace,
      [[trans.logFile, permissions], countLogEntry]];
    END;
  trans.fileInUse ← FALSE;
  END;

RecoverState: PROCEDURE[] =
  BEGIN
  -- Finds the valid data in the old Transaction State File (creates a new
  -- State Table if none), maps stateSpace, and puts the State Table into it.
  -- First look for the old transaction state file.  If it doesn't exist,
  -- we're just starting up with a new system volume, so create the file and
  -- put it into the logical volume root page.  If it does exist, do crash
  -- recovery on it:  discover which copy is the most-recent correct one, and
  -- copy it to stateSpace.
  -- The present algorithm should happily handle a file containing an
  -- unreadable page if the Scavenger were to substitute another page for it
  -- (filled with zeroes).

  createStateTable: BOOLEAN;
  state1Edition, state2Edition: StateEdition;
  state1Valid, state2Valid: BOOLEAN;
  minStateFileCount: File.PageCount = 3*stateCount;
    -- two stable-storage copies + scratch storage.
  dataFile: File.Capability;
  workingStateWindow: Space.WindowOrigin;
  state1Space, state2Space: Space.Handle;
  state1, state2: LONG POINTER TO State;
  stateData: LONG POINTER TO StateData;
    -- (stateSpace is created by InitializeStateA.)

  stateData ← LOOPHOLE[Space.LongPointer[stateSpace]];
  state ← @stateData.state;

-- we use state also as a pointer to stateData (to avoid having to keep two
-- copies of an equivalent pointer in our global frame), so, check that they
-- are indeed equal.
  IF LOOPHOLE[stateData, LONG POINTER] # LOOPHOLE[state, LONG POINTER] THEN
    ERROR Bug[stateDataNotEqualToState];

  state1Space ←
    Space.Create[stateCount, Space.virtualMemory, Space.defaultBase];
  state2Space ←
    Space.Create[stateCount, Space.virtualMemory, Space.defaultBase];
  state1 ← LOOPHOLE[Space.LongPointer[state1Space]];
  state2 ← LOOPHOLE[Space.LongPointer[state2Space]];
  state1Window.base ← 0;
  state2Window.base ← state1Window.base + stateCount;
  workingStateWindow.base ← state2Window.base + stateCount;
  createStateTable ← FALSE; -- assume State file is well-formed..

  DO -- UNTIL the State is well-formed
    -- scope of CreateStateFile --
    BEGIN
    dataFile ← KernelFile.GetRootFile[
      PilotFileTypes.tTransactionStateFile, Volume.systemID];
    state1Window.file ← state2Window.file ←
      workingStateWindow.file ← dataFile;
    IF File.GetSize[dataFile ! File.Unknown => GO TO CreateStateFile] <
      minStateFileCount THEN File.SetSize[dataFile, minStateFileCount];
    -- (fills with zeros)
    [state1Valid, state1Edition] ←
      CheckState[@state1Window, state1Space, state1];
    [state2Valid, state2Edition] ←
      CheckState[@state2Window, state2Space, state2];
    -- If this isn't startup, stateSpace is already mapped.  Since we can't catch the signal
    -- out of SimpleSpace.Map, we test first.
    IF ~Space.GetAttributes[stateSpace].mapped THEN
      SimpleSpace.Map[stateSpace, workingStateWindow, --andPin:--FALSE];
        -- the working copy of the State.
    SimpleSpace.Kill[stateSpace];

-- Find the latest, correct copy of the State Table, and copy it into state:
    SELECT TRUE FROM
      state1Valid AND state2Valid =>
        SELECT
          CompareStateEdition[value: state1Edition, to: state2Edition] FROM
          equal, greater =>
            Inline.LongCOPY[from: state1, nwords: SIZE[StateData], to: state];
          less =>
            Inline.LongCOPY[from: state2, nwords: SIZE[StateData], to: state];
          ENDCASE;
      state1Valid AND ~state2Valid =>
        Inline.LongCOPY[from: state1, nwords: SIZE[StateData], to: state];
      ~state1Valid AND state2Valid =>
        Inline.LongCOPY[from: state2, nwords: SIZE[StateData], to: state];
      ~state1Valid AND ~state2Valid => -- no valid State.
        IF ~createStateTable THEN {
          SimpleSpace.Unmap[stateSpace]; GO TO CreateStateFile}
        ELSE -- create the initial State Table..
          FOR txi: TxIndex IN TxIndex DO -- (TxIndex, not ValidTxIndex)
            state[txi] ←
              [stateEdition: FIRST[StateEdition], seal: stateFormatVersion,
              txEdition: FIRST[ValidTxEdition], fileInUse: FALSE,
              logFile: File.nullID, logFileSize: 0, vp: free[]];
            ENDLOOP;
      ENDCASE;
    EXIT; -- state is now well-formed.

    EXITS
      CreateStateFile =>
        BEGIN
        Space.Unmap[state1Space ! Space.Error => CONTINUE];
          -- ("Error[noWindow]")
        Space.Unmap[state2Space ! Space.Error => CONTINUE];
          -- ("Error[noWindow]")
        IF dataFile # File.nullCapability THEN
          {Runtime.CallDebugger[
            "Crash recovery data malformed. P(roceed) will delete it."L];
          File.Delete[dataFile ! File.Unknown => CONTINUE]};
        dataFile ← File.Create[
          Volume.systemID, minStateFileCount,
          PilotFileTypes.tTransactionStateFile];
        File.MakePermanent[dataFile];
          -- enters file in volume root page as a side effect. (ugh)
        createStateTable ← TRUE;
          -- (we will create it next time through the loop.)

        END;
    END; -- scope of CreateStateFile --
    ENDLOOP; -- loop until the State is well-formed.

-- At this point, state contains a well-formed State Table and is the latest
-- available version.
  Space.Delete[state1Space];
  Space.Delete[state2Space];
  END;

CheckState: PROCEDURE [
  window: POINTER TO Space.WindowOrigin, space: Space.Handle,
  testState: LONG POINTER TO State]
  RETURNS [valid: BOOLEAN, testStateEdition: StateEdition] =
  -- As a side effect, maps space to window.
  BEGIN
  pStateData: LONG POINTER TO StateData = LOOPHOLE[testState];

  Space.Map[space, window↑];
  IF pStateData.checksumData # Checksum[0, SIZE[State], @pStateData.state]
    THEN {valid ← FALSE; RETURN};
  testStateEdition ← testState[FIRST[TxIndex]].stateEdition;
  FOR txi: TxIndex IN TxIndex DO
    -- check for consistency (TxIndex, not ValidTxIndex)
    IF testState[txi].seal # stateFormatVersion
      OR ~(testState[txi].txEdition IN ValidTxEdition)
      OR testState[txi].stateEdition # testStateEdition
      THEN {valid ← FALSE; RETURN};
    ENDLOOP;
  valid ← TRUE;
  RETURN;
  END;

CompareStateEdition: PROCEDURE [value: StateEdition, to: StateEdition]
  RETURNS [relationship: {less, equal, greater}] =
  -- compares "value" to "to", taking possible wraparound into account.
  BEGIN
  wrapLimit: CARDINAL = (LAST[StateEdition] - FIRST[StateEdition])/2;
  -- if more than half of the range behind, we assume it wrapped around.
  IF value=to THEN RETURN[equal]
  ELSE
    IF value<to THEN {
      IF to-value < wrapLimit THEN RETURN[less]
        -- "value" is less than, and close to "to".

      ELSE RETURN[greater]}
    ELSE --value>to--{
      IF value-to < wrapLimit THEN RETURN[greater]
        -- "value" is greater than, and close to "to".

      ELSE RETURN[less]};
  END;

ValidateLogFile: ENTRY PROCEDURE [txi: ValidTxIndex] =
  -- If transaction is active, locates end of valid log entries.
  -- If inactive, sets to standard size and clears it.
  -- Sets logFileSize, fileInUse, pageFree, countPrevLogEntry, as appropriate.
  BEGIN
  pTrans: TxDescPtr = @state[txi];
  IF pTrans.logFile = File.nullID THEN pTrans.fileInUse ← FALSE
  ELSE
    -- scope of FileError --
    BEGIN
    ENABLE {
      File.Unknown => GO TO FileError;
      ANY => ERROR Bug[unexpectedErrorValidateLogFile]};
    pTrans.logFileSize ← ShortenPageCount[
      File.GetSize[File.Capability[pTrans.logFile, permissions]]];
        -- may raise File.Unknown.
    WITH trans: pTrans SELECT FROM
      active =>
        BEGIN
        countOp, countOpPrev: FilePageCount ← 0;
        -- total number of pages logged for this, previous operation.
        pageBase, pageEntry: FilePageNumber ← 0;
        entryData: LONG POINTER TO LogFileEntryData;
        -- scope of PartialLogFileEntry, etc. --
        BEGIN
        pageLog: Space.PageNumber ← Space.VMPageNumber[logSpace];
        entry: LogFileEntryPtr;
        DO
          -- UNTIL pageEntry=trans.logFileSize
          Space.Map[logSpace, [[trans.logFile, permissions], pageBase]];
          -- may raise File.Unknown.
          WHILE pageEntry < pageBase+logSpaceSize DO
            entryData ←
              LOOPHOLE[Space.LongPointerFromPage[
                pageLog + pageEntry - pageBase]];
            entry ← @entryData.logFileEntry;
            IF Checksum[0, SIZE[LogFileEntry], entry] # entryData.checksumData
              OR entry.seal # logFileFormatVersion
              OR entry.transactionHandle.index # txi
              OR entry.transactionHandle.txEdition # trans.txEdition
              OR entry.countPrevEntry # countOp
              OR ~(entry.op.operation IN TransactionState.LogOp)
              OR entry.transactionHandle2.index # txi
              OR entry.transactionHandle2.txEdition # trans.txEdition
            THEN GO TO InvalidLogEntry;
            countOpPrev ← countOp;
            countOp ← countLogEntry +
              (WITH oper: entry.op SELECT FROM
                setContents => ShortenPageCount[oper.size],
                ENDCASE => 0);
            IF (pageEntry ← pageEntry+countOp) > trans.logFileSize
              THEN GO TO PartialLogFileEntry; -- (equal is OK)
            IF pageEntry=trans.logFileSize THEN GO TO Done;
            ENDLOOP;
          Space.Unmap[logSpace];
          pageBase ← pageEntry;
          REPEAT Done => NULL;
          ENDLOOP;
        trans.countPrevLogEntry ← countOp;
        trans.pageFree ← pageEntry;
        EXITS
          PartialLogFileEntry => {Runtime.CallDebugger[
        "Incomplete Transaction Log File entry. (P)roceed will ignore it."L];
            trans.pageFree ← pageEntry-countOp;
              -- back up to last valid entry.
            trans.countPrevLogEntry ← countOpPrev};
          InvalidLogEntry => {
            IF LOOPHOLE[entryData, LONG POINTER TO LONG CARDINAL]↑ # 0
              THEN Runtime.CallDebugger[
                "Garbage Transaction Log File entry. (P)roceed will ignore it."L];
            trans.pageFree ← pageEntry;
            trans.countPrevLogEntry ← countOp};
        END;
        Space.Unmap[logSpace];
        IF trans.fileInUse AND trans.pageFree=0 THEN ReleaseLogInternal[txi];
        trans.fileInUse ← (trans.pageFree > 0);
        END;
      free => {trans.fileInUse ← TRUE;
        -- (assures that file will be filled with zeroes by ReleaseLog.)
        ReleaseLogInternal[txi]};
      ENDCASE;
    EXITS
      FileError => {
        Runtime.CallDebugger[
          "Missing Transaction Log File.  (P)roceed will ignore it."L];
        pTrans.logFile ← File.nullID;
        pTrans.fileInUse ← FALSE};
    END;
  END;

END.
LOG
May 8, 1980  1:21 PM        Gobbel
        Create file.

July 19, 1980  1:43 PM        McJones
        Space.Copy=>CopyOut.  export Transaction.Handle.

July 22, 1980  4:13 PM        Gobbel
        Make it real.

September 2, 1980  8:18 PM        Knutsen
        Major cleanup.  Added State and LogFile crash recovery code.  Split
        code out into TransactionStateImpl.

September 8, 1980  6:53 PM        Gobbel
        Reset spaceList in crash recovery.

September 15, 1980  10:22 AM        Knutsen
        Made compatible with new ReleaseFromTransaction.

September 15, 1980  5:47 PM        Gobbel
        Made RecoverFilesInTransaction, ValidateLogFile, and ReleaseLog able
        to deal with arbitrarily large log files.

October 16, 1980  4:16 PM        Fay
        Added Volume.Unknown to catch phrases for Space.GetWindow and
        Space.Map calls in Abort; made Abort do nothing if transactions
        disabled.

October 16, 1980  4:16 PM        Fay
        Added Volume.Unknown to catch phrases

December 31, 1980  10:38 AM        Gobbel
        Make RestoreFilesInTransaction discard permissions in client's
        original capability

January 14, 1981  7:04 PM        Gobbel
        Use dedicated small space for zeroing log files

August 26, 1982 11:04 am              Levin
         Break out DoCrashRecovery; add TransactionsInProgress.
 
June 1, 1982 2:26 pm              Levin
         Make things SAFE.