-- CedarSnapshotFile.mesa
-- last edited by Levin:  November 22, 1982 2:12 pm

DIRECTORY
  Boot USING [BootFileType, Location, LVBootFiles],
  BootFile USING [Header, MemorySizeToFileSize],
  CedarSnapshotPrivate USING [
    FilePoint, initialSnapshotSize, nullFilePoint, PointAction, SnapshotFile,
    SnapshotFileDescriptor],
  DCSFileTypes USING [tLeaderPage],
  Environment USING [wordsPerPage],
  File USING [
    Capability, Create, delete, Delete, GetAttributes, GetSize, grow, ID, MakePermanent,
    nullID, PageCount, PageNumber, read, SetSize, Type, Unknown, write],
  FileTypes USING [tUntypedFile],
  Heap USING [systemZone],
  KernelFile USING [GetBootLocation],
  Runtime USING [GlobalFrame],
  RuntimeInternal USING [Codebase],
  Space USING [
    Create, Delete, GetAttributes, GetHandle, GetWindow, Handle, LongPointer, Map, PageCount,
    PageFromLongPointer, virtualMemory],
  SpecialFile USING [Link, MakeBootable],
  SpecialSpace USING [realMemorySize],
  SpecialVolume USING [GetLogicalVolumeBootFiles, SetLogicalVolumeBootFiles],
  System USING [GetGreenwichMeanTime, GreenwichMeanTime],
  Volume USING [Close, GetStatus, ID, systemID],
  VolumeExtras USING [OpenVolume];

CedarSnapshotFile: PROGRAM 
  IMPORTS
    BootFile, File, Heap, KernelFile, Runtime, RuntimeInternal, Space, SpecialFile,
    SpecialSpace, SpecialVolume, System, Volume, VolumeExtras
  EXPORTS CedarSnapshotPrivate
  SHARES File =

BEGIN OPEN CedarSnapshotPrivate;

Header: TYPE = LONG POINTER TO SnapshotHeader;
SnapshotHeader: TYPE = MACHINE DEPENDENT RECORD [
  version(0): CARDINAL ← currentVersion,
  creation(1): System.GreenwichMeanTime,
  bootFileCreation(3): System.GreenwichMeanTime,
  bootFileVolume(5): Volume.ID,
  bootFilePoint(10): FilePoint
  ];

currentVersion: CARDINAL = 11082;
previousVersion: CARDINAL = 06012;  -- Cedar 3.4.1 --

pagesForHeader: Space.PageCount =
  (SIZE[SnapshotHeader]+Environment.wordsPerPage-1)/Environment.wordsPerPage;

snapshotSlot: Boot.BootFileType = hardMicrocode;      -- as good as any...
snapshotFileType: File.Type = FileTypes.tUntypedFile;    -- eventually, our own type


InitializeSnapshotFile: PUBLIC PROC [volume: Volume.ID] RETURNS [snapshot: SnapshotFile] = {
  GetFile: PROC [volume: Volume.ID] RETURNS [file: File.ID] = {
    IF (file ← GetSnapshotPoint[volume, clear].filePoint.fID) = File.nullID THEN GO TO noFile;
    [] ← File.GetSize[[file, File.read] ! File.Unknown => GO TO noFile];
    EXITS
      noFile => file ← File.Create[volume, initialSnapshotSize, snapshotFileType].fID;
    };
  file: File.ID = GetFile[volume];
  headerSpace: Space.Handle = Space.Create[pagesForHeader, Space.virtualMemory];
  header: Header = Space.LongPointer[headerSpace];
  snapshot ← Heap.systemZone.NEW[SnapshotFileDescriptor ← [
    volume: volume,
    cap: [file, File.read + File.write + File.grow + File.delete],
    firstFree: 0]];
  EnsureSnapshotFileSize[snapshot, pagesForHeader];
  Space.Map[headerSpace, [snapshot.cap, snapshot.firstFree]];
  snapshot.firstFree ← snapshot.firstFree + pagesForHeader;
  header↑ ← [
    creation: System.GetGreenwichMeanTime[],
    bootFileCreation: GetBootFileCreation[GetCurrentBootFilePoint[]],
    bootFileVolume: Volume.systemID,
    bootFilePoint: GetCurrentBootFilePoint[]
    ];
  Space.Delete[headerSpace];
  };

FinalizeSnapshotFile: PUBLIC PROC [snapshot: SnapshotFile, action: PointAction] = {
  IF snapshot = NIL THEN RETURN;
  IF action = clear THEN File.Delete[snapshot.cap];
  Heap.systemZone.FREE[@snapshot];
  };

EnsureSnapshotFileSize: PUBLIC PROC [snapshot: SnapshotFile, size: File.PageCount] =
  {IF File.GetSize[snapshot.cap] < size THEN File.SetSize[snapshot.cap, size]};

PrepareForOutload: PUBLIC PROC [snapshot: SnapshotFile]
  RETURNS [location: disk Boot.Location] = {
  firstRMPage: File.PageNumber = snapshot.firstFree;
  pagesForRM: File.PageCount = BootFile.MemorySizeToFileSize[SpecialSpace.realMemorySize];
  EnsureSnapshotFileSize[snapshot, firstRMPage + pagesForRM];
  [] ← SpecialFile.MakeBootable[
      file: snapshot.cap, firstPage: firstRMPage, count: pagesForRM,
      lastLink: SpecialFile.Link[0]];
  location.diskFileID ← [fID: snapshot.cap.fID, firstPage: firstRMPage, da:];
  [deviceType: location.deviceType, deviceOrdinal: location.deviceOrdinal,
    link: LOOPHOLE[location.diskFileID.da, SpecialFile.Link]] ←
    KernelFile.GetBootLocation[snapshot.cap, firstRMPage];
  };

