-- InstallImpl.mesa - last edit:
-- Masinter, add comments
-- Lichtenberg  Aug 1984, from Install
-- Daniels	13-Jun-83 14:09:06
-- Loretta	20-Apr-83 12:12:48
-- Karlton	19-Aug-83 16:07:42
-- Bruce	 2-Sep-81 15:25:10
-- Johnsson	16-Jan-84  9:12:07

DIRECTORY
  Environment: TYPE USING [bytesPerPage, PageCount, PageNumber, PageOffset],
  Exec: TYPE USING [
    AddCommand, CheckForAbort, Confirm, EndOfCommandLine, ExecProc,
    FreeTokenString, GetNameandPassword, GetToken, Handle, Outcome, OutputProc],
  File: TYPE USING [
    Create, Delete, File, MakePermanent, nullFile, PageCount, PageNumber,
    SetSize, GetSize, Type, Unknown],
  FileName: TYPE USING [AllocVFN, FreeVFN, NormalizeVFN, VFN],
  FileTransfer: TYPE USING [
    ClientProc, Connection, Create, Destroy, Error, GetStreamInfo, MessageProc,
    ReadStream, SetPrimaryCredentials, SetProcs],
  FileTypes: TYPE USING [tUntypedFile],
  Format: TYPE USING [Line, StringProc],
  Heap: TYPE USING [systemZone],
  OthelloDefs: TYPE USING [LeaderPage, leaderPages, lpNoteLength, lpVersion],
  OthelloOps: TYPE USING [
    GetVolumeBootFile, MakeBootable, MakeUnbootable,
    SetVolumeBootFile, SetPhysicalVolumeBootFile],
  PrincOps: TYPE USING [Port],
  Space: TYPE USING [CopyOut, Interval, Map, PageCount, PageOffset, Unmap],
  SpecialVolume: TYPE USING [OpenVolume],
  Stream: TYPE USING [Delete, GetBlock, Handle],
  String: TYPE USING [
    AppendCharAndGrow, AppendString, AppendStringAndGrow, CopyToNewString,
    Empty, EqualString, EquivalentString, StringBoundsFault, Length],
  TemporaryBooting: TYPE USING [InvalidParameters, BootButton],
  Time: TYPE USING [Append, Unpack],
  Volume: TYPE USING [
    Close, GetLabelString, GetNext, GetStatus, GetType, ID, InsufficientSpace,
    maxNameLength, NeedsScavenging, nullID, Status, systemID, Type, TypeSet,
    GetAttributes];

