-- VerifyDFImpl.Mesa, last edit February 4, 1983 11:36 am by Schmidt
-- last edit April 12, 1983 12:33 pm by Paul Rovner
-- Pilot 6.0/ Mesa 7.0
    
-- Usage:
-- VerifyDF dffile
--  where there is at least one "+" in the df file
--
-- or alternatively
-- VerifyDF bcdfile
--
-- or alternatively
-- VerifyDF dffile bcdfile
--
-- if no extension is present on the first argument,
-- assume it is a DF file
-- an optional /f after VerifyDF and before the args causes verifydf to print
-- the flattened DF file on Flattened.dffile.DF

-- note that calling any procedure in VerifyDFInterface will
-- cause a start trap that will register "VerifyDF"
-- it is ok to call this from the Simple Exec

DIRECTORY
BcdOps: TYPE USING [BcdBase],
Commander: TYPE USING[CommandProc, Register],
CWF: TYPE USING [SetCode, SetWriteProcedure, SWF1, SWF2, SWF3,
 WF0, WF1, WF2, WF3, WF4, WFCR],
DFSubr: TYPE USING [AllocateDFSeq, Criterion, DF, DFSeq, FlattenDF, FreeDFSeq, InsertCRs, LookupDF,
 NextDF, ReadInDir, SortByCap, SortByFileName, StripLongName, TooManyEntries, WriteOut],
Directory: TYPE USING [Error, Handle, ignore, Lookup, UpdateDates],
File: TYPE USING [Capability, nullCapability, read, Unknown],
FQ: TYPE USING[FileQuery, Result],
IFSFile: TYPE USING [CantOpen, Close, Error, FileHandle, GetTimes, UnableToLogin],
IO: TYPE USING[Handle, PutChar, PutF, rope, UserAborted, STREAM, UserAbort, ResetUserAbort, PutRope],
IOMisc: TYPE USING[AskUser],
LeafSubr: TYPE USING [Open, PrintLeafAccessFailure, PrintLeafProblem, RemoteMap, StopLeaf],
LongString: TYPE USING [AppendString, EquivalentString],
ProcBcds: TYPE USING [InnardsObject, InstallAddressesBcd, PrintDepends, ProcDep, ProcMod,
 ReadInSegmentsBcd, UnstallBcd],
Resource: TYPE USING[Acquire, Release, AbortProc],
Rope: TYPE USING[Fetch, Find, ROPE, Text, Cat],
RopeInline: TYPE USING[InlineFlatten],
Space: TYPE USING [Create, Delete, Handle, LongPointer, Map, nullHandle, Unmap,
 virtualMemory],
UnsafeSTP: TYPE USING [Error],
STPSubr: TYPE USING [StopSTP],
Stream: TYPE USING [Delete, Handle],
String: TYPE USING [AppendString],
Subr: TYPE USING [AbortMyself, Any, CheckForModify, CopyString, debugflg, EndsIn, errorflg,
 FileError, FreeString, GetCreateDateWithSpace, GetRemoteFilenameProp,
 MakeTTYProcs, NewStream, PackedTime, PrintGreeting,
 strcpy, SubrStop, TTYProcs, Write],
Time: TYPE USING [Current],
TimeStamp: TYPE USING[Null, Stamp],
UECP: TYPE USING[Argv, Parse],
VerifyDFInterface: TYPE USING [];

VerifyDFImpl: PROGRAM
IMPORTS Commander, CWF, DFSubr, Directory, File, FQ, IFSFile, IO,
 IOMisc, LeafSubr, LongString, ProcBcds, Resource, Rope, RopeInline,
 Space, STP: UnsafeSTP, STPSubr, Stream, String, Subr, Time, UECP
