-- Copyright (C) 1984, 1985  by Xerox Corporation. All rights reserved. 
-- NetDirFileTajo.mesa, HGM,  5-Nov-85 19:32:34

DIRECTORY
  Ascii USING [CR],
  Checksum USING [ComputeChecksum],
  Environment USING [bytesPerPage],
  Inline USING [LongCOPY, LowHalf],
  MFile USING [
    AcquireTemp, AddNotifyProc, CopyFileHandle, Error, Filter, GetLength, Handle,
    ReadOnly, Release, ReleaseChoice, RemoveNotifyProc, Rename, SetAccess,
    SwapNames],
  MSegment USING [Address, Create, Delete, Handle],
  MStream USING [Create],
  Put USING [Text],
  Stream USING [Handle],
  String USING [
    AppendChar, AppendDecimal, AppendString, Equivalent, WordsForString],
  System USING [Pulses, GetClockPulses, PulsesToMicroseconds],
  Time USING [AppendCurrent],

  NameServerDefs USING [CacheEntry, FlushWholeCache, KickProber, msg],
  NetDirDefs,
  PupTypes USING [PupAddress],
  ServerHeap USING [CopyString, Node],
  Stats USING [StatCounterIndex, StatBump];

NetDirFileTajo: MONITOR
  IMPORTS
    Checksum, Inline, MFile, MSegment, MStream, Put, String, System,
    Time, Stats, NameServerDefs, ServerHeap
  EXPORTS NameServerDefs =
  BEGIN OPEN NetDirDefs;

  PupAddress: TYPE = PupTypes.PupAddress;
  CacheEntry: TYPE = NameServerDefs.CacheEntry;

  verbose: BOOLEAN = TRUE;
  useCount: CARDINAL ← 0;
  newVersion, oldVersion: CARDINAL ← 0;  -- 0 if none/unknown
  newInTransit, oldInTransit: BOOLEAN ← FALSE;

  oldFile, newFile: MFile.Handle ← NIL;
  seg: MSegment.Handle ← NIL;
  header: LONG POINTER TO NetDirDefs.NewHeader;
  first: LONG POINTER TO NetDirDefs.NewEntry;
  
  Entry: TYPE = LONG POINTER TO NetDirDefs.NewEntry;

  statMsScanningFile: PUBLIC Stats.StatCounterIndex;

  oldFileName: STRING = "Pup-Network.directory";
  newFileName: STRING = "Pup-Network.big";

  CruftyNetworkDirectoryFile: ERROR = CODE;

  GetStreamForOldDirectoryFile: PUBLIC ENTRY PROCEDURE RETURNS [Stream.Handle] =
    BEGIN
    IF oldFile = NIL THEN RETURN[NIL];
    RETURN[MStream.Create[MFile.CopyFileHandle[oldFile, []], []]];
    END;

  GetStreamForNewDirectoryFile: PUBLIC ENTRY PROCEDURE RETURNS [Stream.Handle] =
    BEGIN
    IF newFile = NIL THEN RETURN[NIL];
    RETURN[MStream.Create[MFile.CopyFileHandle[newFile, []], []]];
    END;

  CreateTempFile: PUBLIC PROCEDURE RETURNS [fh: MFile.Handle, sh: Stream.Handle] =
    BEGIN
    fh ← MFile.AcquireTemp[binary, 256*LONG[Environment.bytesPerPage]];
    sh ← MStream.Create[MFile.CopyFileHandle[fh, [], writeOnly], []];
    END;

  MakeTempFileIntoOldDirectoryFile: PUBLIC PROCEDURE [fh: MFile.Handle]
    RETURNS [ok: BOOLEAN] =
    BEGIN
    IF ~BlessOldDirectoryFile[fh, TRUE] THEN { MFile.Release[fh]; RETURN[FALSE]; };
    ok ← TRUE;
    MFile.SetAccess[fh, rename];
    IF oldFile = NIL THEN BEGIN MFile.Rename[fh, oldFileName]; END
    ELSE
      BEGIN
      old: MFile.Handle ← MFile.CopyFileHandle[oldFile, []];
      MFile.SetAccess[
        old, rename ! MFile.Error => BEGIN ok ← FALSE; CONTINUE; END];
      IF ok THEN MFile.SwapNames[fh, old];
      MFile.Release[old];
      END;
    MFile.Release[fh];
    END;

  MakeTempFileIntoNewDirectoryFile: PUBLIC PROCEDURE [fh: MFile.Handle]
    RETURNS [ok: BOOLEAN] =
    BEGIN
    IF ~BlessNewDirectoryFile[fh, TRUE] THEN { MFile.Release[fh]; RETURN[FALSE]; };
    ok ← TRUE;
    MFile.SetAccess[fh, rename];
    IF newFile = NIL THEN BEGIN MFile.Rename[fh, newFileName]; END
    ELSE
      BEGIN
      old: MFile.Handle ← MFile.CopyFileHandle[newFile, []];
      MFile.SetAccess[
        old, rename ! MFile.Error => BEGIN ok ← FALSE; CONTINUE; END];
      IF ok THEN MFile.SwapNames[fh, old];
      MFile.Release[old];
      END;
    MFile.Release[fh];
    END;

  DeleteTempFile: PUBLIC PROCEDURE [fh: MFile.Handle] =
    BEGIN MFile.Release[fh]; END;

  SearchNetDirForName: PUBLIC ENTRY PROCEDURE [key: LONG STRING, ce: CacheEntry]
    RETURNS [BOOLEAN] =
    BEGIN
    entry: LONG POINTER TO NetDirDefs.NewEntry ← first;
    pulses: System.Pulses ← System.GetClockPulses[];
    IF seg = NIL THEN RETURN[FALSE];
    FOR i: CARDINAL IN [0..header.numberOfEntries) DO
      name: LONG STRING ← FirstName[entry];
      FOR j: CARDINAL IN [0..entry.numberOfNames) DO
        IF String.Equivalent[key, name] THEN
          BEGIN CopyThingsFromFile[ce, entry]; GOTO Foundit; END;
	name ← NextName[name];
        ENDLOOP;
      entry ← entry + entry.words;
      REPEAT
        Foundit => NULL;
        FINISHED => CopyMissedName[ce, key];
      ENDLOOP;
    pulses ← System.Pulses[System.GetClockPulses[] - pulses];
    Stats.StatBump[  -- This might overflow 60 serconds
      statMsScanningFile, Inline.LowHalf[
      System.PulsesToMicroseconds[pulses]/1000]];
    RETURN[TRUE];
    END;

  SearchNetDirForAddress: PUBLIC ENTRY PROCEDURE [key: PupAddress, ce: CacheEntry]
    RETURNS [BOOLEAN] =
    BEGIN
    entry: LONG POINTER TO NetDirDefs.NewEntry ← first;
    pulses: System.Pulses ← System.GetClockPulses[];
    IF seg = NIL THEN RETURN[FALSE];
    FOR i: CARDINAL IN [0..header.numberOfEntries) DO
      aaddress: LONG POINTER TO PupAddress ← Address[entry];
      FOR j: CARDINAL IN [0..entry.numberOfAddresses) DO
        IF key = aaddress↑ THEN
          BEGIN CopyThingsFromFile[ce, entry]; GOTO Foundit; END;
	aaddress ← aaddress + SIZE[PupAddress];
        ENDLOOP;
      entry ← entry + entry.words;
      REPEAT
        Foundit => NULL;
        FINISHED => CopyMissedAddress[ce, key];
      ENDLOOP;
    pulses ← System.Pulses[System.GetClockPulses[] - pulses];
    Stats.StatBump[  -- This might overflow 60 serconds
      statMsScanningFile, Inline.LowHalf[
      System.PulsesToMicroseconds[pulses]/1000]];
    RETURN[TRUE];
    END;

  CopyThingsFromFile: PROCEDURE [ce: CacheEntry, entry: Entry] =
    BEGIN
    name: LONG STRING;
    words: CARDINAL;
    words ← SIZE[PupAddress, entry.numberOfAddresses];
    ce.size ← ce.size + words;
    ce.addrs ← DESCRIPTOR[ServerHeap.Node[words], entry.numberOfAddresses];
    Inline.LongCOPY[to: BASE[ce.addrs], from: Address[entry], nwords: words];
    words ← SIZE[LONG STRING, entry.numberOfNames];
    ce.size ← ce.size + words;
    ce.names ← DESCRIPTOR[ServerHeap.Node[words], entry.numberOfNames];
    name ← FirstName[entry];
    FOR i: CARDINAL IN [0..entry.numberOfNames) DO
      ce.size ← ce.size + String.WordsForString[name.length];
      ce.names[i] ← ServerHeap.CopyString[name];
      name ← NextName[name];
      ENDLOOP;
    END;

  CopyMissedName: PROCEDURE [ce: CacheEntry, key: LONG STRING] =
    BEGIN
    words: CARDINAL = SIZE[LONG STRING];
    ce.names ← DESCRIPTOR[ServerHeap.Node[words], 1];
    ce.names[0] ← ServerHeap.CopyString[key];
    ce.size ← ce.size + words + String.WordsForString[key.length];
    END;

  CopyMissedAddress: PROCEDURE [ce: CacheEntry, key: PupAddress] =
    BEGIN
    words: CARDINAL = SIZE[PupAddress];
    ce.addrs ← DESCRIPTOR[ServerHeap.Node[words], 1];
    ce.addrs[0] ← key;
    ce.size ← ce.size + words;
    END;


  FirstName: PROCEDURE [entry: Entry] RETURNS [name: LONG STRING] =
    BEGIN
    name ← LOOPHOLE[entry];
    name ← name + SIZE[NetDirDefs.NewEntry];
    END;

  NextName: PROCEDURE [name: LONG STRING] RETURNS [LONG STRING] =
    BEGIN
    RETURN[name + String.WordsForString[name.length]];
    END;

  Address: PROCEDURE [entry: Entry] RETURNS [address: LONG POINTER TO PupAddress] =
    BEGIN
    name: LONG STRING ← FirstName[entry];
    FOR i: CARDINAL IN [0..entry.numberOfNames) DO
      name ← NextName[name];
      ENDLOOP;
    address ← LOOPHOLE[name];
    END;

  -- These routines are actually called from PupDirServer

  GetOldDirectoryVersion: PUBLIC PROCEDURE RETURNS [CARDINAL, BOOLEAN] =
    BEGIN RETURN[oldVersion, oldInTransit]; END;

  GetNewDirectoryVersion: PUBLIC PROCEDURE RETURNS [CARDINAL, BOOLEAN] =
    BEGIN RETURN[newVersion, newInTransit]; END;

  OpenDirectoryFiles: PUBLIC ENTRY PROCEDURE =
    BEGIN
    IF (useCount ← useCount + 1) = 1 THEN
      BEGIN
      MFile.AddNotifyProc[InspectOld, [oldFileName, null, readOnly], NIL];
      MFile.AddNotifyProc[InspectNew, [newFileName, null, readOnly], NIL];
      MFile.AddNotifyProc[InspectOld, [oldFileName, null, writeOnly], NIL];
      MFile.AddNotifyProc[InspectNew, [newFileName, null, writeOnly], NIL];
      ResetDirectoryFiles[];
      END;
    END;

  CloseDirectoryFiles: PUBLIC ENTRY PROCEDURE =
    BEGIN
    IF useCount # 0 AND (useCount ← useCount - 1) = 0 THEN
      BEGIN
      MFile.RemoveNotifyProc[InspectOld, [oldFileName, null, readOnly], NIL];
      MFile.RemoveNotifyProc[InspectNew, [newFileName, null, readOnly], NIL];
      MFile.RemoveNotifyProc[InspectOld, [oldFileName, null, writeOnly], NIL];
      MFile.RemoveNotifyProc[InspectNew, [newFileName, null, writeOnly], NIL];
      IF seg # NIL THEN BEGIN MSegment.Delete[seg]; seg ← NIL; header ← NIL; END;
      IF oldFile # NIL THEN BEGIN MFile.Release[oldFile]; oldFile ← NIL; END;
      IF newFile # NIL THEN BEGIN MFile.Release[newFile]; newFile ← NIL; END;
      END;
    END;

  ResetDirectoryFiles: INTERNAL PROCEDURE =
    BEGIN
    ResetNewDirectoryFile[];
    ResetOldDirectoryFile[];
    END;
    
  ResetNewDirectoryFile: INTERNAL PROCEDURE =
    BEGIN
    newVersion ← 0;
    IF newFile = NIL THEN
      BEGIN
      newFile ← MFile.ReadOnly[newFileName, [ReleaseNewFile] ! MFile.Error => CONTINUE];
      IF newFile = NIL THEN RETURN;
      END;
    IF ~BlessNewDirectoryFile[newFile, FALSE] THEN RETURN;
    IF seg # NIL THEN BEGIN MSegment.Delete[seg]; seg ← NIL; END;
    seg ← MSegment.Create[MFile.CopyFileHandle[newFile, []], [ReleaseSeg], 0];
    header ← MSegment.Address[seg];
    first ← LOOPHOLE[header];
    first ← first + SIZE[NetDirDefs.NewHeader];
    newVersion ← header.version;
    IF verbose THEN
      BEGIN
      text: STRING = [75];
      Time.AppendCurrent[text];
      String.AppendString[text, "  Using "L];
      String.AppendString[text, newFileName];
      String.AppendString[text, "!"L];
      String.AppendDecimal[text, newVersion];
      LogString[text];
      END;
    END;

  ResetOldDirectoryFile: INTERNAL PROCEDURE =
    BEGIN
    seg: MSegment.Handle;
    header: LONG POINTER TO NetDirDefs.Header;
    oldVersion ← 0;
    IF oldFile = NIL THEN
      BEGIN
      oldFile ← MFile.ReadOnly[oldFileName, [ReleaseOldFile] ! MFile.Error => CONTINUE];
      IF oldFile = NIL THEN RETURN;
      END;
    IF ~BlessOldDirectoryFile[oldFile, FALSE] THEN RETURN;
    seg ← MSegment.Create[MFile.CopyFileHandle[oldFile, []], [ReleaseSeg], 0];
    header ← MSegment.Address[seg];
    oldVersion ← header.version;
    IF verbose THEN
      BEGIN
      text: STRING = [75];
      Time.AppendCurrent[text];
      String.AppendString[text, "  Using "L];
      String.AppendString[text, oldFileName];
      String.AppendString[text, "!"L];
      String.AppendDecimal[text, oldVersion];
      LogString[text];
      END;
    MSegment.Delete[seg];
    END;

  LogString: PROCEDURE [text: LONG STRING] =
    BEGIN
    String.AppendChar[text, '.];
    String.AppendChar[text, Ascii.CR];
    Put.Text[NIL, text];
    IF NameServerDefs.msg # NIL THEN Put.Text[NameServerDefs.msg, text];
    END;

  BlessOldDirectoryFile: PROCEDURE [file: MFile.Handle, new: BOOLEAN]
    RETURNS [ok: BOOLEAN] =
    BEGIN
    seg: MSegment.Handle;
    h: LONG POINTER TO Header;
    e: EntryBase;
    n: NameBase;
    a: AddrBase;
    at: AddrTableBase;
    nt: NameTableBase;
    bytes: LONG CARDINAL ← MFile.GetLength[file];
    message: STRING ← NIL;
    CheckPosition: PROCEDURE [offset: Offset] =
      BEGIN
      off: LONG CARDINAL = LONG[LOOPHOLE[offset, CARDINAL]];
      IF off > bytes THEN ERROR CruftyNetworkDirectoryFile;
      END;
    ok ← TRUE;
    seg ← MSegment.Create[MFile.CopyFileHandle[file, []], [NIL, NIL]];
    h ← MSegment.Address[seg];
    e ← LOOPHOLE[h];
    n ← LOOPHOLE[h];
    a ← LOOPHOLE[h];
    at ← LOOPHOLE[h];
    nt ← LOOPHOLE[h];
    BEGIN
    ENABLE CruftyNetworkDirectoryFile => GOTO Bad;
    message ← "File Too Short"L;
    IF bytes < 2*SIZE[Header] THEN ERROR CruftyNetworkDirectoryFile;
    message ← "File Too Long"L;
    IF bytes > 2*LONG[LAST[CARDINAL]] THEN ERROR CruftyNetworkDirectoryFile;
    message ← "Software Checksum Mismatch"L;
    IF ~TestFileChecksum[file] THEN ERROR CruftyNetworkDirectoryFile;
    message ← "Bad internal pointer"L;
    IF h.numberOfNames > maxNamesInFile THEN GOTO Bad;
    FOR i: CARDINAL IN [0..h.numberOfNames) DO
      CheckPosition[nt[h.nameLookupTable][i]]; ENDLOOP;
    IF h.numberOfAddrs > maxAddrsInFile THEN GOTO Bad;
    FOR i: CARDINAL IN [0..h.numberOfAddrs) DO
      CheckPosition[at[h.addrLookupTable][i]]; ENDLOOP;
    EXITS
      Bad =>
        BEGIN
        ok ← FALSE;
        IF verbose THEN
          BEGIN
          text: STRING = [100];
          Time.AppendCurrent[text];
          String.AppendString[text, "  "L];
          String.AppendString[text, IF new THEN "New "L ELSE "Old "L];
          String.AppendString[text, oldFileName];
          String.AppendString[text, " is crufty: "L];
          String.AppendString[text, message];
          LogString[text];
          END;
        END;
    END;
    MSegment.Delete[seg];
    END;

  BlessNewDirectoryFile: PROCEDURE [file: MFile.Handle, new: BOOLEAN]
    RETURNS [ok: BOOLEAN] =
    BEGIN
    seg: MSegment.Handle;
    header: LONG POINTER TO NewHeader;
    entry: LONG POINTER TO NewEntry;
    bytes: LONG CARDINAL ← MFile.GetLength[file];
    words: LONG CARDINAL = bytes/2;
    message: STRING ← NIL;
    CheckPosition: PROCEDURE [p: LONG POINTER] =
      BEGIN
      off: LONG CARDINAL ← LOOPHOLE[p];
      off ← off - LOOPHOLE[header, LONG CARDINAL];
      IF off > words THEN ERROR CruftyNetworkDirectoryFile;
      END;
    ok ← TRUE;
    seg ← MSegment.Create[MFile.CopyFileHandle[file, []], [NIL, NIL]];
    header ← MSegment.Address[seg];
    entry ← LOOPHOLE[header];
    entry ← entry + SIZE[NewHeader];
    BEGIN
    ENABLE CruftyNetworkDirectoryFile => GOTO Bad;
    message ← "File Too Short"L;
    IF bytes < 2*SIZE[Header] THEN ERROR CruftyNetworkDirectoryFile;
    message ← "File Too Long"L;
    message ← "Software Checksum Mismatch"L;
    IF ~TestFileChecksum[file] THEN ERROR CruftyNetworkDirectoryFile;
    message ← "Entry Too Long"L;
    FOR i: CARDINAL IN [0..header.numberOfEntries) DO
      name: LONG STRING;
      CheckPosition[entry];
      name ← FirstName[entry];
      FOR i: CARDINAL IN [0..entry.numberOfNames) DO
        CheckPosition[name];
	name ← NextName[name];
        ENDLOOP;
      entry ← entry + entry.words;
      ENDLOOP;
    EXITS
      Bad =>
        BEGIN
        ok ← FALSE;
        IF verbose THEN
          BEGIN
          text: STRING = [100];
          Time.AppendCurrent[text];
          String.AppendString[text, "  "L];
          String.AppendString[text, IF new THEN "New "L ELSE "Old "L];
          String.AppendString[text, newFileName];
          String.AppendString[text, " is crufty: "L];
          String.AppendString[text, message];
          LogString[text];
          END;
        END;
    END;
    MSegment.Delete[seg];
    END;

  TestFileChecksum: PROCEDURE [file: MFile.Handle] RETURNS [ok: BOOLEAN] =
    BEGIN
    bytes: LONG CARDINAL = MFile.GetLength[file];
    words: LONG CARDINAL;
    seg: MSegment.Handle;
    p: LONG POINTER;
    checksum: WORD ← 0;
    IF (bytes MOD 2) # 0 THEN RETURN[FALSE];
    words ← bytes/2;
    words ← words - 1; -- Checksum
    seg ← MSegment.Create[MFile.CopyFileHandle[file, []], [], 0];
    p ← MSegment.Address[seg];
    UNTIL words = 0 DO
      clump: CARDINAL = Inline.LowHalf[MIN[words, 10000]];
      checksum ← Checksum.ComputeChecksum[checksum, clump, p];
      p ← p + clump;
      words ← words - clump;
      ENDLOOP;
    ok ← p↑ = checksum;
    MSegment.Delete[seg];
    END;

  ReleaseOldFile: ENTRY PROCEDURE [file: MFile.Handle, instanceData: LONG POINTER]
    RETURNS [MFile.ReleaseChoice] =
    BEGIN
    oldFile ← NIL;
    oldInTransit ← TRUE;
    RETURN[goAhead];
    END;

  ReleaseNewFile: ENTRY PROCEDURE [file: MFile.Handle, instanceData: LONG POINTER]
    RETURNS [MFile.ReleaseChoice] =
    BEGIN
    newFile ← NIL;
    newInTransit ← TRUE;
    RETURN[goAhead];
    END;

  ReleaseSeg: ENTRY PROCEDURE [
    segment: MSegment.Handle, instanceData: LONG POINTER]
    RETURNS [MFile.ReleaseChoice] = BEGIN seg ← NIL; RETURN[goAhead]; END;

  InspectOld: PROCEDURE [
    name: LONG STRING, file: MFile.Handle, clientInstanceData: LONG POINTER]
    RETURNS [BOOLEAN] =
    BEGIN
    IF InspectOldLocked[] THEN NameServerDefs.KickProber[];
    RETURN[FALSE];
    END;

  InspectOldLocked: ENTRY PROCEDURE RETURNS [BOOLEAN] =
    BEGIN
    IF oldFile # NIL THEN
      BEGIN oldInTransit ← FALSE; RETURN[FALSE]; END;  -- duplicate supression
    ResetOldDirectoryFile[];
    oldInTransit ← FALSE;
    RETURN[TRUE];
    END;

  InspectNew: PROCEDURE [
    name: LONG STRING, file: MFile.Handle, clientInstanceData: LONG POINTER]
    RETURNS [BOOLEAN] =
    BEGIN
    IF InspectNewLocked[] THEN
      BEGIN
      IF newVersion # 0 THEN NameServerDefs.FlushWholeCache[];
      NameServerDefs.KickProber[];
      END;
    RETURN[FALSE];
    END;

  InspectNewLocked: ENTRY PROCEDURE RETURNS [BOOLEAN] =
    BEGIN
    IF newFile # NIL THEN
      BEGIN newInTransit ← FALSE; RETURN[FALSE]; END;  -- duplicate supression
    ResetNewDirectoryFile[];
    newInTransit ← FALSE;
    RETURN[TRUE];
    END;

  END.