DIRECTORY
BcdDefs USING [GFTIndex, MTIndex, NullVersion, VersionStamp],
BcdOps USING [BcdBase, MTHandle, ProcessModules],
Boot USING [Location, LP],
BootSwap USING [mdsiGerm, Mon, OutLoad, sPilotSwitches],
BootSwapCross USING [pMon],
CachedSpace USING [Desc, Handle, Level],
CedarInitOps USING [EnsureUsableMicrocode],
CedarSnapshot USING [After, CheckpointProc, Outcome, RollbackProc],
CedarSnapshotPrivate
USING [
CorkHandle, CorkRestOfWorld, EnsureSnapshotFileSize, EnumerateCheckpointProcs, EnumerateRollbackProcs, FinalizeFeedback, FinalizeSnapshotFile, initialClientScriptPages, InitializeFeedback, InitializeSnapshotFile, initialLinkScriptPages, initialVMScriptPages, InstallSnapshotFile, maxReasonableSwapUnit, PrepareForOutload, SetPhase, SnapshotFile, snapshotLock, Twiddle, UncorkRestOfWorld],
DeviceCleanup USING [Perform],
DiskChannel USING [Idle, Restart],
DirectoryFiles USING [DCEntry, --directoryCache,-- maxDCache],
DirectoryFilesImpl USING [directoryCache],
DirectoryInternal USING [DirectoryPageHandle, leaderPageSize],
Environment USING [wordsPerPage],
File
USING [
Capability, Create, GetAttributes, GetSize, ID, nullID, PageCount, PageNumber, Permissions, read, Unknown, write],
Heap USING [systemZone],
Hierarchy USING [GetDescriptor],
Inline USING [BITAND, BITOR, LowHalf],
LogicalVolume USING [CloseLogicalVolume, OpenLogicalVolume],
MapLog USING [WriteLog],
PilotFileTypes USING [PilotFileType, tAnonymousFile, tVMBackingFile],
PilotLoaderOps
USING [
CloseLinkSpace, LinkSegmentLength, OpenLinkSpace, ReadLink, WriteLink],
PilotLoadStateOps
USING [
AcquireBcd, ConfigIndex, EnumerateBcds, GetMap, GetModule, InputLoadState, Map, NullConfig, ReleaseBcd, ReleaseLoadState, ReleaseMap],
PilotMP USING [cClient],
PilotSwitches USING [switches --.m--],
PrincOps USING [ControlLink, GFTIndex, GlobalFrameHandle],
PrincOpsRuntime USING [GetFrame, GFT],
ProcessInternal USING [DisableInterrupts, EnableInterrupts],
ProcessOperations USING [ReadPSB, ReadPTC, ReadWDC, WritePSB, WritePTC, WriteWDC],
ProcessorFace USING [SetMP],
PSB USING [PsbHandle],
RuntimeInternal USING [Codebase, WorryCallDebugger],
SDDefs USING [SD],
Space
USING [
CopyIn, CopyOut, Create, Deactivate, defaultWindow, Delete, GetAttributes, GetHandle, GetWindow, Handle, LongPointer, MakeReadOnly, MakeWritable, Map, nullHandle, PageCount, PageFromLongPointer, PageOffset, Unmap, virtualMemory, WindowOrigin],
SpaceImplInternal USING [EnterSpace],
SpecialSpace USING [MakeResident, MakeSwappable],
SpecialTerminal USING [TurnOff, TurnOn],
SubVolumeImpl USING [CacheEntry, ceFirst, ceLast, CePtr],
System USING [GetUniversalID, VolumeID],
TemporaryBooting USING [Switches],
TemporarySetGMT USING [SetGMT],
TerminalMultiplex USING [PermitDebuggerSwaps, PreventDebuggerSwaps],
TransactionExtras USING [DoCrashRecovery, TransactionsInProgress],
UserCredentialsUnsafe USING [GetProc, Login, PutProc],
VM USING [PageNumber],
Volume USING [ID, InsufficientSpace, nullID, systemID, Unknown],
VolumeImplInterface USING [SubvolumeOffline, SubvolumeOnline];
Checkpoint:
PUBLIC
ENTRY
PROC [volume: Volume.
ID ← Volume.nullID]
RETURNS [outcome: CedarSnapshot.Outcome] =
BEGIN
systemVolume: Volume.ID ← Volume.systemID;
snapshotFile: SnapshotFile ← NIL;
outloadLocation: disk Boot.Location;
Space script management
Disposition: TYPE = {keep, delete};
SpaceScriptEntryRP:
TYPE =
SpaceScriptBase RELATIVE ORDERED POINTER [0..LAST[CARDINAL]] TO SpaceScriptEntry;
SpaceScriptEntryPtr: TYPE = LONG POINTER TO SpaceScriptEntry;
SpaceScriptEntryType: TYPE = {space, subSpace, mappingSpace, end};
SpaceScriptEntry:
TYPE =
RECORD [
entry:
SELECT type: SpaceScriptEntryType
FROM
space => [
readOnly, ensureBackingStorage: BOOLEAN, level: CachedSpace.Level,
page: VM.PageNumber, size: Space.PageCount],
subSpace => [
disposition: Disposition, level: CachedSpace.Level,
page: VM.PageNumber, size: Space.PageCount],
mappingSpace => [
readOnly, ensureBackingStorage: BOOLEAN, level: CachedSpace.Level,
page: VM.PageNumber],
end => NULL,
ENDCASE];
SpaceScriptBase: TYPE = LONG BASE POINTER;
SpaceScript: TYPE = LONG POINTER TO SpaceScriptDescriptor;
SpaceScriptDescriptor:
TYPE =
RECORD [
base: SpaceScriptBase ← NIL,
end: SpaceScriptEntryRP ← FIRST[SpaceScriptEntryRP],
pages: Space.PageCount ← NULL,
limit: SpaceScriptEntryRP ← FIRST[SpaceScriptEntryRP],
space: Space.Handle ← Space.nullHandle,
pinned: BOOLEAN ← FALSE,
baseOfSpaces: File.PageNumber ← NULL,
pagesForSpaces: File.PageCount ← 0];
entrySizes:
ARRAY SpaceScriptEntryType
OF
CARDINAL = [
SIZE[space SpaceScriptEntry], SIZE[subSpace SpaceScriptEntry],
SIZE[mappingSpace SpaceScriptEntry], SIZE[end SpaceScriptEntry]];
InitializeSpaceScript:
PROC [script: SpaceScript, pages: Space.PageCount] =
BEGIN
PrepareSpaceScript[script, pages];
END;
ExtendSpaceScript:
PROC [script: SpaceScript, increment: Space.PageCount ← 1] =
BEGIN
We depend on the Space.ForceOut that is implicit in the following Delete!
Space.Delete[script.space];
PrepareSpaceScript[script, script.pages + increment];
END;
FlushSpaceScript:
PROC [script: SpaceScript] =
BEGIN
There is a certain subtlety in the code below. If this procedure is
being called during checkpoint cleanup as a result of a signal out of
EnsureSnapshotFileSize in PrepareSpaceScript, then script.space will
not be mapped. If, however, this procedure is invoked in the normal
case of a successful checkpoint, script.space will be both mapped
and dirty, so the desired actions are Space.ForceOut and Space.Unmap.
Fortunately, Space.Delete handles all of this for us, so we don't need
to know which case it is. (Note: this subtlety could be eliminated
by deferring the creation of script.space in PrepareSpaceScript until
after the EnsureSnapshotFileSize. However, we would then need to
reset script.space to Space.nullHandle whenever the space is deleted.
The present course seems more straightforward.)
IF script.space ~= Space.nullHandle
THEN {
IF script.pinned THEN SpecialSpace.MakeSwappable[script.space]; -- is this necessary?
Space.Delete[script.space];
};
END;
PrepareSpaceScript:
PROC [script: SpaceScript, pages: Space.PageCount] =
BEGIN
script.space ← Space.Create[pages, Space.virtualMemory];
EnsureSnapshotFileSize[snapshotFile, snapshotFile.firstFree + pages];
Space.Map[script.space, [snapshotFile.cap, snapshotFile.firstFree]];
script.base ← Space.LongPointer[script.space];
script.limit ← LOOPHOLE[pages*Environment.wordsPerPage];
script.pages ← pages;
END;
InstallSpaceScript:
PROC [script: SpaceScript] =
BEGIN
AllocateEntry[script, end]^ ← [end[]];
Space.ForceOut[script.space] might seem appropriate here, but it will
be done at cleanup time by FlushSpaceScript (see comments there).
snapshotFile.firstFree ← snapshotFile.firstFree + script.pages;
END;
AddSpaceOrChildren:
PROC [
script: SpaceScript, parent: Space.Handle, ensureBackingStorage: BOOLEAN] =
BEGIN
child: Space.Handle;
parentSize: Space.PageCount;
readOnly, pinned: BOOLEAN;
If the space under consideration is pinned, there is no need to save
it in the checkpoint file, since it will be saved by OutLoad. Also, as
it turns out, the implementation of CopyOut/CopyIn can't handle such spaces,
even though the operations make sense.
[pinned, readOnly] ← GetPinnedAndReadOnly[parent];
IF pinned THEN RETURN;
Because of limitations in the implementation of CopyIn/CopyOut, we
dare not present a space beyond a certain size, even though it has
swap units. If a space is too big and has swap units, we perform
the CopyIn/CopyOut on the swap units instead. If, however, a big
space has no swap units, we have two options: we can do the copy in
a single lump and hope enough main memory can be found, or we can
temporarily add our own swap units and copy them out. Although the
latter course is feasible, it doesn't seem worth the effort, since
the guy who created the space will cause it to swap as a unit when
he touches it, and there will have to be sufficient main memory then
to accommodate it.
Possible future optimization: don't enter the space in the script if it is
dead (i.e., the backing storage contents are worthless). To do this, we must
iterate through the region cache, looking at every descriptor that overlaps
the interval associated with 'space' and ask about its state. If all regions
happen to be dead, we don't enter the space in the script.
[size: parentSize, lowestChild: child] ← Space.GetAttributes[parent];
IF parentSize <= maxReasonableSwapUnit
OR child = Space.nullHandle
THEN
BEGIN
sH: CachedSpace.Handle = LOOPHOLE[parent];
AllocateEntry[script, space]^ ← [space[
readOnly: readOnly, ensureBackingStorage: ensureBackingStorage,
level: sH.level, page: sH.page, size: parentSize]];
script.pagesForSpaces ← script.pagesForSpaces + parentSize;
END
ELSE
BEGIN
We do the CopyOut on the swap units here. We must be careful,
however, since the parent space may not be completely tiled with
swap units. This can't happen with uniform swap units, only with
explicit subspaces; for example, the bitmap allocated by UserTerminalImpl
works this way.
expectedBase: Space.PageOffset ← 0;
AddSubSpace:
PROC [space: Space.Handle, size: Space.PageCount, disposition: Disposition] =
BEGIN
sH: CachedSpace.Handle = LOOPHOLE[space];
AllocateEntry[script, subSpace]^ ← [subSpace[
disposition: disposition, level: sH.level, page: sH.page, size: size]];
script.pagesForSpaces ← script.pagesForSpaces + size;
END;
FillTo:
PROC [nextBase: Space.PageOffset] =
BEGIN
fillerSize: Space.PageCount = nextBase - expectedBase;
filler: Space.Handle =
Space.Create[size: fillerSize, parent: parent, base: expectedBase];
AddSubSpace[filler, fillerSize, delete];
END;
sH: CachedSpace.Handle = LOOPHOLE[parent];
AllocateEntry[script, mappingSpace]^ ← [mappingSpace[
readOnly: readOnly, ensureBackingStorage: ensureBackingStorage,
level: sH.level, page: sH.page]];
DO
base: Space.PageOffset;
size: Space.PageCount;
nextSibling: Space.Handle;
[base: base, size: size, nextSibling: nextSibling] ← Space.GetAttributes[child];
IF base ~= expectedBase THEN FillTo[base];
AddSubSpace[child, size, keep];
expectedBase ← base + size;
IF nextSibling = Space.nullHandle THEN EXIT;
child ← nextSibling;
ENDLOOP;
IF expectedBase ~= parentSize THEN FillTo[parentSize];
END;
END;
AllocateEntry:
PROC [script: SpaceScript, type: SpaceScriptEntryType]
RETURNS [next: SpaceScriptEntryPtr] =
BEGIN
WHILE script.end + entrySizes[type] >= script.limit DO ExtendSpaceScript[script]; ENDLOOP;
next ← @script.base[script.end];
script.end ← script.end + entrySizes[type];
Twiddle[];
END;
SpaceFromScriptEntry:
PROC [entry: SpaceScriptEntryPtr]
RETURNS [space: Space.Handle] =
BEGIN
WITH e: entry
SELECT
FROM
space => space ← LOOPHOLE[CachedSpace.Handle[level: e.level, page: e.page]];
subSpace => space ← LOOPHOLE[CachedSpace.Handle[level: e.level, page: e.page]];
mappingSpace => space ← LOOPHOLE[CachedSpace.Handle[level: e.level, page: e.page]];
ENDCASE => ERROR SnapshotBug;
END;
EnumerateSpaceScript:
PROC [script: SpaceScript, proc:
PROC [SpaceScriptEntryPtr]] =
BEGIN
next: SpaceScriptEntryRP ← FIRST[SpaceScriptEntryRP];
UNTIL next >= script.end
DO
proc[@script.base[next]];
next ← next + entrySizes[script.base[next].type];
ENDLOOP;
END;
EnumerateChildren:
PROC [space: Space.Handle, proc:
PROC [Space.Handle]] =
BEGIN
FOR child: Space.Handle ← Space.GetAttributes[space].lowestChild, Space.GetAttributes[child].nextSibling
UNTIL child = Space.nullHandle DO proc[child]; ENDLOOP;
END;
Client space script
The following must be done before the Cork is started.
clientScript: SpaceScript ← Heap.systemZone.NEW[SpaceScriptDescriptor ← []];
ProcessSpaceForClientScript:
PROC [space: Space.Handle] =
BEGIN
CheckPotentiallyUnmappedSpace:
PROC [space: Space.Handle] =
BEGIN
IF Space.GetAttributes[space].mapped THEN CheckAndAddSpace[space, space]
ELSE EnumerateChildren[space, CheckPotentiallyUnmappedSpace];
END;
CheckAndAddSpace:
PROC [space, mappingSpace: Space.Handle] =
BEGIN
window: Space.WindowOrigin = Space.GetWindow[mappingSpace];
permissions: File.Permissions = File.read + File.write;
SELECT
TRUE
FROM
window = Space.defaultWindow =>
ERROR UnacceptableSpace[dataSpace];
File.GetAttributes[window.file].type
IN PilotFileTypes.PilotFileType =>
ERROR UnacceptableSpace[mappedToPilotFile];
Inline.
BITAND[window.file.permissions, permissions] ~= permissions =>
ERROR UnacceptableSpace[insufficientPermissions];
ENDCASE;
AddSpaceOrChildren[clientScript, space, FALSE];
END;
mapped: BOOLEAN ← FALSE;
mappingSpace: Space.Handle ← space;
IF space = Space.nullHandle THEN ERROR UnacceptableSpace[nullHandle];
UNTIL mappingSpace = Space.nullHandle
DO
parent: Space.Handle;
[mapped: mapped, parent: parent] ← Space.GetAttributes[mappingSpace];
IF mapped THEN EXIT;
mappingSpace ← parent;
ENDLOOP;
IF mapped THEN CheckAndAddSpace[space, mappingSpace]
ELSE EnumerateChildren[space, CheckPotentiallyUnmappedSpace];
END;
BuildClientSpaceScript:
PROC =
BEGIN
DoOneCheckpointProc:
PROC [proc: CedarSnapshot.CheckpointProc] =
{proc[ProcessSpaceForClientScript]};
InitializeSpaceScript[clientScript, initialClientScriptPages];
EnumerateCheckpointProcs[DoOneCheckpointProc];
InstallSpaceScript[clientScript];
END;
CheckpointClientSpaces:
PROC =
BEGIN
window: Space.WindowOrigin;
CheckpointOneClientSpace:
PROC [entry: SpaceScriptEntryPtr] =
BEGIN
space: Space.Handle;
size: Space.PageCount;
deactivate: BOOLEAN;
WITH e: entry
SELECT
FROM
space => {size ← e.size; deactivate ← FALSE};
subSpace => {size ← e.size; deactivate ← TRUE};
ENDCASE => RETURN;
space ← SpaceFromScriptEntry[entry];
Space.CopyOut[space: space, window: window];
The following is an optimization. If the entry in the space script
is a subSpace, it means the client space is "unreasonably large"
(see comments in AddSpaceOrChildren). We would prefer not to have this
unreasonably large space hanging around in memory, so we Deactivate it.
IF deactivate THEN Space.Deactivate[space];
window.base ← window.base + size;
Twiddle[];
END;
clientScript.baseOfSpaces ← snapshotFile.firstFree;
EnsureSnapshotFileSize[snapshotFile, clientScript.baseOfSpaces + clientScript.pagesForSpaces];
window ← [snapshotFile.cap, clientScript.baseOfSpaces];
EnumerateSpaceScript[clientScript, CheckpointOneClientSpace];
snapshotFile.firstFree ← snapshotFile.firstFree + clientScript.pagesForSpaces;
END;
RollbackClientSpaces:
PROC =
BEGIN
window: Space.WindowOrigin ← [snapshotFile.cap, clientScript.baseOfSpaces];
RollbackOneClientSpace:
PROC [entry: SpaceScriptEntryPtr] =
BEGIN
space: Space.Handle;
size: Space.PageCount;
disposition: Disposition ← keep;
WITH e: entry
SELECT
FROM
space => size ← e.size;
subSpace => {size ← e.size; disposition ← e.disposition};
ENDCASE => RETURN;
space ← SpaceFromScriptEntry[entry];
Space.CopyIn[space: space, window: window];
IF disposition = delete THEN Space.Delete[space];
window.base ← window.base + size;
Twiddle[];
END;
EnumerateSpaceScript[clientScript, RollbackOneClientSpace];
END;
CleanupClientSpaces:
PROC [after: CedarSnapshot.After] =
BEGIN
If after = rollback, the spaces with disposition = delete have
already been deleted.
IF after = checkpoint
THEN
BEGIN
CleanupOneClientSpace:
PROC [entry: SpaceScriptEntryPtr] =
BEGIN
WITH e: entry
SELECT
FROM
subSpace =>
IF e.disposition = delete THEN Space.Delete[SpaceFromScriptEntry[entry]];
ENDCASE;
END;
IF clientScript = NIL THEN RETURN;
EnumerateSpaceScript[clientScript, CleanupOneClientSpace];
END;
FlushSpaceScript[clientScript];
Heap.systemZone.FREE[@clientScript];
END;
VM space script
nSpecials: CARDINAL = IF PilotSwitches.switches.m = down THEN 1 ELSE 2;
The following must be done before the Cork is started.
vmScript: SpaceScript ← Heap.systemZone.NEW[SpaceScriptDescriptor ← []];
ProcessSpaceForVMScript:
PROC [space: Space.Handle] =
BEGIN
size: Space.PageCount;
mapped: BOOLEAN;
[size: size, mapped: mapped] ← Space.GetAttributes[space];
IF mapped
THEN
BEGIN
window: Space.WindowOrigin ← Space.GetWindow[space];
SELECT
TRUE
FROM
window = Space.defaultWindow =>
BEGIN
FindBackingFile:
PROC =
BEGIN
desc: CachedSpace.Desc;
validSpace, validSwapUnit: BOOLEAN;
[validSpace, validSwapUnit] ← Hierarchy.GetDescriptor[@desc, LOOPHOLE[space]];
IF ~(validSpace
OR validSwapUnit)
OR desc.dataOrFile ~= data
THEN
ERROR SnapshotBug;
window ← desc.window;
END;
SpaceImplInternal.EnterSpace[FindBackingFile];
AddSpaceOrChildren[vmScript, space, File.GetAttributes[window.file].type ~= PilotFileTypes.tVMBackingFile];
END;
window.file.fID = File.nullID => NULL; -- initial, pinned space
File.GetAttributes[window.file ! File.Unknown =>
CONTINUE].type = PilotFileTypes.tVMBackingFile =>
BEGIN
The first entry in the vmScript is the space/region data base, the
second is the maplog (unless PilotSwitches.switches.m = down).
It is assumed that these two spaces are not pinned! (If they
were, we wouldn't have to copy them.)
sH: CachedSpace.Handle = LOOPHOLE[space];
entry: SpaceScriptEntryRP =
IF window.base = 0
THEN
IF PilotSwitches.switches.m = down THEN ERROR SnapshotBug
ELSE LOOPHOLE[SIZE[space SpaceScriptEntry]]
ELSE FIRST[SpaceScriptEntryRP];
vmScript.base[entry] ← [space[
readOnly: FALSE, ensureBackingStorage: FALSE, level: sH.level,
page: sH.page, size: size]];
vmScript.pagesForSpaces ← vmScript.pagesForSpaces + size;
END;
ENDCASE =>
This deactivation isn't really necessary, but we want to be friendly.
After the rollback, non-pinned spaces mapped to persistent files (as opposed
to data spaces) will reflect the current contents of the disk. Also,
by doing a Deactivate here, we increase the amount of memory available for
the CopyOuts of data spaces. Note: the spaces that are mapped to the files
that implement the Common Software directory are fixed up explicitly during
rollback by RationalizeDirectory, below. Deactivation here is not sufficient.
Space.Deactivate[space];
END
ELSE EnumerateChildren[space, ProcessSpaceForVMScript];
END;
BuildVMSpaceScript:
PROC =
BEGIN
InitializeSpaceScript[vmScript, initialVMScriptPages];
THROUGH [0..nSpecials) DO [] ← AllocateEntry[vmScript, space] ENDLOOP;
EnumerateChildren[Space.virtualMemory, ProcessSpaceForVMScript];
InstallSpaceScript[vmScript];
END;
CheckpointVMSpaces:
PROC =
BEGIN
window: Space.WindowOrigin;
dbSpaceEntry: SpaceScriptEntryPtr = @vmScript.base[FIRST[SpaceScriptEntryRP]];
deactivate: BOOLEAN;
CheckpointOneVMSpace:
PROC [entry: SpaceScriptEntryPtr] =
BEGIN
size: Space.PageCount;
WITH e: entry
SELECT
FROM
space => {size ← e.size; deactivate ← e.ensureBackingStorage};
subSpace => size ← e.size;
mappingSpace => {deactivate ← e.ensureBackingStorage; RETURN};
ENDCASE => RETURN;
We treat the space/region data base as a special case, since it
must be written last. However, we reserve space for it now.
IF entry ~= dbSpaceEntry
THEN
BEGIN
space: Space.Handle = SpaceFromScriptEntry[entry];
Space.CopyOut[space: space, window: window];
The following attempts to prevent a subtle failure at rollback time.
If this entry's backing storage might go away (i.e., it is a space
(or subSpace of a mappingSpace) with ensureBackingStorage = TRUE,
then we want to be sure to deactivate it. Otherwise, if it is in real
memory when rollback occurs AND the backing storage has gone away AND
rollback hasn't yet reached this space in the script AND the swapper
decides to kick this space out in order to satisfy some other fault AND
this space is dirty, disaster will ensue.
IF deactivate THEN Space.Deactivate[space];
END;
window.base ← window.base + size;
Twiddle[];
END;
SpecialSpace.MakeResident[vmScript.space]; vmScript.pinned ← TRUE;
vmScript.baseOfSpaces ← snapshotFile.firstFree;
EnsureSnapshotFileSize[snapshotFile, vmScript.baseOfSpaces + vmScript.pagesForSpaces];
window ← [snapshotFile.cap, vmScript.baseOfSpaces];
EnumerateSpaceScript[vmScript, CheckpointOneVMSpace];
Write the space/region data base. This requires a bit of care, since we
want to be certain that the data base saved in the outloadFile is consistent
with the region and space caches saved by the Snapshot.Outload. Once the
data base is written, no changes to the caches are permitted. Since the
act of doing a CopyOut on the data base may cause the caches to be loaded
with descriptors for the space and regions that describe the data base
itself, we CopyOut the data base a second time, assuming that the caches
will not change again. Note that we require that there be enough main
memory to hold the entire space/region data base at one time. If, in the
future, this becomes unworkable, a more elaborate scheme for copying out
the data base in pieces without sacrificing its consistency will have to
be worked out.
window ← [snapshotFile.cap, vmScript.baseOfSpaces];
Space.CopyOut[space: SpaceFromScriptEntry[dbSpaceEntry], window: window];
Space.CopyOut[space: SpaceFromScriptEntry[dbSpaceEntry], window: window]; -- no, you aren't seeing double...
snapshotFile.firstFree ← snapshotFile.firstFree + vmScript.pagesForSpaces;
END;
RollbackVMSpaces:
PROC =
BEGIN
window: Space.WindowOrigin ← [snapshotFile.cap, vmScript.baseOfSpaces];
mappingSpace: Space.Handle ← Space.nullHandle;
readOnly: BOOLEAN ← FALSE;
EnsureWritable:
PROC =
BEGIN
window: Space.WindowOrigin ← Space.GetWindow[mappingSpace];
window.file.permissions ← Inline.BITOR[window.file.permissions, File.write];
Space.MakeWritable[mappingSpace, window.file];
END;
EnsureBackingStorage:
PROC [space: Space.Handle] =
BEGIN
The following dicey bit of code ensures that anonymous memory exists
to back up the data space being restored. It obtains the file ID of the
backing file behind the data space and touches it (File.GetSize) to see if
it still exists. If not, we create a new backing file by unmapping and
remapping the space.
needFile: BOOLEAN ← FALSE;
desc: CachedSpace.Desc;
EnsureBackingStorageInternal:
PROC =
BEGIN
validSpace, validSwapUnit: BOOLEAN;
[validSpace, validSwapUnit] ← Hierarchy.GetDescriptor[@desc, LOOPHOLE[space]];
IF ~(validSpace AND ~validSwapUnit) THEN ERROR SnapshotBug;
END;
SpaceImplInternal.EnterSpace[EnsureBackingStorageInternal];
[] ← File.GetSize[desc.window.file ! File.Unknown => {needFile ← TRUE; CONTINUE}];
IF ~needFile THEN RETURN;
Space.Unmap[mappingSpace ! File.Unknown =>
-- the maplog fixup wasn't done
{MapLog.WriteLog[interval: desc.interval, pSpaceD: NIL]; CONTINUE}];
Space.Map[mappingSpace];
END;
RollbackOneVMSpace:
PROC [entry: SpaceScriptEntryPtr] =
BEGIN
IF entry.type ~= subSpace
AND readOnly
THEN
We are about to start processing a new mapping space. If the
previous space was read-only, we must reapply its write protection.
Space.MakeReadOnly[mappingSpace];
WITH e: entry
SELECT
FROM
space =>
BEGIN
space: Space.Handle = LOOPHOLE[CachedSpace.Handle[level: e.level, page: e.page]];
IF e.ensureBackingStorage THEN EnsureBackingStorage[space];
IF (readOnly ← e.readOnly) THEN EnsureWritable[];
Space.CopyIn[space: space, window: window];
window.base ← window.base + e.size;
END;
mappingSpace =>
BEGIN
mappingSpace ← LOOPHOLE[CachedSpace.Handle[level: e.level, page: e.page]];
IF e.ensureBackingStorage THEN EnsureBackingStorage[mappingSpace];
IF (readOnly ← e.readOnly) THEN EnsureWritable[];
END;
subSpace =>
BEGIN
space: Space.Handle = LOOPHOLE[CachedSpace.Handle[level: e.level, page: e.page]];
Space.CopyIn[space: space, window: window];
SELECT e.disposition
FROM
The following isn't really necessary, but it helps to keep memory
available for subsequent CopyIns.
keep => Space.Deactivate[space];
delete => Space.Delete[space];
ENDCASE;
window.base ← window.base + e.size;
END;
end => NULL;
ENDCASE;
Twiddle[];
END;
EnumerateSpaceScript[vmScript, RollbackOneVMSpace];
END;
CleanupVMSpaces:
PROC [after: CedarSnapshot.After] =
BEGIN
If after = rollback, the spaces with disposition = delete have
already been deleted.
IF after = checkpoint
THEN
BEGIN
CleanupOneVMSpace:
PROC [entry: SpaceScriptEntryPtr] =
BEGIN
WITH e: entry
SELECT
FROM
subSpace =>
IF e.disposition = delete THEN Space.Delete[SpaceFromScriptEntry[entry]];
ENDCASE;
END;
IF vmScript = NIL THEN RETURN;
EnumerateSpaceScript[vmScript, CleanupOneVMSpace];
END;
FlushSpaceScript[vmScript];
Heap.systemZone.FREE[@vmScript];
END;
Link script
PLinkScriptEntry: TYPE = LONG ORDERED POINTER TO LinkScriptEntry;
LinkScriptEntry:
TYPE =
RECORD [
frame: PrincOps.GlobalFrameHandle,
mth: BcdOps.MTHandle,
config: PilotLoadStateOps.ConfigIndex,
nLinks: CARDINAL,
links: ARRAY [0..0) OF PrincOps.ControlLink];
linkScriptBase, linkScriptNext, linkScriptLimit: PLinkScriptEntry ← NIL;
linkScriptSpace: Space.Handle ← Space.nullHandle;
nLinkScriptPages: Space.PageCount;
nLinkScriptEntries: CARDINAL ← 0;
UnSavedLinks: SIGNAL = CODE;
InitializeLinkScript:
PROC [pages: Space.PageCount] =
BEGIN
linkScriptSpace ← Space.Create[pages, Space.virtualMemory];
EnsureSnapshotFileSize[snapshotFile, snapshotFile.firstFree + pages];
Space.Map[linkScriptSpace, [snapshotFile.cap, snapshotFile.firstFree]];
linkScriptBase ← linkScriptNext ← LOOPHOLE[Space.LongPointer[linkScriptSpace]];
linkScriptLimit ← linkScriptBase + pages*Environment.wordsPerPage;
nLinkScriptPages ← pages;
END;
ExtendLinkScript:
PROC [increment: Space.PageCount ← 1] =
BEGIN
offset: LONG CARDINAL = linkScriptNext - linkScriptBase;
We depend on the Space.ForceOut that is implicit in the following Delete!
Space.Delete[linkScriptSpace];
InitializeLinkScript[nLinkScriptPages + increment];
linkScriptNext ← linkScriptBase + offset;
END;
FlushLinkScript:
PROC =
BEGIN
There is a certain subtlety in the code below. If this procedure is
being called during checkpoint cleanup as a result of a signal out of
EnsureSnapshotFileSize in InitializeLinkScript, then linkScriptSpace will
not be mapped. If, however, this procedure is invoked in the normal
case of a successful checkpoint, linkScriptSpace will be both mapped
and dirty, so the desired actions are Space.ForceOut and Space.Unmap.
Fortunately, Space.Delete handles all of this for us, so we don't need
to know which case it is. (Note: this subtlety could be eliminated
by deferring the creation of linkScriptSpace in InitializeLinkScript until
after the EnsureSnapshotFileSize. However, we would then need to
reset linkScriptSpace to Space.nullHandle whenever the space is deleted.
The present course seems more straightforward.)
IF linkScriptSpace ~= Space.nullHandle THEN Space.Delete[linkScriptSpace];
END;
InstallLinkScript:
PROC =
BEGIN
Space.ForceOut[linkScriptSpace] might seem appropriate here, but it
will be done at cleanup time by FlushLinkScript (see comments there).
snapshotFile.firstFree ← snapshotFile.firstFree + nLinkScriptPages;
END;
CheckpointLinks:
PROC =
BEGIN OPEN PilotLoadStateOps;
ProcessBcd:
PROC [config: ConfigIndex]
RETURNS [
BOOLEAN] =
BEGIN
bcd: BcdOps.BcdBase ← AcquireBcd[config];
map: Map ← GetMap[config];
ProcessLinks:
PROC [mth: BcdOps.MTHandle, mti: BcdDefs.MTIndex]
RETURNS [BOOLEAN] =
BEGIN OPEN PrincOps, PrincOpsRuntime;
rgfi: GFTIndex = map[mth.gfi];
gf: GlobalFrameHandle = GetFrame[GFT[rgfi]];
We are interested in modules that have codelinks, some of which are
unresolved. These link fields will be saved by Checkpoint and restored
by Rollback. However, there is a complication. If the code
is pinned, Pilot will not allow the space to be made writable (in fact,
we will crash with CachedRegionImplB.Bug[makeWritableButNotSwappable]).
However, in this case there is no point in saving the links explicitly
in the checkpoint, since they will be saved by the OutLoad of real memory
anyway. Of course, if the code is made swappable after Checkpoint and
the unbound links become bound, the links on disk will change at that
time. Nothing bad will happen at Rollback, since the code will once again
be pinned and the (restored) links will say unbound. However, should
the code subsequently be made swappable WITHOUT rebinding the nominally
unbound links, the old (garbage) links on disk will eventually prevail.
This situation is deemed unnecessarily perverse for us to have to deal
with intelligently, but, to be polite, we return a warning indication
to the client of Checkpoint.
IF ~GetModule[rgfi].resolved
AND gf.codelinks
THEN
BEGIN
space: Space.Handle =
Space.GetHandle[Space.PageFromLongPointer[
RuntimeInternal.Codebase[LOOPHOLE[gf]]-1]];
In practice, an initially pinned space can never have its links changed
in a bad way, since it has no backing space. So, we could change the
following code to complain only if Space.GetWindow[space].file.fID ~=
File.nullID, but we leave it this way in case initial spaces ever
acquire backing storage (something I would like to do someday, although
it requires that potentially bogus links be cleaned up at boot time).
IF GetPinnedAndReadOnly[space].pinned THEN SIGNAL UnSavedLinks
ELSE
BEGIN OPEN PilotLoaderOps;
nLinks: CARDINAL ← LinkSegmentLength[mth, bcd];
thisEntrySize: CARDINAL ← SIZE[LinkScriptEntry] + nLinks*SIZE[ControlLink];
UNTIL linkScriptNext + thisEntrySize <= linkScriptLimit
DO
ExtendLinkScript[
(thisEntrySize + Environment.wordsPerPage - 1)/Environment.wordsPerPage
! UNWIND => {ReleaseMap[map]; ReleaseBcd[bcd]}];
ENDLOOP;
linkScriptNext^ ← [frame: gf, mth: mth, config: config, nLinks: nLinks, links: ];
OpenLinkSpace[gf, mth, bcd];
FOR i:
CARDINAL
IN [0..nLinks)
DO
linkScriptNext.links[i] ← ReadLink[i];
ENDLOOP;
CloseLinkSpace[gf];
linkScriptNext ← linkScriptNext + thisEntrySize;
nLinkScriptEntries ← nLinkScriptEntries + 1;
Twiddle[];
END;
END;
RETURN [FALSE]
END;
[] ← BcdOps.ProcessModules[bcd, ProcessLinks];
ReleaseMap[map];
ReleaseBcd[bcd];
RETURN[FALSE]
END;
[] ← InputLoadState[];
BEGIN
ENABLE UNWIND => ReleaseLoadState[];
InitializeLinkScript[initialLinkScriptPages];
[] ← EnumerateBcds[recentlast, ProcessBcd];
InstallLinkScript[];
END;
ReleaseLoadState[];
END;
RollbackLinks:
PROC =
BEGIN
OPEN PilotLoadStateOps;
lSE: PLinkScriptEntry ← linkScriptBase;
config: ConfigIndex ← NullConfig;
bcd: BcdOps.BcdBase;
THROUGH [0..nLinkScriptEntries)
DO
OPEN PilotLoaderOps;
nLinks: CARDINAL = lSE.nLinks;
IF lSE.config ~= config
THEN
BEGIN
IF config ~= NullConfig THEN ReleaseBcd[bcd];
bcd ← PilotLoadStateOps.AcquireBcd[config←lSE.config];
END;
IF bcdVersion[config] = bcd.version
THEN
BEGIN
Restore links only if bcd hasn't been overwritten.
OpenLinkSpace[lSE.frame, lSE.mth, bcd];
FOR i:
CARDINAL
IN [0..nLinks)
DO
WriteLink[i, lSE.links[i]];
ENDLOOP;
CloseLinkSpace[lSE.frame];
END;
lSE ← lSE + SIZE[LinkScriptEntry] + nLinks*SIZE[PrincOps.ControlLink];
Twiddle[];
ENDLOOP;
IF config ~= NullConfig THEN ReleaseBcd[bcd];
END;
CleanupLinks:
PROC [after: CedarSnapshot.After] =
BEGIN
FlushLinkScript[];
END;
Bcd Validation
BcdVersionTable: TYPE = ARRAY PilotLoadStateOps.ConfigIndex OF BcdDefs.VersionStamp;
bcdVersionSpace: Space.Handle ← Space.nullHandle;
bcdVersion: LONG POINTER TO BcdVersionTable ← NIL;
BcdOverwritten: SIGNAL = CODE;
BuildBcdTable:
PROC =
BEGIN
ProcessBcd:
PROC [config: PilotLoadStateOps.ConfigIndex]
RETURNS [
BOOLEAN] =
BEGIN
bcd: BcdOps.BcdBase = PilotLoadStateOps.AcquireBcd[config];
bcdVersion[config] ← bcd.version;
PilotLoadStateOps.ReleaseBcd[bcd];
The following ensures that the bcd is not saved as part of the memory in the
outload file. It assumes that nothing later in checkpoint touches the bcds again.
Space.Deactivate[Space.GetHandle[Space.PageFromLongPointer[bcd]]];
RETURN[FALSE]
END;
pagesForBcdTable: Space.PageCount =
(SIZE[BcdVersionTable]+Environment.wordsPerPage-1)/Environment.wordsPerPage;
EnsureSnapshotFileSize[snapshotFile, snapshotFile.firstFree + pagesForBcdTable];
bcdVersionSpace ← Space.Create[pagesForBcdTable, Space.virtualMemory];
Space.Map[bcdVersionSpace, [snapshotFile.cap, snapshotFile.firstFree]];
snapshotFile.firstFree ← snapshotFile.firstFree + pagesForBcdTable;
bcdVersion ← Space.LongPointer[bcdVersionSpace];
bcdVersion^ ← ALL[BcdDefs.NullVersion];
[] ← PilotLoadStateOps.InputLoadState[];
[] ← PilotLoadStateOps.EnumerateBcds[recentlast, ProcessBcd];
PilotLoadStateOps.ReleaseLoadState[];
END;
ValidateBcds:
PROC =
BEGIN
CheckBcd:
PROC [config: PilotLoadStateOps.ConfigIndex]
RETURNS [
BOOLEAN] =
BEGIN
bcd: BcdOps.BcdBase = PilotLoadStateOps.AcquireBcd[config];
-- If the BCD has completely vanished, touching bcd.version will produce an address fault that can't be satisfied in the File Helper (it will get File.Unknown during GetFileDescriptor). Accordingly, we must test for this case ourselves by hand.
file: File.Capability =
Space.GetWindow[Space.GetHandle[Space.PageFromLongPointer[bcd]]].file;
{ENABLE File.Unknown => GO TO overWritten;
[] ← File.GetSize[file]; -- dummy operation; triggers File.Unknown if BCD is gone
IF bcdVersion[config] ~= bcd.version THEN GO TO overWritten;
EXITS
overWritten => SIGNAL BcdOverwritten;
};
PilotLoadStateOps.ReleaseBcd[bcd];
RETURN[FALSE]
END;
[] ← PilotLoadStateOps.InputLoadState[];
[] ← PilotLoadStateOps.EnumerateBcds[recentlast, CheckBcd];
PilotLoadStateOps.ReleaseLoadState[];
END;
CleanupBcdTable:
PROC [after: CedarSnapshot.After] =
BEGIN
IF bcdVersionSpace ~= Space.nullHandle THEN Space.Delete[bcdVersionSpace];
END;
System volume cleanup
CheckpointSystemVolume:
PROC =
BEGIN
We now flush the file cache to make sure that files deleted after the
checkpoint will not be "remembered" when the rollback occurs. It is
assumed that uses of the space machinery by the volume open/close do not
cause any changes in the space/region caches (for reasons described in
CheckpointVMSpaces, above). This works because the volume stuff uses
SimpleSpace exclusively, which pins all descriptors in the caches. Since
this is a logical requirement of the FileMgr, we aren't likely to get
burned in the future.
FlushTempFileListCache:
PROC =
BEGIN
This crock is necessary because the FileMgr (in particular, FileImpl)
holds onto a page of the temporary file list even when the volume is
closed. We rely here on the fact that the implementation gives up the
page when an attempt is made to create a temporary file on another volume,
even if the volume ID is bogus.
bogusVolume: System.VolumeID = [System.GetUniversalID[]]; -- can't have been used yet
[] ← File.Create[volume: bogusVolume, initialSize: 1, type: PilotFileTypes.tAnonymousFile !
Volume.Unknown => GO TO flushed];
ERROR SnapshotBug;
END;
WaitForDiskToIdle:
PROC =
BEGIN
All this is because the disk must be quiet before we can close the volume.
BEGIN OPEN SubVolumeImpl;
FOR cePtr: CePtr ← ceFirst, cePtr +
SIZE[CacheEntry]
WHILE cePtr <= ceLast
DO
IF cePtr.occupied
THEN
BEGIN
DiskChannel.Idle[cePtr.svDesc.channel];
DiskChannel.Restart[cePtr.svDesc.channel];
END;
ENDLOOP;
END;
END;
FlushTempFileListCache[];
WaitForDiskToIdle[];
We now want to close the system volume, causing Pilot (specifically, the
FileMgr) to forget everything it has cached about the contents of the
system volume. This includes the file cache, the VFM, and the volume
root page. LogicalVolume.CloseLogicalVolume should do all of these, but
because of a bug, it retains a cached copy of the volume root page.
Witness the crazy stuff below with bringing subvolumes- on- and off-line,
and observe the 'root' parameter is TRUE in the offline case. This has
the effect of leaving the logical volume table unchanged, but causing the
volume root page to be flushed. Look at the code in VolumeImpl if you are
skeptical.
VolumeImplInterface.SubvolumeOnline[lvID: systemVolume, root: TRUE];
LogicalVolume.CloseLogicalVolume[@systemVolume];
VolumeImplInterface.SubvolumeOffline[lvID: systemVolume, root: TRUE];
END;
RollbackSystemVolume:
PROC =
BEGIN
[] ← LogicalVolume.OpenLogicalVolume[@systemVolume];
END;
CleanupSystemVolume:
PROC =
BEGIN
[] ← LogicalVolume.OpenLogicalVolume[@systemVolume];
END;
OutLoad implementation
OutLoad:
PROC
RETURNS [inLoaded:
BOOLEAN] =
BEGIN
This code is stolen directly from SnapshotImpl.OutLoad. Why? Well
Snapshot.Outload requires that the volume containing the boot file
be open, and we can't tolerate that.
psb: PSB.PsbHandle; ptc: CARDINAL; wdc: CARDINAL;
Save process state not captured in PDA:
ProcessInternal.DisableInterrupts[]; -- make it hold still first
psb ← ProcessOperations.ReadPSB[];
ptc ← ProcessOperations.ReadPTC[];
wdc ← ProcessOperations.ReadWDC[];
DeviceCleanup.Perform[turnOff]; -- turn all devices off.
Save our state on a boot file: (If the boot file is inloaded
later, we will reappear here with inLoaded=TRUE.)
inLoaded ← BootSwap.OutLoad[@outloadLocation, restore] ~= outLoaded;
IF inLoaded
THEN
BEGIN
Restore process state not captured in PDA.
ProcessOperations.WriteWDC[wdc];
ProcessOperations.WritePTC[ptc];
ProcessOperations.WritePSB[psb];
The following is a temporary substitute for a clock chip.
-- We must do it with interrupts off or Communication will be using the Ethernet--
if we get an allocation trap, all is lost.
[] ← TemporarySetGMT.SetGMT[];
END;
DeviceCleanup.Perform[turnOn]; -- turn devices back on
ProcessorFace.SetMP[PilotMP.cClient]; -- announce our return
ProcessInternal.EnableInterrupts[];
END;
Directory cleanup
RationalizeDirectory:
PROC =
BEGIN
FOR i:
CARDINAL
IN [1 .. DirectoryFiles.maxDCache]
DO
dCE: LONG POINTER TO DirectoryFiles.DCEntry = @DirectoryFilesImpl.directoryCache[i];
IF dCE.refCount > 0
THEN
BEGIN
This fixes up the space descriptor (in particular, the countMapped
information) to account for any change in file size. It also updates
the directory cache entry.
window: Space.WindowOrigin = Space.GetWindow[dCE.dir.space];
dirFileSize: Space.PageCount = Inline.LowHalf[File.GetSize[window.file]];
directoryOrigin: DirectoryInternal.DirectoryPageHandle = Space.LongPointer[dCE.dir.space];
Space.Unmap[dCE.dir.space];
Space.Map[dCE.dir.space, window];
dCE.dir.top ← directoryOrigin.top;
dCE.dir.size ← dirFileSize - DirectoryInternal.leaderPageSize;
Twiddle[];
END;
ENDLOOP;
END;
Miscellaneous
GetPinnedAndReadOnly:
PROC [space: Space.Handle]
RETURNS [pinned, readOnly:
BOOLEAN] =
BEGIN
GetPinnedAndReadOnlyInternal:
PROC =
BEGIN
desc: CachedSpace.Desc;
validSpace, validSwapUnit: BOOLEAN;
[validSpace, validSwapUnit] ← Hierarchy.GetDescriptor[@desc, LOOPHOLE[space]];
IF ~(validSpace OR validSwapUnit) THEN ERROR SnapshotBug;
If validSwapUnit, the following are the parent's state
pinned ← desc.pinned;
readOnly ← desc.writeProtected;
END;
SpaceImplInternal.EnterSpace[GetPinnedAndReadOnlyInternal];
END;
Cleanup:
PROC [after: CedarSnapshot.After] =
BEGIN
UncorkRestOfWorld[corkHandle];
CleanupBcdTable[after];
CleanupLinks[after];
CleanupVMSpaces[after];
CleanupClientSpaces[after];
END;
GetGermSwitches:
PROC
RETURNS [TemporaryBooting.Switches] =
BEGIN
OPEN BootSwap;
RETURN[
LOOPHOLE[Boot.
LP[highbits: mdsiGerm, lowbits: @SDDefs.
SD[sPilotSwitches]],
LONG POINTER TO TemporaryBooting.Switches]^]
END;
ValidateMicrocodeAndUser:
PROC =
BEGIN
OPEN UserCredentialsUnsafe;
TurnOnInitialTerminal:
PROC
RETURNS [g: GetProc, p: PutProc] = {
[g, p] ← SpecialTerminal.TurnOn[];
};
TurnOffInitialTerminal: PROC = {SpecialTerminal.TurnOff[]};
CedarInitOps.EnsureUsableMicrocode[];
Login[TurnOnInitialTerminal, TurnOffInitialTerminal];
END;
CallRollbackProcs:
PROC [after: CedarSnapshot.After] =
BEGIN
DoOneRollbackProc:
PROC [proc: CedarSnapshot.RollbackProc] =
{proc[after]};
EnumerateRollbackProcs[DoOneRollbackProc];
END;
*** Here's the real work ***
corkHandle: CorkHandle ← NIL;
checkpointProcsCalled: BOOL ← FALSE;
switches: TemporaryBooting.Switches;
InitializeFeedback[];
outcome ← checkpointed;
IF volume = Volume.nullID THEN volume ← systemVolume;
BEGIN
ENABLE Volume.InsufficientSpace => {outcome ← insufficientDiskSpace; GO TO cantCheckpoint};
SetPhase[0, checkpoint];
snapshotFile ← InitializeSnapshotFile[volume];
Call registered procedures to acquire client spaces to be saved.
SetPhase[1, checkpoint];
BuildClientSpaceScript[];
checkpointProcsCalled ← TRUE;
Stifle interference.
corkHandle ← CorkRestOfWorld[];
Check for open transactions.
SetPhase[2, checkpoint];
IF TransactionExtras.TransactionsInProgress[]
THEN
{outcome ← transactionsInProgress; GO TO cantCheckpoint};
Save all necessary code links.
SetPhase[3, checkpoint];
CheckpointLinks[ ! UnSavedLinks => {outcome ← potentiallyDangerousCodeLinks; RESUME}];
Prepare the script for data spaces.
SetPhase[4, checkpoint];
BuildVMSpaceScript[];
Save the client spaces.
SetPhase[5, checkpoint];
CheckpointClientSpaces[];
Save versions of loaded BCDs.
SetPhase[6, checkpoint];
BuildBcdTable[];
Save space/region data base, maplog, and all data spaces.
SetPhase[7, checkpoint];
CheckpointVMSpaces[];
Prepare the snapshot file for the upcoming Outload.
SetPhase[8, checkpoint];
outloadLocation ← PrepareForOutload[snapshotFile];
EXITS
cantCheckpoint => {
Cleanup[checkpoint];
FinalizeSnapshotFile[snapshotFile, clear];
IF checkpointProcsCalled THEN CallRollbackProcs[checkpoint];
FinalizeFeedback[];
RETURN};
END;
Flush FileMgr caches and shut down the system volume.
SetPhase[9, checkpoint];
CheckpointSystemVolume[];
Save real memory.
SetPhase[10, checkpoint];
TerminalMultiplex.PreventDebuggerSwaps[];
IF ~OutLoad[].inLoaded
THEN
BEGIN
TerminalMultiplex.PermitDebuggerSwaps[];
SetPhase[11, checkpoint];
CleanupSystemVolume[];
SetPhase[12, checkpoint];
InstallSnapshotFile[snapshotFile, outloadLocation]; -- now Rollback will work
SetPhase[13, checkpoint];
Cleanup[checkpoint];
FinalizeSnapshotFile[snapshotFile, keep];
SetPhase[14, checkpoint];
CallRollbackProcs[checkpoint];
FinalizeFeedback[];
RETURN
END;
Rollback begins here.
outcome ← rolledBack;
Get boot switches from germ
switches ← GetGermSwitches[];
IF switches.i = down THEN RuntimeInternal.WorryCallDebugger["Rollback KeyStop"L];
Inload has restored real memory. Reopen the system volume before restoring VM.
SetPhase[0, rollback];
RollbackSystemVolume[];
Restore space/region data base, maplog, and data spaces.
SetPhase[1, rollback];
RollbackVMSpaces[];
Ensure BCDs haven't been smashed.
SetPhase[2, rollback];
ValidateBcds[ ! BcdOverwritten => {outcome ← overwrittenBcd; RESUME}];
Restore code links that may have been invalidated.
SetPhase[3, rollback];
RollbackLinks[];
Restore client spaces.
SetPhase[4, rollback];
RollbackClientSpaces[];
Now that memory is back together, make the directory system believe the current
state of the directory files on the disk.
SetPhase[5, rollback];
RationalizeDirectory[];
Fix up the transaction machinery.
SetPhase[6, rollback];
TransactionExtras.DoCrashRecovery[];
State restored; release checkpoint data structures.
SetPhase[7, rollback];
Cleanup[rollback];
FinalizeSnapshotFile[snapshotFile, keep];
Validate microcode and user
SetPhase[8, rollback];
ValidateMicrocodeAndUser[];
TerminalMultiplex.PermitDebuggerSwaps[];
We're on our way...
SetPhase[9, rollback];
CallRollbackProcs[rollback];
FinalizeFeedback[];
END;