InstallSnapshotFile: PUBLIC PROC [snapshot: SnapshotFile, location: disk Boot.Location] = {
  bootFiles: Boot.LVBootFiles;
  SpecialVolume.GetLogicalVolumeBootFiles[snapshot.volume, @bootFiles];
  bootFiles[snapshotSlot] ← 
    [snapshot.cap.fID, location.diskFileID.firstPage, LOOPHOLE[location.diskFileID.da]];
  SpecialVolume.SetLogicalVolumeBootFiles[snapshot.volume, @bootFiles];
  File.MakePermanent[snapshot.cap];
  };

GetSnapshotPoint: PUBLIC PROC [volume: Volume.ID, action: PointAction]
  RETURNS [filePoint: FilePoint, valid: BOOL ← FALSE] = {
  bootFiles: Boot.LVBootFiles;
  SpecialVolume.GetLogicalVolumeBootFiles[volume, @bootFiles];
  filePoint ← [fID: bootFiles[snapshotSlot].fID, firstPage: bootFiles[snapshotSlot].firstPage];
  IF filePoint = nullFilePoint THEN RETURN;
  valid ← ValidSnapshot[volume];
  IF action = keep THEN RETURN;
  [fID: bootFiles[snapshotSlot].fID, firstPage: bootFiles[snapshotSlot].firstPage] ←
    nullFilePoint;
  SpecialVolume.SetLogicalVolumeBootFiles[volume, @bootFiles];
  };

ValidSnapshot: PROC [volume: Volume.ID] RETURNS [ok: BOOL ← FALSE] = {
  headerSpace: Space.Handle = Space.Create[pagesForHeader, Space.virtualMemory];
  header: Header = Space.LongPointer[headerSpace];
  bootFiles: Boot.LVBootFiles;
  SpecialVolume.GetLogicalVolumeBootFiles[volume, @bootFiles];
  {ENABLE --BogusFilePoint, File.Unknown, Volume.NeedsScavenging-- ANY => {ok ← FALSE; CONTINUE};
   snapshotVolumeOpened: BOOL ← FALSE;
   IF Volume.GetStatus[volume] ~= open THEN {
     VolumeExtras.OpenVolume[volume: volume, readOnly: TRUE];
     snapshotVolumeOpened ← TRUE;
     };
   {ENABLE UNWIND => IF snapshotVolumeOpened THEN Volume.Close[volume];
    Space.Map[headerSpace, [[bootFiles[snapshotSlot].fID, File.read], 0]];
    SELECT header.version FROM
      currentVersion => {
        bootFileVolumeOpened: BOOL ← FALSE;
        IF Volume.GetStatus[header.bootFileVolume] ~= open THEN {
          VolumeExtras.OpenVolume[volume: header.bootFileVolume, readOnly: TRUE];
          bootFileVolumeOpened ← TRUE;
          };
        ok ← header.bootFileCreation = GetBootFileCreation[header.bootFilePoint];
        IF bootFileVolumeOpened THEN Volume.Close[header.bootFileVolume];
        };
      previousVersion =>
        ok ← header.bootFileCreation =
           GetBootFileCreation[[bootFiles[pilot].fID, bootFiles[pilot].firstPage]];
      ENDCASE;
    };
   IF snapshotVolumeOpened THEN Volume.Close[volume];
   };
  Space.Delete[headerSpace];
  };

BogusFilePoint: ERROR = CODE;

GetBootFileCreation: PROC [filePoint: FilePoint] RETURNS [creation: System.GreenwichMeanTime] = {
  -- This procedure differs from Runtime.GetBuildTime in that it deals with the boot file at
  -- arm's length.
  pagesForBootFileHeader: Space.PageCount =
    (SIZE[BootFile.Header]+Environment.wordsPerPage-1)/Environment.wordsPerPage;
  bootHeaderSpace: Space.Handle = Space.Create[pagesForBootFileHeader, Space.virtualMemory];
  bootHeader: LONG POINTER TO BootFile.Header = Space.LongPointer[bootHeaderSpace];
  cap: File.Capability = [filePoint.fID, File.read];
  IF File.GetSize[cap] < filePoint.firstPage + pagesForBootFileHeader THEN
    ERROR BogusFilePoint;
  Space.Map[bootHeaderSpace, [cap, filePoint.firstPage]];
  creation ← LOOPHOLE[bootHeader.creationDate];
  Space.Delete[bootHeaderSpace];
  };

GetCurrentBootFilePoint: PROC RETURNS [FilePoint] = {
    -- Pilot should really give us some help here.
    file: File.ID;
    someBootFileCode: Space.Handle ←
      Space.GetHandle[Space.PageFromLongPointer[
        RuntimeInternal.Codebase[Runtime.GlobalFrame[Space.GetHandle]]]];
    DO
      parent: Space.Handle;
      mapped: BOOL;
      [parent: parent, mapped: mapped] ← Space.GetAttributes[someBootFileCode];
      IF mapped THEN {file ← Space.GetWindow[someBootFileCode].file.fID; EXIT};
      someBootFileCode ← parent;
      ENDLOOP;
  RETURN[
    [fID: file,
     firstPage: SELECT File.GetAttributes[[file, File.read]].type FROM
       FileTypes.tUntypedFile => 0,  -- probably a volume root file
       DCSFileTypes.tLeaderPage => 1,
       ENDCASE => 0]  -- unexpected:  0 probably isn't right, but...
    ]
  };

END.