-- File: Image.Mesa
-- Last edited by Sandman; September 12, 1980  11:03 AM
-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  AllocDefs USING [DefaultDataSegmentInfo],
  AltoDefs USING [PageCount, PageNumber, PageSize],
  AltoFileDefs USING [eofDA, FA, TIME, vDA],
  CacheOps USING [
    Flush, GetCoreFile, GetCS, GetPageItem, PageItem, PageNumber, SetCoreFile,
    SetPageItem],
  BootmesaOps,
  CommanderDefs USING [AddCommand],
  ControlDefs USING [GFTIndex],
  FileLookupDefs USING [GetFileName],
  ImageFormat USING [
    FirstImageDataPage, HeaderPages, ImagePrefix, MapItem, VersionID],
  IODefs USING [CR, NumberFormat, WriteChar, WriteLine, WriteNumber, WriteString],
  MiscDefs USING [DAYTIME, Zero],
  SegmentDefs USING [
    AddressFromPage, Append, DataSegmentHandle, DefaultVersion, DeleteFileSegment,
    FileHandle, FileSegmentAddress, FileSegmentHandle, LockFile, NewFile,
    NewFileSegment, Read, SwapIn, Unlock, UnlockFile, Write],
  SegOps USING [EnumerateSegs, Seg, vmFile],
  WartDefs USING [MaxImagePage, TableBase],
  StreamDefs USING [
    CleanupDiskStream, CreateWordStream, GetFA, GetIndex, SetIndex, StreamIndex,
    WriteBlock],
  String USING [AppendString];

