-- FindBadModules.mesa  
-- Last modified by Sandman on 24-Feb-83 11:11:29

DIRECTORY
  BcdDefs,
  BcdOps,
  Environment,
  Exec,
  FileName,
  FileTransfer,
  Format,
  MSegment USING [Address, Create, Delete, Handle],
  MStream,
  PrincOps,
  Stream;

FindBadModules: PROGRAM
    IMPORTS Exec, FileName, FileTransfer, Format, MSegment, MStream, Stream =
  BEGIN
  
  BYTE: TYPE = PrincOps.BYTE;
  PrefixHandle: TYPE = PrincOps.PrefixHandle;
  BcdBase: TYPE = BcdOps.BcdBase;
  
  
  pagesPerBuffer: CARDINAL = 100;
  buffer: LONG POINTER;
  conn: FileTransfer.Connection ← NIL;
  execOut: Format.StringProc;
  exec: Exec.Handle;
  
  Run: Exec.ExecProc = {
    name, switches: LONG STRING ← NIL;
    pagesPerBuffer: Environment.PageCount = 100;
    bufferSegment: MSegment.Handle ← NIL;
    Finalize: PROC = {
      IF bufferSegment # NIL THEN {
        MSegment.Delete[bufferSegment];  bufferSegment ← buffer ← NIL};
      IF conn # NIL THEN {
        FileTransfer.Close[conn];  FileTransfer.Destroy[conn];  conn ← NIL};
      IF name # NIL THEN name ← Exec.FreeTokenString[name];
      IF switches # NIL THEN switches ← Exec.FreeTokenString[switches]};
    BEGIN
        ENABLE {
      ABORTED => GO TO aborted; 
      FileTransfer.Error --[code]-- => 
        SELECT code FROM
	  retry   => GOTO timedOut;
	  unknown => GOTO fileTransferProblem;
	  ENDCASE;
      UNWIND => Finalize[]};
    exec ← h;
    execOut ← Exec.OutputProc[h];
    bufferSegment ← MSegment.Create[pages: pagesPerBuffer, release: []];
    buffer ← MSegment.Address[bufferSegment];
    conn ← FileTransfer.Create[];
    FileTransfer.SetProcs[
      conn: conn, clientData: NIL, messages: PutMessages];
    DO
      [name, switches] ← Exec.GetToken[h];
      switches ← Exec.FreeTokenString[switches];
      IF name = NIL THEN EXIT;
      Collect[name];
      IF Exec.CheckForAbort[h] THEN {outcome ← abort; EXIT};
      ENDLOOP;
    outcome ← normal;
    EXITS
      aborted => {
        outcome ← abort;
        Format.CR[execOut];  Format.Line[execOut, "...aborted"L]};  
      timedOut => {
        outcome ← error;
        Format.CR[execOut];  Format.Line[execOut, "...connection timed out!"L]};  
      fileTransferProblem => {
        outcome ← error;
        Format.CR[execOut];
	Format.Line[execOut, "...unknown FileTransfer problem!"L]};  
    END; -- of ENABLE
    Finalize[]};
      
  PutMessages: FileTransfer.MessageProc = {
    IF level = fatal THEN { 
      execOut["Fatal error: "L];
      IF s1 # NIL THEN execOut[s1];
      IF s2 # NIL THEN execOut[s2];
      IF s3 # NIL THEN execOut[s3];
      IF s4 # NIL THEN execOut[s4]}};
    
  Collect: PROC [name: LONG STRING] = {
    OPEN FileTransfer;
    ENABLE Error => IF code = skip THEN CONTINUE;
    vfn: FileName.VFN ← FileName.AllocVFN[name];
    stream: Stream.Handle;
    stream ← ReadStream[conn, vfn ! UNWIND => FileName.FreeVFN[vfn]];
    WHILE stream # NIL DO
      ENABLE UNWIND => FileName.FreeVFN[vfn];
      stream.options.signalEndOfStream ← TRUE;
      Format.Char[execOut, '.];
      CheckFile[stream ! BadFile => {
        Format.Text[execOut, "\n*** "L];
	Format.Line[execOut, FileTransfer.GetStreamName[stream]];
	CONTINUE}];
      stream ← ReadNextStream[stream ! Error => IF code = skip THEN CONTINUE]
      ENDLOOP;
    FileName.FreeVFN[vfn]};
    
  CheckFile: PROC [stream: Stream.Handle] = {
    tooLong: BOOLEAN ← TRUE;
    bcd: BcdOps.BcdBase ← buffer;
    mtb, sgb: BcdDefs.Base;
    codebase: PrincOps.PrefixHandle;
    mth: BcdOps.MTHandle;
    sgh: BcdOps.SGHandle;
    IF Exec.CheckForAbort[exec] THEN ERROR ABORTED;
    [] ← Stream.GetBlock[
      stream, [buffer, 0, pagesPerBuffer*Environment.bytesPerPage] 
      ! Stream.EndOfStream => {tooLong ← FALSE;  CONTINUE}];
    IF ~tooLong THEN {
      bcd ← LOOPHOLE[buffer, BcdOps.BcdBase];
      IF bcd.versionIdent # BcdDefs.VersionID THEN GOTO obsoleteBcd;
      IF bcd.definitions THEN GOTO definitions;
      IF bcd.tableCompiled THEN GOTO tableCompiled;
      IF bcd.nConfigs # 0 THEN GOTO binderBcd;
      IF bcd.nPages > pagesPerBuffer THEN GOTO tooLong;
      mtb ← LOOPHOLE[bcd + bcd.mtOffset];
      mth ← @mtb[FIRST[BcdDefs.MTIndex]];
      sgb ← LOOPHOLE[bcd + bcd.sgOffset];
      sgh ← @sgb[mth.code.sgi];  -- Bcd's code segment table entry
      IF sgh.pages + sgh.base > pagesPerBuffer THEN GOTO tooLong;
      IF sgh.file # BcdDefs.FTSelf THEN GOTO punt;  -- tablecompiled, or ...
      codebase ← LOOPHOLE[buffer + (sgh.base-1)*Environment.wordsPerPage];
      sgh ← @sgb[mth.sseg];  -- Bcd's symbol segment table entry
      IF sgh.file # BcdDefs.FTSelf THEN GOTO punt;  -- tablecompiled, or ...
      IF sgh.pages + sgh.base > pagesPerBuffer THEN GOTO tooLong;
      IF mth.linkLoc # dontcare THEN ERROR BadFile};
    EXITS
      tooLong => {};
      definitions => {};
      tableCompiled => {};
      obsoleteBcd => {};
      binderBcd => {};
      punt => {}};
      
  BadFile: ERROR = CODE;
  
  -- MAIN BODY CODE
  
  Exec.AddCommand[name: "FindBadModules.~", proc: Run];
  
  END.