InstallLispImpl: MONITOR
  IMPORTS
    Exec, File, FileName, FileTransfer, Format, Heap, OthelloOps,
    Space, SpecialVolume, Stream, String, Time, TemporaryBooting, Volume =
  BEGIN

  volumeName: STRING = [Volume.maxNameLength];
  volume: Volume.ID ← Volume.nullID;
  volumeOpened: BOOLEAN ← FALSE;
  originalVolumeStatus: Volume.Status ← unknown;

  write: Format.StringProc ← NIL;
  exec: Exec.Handle ← NIL;
  
  expandVolume: BOOLEAN ← FALSE;
  setDefault: BOOLEAN ← FALSE;
  startLisp: BOOLEAN ← FALSE;
  
  InstallFiles: ENTRY Exec.ExecProc = {
    ENABLE UNWIND => {volumeOpened ← FALSE};
    token, switches: LONG STRING ← NIL;
    haveOne: BOOLEAN ← FALSE;

    FreeTokens: PROCEDURE = {
      [] ← Exec.FreeTokenString[token]; [] ← Exec.FreeTokenString[switches]};
    exec ← h;
    write ← h.OutputProc[];
    [] ← SetVolumeName["Extra"L];
    
    expandVolume ← FALSE;
    setDefault ← FALSE;
    startLisp ← FALSE;

    UNTIL h.EndOfCommandLine[] DO  -- parse command line
      ENABLE UNWIND => FreeTokens[];
      [token, switches] ← h.GetToken[];
      CheckSwitches[switches];
      IF String.Empty[token] THEN {
        outcome ← IF haveOne THEN error ELSE InstallHelp[h]; EXIT};
      IF ~SetVolumeName[token] THEN {
        write["Error: volume "L];
        write[token];
        write[" not found!"L];
        outcome ← error;
        EXIT};
      FreeTokens[];
      [token, switches] ← h.GetToken[];
      CheckSwitches[switches];
      IF String.Empty[token] THEN IF ~DoNoFileOps[] THEN outcome ← abort;
      IF outcome = abort THEN EXIT;
      IF ~String.Empty[switches] OR ~String.EqualString[token, "←"L] THEN {
        write["Missing ""←""; OK to continue?"L];
        IF ~h.Confirm[] THEN {outcome ← abort; EXIT}}
      ELSE {FreeTokens[]; [token, switches] ← h.GetToken[]};
      CheckSwitches[switches];
      IF h.CheckForAbort[] THEN {outcome ← abort; EXIT};
      IF ~String.Empty[token] THEN
        IF startLisp THEN Format.Line[write,"Will boot Lisp when done."L];
	IF expandVolume THEN Format.Line[write,"Will expand VMem size"L];
	IF setDefault THEN Format.Line[write,"Will make this default lisp"L];
        outcome ← InstallFileOnVolume[token ! UNWIND => CloseVolume[]];
      haveOne ← TRUE;
      REPEAT FINISHED => IF ~haveOne THEN outcome ← InstallHelp[h]
      ENDLOOP;
    FreeTokens[];
    IF startLisp THEN TemporaryBooting.BootButton[];
    };
    
  DoNoFileOps: PROC RETURNS [foo: BOOLEAN] = BEGIN
    OPEN OthelloOps;
     file: File.File;
     firstPage: File.PageNumber;
     
     IF ~(expandVolume OR setDefault OR startLisp) THEN RETURN[TRUE];
     IF ~OpenVolume[] THEN RETURN[FALSE];
     [file, firstPage] ← GetVolumeBootFile[volume,hardMicrocode];
     IF file = File.nullFile THEN {
     	    Format.Line[write,"No sysout on that volume"L];
	    RETURN[FALSE]
	    };
     IF expandVolume THEN 
     	{MakeUnbootable[file,hardMicrocode,firstPage];
	 SetBootFileSize[file,volume];
	 MakeBootable[file,hardMicrocode,firstPage];
	 };
     IF startLisp OR setDefault THEN SetPhysicalVolumeBootFile[file,hardMicrocode, firstPage];
     CloseVolume[];
     IF startLisp THEN TemporaryBooting.BootButton[];
   END;
   

  CheckSwitches: PROC [switches: LONG STRING] = BEGIN
      i: CARDINAL;
      length: CARDINAL ← String.Length[switches];
      IF ~String.Empty[switches] THEN
       FOR i IN [0..length) DO 
         SELECT switches[i] FROM
	   'x,'X => expandVolume ← TRUE;
	   'd,'D => setDefault ← TRUE;
	   's,'S => startLisp ← TRUE;
	 ENDCASE => {};
        ENDLOOP;
   END;


  SetVolumeName: PROCEDURE [v: LONG STRING] RETURNS [ok: BOOLEAN ← TRUE] = {
    CloseVolume[];
    volumeName.length ← 0;
    String.AppendString[
      volumeName, v !
      String.StringBoundsFault => {
        write["Volume name too long!"L]; ok ← FALSE; CONTINUE}]};

  OpenVolume: PROCEDURE RETURNS [BOOLEAN] = {
    OPEN Volume;
    myType: Volume.Type = Volume.GetType[Volume.systemID];
    all: TypeSet ← [
      normal: TRUE, debugger: myType = debugger OR myType = debuggerDebugger,
      debuggerDebugger: myType = debuggerDebugger];
    name: STRING = [maxNameLength];
    IF ~volumeOpened THEN {
      FOR volume ← GetNext[nullID, all], GetNext[volume, all] UNTIL volume =
        nullID DO
        name.length ← 0;
        GetLabelString[volume, name];
        IF String.EquivalentString[name, volumeName] THEN EXIT;
        ENDLOOP;
      SELECT volume FROM
        nullID => {
          write[volumeName]; Format.Line[write, " not found!"L]; RETURN[FALSE]};
        Volume.systemID => {
          write[volumeName];
          Format.Line[write, " is your system volume!"L];
          RETURN[FALSE]};
        ENDCASE;
      SELECT (originalVolumeStatus ← Volume.GetStatus[volume]) FROM
        openRead => Volume.Close[volume];
        openReadWrite => RETURN[TRUE];
        unknown, partiallyOnLine, closedAndInconsistent => RETURN[FALSE];
        ENDCASE => NULL;
      SpecialVolume.OpenVolume[volume: volume, access: readWrite
        ! Volume.NeedsScavenging => GOTO YouLose];
      volumeOpened ← TRUE};
    RETURN[TRUE];
    EXITS YouLose => {
      write[volumeName];
      Format.Line[write, " needs scavenging."L];
      RETURN[FALSE]}};

  CloseVolume: PROC = {
    IF volumeOpened THEN {
      Volume.Close[volume];
      IF originalVolumeStatus = openRead THEN
        SpecialVolume.OpenVolume[volume: volume, access: read];
      volumeOpened ← FALSE}};

  PortRep: TYPE = PrincOps.Port;

  InstallFileOnVolume: PROC [name : LONG STRING]
    RETURNS [outcome: Exec.Outcome ← normal] = { -- from OthelloFTP (sort of)
    OPEN OthelloOps;
    created: BOOLEAN ← FALSE;
    file: File.File;
    firstPage: File.PageNumber;
    -- clean the PORT up from last time
    LOOPHOLE[GetFile, PortRep].in ← 0;
    -- CONNECT GetFile.out TO Retrieve;
    LOOPHOLE[GetFile, PortRep].out ← LOOPHOLE[Retrieve];
    write["Opening "];
    write[volumeName];
    write["... "L];
    IF ~OpenVolume[] THEN RETURN[error] ELSE Format.Line[write, " open."L];
    [file, firstPage] ← GetVolumeBootFile[volume, hardMicrocode];
    IF NOT GetFile[name: name] THEN {CloseVolume[]; RETURN[error]};
    IF (created ← file = File.nullFile) THEN
      file ← File.Create[volume, 1, FileTypes.tUntypedFile]
    ELSE
      MakeUnbootable[
        file, hardMicrocode, firstPage !
	File.Unknown => CONTINUE;
        TemporaryBooting.InvalidParameters => {
	  outcome ← warning;
          write["Warning: trouble making unbootable"L]; CONTINUE}];
    write[" Fetching... "L];
    IF NOT GetFile[
      file: file ! UNWIND => {IF created THEN file.Delete; CloseVolume[]}]
      THEN {IF created THEN file.Delete; CloseVolume[]; RETURN[error]};
    write["Installing..."L];
    SetVolumeBootFile[file, hardMicrocode, OthelloDefs.leaderPages];
    file.MakePermanent;
    File.SetSize[file,File.GetSize[file]+1];
    IF expandVolume THEN {
       write["Expanding..."L];
       SetBootFileSize[file,volume];
       write["OK..."L];
       };
    MakeBootable[
      file, hardMicrocode, OthelloDefs.leaderPages !
      TemporaryBooting.InvalidParameters => {
	outcome ← warning;
        write["Warning: trouble making bootable"L]; CONTINUE}];
    IF setDefault THEN OthelloOps.SetPhysicalVolumeBootFile[file,hardMicrocode, OthelloDefs.leaderPages];
    Format.Line[write, " installed."L];
    CloseVolume[]};
    
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
  SetBootFileSize: PRIVATE PROC[file: File.File, lvID: Volume.ID] = BEGIN
    oldSize: File.PageCount ← File.GetSize[file];
    newSize: File.PageCount ← oldSize + Volume.GetAttributes[lvID].freePageCount;
    File.SetSize[file, newSize 
	  ! File.Unknown => {write["Warning: Trouble making file fill volume - File.Unknown"L];
	    CONTINUE};
	    Volume.InsufficientSpace => {write["."L]; newSize ← newSize - 100;
	    IF newSize < oldSize THEN CONTINUE ELSE RETRY}];
    END;
    
   --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


  GetFile: PORT [
    file: File.File ← File.nullFile, name: LONG STRING ← NIL]
    RETURNS [BOOLEAN];

  Retrieve: PROC [file: File.File, name: LONG STRING]
    RETURNS [gotIt: BOOLEAN ← TRUE] = {
    OPEN FileName, FileTransfer;
    ResumeSetup: PORT [BOOLEAN]
      RETURNS [file: File.File, name: LONG STRING];
    Cleanup: PROC = {
      IF readStream # NIL THEN {readStream.Delete; readStream ← NIL};
      IF buffer # NIL THEN buffer ← Space.Unmap[buffer];
      IF vfn # NIL THEN {FreeVFN[vfn]; vfn ← NIL};
      IF conn # NIL THEN {conn.Destroy; conn ← NIL}};
    LoginUser: FileTransfer.ClientProc = {
      user: STRING = [64];
      password: STRING = [64];
      exec.GetNameandPassword[user, password];
      conn.SetPrimaryCredentials[user: user, password: password]};
    Message: MessageProc = {write[s1]; write[s2]; write[s3]; write[s4]};
    bufferPages: Space.PageCount = 64;
    bufferBytes: CARDINAL = CARDINAL[bufferPages*Environment.bytesPerPage];
    buffer: LONG POINTER ← NIL;
    conn: Connection ← FileTransfer.Create[];
    vfn: VFN ← NIL;
    readStream: Stream.Handle ← NIL;
    fileSize: File.PageCount;
    -- CONNECT ResumeSetup.out TO GetFile
    LOOPHOLE[ResumeSetup, PortRep].out ← @GetFile;
    -- CONNECT GetFile.out TO ResumeSetup
    LOOPHOLE[GetFile, PortRep].out ← @ResumeSetup;
    conn.SetProcs[clientData: NIL, messages: Message, login: LoginUser];
    vfn ← AllocVFN[name];
    readStream ← conn.ReadStream[files: vfn ! Error => {Cleanup[]; GOTO noGood}];
    file ← ResumeSetup[gotIt].file;
    fileSize ←
      (FileTransfer.GetStreamInfo[readStream].size + Environment.bytesPerPage -
         1)/Environment.bytesPerPage + OthelloDefs.leaderPages;
    file.SetSize[
      fileSize !
      Volume.InsufficientSpace => {
        write["Not enough room for file!"L]; Cleanup[]; GOTO noGood}];
    buffer ← Space.Map[
      window: [file: File.nullFile, base: NULL, count: bufferPages], 
      class: data, swapUnits: [uniform[4]]].pointer;
    SetLeaderPage[file, readStream, vfn];
    FOR windowPage: Space.PageOffset ← OthelloDefs.leaderPages,
      windowPage + bufferPages WHILE windowPage < fileSize DO
      bytesTransferred: CARDINAL ← readStream.GetBlock[
        [buffer, 0, bufferBytes]].bytesTransferred;
      [] ← Space.CopyOut[buffer, [file, windowPage, bufferPages]];
      ENDLOOP;
    Cleanup[];
    RETURN[TRUE];
    EXITS noGood => RETURN[FALSE]};
 
  SetLeaderPage: PROC [
    file: File.File, stream: Stream.Handle, vfn: FileName.VFN] = {
    lp: LONG POINTER TO OthelloDefs.LeaderPage = Space.Map[
      [file, 0, OthelloDefs.leaderPages]].pointer;
    note: LONG STRING ← Heap.systemZone.NEW[StringBody[60]];
    -- STARTKLUDGE: work around the FileTransfer not giving the host name
    <<String.AppendStringAndGrow[
      @note, FileTransfer.GetStreamName[stream], Heap.systemZone];>>
    vfn.NormalizeVFN;
    IF NOT String.Empty[vfn.host] THEN {
      String.AppendStringAndGrow[@note, "["L, Heap.systemZone];
      String.AppendStringAndGrow[@note, vfn.host, Heap.systemZone];
      String.AppendStringAndGrow[@note, "]"L, Heap.systemZone]};
    IF NOT String.Empty[vfn.directory] THEN {
      String.AppendStringAndGrow[@note, vfn.directory, Heap.systemZone];
      String.AppendStringAndGrow[@note, ">"L, Heap.systemZone]};
    IF NOT String.Empty[vfn.name] THEN
      String.AppendStringAndGrow[@note, vfn.name, Heap.systemZone];
    IF NOT String.Empty[vfn.version] THEN {
      String.AppendStringAndGrow[@note, "!"L, Heap.systemZone];
      String.AppendStringAndGrow[@note, vfn.version, Heap.systemZone]};
    -- ENDKLUDGE
    String.AppendStringAndGrow[@note, " ("L, Heap.systemZone];
    -- STARTKLUDGE: Time.Append screws up on StringBoundsFault
    {oldLength: CARDINAL = note.length;
    Time.Append[
      s: note, zone: TRUE,
      unpacked: Time.Unpack[FileTransfer.GetStreamInfo[stream].create] !
      String.StringBoundsFault => {
        ns ← String.CopyToNewString[
	  s: note, z: Heap.systemZone, longer: s.maxlength - s.length + 20];
	Heap.systemZone.FREE[@note];
	note ← ns;
	note.length ← oldLength;
        RETRY}];
    };-- ENDKLUDGE
    String.AppendCharAndGrow[@note, '), Heap.systemZone];
    lp.version ← OthelloDefs.lpVersion;
    lp.length ← MIN[note.length, OthelloDefs.lpNoteLength];
    FOR i: CARDINAL IN [0..lp.length) DO
      lp.note[i] ← note[i];
      ENDLOOP;
    [] ← Space.Unmap[lp];
    Heap.systemZone.FREE[@note]};
    
  InstallHelp: Exec.ExecProc = {
    Format.Line[
      h.OutputProc[],
      "Command format: InstallLisp.~ volume ← sysoutfile
          Switches: /x to expand volume file size
	            /d to make this lisp the boot lisp
	            /s to start lisp from this volume"L]};

  {Exec.AddCommand["InstallLisp.~"L, InstallFiles, InstallHelp]};

  END.