EXPORTS VerifyDFInterface = {

-- MDS usage!!
stdout: IO.Handle;
-- end of mds

MAXFILES: CARDINAL = 1200;
NPAGESINHUGEZONE: CARDINAL = 500;

abortProc: Resource.AbortProc = TRUSTED{
--PROC [data: REF ANY] RETURNS[abort: BOOL];
in: IO.STREAM = NARROW[data, IO.STREAM];
abort ← in.UserAbort[];
IF abort THEN in.ResetUserAbort[];
};

Main: Commander.CommandProc = TRUSTED {
--PROC [cmd: Handle]
ENABLE UNWIND => [] ← Resource.Release[$VerifyDF];
 h: Subr.TTYProcs;
 in: IO.Handle = cmd.in;
 out: IO.Handle = cmd.out;
 success: BOOLFALSE;
 otherOwner: Rope.ROPENIL;
 [success, otherOwner] ← Resource.Acquire[resource: $VerifyDF,
  owner: "VerifyDF",
  waitForIt: FALSE];
IF NOT success
THEN {
  out.PutRope[Rope.Cat["Waiting for ", otherOwner, " to finish..."]];
  [success, ] ← Resource.Acquire[resource: $VerifyDF,
  owner: "VerifyDF",
  waitForIt: TRUE,
  abortProc: abortProc,
  abortProcData: in
  ];
IF NOT success THEN {
  out.PutRope["ABORTED\n"];
RETURN;
  } ELSE out.PutRope["proceeding\n"];
  };

 h ← Subr.MakeTTYProcs[in, out, NIL, MyConfirm];
 VerifyDFUsingProcs[h, cmd.commandLine];
 [] ← Resource.Release[$VerifyDF];
 };

VerifyDFUsingProcs: PROC[h: Subr.TTYProcs, commandLine: Rope.ROPE] = {
 strdffilename: STRING ← [100];
 strbcdfilename: STRING ← [100];
 starttime: Subr.PackedTime;
 printFlattened: BOOLFALSE;
 checkForOverwrite: BOOLTRUE;
 argv: UECP.Argv ← UECP.Parse[commandLine];
dffilename, bcdfilename, firstname, tok: Rope.ROPE;
parm: CARDINAL;
flat: Rope.Text;

  Cleanup: PROC = {
  STPSubr.StopSTP[];
  LeafSubr.StopLeaf[];
  Subr.SubrStop[];
  };

 {
ENABLE {
  IFSFile.Error, IFSFile.UnableToLogin => {
   LeafSubr.PrintLeafProblem[reason];
   GOTO leave;
   };
  IFSFile.CantOpen => {
   LeafSubr.PrintLeafAccessFailure[reason];
   GOTO leave;
   };
  STP.Error => {
   CWF.WF0["FTP Error. "L];
   IF error ~= NIL THEN
    CWF.WF2["message: %s, code %u in Stp.Mesa\n"L, error, @code];
   GOTO leave;
   };
  Subr.AbortMyself, IO.UserAborted => {
   h.out.ResetUserAbort[];
   CWF.WF0["VerifyDF Aborted.\n"L];
   GOTO leave;
   };
  UNWIND => Cleanup[];
  };
  
-- set up WF stuff
 stdout ← h.out;
[] ← CWF.SetWriteProcedure[MyPutChar];
 Subr.debugflg ← Subr.errorflg ← FALSE;
 Subr.PrintGreeting["VerifyDF"L];
 starttime ← Time.Current[];
IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;

 parm ← 1;
DO
  IF parm >= argv.argc THEN GOTO usage;
  tok ← argv[parm];
  IF tok.Fetch[0] = '- OR tok.Fetch[0] = '/ THEN
   SELECT tok.Fetch[1] FROM
   'a, 'A =>
    checkForOverwrite ← FALSE;
   'f, 'F => {
    printFlattened ← TRUE;
    Subr.debugflg ← TRUE;
    };
   ENDCASE => h.out.PutF["Unknown flag '%s'\n", IO.rope[tok]]
  ELSE
   EXIT;
  parm ← parm + 1;
  ENDLOOP;
 firstname ← argv[parm];
 parm ← parm + 1;
IF Rope.Find[s1: firstname, s2: ".bcd", case: FALSE] > 0 THEN {
  bcdfilename ← firstname;
  dffilename ← NIL;
  }
ELSE {
  dffilename ← firstname;
  IF parm < argv.argc THEN
   bcdfilename ← argv[parm];
  };

 flat ← RopeInline.InlineFlatten[bcdfilename];
IF flat ~= NIL THEN Subr.strcpy[strbcdfilename, LOOPHOLE[flat]];
 flat ← RopeInline.InlineFlatten[dffilename];
IF flat ~= NIL THEN Subr.strcpy[strdffilename, LOOPHOLE[flat]];
-- bcdfilename and dffilename may be stored in!
 VerifyBcds[bcdfilename: strbcdfilename, dffilename: strdffilename, h: h,
  checkForOverwrite: checkForOverwrite, printFlattened: printFlattened,
  useHugeZone: TRUE, wantRTVersionID: 0
  ! DFSubr.TooManyEntries => {
   CWF.WF0["Error - there were too many entries in all the DF files that needed to be read in.\n"L];
   CWF.WF0["Try a smaller set of DF files.\n"L];
   Subr.errorflg ← TRUE;
   CONTINUE;
   }
  ];
IF Subr.errorflg THEN CWF.WF0["There were Error(s) in the DF file(s).\n"L];
 starttime ← Time.Current[] - starttime;
CWF.WF1["\nTotal elapsed time for VerifyDF %lr\n"L, @starttime];
EXITS
 leave => NULL;
 usage =>CWF.WF0["Usage: VerifyDF DFFileName BCDFileName or VerifyDF BCDFileName or VerifyDF DFFileName\n"L];
 };
 Cleanup[];
 };


-- assumes a HugeZone already exists and is available
-- if wantRTVersionID = 0 then simply checks agreement
VerifyBcds: PUBLIC PROC[bcdfilename, dffilename: LONG STRING,
  h: Subr.TTYProcs, checkForOverwrite, printFlattened, useHugeZone: BOOL,
  wantRTVersionID: CARDINAL] = {
 dfseq, dfseqother: DFSubr.DFSeq ← NIL;
 goaround: BOOL;
 ldspace: Space.Handle ← Space.nullHandle;

  Cleanup: PROC = {
  DFSubr.FreeDFSeq[@dfseq];
  DFSubr.FreeDFSeq[@dfseqother];
  IF ldspace ~= Space.nullHandle THEN Space.Delete[ldspace];
  ldspace ← Space.nullHandle;
  };
  
 {
ENABLE UNWIND => Cleanup[];
  
 dfseq ← DFSubr.AllocateDFSeq[maxEntries: MAXFILES,
  zoneType: IF useHugeZone THEN huge ELSE shared];
 dfseqother ← DFSubr.AllocateDFSeq[maxEntries: MAXFILES, zoneType: shared];
IF dffilename ~= NIL AND dffilename.length > 0 THEN {
  IF NOT Subr.Any[dffilename, '.] THEN
   LongString.AppendString[dffilename, ".DF"L];
  DFSubr.FlattenDF[dfseq: dfseq, dffilename: dffilename, h: h,
   setRecorder: TRUE, checkForOverwrite: checkForOverwrite,
   printStatus: Subr.debugflg];
  IF dfseq.size = 0 THEN GOTO leave;
  };
IF printFlattened THEN {
  sfn: STRING ← [100];
  root: STRING ← [100];
  Subr.strcpy[root, dffilename];
  IF Subr.EndsIn[root, ".df"L] THEN root.length ← root.length - 3;
  CWF.SWF1[sfn, "%s.Flat"L, root];
  IF Subr.CheckForModify[sfn, h] THEN {
   sh: Stream.Handle ← Subr.NewStream[sfn, Subr.Write];
   DFSubr.WriteOut[dfseq: dfseq, topLevelFile: NIL, outputStream: sh,
    print: FALSE, altoCompatibility: FALSE, wrapUsingLists: FALSE];
   Stream.Delete[sh];
   CWF.WF1["Flattened DF file written on %s.\n"L, sfn];
   };
  };
-- now analyze the BCD
-- the algorithm used is as follows:
-- initally all .need flags are FALSE, all .eval flags are false
--  for each file we discover, the corresponding .need flag is set to TRUE
--  on the nxt goround, a file is analyzed if .need is TRUE and .eval is FALSE
--  after analysis, the .eval flag is set to TRUE
 goaround ← TRUE;
 ldspace ← Space.Create[size: 1, parent: Space.virtualMemory];
 Space.Map[ldspace];
IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
IF bcdfilename ~= NIL AND bcdfilename.length > 0 THEN {
  wantRTVersionID ← AnalBcd[bcdfilename, dfseq, dfseqother, h, ldspace, wantRTVersionID];
  IF dffilename.length = 0 THEN {
   dfdep: DFSubr.DF ← DFSubr.NextDF[dfseqother]; -- add top-level .Bcd to needed list
   dfdep.shortname ← Subr.CopyString[bcdfilename, dfseqother.dfzone];
   };
  }
ELSE {
  anal: BOOLFALSE;
  df: DFSubr.DF;
  FOR i: CARDINAL IN [0 .. dfseq.size) DO
   IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
   df ← @dfseq[i];
   IF df.topmark AND Subr.EndsIn[df.shortname, ".bcd"L]THEN {
    wantRTVersionID ← AnalBcd[df.shortname, dfseq, dfseqother,
     h, ldspace, wantRTVersionID];
    anal ← TRUE;
    };
   ENDLOOP;
  IF NOT anal THEN {
   CWF.WF1["Error - no '+' entries in %s.\n"L, dffilename];
   Subr.errorflg ← TRUE;
   RETURN;
   };
  };
  
WHILE goaround DO
  goaround ← FALSE;
  FOR i: CARDINAL IN [0.. dfseq.size) DO
   IF NOT dfseq[i].eval AND dfseq[i].need THEN {
    IF Subr.EndsIn[dfseq[i].shortname, ".bcd"L]
    OR NOT Subr.Any[dfseq[i].shortname, '.] THEN
     wantRTVersionID ← AnalBcd[dfseq[i].shortname, dfseq, dfseqother,
      h, ldspace, wantRTVersionID];
    dfseq[i].eval ← TRUE;
    goaround ← TRUE;
    IF h.in.UserAbort[] THEN
     SIGNAL Subr.AbortMyself;
    };
   ENDLOOP;
  ENDLOOP;
 CheckReadOnlyFiles[dfseq, h, ldspace];
-- also prints unused entries
 CheckForCommonBlunders[dfseq];
-- now print out extra missing entries
IF dfseqother.size > 0 THEN {
  shortname: STRING ← [100];
  miss: STRING ← [100];
  CWF.WF0["Missing Entries:\n"L];
  [] ← DFSubr.StripLongName[dffilename, NIL, NIL, shortname];
  IF shortname.length > 0 THEN
   CWF.SWF1[miss, "MissingEntries.%s$"L, shortname]
  ELSE Subr.strcpy[miss, "MissingEntries.DF$"L];
  IF Subr.CheckForModify[miss, h] THEN {
   sh: Stream.Handle;
   DFSubr.SortByCap[dfseqother, 0, dfseqother.size-1];
   FillInCreateDates[dfseqother, ldspace, h];
   DFSubr.SortByFileName[dfseqother, 0, dfseqother.size-1, FALSE];
   DFSubr.InsertCRs[dfseqother];
   sh ← Subr.NewStream[miss, Subr.Write];
   DFSubr.WriteOut[dfseq: dfseqother, topLevelFile: NIL,
    outputStream: sh, print: TRUE];
   Stream.Delete[sh];
   CWF.WF1["List written on %s.\n"L, miss];
   };
  }
ELSE CWF.WF0["\nNo files were omitted from the DF files.\n"L];
EXITS
 leave => NULL;
 };
 Cleanup[];
 };

-- Analyze Bcd
AnalBcd: PROC[startsfn: LONG STRING, dfseq, dfseqother: DFSubr.DFSeq,
  h: Subr.TTYProcs, ldspace: Space.Handle, wantRTVersionID: CARDINAL]
  RETURNS[newRTVersionID: CARDINAL] = {
 dftop: DFSubr.DF;
 innardsobject: ProcBcds.InnardsObject ← [bcdheaderspace: Space.nullHandle];
 sfn: STRING ← [100];
 skip: BOOLFALSE;

  -- versionstamp rule:
  -- versionstamp is set by procMod
  -- or first call on procDep, subsequent calls to procDep
  -- do not change it
  
  -- dftop, sfn passed from the open
  procMod: ProcBcds.ProcMod = {
  uns ← NIL;
  -- check RTBcd.VersionID
  IF rtVersionID > 0 THEN {
   IF wantRTVersionID = 0 THEN newRTVersionID ← rtVersionID
   ELSE newRTVersionID ← wantRTVersionID;
   IF rtVersionID ~= newRTVersionID THEN
    CWF.WF1["Warning - there are two different RTBcd version stamps (RTBcd.VersionID) mentioned in this DF file. One of the files involved is %s.\n"L,
     sfn];
   };
   IF dftop ~= NIL THEN {
   dftop.eval ← TRUE;
   IF dftop.versionstamp ~= TimeStamp.Null AND dftop.versionstamp ~= bcdvers THEN {
    skip ← TRUE;
    CWF.WF4["Error - the version of %s listed in the DF file is stamped %a, but %s wants it to be of %a.\n"L,
     dftop.shortname, @bcdvers,
     dftop.recorder, @dftop.versionstamp];
    Subr.errorflg ← TRUE;
    };
   dftop.versionstamp ← bcdvers;
   Subr.FreeString[dftop.recorder, dfseq.dfzone];
   dftop.recorder ← Subr.CopyString[dftop.shortname, dfseq.dfzone];
   };
  IF (dftop = NIL OR NOT dftop.readonly) AND sourcefile.length > 0 THEN {   
   -- if not readonly, ask about sourcefile
   dfsrc: DFSubr.DF;
   dfsrc ← DFSubr.LookupDF[dfseq, sourcefile];
   IF dfsrc = NIL AND DFSubr.LookupDF[dfseqother, sourcefile] = NIL
   THEN {
    CWF.WF3["Error - %s is the source file for %s\n\tbut %s is not in the df file(s).\n"L,
     sourcefile, sfn, sourcefile];
    dfsrc ← DFSubr.NextDF[dfseqother];
    dfsrc.shortname ← Subr.CopyString[sourcefile,
     dfseqother.dfzone];
    dfsrc.createtime ← sourcevers.time;
    Subr.errorflg ← TRUE;
    };
   IF dfsrc.createtime > 0 AND dfsrc.createtime ~= sourcevers.time THEN {
    CWF.WF4["Error - %s of %lt is the source file for %s,\n\tbut the DF file lists version of %lt.\n"L,
     dfsrc.shortname, @sourcevers.time, sfn, @dfsrc.createtime];
    Subr.errorflg ← TRUE;
    };
   dfsrc.eval ← TRUE;
   };
  };
  
  -- filename will probably have ".bcd" at the end
  -- look at otherdepends, canignore, and defstype only
  -- sfn, dfseq, dfseqother, skip, and dftop passed in from outside
  procDep: ProcBcds.ProcDep = {
  dfdep: DFSubr.DF;
  IF skip OR filename.length = 0 THEN RETURN;
  IF relcode ~= otherdepends AND relcode ~= canignore
   AND relcode ~= defstype THEN RETURN;
  IF dfseq.size = 0
  OR (dfdep ← DFSubr.LookupDF[dfseq, filename]) = NIL THEN {
   IF DFSubr.LookupDF[dfseqother, filename] = NIL THEN {
    CWF.WF2["Error - %s is not in the DF file(s),\n\tbut %s depends on it.\n"L,
     filename, sfn];
    Subr.errorflg ← TRUE;
    dfdep ← DFSubr.NextDF[dfseqother];
    dfdep.shortname ← Subr.CopyString[filename,
     dfseqother.dfzone];
    dfdep.versionstamp ← bcdvers;
    };
   }
  ELSE {
   IF NOT dfdep.need THEN {
    -- forces analysis only if not READONLY
    IF dftop ~= NIL THEN dfdep.need ← NOT dftop.readonly
    ELSE dfdep.need ← TRUE;
    };
   --
   -- check its the right version
   IF bcdvers = TimeStamp.Null
   OR LongString.EquivalentString[filename, sfn] THEN
    RETURN;
   IF dfdep.versionstamp = TimeStamp.Null THEN {
    dfdep.versionstamp ← bcdvers;
    -- enter who recorded this entry
    Subr.FreeString[dfdep.recorder, dfseq.dfzone];
    dfdep.recorder ← Subr.CopyString[sfn, dfseq.dfzone];
    };
   IF bcdvers # dfdep.versionstamp AND dfdep.recorder ~= NIL THEN {
    IF LongString.EquivalentString[dfdep.shortname, dfdep.recorder] THEN
     CWF.WF4["Error - %s is stamped %a\n but %s wants it to be %a.\n"L,
      dfdep.shortname, @dfdep.versionstamp,
      sfn, @bcdvers]
    ELSE {
     CWF.WF4["Error - %s wants %s of %a\n but %s"L,
      sfn, dfdep.shortname, @bcdvers,
      dfdep.recorder];
     CWF.WF1[" wants it to be %a.\n"L, @dfdep.versionstamp];
     };
    Subr.errorflg ← TRUE;
    };
   };
  };

 {
ENABLE {
  UNWIND => {
   IF innardsobject.fh ~= NIL THEN IFSFile.Close[innardsobject.fh];
   innardsobject.fh ← NIL;
   };
  File.Unknown => {
   CWF.WF1["ERROR File.Unknown - problem reading %s.\n"L, sfn];
   GOTO err;
   };
  };
  
  
 success: BOOL;
 newRTVersionID ← wantRTVersionID;
 Subr.strcpy[sfn, startsfn];
IF NOT Subr.Any[sfn, '.] THEN String.AppendString[sfn, ".Bcd"L];
 dftop ← DFSubr.LookupDF[dfseq, sfn];
IF dftop ~= NIL AND dftop.readonly THEN RETURN; -- don't analyze readonly things
IF dftop ~= NIL THEN
  [innardsobject.cap, innardsobject.fh] ← CachedBcd[dftop, h, ldspace
    ! Subr.FileError => {
    IF error = wrongVersion THEN
     CWF.WF2[" File %s of %lt cannot be found.\n"L, sfn, @dftop.createtime]
    ELSE CWF.WF1[" File %s cannot be found.\n"L, sfn];
    GOTO err
    };
    IFSFile.UnableToLogin =>
     IF reason = io THEN {
     CWF.WF1["Unable to analyze - no Leaf server on %s.\n"L,
      dftop.host];
     GOTO err;
     };
   ]
ELSE {
  innardsobject.cap ← Directory.Lookup[fileName: sfn,
   permissions: Directory.ignore
  ! Directory.Error => {
   CWF.WF1[" File %s cannot be fourd.\n"L, sfn];
   GOTO err
   }];
  CWF.WF1["Analyzing %s."L, sfn];
  };
CWF.WFCR[];
 ProcBcds.ReadInSegmentsBcd[@innardsobject];
 ProcBcds.InstallAddressesBcd[@innardsobject];
 [success] ← ProcBcds.PrintDepends[innards: @innardsobject,
  procMod: procMod, procDep: procDep,
  print: FALSE, calltwice: FALSE, less: TRUE, bcdfilename: sfn];
IF NOT success THEN {
  CWF.WF1["Error - couldn't analyze %s correctly.\n"L, sfn];
  Subr.errorflg ← TRUE;
  };
IF innardsobject.fh ~= NIL THEN {
  IFSFile.Close[innardsobject.fh];
  innardsobject.fh ← NIL;
  };
 ProcBcds.UnstallBcd[@innardsobject];
EXITS
 err =>  Subr.errorflg ← TRUE;
 }};

-- if fh is NIL, is local, otherwise remote
CachedBcd: PROC[df: DFSubr.DF, h: Subr.TTYProcs, ldspace: Space.Handle]
  RETURNS[cap: File.Capability, fh: IFSFile.FileHandle] = {
 localcreatetime: LONG CARDINAL;
 fh ← NIL;
 cap ← File.nullCapability;
 {
 cap ← Directory.Lookup[fileName: df.shortname, permissions: Directory.ignore
  ! Directory.Error => GOTO notonlocal];
 localcreatetime ← Subr.GetCreateDateWithSpace[cap, ldspace];
IF localcreatetime = df.createtime OR (df.createtime = 0 AND df.host = NIL) THEN {
  CWF.WF1["Analyzing %s."L, df.shortname];
  cap ← Directory.UpdateDates[cap, File.read];
  RETURN[cap, NIL];
  };
EXITS
 notonlocal => NULL;
 };
IF LongString.EquivalentString[df.host, "Unknown"L] THEN
  ERROR Subr.FileError[notFound];
CWF.WF3["Analyzing [%s]<%s>%s."L, df.host, df.directory, df.shortname];
 fh ← LeafOpenWithCreate[df.host, df.directory, df.shortname, df.version,
  df.createtime, df.criterion, h]; -- may raise Subr.FileError
 };


-- looks on remote server for file with createtime
-- enumerates if necessary
-- may raise Subr.FileError
LeafOpenWithCreate: PROC[host, directory, shortname: LONG STRING,
 version: CARDINAL, createtime: LONG CARDINAL, criterion: DFSubr.Criterion,
 tty: Subr.TTYProcs] RETURNS[fh: IFSFile.FileHandle] =
 {
 sfn: STRING ← [100];
  
IF version > 0 AND criterion = none AND createtime = 0 THEN
  CWF.SWF3[sfn, "<%s>%s!%u"L, directory, shortname, @version]
ELSE
  CWF.SWF2[sfn, "<%s>%s"L, directory, shortname]; -- this gets !H
 fh ← LeafSubr.Open[host, sfn, tty, oldReadOnly];
IF createtime ~= 0 AND IFSFile.GetTimes[fh].create ~= createtime THEN {
  fres: FQ.Result;
  remoteVersion: CARDINAL;
  targetFileName: STRING ← [125];
  IFSFile.Close[fh];
  [fres: fres, remoteVersion: remoteVersion] ← FQ.FileQuery[host, directory,
   shortname, version, createtime, FALSE, tty, targetFileName];
  SELECT fres FROM
  foundCorrectVersion => {
   CWF.SWF3[sfn, "<%s>%s!%u"L, directory, shortname, @remoteVersion];
   fh ← LeafSubr.Open[host, sfn, tty, oldReadOnly];
   };
  foundWrongVersion =>
   ERROR Subr.FileError[wrongVersion];
  notFound =>
   ERROR Subr.FileError[notFound];
  ENDCASE => ERROR;
  };
 };

-- look at ReadOnly files
-- see if their create times agree with their functional stamps
CheckReadOnlyFiles: PROC[dfseq: DFSubr.DFSeq, h: Subr.TTYProcs, ldspace: Space.Handle] = {
 space: Space.Handle ← Space.Create[size: 1, parent: Space.virtualMemory];
 fh: IFSFile.FileHandle;
 bcd: BcdOps.BcdBase;
 df: DFSubr.DF;
 cap: File.Capability;
 bcd ← Space.LongPointer[space];
CWF.WF0["(Examining ReadOnly files.)\n"L];
FOR i: CARDINAL IN [0 .. dfseq.size) DO
  IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
  df ← @dfseq[i];
  IF df.readonly
  AND Subr.EndsIn[df.shortname, "bcd"L]
  AND df.versionstamp ~= TimeStamp.Null THEN {
   ENABLE File.Unknown => {
    CWF.WF1["ERROR File.Unknown - problem reading %s.\n"L, df.shortname];
    LOOP;
    };
   df.eval ← TRUE;
   [cap, fh] ← CachedBcd[df, h, ldspace
    ! Subr.FileError => {
     IF error = wrongVersion THEN
      CWF.WF2[" File %s of %lt cannot be fourd.\n"L,
       df.shortname, @df.createtime]
     ELSE CWF.WF1[" File %s cannot be fourd.\n"L, df.shortname];
     Subr.errorflg ← TRUE;
     LOOP;
     };
     IFSFile.UnableToLogin =>
      IF reason = io THEN {
      CWF.WF1["Unable to analyze - no Leaf server on %s.\n"L, df.host];
      LOOP;
      };
    ];
   CWF.WFCR[];
   IF fh = NIL THEN
    Space.Map[space, [cap, 1]]
   ELSE LeafSubr.RemoteMap[space, fh, 0];
   IF bcd.version ~= df.versionstamp THEN {
    CWF.WF3["Error - %s of %lt has a version stamp of %a,\n"L,
     df.shortname, @df.createtime, @bcd.version];
    CWF.WF2["\tbut %s wants one with version stamp %a.\n"L,
     df.recorder, @df.versionstamp];
    Subr.errorflg ← TRUE;
    };
   Space.Unmap[space];
   IF fh ~= NIL THEN IFSFile.Close[fh];
   };
  ENDLOOP;
 Space.Delete[space];
 };

-- fill in create dates for entries where we know the version stamp but
-- don't know their create dates; involves looking on the local disk
FillInCreateDates: PROC[dfseq: DFSubr.DFSeq, ldspace: Space.Handle, h: Subr.TTYProcs] = {
 df: DFSubr.DF;
 bcd: BcdOps.BcdBase;
 space: Space.Handle ← Space.Create[size: 1, parent: Space.virtualMemory];
 host: STRING ← [30];
 directory: STRING ← [100];
 fullname: STRING ← [125];
 short: STRING ← [100];

  FillDefs: PROC = {
  IF df.host = NIL THEN
   df.host ← Subr.CopyString["Unknown"L, dfseq.dfzone];
  IF df.directory = NIL THEN
   df.directory ← Subr.CopyString["Unknown"L, dfseq.dfzone];
  };

CWF.WF0["(Filling in create dates from files on local disk... "L];
 DFSubr.ReadInDir[dfseq];
FOR i: CARDINAL IN [0 .. dfseq.size) DO
  {
  IF h.in.UserAbort[] THEN SIGNAL Subr.AbortMyself;
  df ← @dfseq[i];
  IF NOT df.presentonlocaldisk THEN GOTO next;
  IF Subr.EndsIn[df.shortname, "bcd"L] AND df.createtime = 0
   AND df.versionstamp ~= TimeStamp.Null THEN {
   Space.Map[space, [df.cap, 1]];
   bcd ← Space.LongPointer[space];
   -- this is the bcd version stamp
   IF df.versionstamp = bcd.version THEN {
    df.createtime ← Subr.GetCreateDateWithSpace[df.cap, ldspace];
    Subr.GetRemoteFilenameProp[df.cap, fullname];
    IF fullname.length > 0 THEN {
     [] ← DFSubr.StripLongName[fullname, host,
      directory, short];
     IF LongString.EquivalentString[short,
     df.shortname] AND host.length > 0
     AND directory.length > 0
     THEN {
      IF df.host ~= NIL THEN
       Subr.FreeString[df.host, dfseq.dfzone];
      df.host ← Subr.CopyString[host, dfseq.dfzone];
      IF df.directory ~= NIL THEN
       Subr.FreeString[df.directory, dfseq.dfzone];
      df.directory ← Subr.CopyString[directory, dfseq.dfzone];
      Space.Unmap[space];
      LOOP;  -- avoid FillDefs
      }
     };
    };
   Space.Unmap[space];
   }
  ELSE IF df.createtime ~= 0
  AND df.createtime = Subr.GetCreateDateWithSpace[df.cap, ldspace] THEN {
   Subr.GetRemoteFilenameProp[df.cap, fullname];
   IF fullname.length > 0 THEN {
    [] ← DFSubr.StripLongName[fullname, host,
     directory, short];
    IF LongString.EquivalentString[short, df.shortname]
    AND host.length > 0 AND directory.length > 0 THEN {
     IF df.host ~= NIL THEN
      Subr.FreeString[df.host, dfseq.dfzone];
     df.host ← Subr.CopyString[host, dfseq.dfzone];
     IF df.directory ~= NIL THEN
      Subr.FreeString[df.directory, dfseq.dfzone];
     df.directory ← Subr.CopyString[directory, dfseq.dfzone];
     LOOP;  -- avoid FillDefs
     }
    };
   };
  GOTO next;
  EXITS
  next => FillDefs[];
  };
  ENDLOOP;
 Space.Delete[space];
CWF.WF0["done.)\n"L];
 };

CheckForCommonBlunders: PROC[dfseq: DFSubr.DFSeq] = {
 df: DFSubr.DF;
 p: BOOL;
-- check various features of the DF file
 p ← FALSE;
-- Directory XXX
FOR i: CARDINAL IN [0 .. dfseq.size) DO
  df ← @dfseq[i];
  IF NOT df.readonly AND df.releaseDirectory = NIL THEN {
   IF NOT p THEN {
    CWF.WF2["Warning - these files are in directories that are not ReadOnly and have no ReleaseAs or CameFrom clause.\n\t%s in %s"L,
     df.shortname, df.recorder];
    p ← TRUE;
    }
   ELSE
    CWF.WF2[", %s in %s"L, df.shortname, df.recorder];
   };
  ENDLOOP;
IF p THEN CWF.WFCR[];
 p ← FALSE;
-- ReadOnly XXX ReleaseAs YYY
-- doesn't work because Imports look like
-- ReadOnly XXX ReleaseAs YYY
-- FOR i: CARDINAL IN [0 .. dfseq.size) DO
  -- df ← @dfseq[i];
  -- IF df.readonly AND df.releaseDirectory ~= NIL AND NOT df.cameFrom THEN {
   -- IF NOT p THEN {
    -- CWF.WF2["Warning - these files are in directories that are ReadOnly and also have ReleaseAs clauses.\n\t%s in %s"L,
     -- df.shortname, df.recorder];
    -- p ← TRUE;
    -- }
   -- ELSE
    -- CWF.WF2[", %s in %s"L, df.shortname, df.recorder];
   -- };
  -- ENDLOOP;
-- IF p THEN CWF.WFCR[];
 p ← FALSE;
-- ReleaseAs Cedar>Top>X.DF
FOR i: CARDINAL IN [0 .. dfseq.size) DO
  df ← @dfseq[i];
  IF NOT df.cameFrom
  AND df.releaseDirectory ~= NIL
  AND (Count[df.releaseDirectory, '>] > 1
    OR Subr.EndsIn[df.releaseDirectory, ".df"L]) THEN {
   IF NOT p THEN {
    CWF.WF2["Warning - the ReleaseAs clauses for these files may be incorrect.\n\t%s in %s"L,
     df.shortname, df.recorder];
    p ← TRUE;
    }
   ELSE
    CWF.WF2[", %s in %s"L, df.shortname, df.recorder];
   };
  ENDLOOP;
IF p THEN CWF.WFCR[];
 p ← FALSE;
-- print out warnings about wrong RTBcd version stamps
FOR i: CARDINAL IN [0 .. dfseq.size) DO
  df ← @dfseq[i];
  IF NOT df.eval
  AND (Subr.EndsIn[df.shortname, ".Mesa"L]
   OR Subr.EndsIn[df.shortname, ".Bcd"L]
   OR Subr.EndsIn[df.shortname, ".Config"L])
  AND NotDuplicated[dfseq, i] THEN {
    IF NOT p THEN {
    CWF.WF2["Warning - these files are not needed by any top-level bcd files.\n\t%s in %s"L,
     df.shortname, df.recorder];
    p ← TRUE;
    }
   ELSE
    CWF.WF2[", %s in %s"L, df.shortname, df.recorder];
   };
  ENDLOOP;
IF p THEN CWF.WFCR[];
 };

NotDuplicated: PROC[dfseq: DFSubr.DFSeq, i: CARDINAL] RETURNS[BOOL] = {
 dfi, dfj: DFSubr.DF;
 dfi ← @dfseq[i];
IF dfi.createtime = 0 THEN RETURN[TRUE];
FOR j: CARDINAL IN [0 .. i) DO
  dfj ← @dfseq[j];
  IF dfj.eval
  AND dfj.createtime = dfi.createtime
  AND LongString.EquivalentString[dfj.shortname, dfi.shortname] THEN
   RETURN[FALSE];
  ENDLOOP;
RETURN[TRUE];
 };

Count: PROC[s: LONG STRING, c: CHAR] RETURNS[nOcurrences: CARDINAL] = {
 nOcurrences ← 0;
FOR i: CARDINAL IN [0 .. s.length) DO
  IF s[i] = c THEN
   nOcurrences ← nOcurrences + 1;
  ENDLOOP;
 };

CWFAProc: PROC[lp: LONG POINTER, fmt: LONG STRING, wp: PROC[CHARACTER]] = {
 pts: LONG POINTER TO TimeStamp.Stamp ← lp;
 hex: PACKED ARRAY [0 .. 12) OF [0 .. 16) ← LOOPHOLE[pts^];
FOR i: CARDINAL IN [0 .. 12) DO
  IF hex[i] IN [0 .. 9] THEN
   wp['0 + hex[i]]
  ELSE
   wp['A + (hex[i] - 10)];
  ENDLOOP;
 };

MyConfirm: PROC[in, out: IO.Handle, data: REF ANY, msg: Rope.ROPE, dch: CHAR]
RETURNS[CHAR] = {
 value: ATOM;
 value ← IOMisc.AskUser[msg: msg, in: in, out: out,
keyList: LIST[$Yes, $No, $All, $Quit]]; -- order is important
SELECT value FROM
 $All => RETURN['a];
 $No => RETURN['n];
 $Quit => RETURN['q];
 $Yes => RETURN['y];
ENDCASE => ERROR;
 };

MyPutChar: PROC[ch: CHAR] = {
 stdout.PutChar[ch];
 };

-- start code
Commander.Register[key: "VerifyDF", proc: Main, doc: "verify consistency and completeness of a .df file"];
CWF.SetCode['a, CWFAProc];
}.