Image: PROGRAM
  IMPORTS
    CacheOps, CommanderDefs, BootmesaOps, FileLookupDefs, IODefs, MiscDefs,
    StreamDefs, SegmentDefs, String, SegOps
  EXPORTS BootmesaOps
  SHARES ControlDefs =
  BEGIN OPEN SegOps, AltoDefs, ControlDefs, BootmesaOps;
  
  data: POINTER TO BootmesaOps.BootData ← @dataObject;
  
  FileHandle: TYPE = SegmentDefs.FileHandle;
  FileSegmentHandle: TYPE = SegmentDefs.FileSegmentHandle;
  DataSegmentHandle: TYPE = SegmentDefs.DataSegmentHandle;
  
  -- image file management
  
  mapindex: CARDINAL ← 0;
  
  MapOverflow: PUBLIC ERROR = CODE;
  
  EnterMapItem: PROCEDURE [base: PageNumber, pages: PageCount] =
    BEGIN
    map: POINTER TO ARRAY [0..0) OF normal ImageFormat.MapItem =
      LOOPHOLE[@data.image.map];
    IF mapindex > AltoDefs.PageSize - SIZE[ImageFormat.ImagePrefix] THEN
      BootmesaError["MapOverflow"L];
    IF pages > 127 THEN BootmesaError["Segment too big"L];
    map[mapindex] ← ImageFormat.MapItem[base, pages, normal[]];
    mapindex ← mapindex + SIZE[normal ImageFormat.MapItem];
    RETURN
    END;
    
  EnterMapSegment: PUBLIC PROCEDURE [s: Seg] =
    BEGIN IF s.in THEN EnterMapItem[s.vmPage, s.pages]; RETURN END;
    
  SegmentToMap: PUBLIC PROCEDURE [s: Seg] =
    BEGIN OPEN IODefs;
    octal: NumberFormat = NumberFormat[8, FALSE, FALSE, 1];
    base: NumberFormat = NumberFormat[8, FALSE, FALSE, 4];
    pages: NumberFormat = NumberFormat[8, FALSE, FALSE, 6];
    address: NumberFormat = NumberFormat[8, FALSE, TRUE, 9];
    filename: STRING;
    s.imageBase ← StreamDefs.GetIndex[data.imageStream].page + 1;
    WriteNumber[s.imageBase, base];
    WriteNumber[s.pages, pages];
    WriteNumber[SegmentDefs.AddressFromPage[s.vmPage], address];
    WriteString["  "L];
    filename ←
      IF s.realFile = SegOps.vmFile THEN "VM"
      ELSE FileLookupDefs.GetFileName[
	IF s.trueFile = NIL THEN s.realFile ELSE s.trueFile];
    WriteString[filename];
    WriteString[" ["L];
    WriteNumber[s.base, octal];
    WriteChar[',];
    WriteNumber[s.pages, octal];
    WriteChar[']];
    IF ~s.data THEN PrintModuleNames[s];
    WriteChar[CR];
    RETURN
    END;
    
  cseg1, cseg2: SegmentDefs.FileSegmentHandle ← NIL;
  
  CacheSegment: PROCEDURE [seg: SegmentDefs.FileSegmentHandle] =
    BEGIN OPEN SegmentDefs;
    Unlock[seg];
    IF cseg1 # NIL THEN DeleteFileSegment[cseg1];
    cseg1 ← cseg2;
    cseg2 ← seg;
    RETURN
    END;
    
  WriteSegment: PROCEDURE [fs: Seg] =
    BEGIN OPEN SegmentDefs;
    f: FileHandle ← fs.realFile;
    s: FileSegmentHandle;
    base: WartDefs.TableBase = segTable;
    page: CARDINAL ← GetCurrentPosition[];
    IF page > WartDefs.MaxImagePage THEN BootmesaError["Image file too large!"];
    IF ~fs.data THEN [] ← MarkImageSegment[fs];
    IF fs = data.vmTableSeg OR fs = data.daSeg THEN RETURN;
    SegmentToMap[fs];
    IF f = SegOps.vmFile THEN WriteVMSegment[fs]
    ELSE IF fs = BootmesaOps.fakebcdseg THEN
      BEGIN
      EnterMapSegment[fs];
      IF f = data.imageFile THEN
	BEGIN IF (f ← fs.trueFile) = NIL THEN ERROR; fs.trueFile ← NIL; END;
      s ← fs.link2;
      IF StreamDefs.WriteBlock[
	data.imageStream, FileSegmentAddress[s], s.pages*PageSize] # CARDINAL[
	s.pages*PageSize] THEN ERROR;
      END
    ELSE
      BEGIN
      EnterMapSegment[fs];
      IF f = data.imageFile THEN
	BEGIN IF (f ← fs.trueFile) = NIL THEN ERROR; fs.trueFile ← NIL; END;
      s ← NewFileSegment[f, fs.base, fs.pages, Read];
      SwapIn[s];
      IF StreamDefs.WriteBlock[
	data.imageStream, FileSegmentAddress[s], s.pages*PageSize] # CARDINAL[
	s.pages*PageSize] THEN ERROR;
      CacheSegment[s];
      END;
    base[fs.index].base ← page;
    RETURN
    END;
    
  WriteVMSegment: PROCEDURE [fs: Seg] =
    BEGIN OPEN CacheOps;
    p, basepage, segbase: PageNumber;
    pi: PageItem;
    segcount: PageCount ← 0;
    
    getda: PROCEDURE RETURNS [AltoFileDefs.vDA] =
      BEGIN
      fa: AltoFileDefs.FA;
      StreamDefs.GetFA[data.imageStream, @fa];
      RETURN[fa.da]
      END;
      
    basepage ← fs.vmPage;
    FOR p IN [basepage..basepage + fs.pages) DO
      IF segcount = 0 THEN segbase ← p;
      IF GetPageItem[p].p.page = 0 THEN
	BEGIN
	IF segcount # 0 THEN
	  BEGIN EnterMapItem[segbase, segcount]; segcount ← 0; END;
	END
      ELSE
	BEGIN
	segcount ← segcount + 1;
	pi ← PageItem[StreamDefs.GetIndex[data.imageStream].page + 1, getda[]];
	IF StreamDefs.WriteBlock[
	  data.imageStream, SegmentDefs.FileSegmentAddress[GetCS[GetPageItem[p]]],
	  PageSize] # PageSize THEN ERROR;
	SetPageItem[p, pi];
	END;
      ENDLOOP;
    IF segcount # 0 THEN EnterMapItem[segbase, segcount];
    RETURN
    END;
    
  MarkImageSegment: PROCEDURE [fs: Seg] RETURNS [BOOLEAN] =
    BEGIN
    f: FileHandle ← fs.realFile;
    IF f # SegOps.vmFile AND (fs.in OR ~fs.data) THEN
      BEGIN
      IF fs.trueFile # NIL THEN ERROR;
      fs.trueFile ← f;
      fs.realFile ← data.imageFile;
      data.imageFile.segcount ← data.imageFile.segcount + 1;
      IF fs.in THEN data.imageFile.swapcount ← data.imageFile.swapcount + 1;
      END;
    RETURN[FALSE]
    END;
    
  GetCurrentPosition: PROCEDURE RETURNS [page: CARDINAL] =
    BEGIN page ← StreamDefs.GetIndex[data.imageStream].page + 1; RETURN END;
    
  GetImageHeader: PUBLIC PROCEDURE
    RETURNS [header: SegmentDefs.FileSegmentHandle] =
    BEGIN RETURN[data.headerSeg] END;
    
  WriteData: PUBLIC PROCEDURE =
    BEGIN
    
    FindVMSegments: PROCEDURE [fs: Seg] RETURNS [BOOLEAN] =
      BEGIN
      IF fs = data.vmTableSeg OR ~fs.data THEN RETURN[FALSE];
      WriteSegment[fs];
      RETURN[FALSE];
      END;
      
    [] ← EnumerateSegs[FindVMSegments];
    RETURN
    END;
    
  WriteNonData: PUBLIC PROCEDURE =
    BEGIN
    
    Resident: PROCEDURE [fs: Seg] RETURNS [BOOLEAN] =
      BEGIN
      IF fs.data OR ~fs.resident THEN RETURN[FALSE];
      WriteSegment[fs];
      RETURN[FALSE];
      END;
      
    SwappedIn: PROCEDURE [fs: Seg] RETURNS [BOOLEAN] =
      BEGIN
      IF fs.data OR fs.resident OR ~fs.in THEN RETURN[FALSE];
      WriteSegment[fs];
      RETURN[FALSE];
      END;
      
    Other: PROCEDURE [fs: Seg] RETURNS [BOOLEAN] =
      BEGIN
      IF fs.data OR fs.resident OR fs.in THEN RETURN[FALSE];
      WriteSegment[fs];
      RETURN[FALSE];
      END;
      
    [] ← EnumerateSegs[Resident];
    [] ← EnumerateSegs[SwappedIn];
    [] ← EnumerateSegs[Other];
    RETURN
    END;
    
  versionID: CARDINAL ← 0;
  
  InitializeImage: PUBLIC PROCEDURE =
    BEGIN OPEN ImageFormat, SegmentDefs;
    name: STRING ← [40];
    time: AltoFileDefs.TIME ← MiscDefs.DAYTIME[];
    seg: SegmentDefs.FileSegmentHandle;
    String.AppendString[name, data.imageFileRoot];
    String.AppendString[name, ".Image"L];
    data.imageFile ← NewFile[name, Read + Write + Append, DefaultVersion];
    SegmentDefs.LockFile[data.imageFile];
    data.imageStream ← StreamDefs.CreateWordStream[
      data.imageFile, Write + Append];
    StreamDefs.SetIndex[
      data.imageStream, StreamDefs.StreamIndex[FirstImageDataPage - 1, 0]];
    StreamDefs.CleanupDiskStream[data.imageStream];
    data.headerSeg ← seg ← NewFileSegment[data.imageFile, 1, HeaderPages, Write];
    MakeResident[seg];
    data.image ← FileSegmentAddress[data.headerSeg ← seg];
    MiscDefs.Zero[data.image, HeaderPages*AltoDefs.PageSize];
    data.image.prefix.versionident ←
      IF versionID # 0 THEN versionID ELSE ImageFormat.VersionID;
    data.image.prefix.options ← 0;
    data.image.prefix.type ← bootmesa;
    data.image.prefix.leaderDA ← AltoFileDefs.eofDA;
    --data.image.prefix.creator ← NullVersion;
    IODefs.WriteChar[IODefs.CR];
    IODefs.WriteLine["  Image"L];
    IODefs.WriteLine["Base Pages  Address  Source [base,pages]"L];
    RETURN
    END;
    
  WriteImage: PUBLIC PROCEDURE =
    BEGIN OPEN SegOps, CacheOps;
    WriteData[];
    SegmentDefs.UnlockFile[GetCoreFile[]];
    [] ← Flush[0, AllocDefs.DefaultDataSegmentInfo, NIL];
    SetCoreFile[data.imageFile];
    WriteNonData[];
    data.imageStream.destroy[data.imageStream];
    RETURN
    END;
    
  PrintModuleNames: PROCEDURE [seg: Seg] =
    BEGIN
    name: STRING ← [60];
    gfi: ControlDefs.GFTIndex;
    mt: DESCRIPTOR FOR ARRAY OF BootmesaOps.ModuleInfo ← data.moduleTable;
    IF seg.data THEN RETURN;
    FOR gfi IN [1..LENGTH[data.moduleTable]) DO
      IF mt[gfi].whenLoaded = notLoaded THEN LOOP;
      IF mt[gfi].code = seg THEN
	BEGIN
	ModuleName[mt[gfi].frame, name];
	IODefs.WriteChar[' ];
	IODefs.WriteString[name];
	name.length ← 0;
	EXIT;
	END;
      ENDLOOP;
    RETURN
    END;
    
  SetID: PROCEDURE [n: CARDINAL] = {versionID ← n};
    
  CommanderDefs.AddCommand["SetVersionID", LOOPHOLE[SetID], 1].params[0] ←
    [type: numeric, prompt: "VersionID"];
  
  END..