-- Copyright (C) 1984  by Xerox Corporation. All rights reserved. 
-- OthelloFetchImpl.mesa
--  Johnsson	26-Feb-84 15:24:23
--  Davirro	 5-Nov-84 10:53:21
--  Masinter	16-Dec-84 15:54:49

DIRECTORY
  Auth,
  Environment USING [bytesPerPage, bytesPerWord],
  File USING [
    Create, Delete, File, MakePermanent, nullFile, PageNumber, PageCount, GetSize, SetSize, Unknown],
  FileTypes USING [tUntypedFile],
  Heap USING [systemZone],
  NSName,
  NSString,
  OthelloDefs USING [
    AbortingCommand, CommandProcessor, Confirm, FlipCursor, GetLvIDFromUser, GetName,
    IndexTooLarge, LeaderPage, leaderPages, lpNoteLength, lpVersion,
    MyNameIs, Question, RegisterCommandProc, WriteLine, WriteLongNumber, WriteString, Yes],
  OthelloFetch USING [Destination, Handle, Object],
  OthelloOps USING [
    BootFileType, GetVolumeBootFile, GetPhysicalVolumeBootFile, MakeBootable, MakeUnbootable,
    SetPhysicalVolumeBootFile, SetVolumeBootFile, VoidVolumeBootFile, VoidPhysicalVolumeBootFile],
  PhysicalVolume USING [ID],
  Process USING [Detach],
  Profile ,
  Space USING [Map, CopyIn, ScratchMap, Unmap, Window],
  Stream,
  String ,
  TemporaryBooting USING [InvalidParameters],
  Volume USING [Close, GetAttributes, ID, InsufficientSpace, nullID, Open];

OthelloFetchImpl: PROGRAM
  IMPORTS Auth, File, Heap, NSString, OthelloDefs, OthelloOps, Process, Space, Stream, String, TemporaryBooting, Volume
  EXPORTS OthelloDefs, OthelloFetch, Profile =
  BEGIN
  
  Object: TYPE = OthelloFetch.Object;
  
  Handle: TYPE = OthelloFetch.Handle;
  
  list: Handle ← NIL;
  
  current: Handle ← NIL;
  
  cmFile: LONG STRING ← NIL;
  fileName: LONG STRING ← NIL;

  z: UNCOUNTED ZONE = Heap.systemZone;

  S: PROC [s: LONG STRING] RETURNS [NSString.String] = INLINE {
    RETURN[NSString.StringFromMesaString[s]]};
    
  -- Fetcher registration
  
  Register: PUBLIC PROC [h: Handle] =
    BEGIN
    h.next ← list;
    list← h;
    END;
  
  Select: PUBLIC PROC [h: Handle] =
    BEGIN
    IF current # NIL THEN current.Close[];
    current← h;
    END;
  

  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  -- String/Credentials Commands
  --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  domain, org: LONG STRING ← NIL;
  
  GetDefaultDomain: --Profile--PUBLIC PROC [proc: PROC [LONG STRING]] = {
    proc[domain]};
  
  GetDefaultOrganization: --Profile--PUBLIC PROC [proc: PROC [LONG STRING]] = {
    proc[org]};
  
  GetID: --Profile--PUBLIC PROC [
    flavor: Auth.Flavor, proc: PROC [id: Auth.IdentityHandle]] =
    BEGIN
    SELECT flavor FROM
      simple => proc[simple];
      strong => proc[strong];
      ENDCASE => ERROR;
    END;

  Qualify: --Profile--PUBLIC PROC [
    token, newToken: LONG STRING,
    qualification: Profile.Qualification ← clearinghouse] = {
    namePart: CARDINAL ← String.Length[token];
    currentQual: Profile.Qualification ← none;
    octalAddress: BOOLEAN ← TRUE; -- only '0..'7 and '# allowed
    ss: String.SubStringDescriptor;
    chChar: CHARACTER = ':;
    regChar: CHARACTER = '.;
    IF String.Length[token] = 0 THEN RETURN;
    FOR i: CARDINAL IN [0..token.length) DO
       SELECT token[i] FROM
         regChar => {namePart ← i; currentQual ← registry};  -- look for last dot
	 chChar => {namePart ← i; currentQual ← clearinghouse; EXIT}; -- first :
	 IN['0..'7], '# => NULL;
	 ENDCASE => octalAddress ← FALSE;
       ENDLOOP;
    IF currentQual = qualification OR octalAddress THEN {
      String.AppendString[newToken, token]; RETURN};
    ss ← [base: token, offset: 0, length: namePart];
    String.AppendSubString[newToken, @ss];
    SELECT qualification FROM
      none => NULL;
    <<registry =>
        IF String.Length[defaultRegistry] > 0 THEN {
	  String.AppendChar[newToken, regChar];
	  String.AppendString[newToken, defaultRegistry]};>>
      clearinghouse =>
        IF String.Length[domain] > 0 OR 
	   String.Length[org] > 0 THEN {
	  String.AppendChar[newToken, chChar];
	  String.AppendString[newToken, domain];
	  String.AppendChar[newToken, chChar];
	  String.AppendString[newToken, org]};
      ENDCASE};
      
  ClearinghouseCmd: PROC = {
    OthelloDefs.MyNameIs[
	  myNameIs: "Clearinghouse"L,
	  myHelpIs: "Set defaults for Clearinghouse"L];
    OthelloDefs.GetName["Domain: "L, @domain];
    OthelloDefs.GetName["Organization: "L, @org]};
  
  userName: PUBLIC LONG STRING ← NIL;
  userPassword: PUBLIC LONG STRING ← NIL;
  strong, simple: Auth.IdentityHandle ← NIL;
  
  LoginCmd: PROC = {
    OthelloDefs.MyNameIs[
      myNameIs: "Login"L, myHelpIs: "Set user name & password"L];
    OthelloDefs.GetName["User: "L, @userName];
    OthelloDefs.GetName["Password: "L, @userPassword, stars];
    IF simple # NIL THEN Auth.FreeIdentity[@simple, z];
    IF strong # NIL THEN Auth.FreeIdentity[@strong, z];
    simple ← MakeID[flavor: simple];
    strong ← MakeID[flavor: strong]};

  MakeID: PROC [flavor: Auth.Flavor] RETURNS [id: Auth.IdentityHandle] = {
    myName: NSName.NameRecord ← [
      org: S[org], domain: S[domain], local: S[userName]];
    id ← Auth.MakeIdentity[
      myName: @myName,
      password: S[userPassword],
      z: z,
      style: flavor,
      dontCheck: TRUE]};
    
  directory: PUBLIC LONG STRING ← NIL;

  Directory: PROC = {
    OthelloDefs.MyNameIs[
      myNameIs: "Directory"L,
      myHelpIs: "Set Default FTP directory"L];
    OthelloDefs.GetName["Directory: "L, @directory]};

  
  -- Simple Fetches

  FetchBoot: PROC = {
    Fetch[pilot, "Boot file name: "L, "Fetch Boot File"L, "Fetch Boot File"L, "boot"L]};

  FetchGerm: PROC = {
    Fetch[germ,  "Germ file name: "L, "Germ Fetch"L, "Fetch Germ"L, "germ"L]};

  FetchPilotMicrocode: PROC = {
    Fetch[
      softMicrocode,
      "Pilot microcode file name: "L,
      "Pilot Microcode Fetch"L,
      "Fetch and Install Pilot Microcode"L,
      "db"L]};

  FetchDiagnosticMicrocode: PROC = {
    Fetch[
      hardMicrocode,
      "Diagnostic microcode file name: "L,
      "Diagnostic Microcode Fetch"L,
      "Fetch and Install Diagnostic Microcode"L,
      "db"L]};
  
  FetchLisp: PROC = {
    Fetch[
      hardMicrocode,
      "Lisp sysout file name: "L,
      "Lisp Sysout Fetch"L,
      "Fetch and Install Interlisp Sysout"L,
      "sysout"L]};

  Fetch: PROC [
    type: OthelloOps.BootFileType, prompt, name, helpMsg, extension: STRING] = {
    created: BOOLEAN;
    file: File.File;
    firstPage: File.PageNumber;
    lvID: Volume.ID;
    OthelloDefs.MyNameIs[myNameIs: name, myHelpIs: helpMsg];
    CheckOpen[];
    lvID ← OthelloDefs.GetLvIDFromUser[].lvID;
    OthelloDefs.GetName[prompt, @fileName];
    Volume.Open[lvID];
    [file, firstPage] ← OthelloOps.GetVolumeBootFile[lvID, type];
    IF (created ← file = File.nullFile) THEN
      file ← File.Create[lvID, 1, FileTypes.tUntypedFile]
    ELSE OthelloOps.MakeUnbootable[file, type, firstPage ! 
      File.Unknown => CONTINUE;
      TemporaryBooting.InvalidParameters => {
        OthelloDefs.WriteLine["Warning, trouble making unbootable"L];
	CONTINUE}];
    current.Retrieve[fileName, [pilotFileSystemWrite[file]]
      ! UNWIND => {IF created THEN File.Delete[file]; Volume.Close[lvID]}];
    OthelloDefs.WriteString["Installing..."L];
    OthelloOps.SetVolumeBootFile[file, type, OthelloDefs.leaderPages];
    IF created THEN File.MakePermanent[file];
-- add for LISP
    IF String.Equal[extension, "sysout"L] THEN {
   	OthelloDefs.WriteString["Expanding to fill volume..."L];
   	SetBootFileSize[file,lvID];
    	OthelloDefs.WriteString["OK..."L];
	};
-- end
    OthelloOps.MakeBootable[file, type, OthelloDefs.leaderPages
    ! TemporaryBooting.InvalidParameters => {
      OthelloDefs.WriteLine["Warning, trouble making bootable"L]; CONTINUE}];
    OthelloDefs.WriteLine["done"L];
    IF type IN [hardMicrocode..germ] AND
       OthelloDefs.Yes["Shall I also use this for the Physical Volume? "L
       ! UNWIND => Volume.Close[lvID]] THEN
      OthelloOps.SetPhysicalVolumeBootFile[file, type, OthelloDefs.leaderPages];
    Volume.Close[lvID]};
  
  
  bufPages: CARDINAL = 8;

  StartFeedback: PUBLIC SIGNAL = CODE;

  GrabBitsFromStream: PUBLIC PROC [
    rs: Stream.Handle, rsSizePages: LONG CARDINAL,
    destination: OthelloFetch.Destination, note: LONG STRING ← NIL] = {
    WITH destination SELECT FROM
      pilotFileSystemWrite => {
	buffer: LONG POINTER ← NIL;
        base: File.PageNumber ← 0;
	got: CARDINAL;
        File.SetSize[localFile, rsSizePages + OthelloDefs.leaderPages
	! Volume.InsufficientSpace => OthelloDefs.AbortingCommand["Volume Full"L]];
	SetLeaderPage[localFile, note];
	SIGNAL StartFeedback;
	WHILE base < rsSizePages DO
          thisPages: CARDINAL = CARDINAL[MIN[rsSizePages-base, bufPages]];
	  size:      CARDINAL = thisPages*Environment.bytesPerPage;
	  start:     CARDINAL ← 0;
	  buffer ← Space.Map[
	    window:[localFile, base+OthelloDefs.leaderPages, thisPages],
	    life: dead].pointer;
          DO
	    [bytesTransferred: got] ← rs.GetBlock[[
	      blockPointer: buffer, startIndex: start, stopIndexPlusOne: size] !
	        Stream.EndOfStream => {
	          got ← 0; start ← start + nextIndex; CONTINUE};
	        UNWIND => [] ← Space.Unmap[buffer]];
	    IF got = 0 THEN {[] ← Space.Unmap[buffer]; RETURN};
            IF (start ← start + got) = size THEN EXIT;
            ENDLOOP;
          --buffer ← Space.Unmap[buffer, return];
	  Process.Detach[LOOPHOLE[FORK Space.Unmap[buffer]]]; buffer ← NIL;
          OthelloDefs.FlipCursor[];
	  base ← base + thisPages;
          ENDLOOP;
	buffer ← Space.ScratchMap[1]; -- check for any leftover stuff
	[bytesTransferred: got] ← rs.GetBlock[[
	  blockPointer: buffer, startIndex: 0,
	  stopIndexPlusOne: Environment.bytesPerPage] !
	    Stream.EndOfStream => {got ← nextIndex; CONTINUE};
	    UNWIND => [] ← Space.Unmap[buffer]];
	[] ← Space.Unmap[buffer];
	IF got # 0 THEN OthelloDefs.AbortingCommand[
	  "File longer than advertised length"L]};
      string => {
	SIGNAL StartFeedback;
	DO
          stringOverhead: CARDINAL = SIZE[StringBody]*Environment.bytesPerWord;
          string: LONG STRING = Space.ScratchMap[bufPages];
          string↑ ← [
	    length: 0,
	    maxlength: bufPages*Environment.bytesPerPage - stringOverhead,
	    text: ];
	  WHILE string.length < string.maxlength DO
            got: CARDINAL;
	    [bytesTransferred: got] ← rs.get[
	      rs,
	      [blockPointer: LOOPHOLE[@string.text],
	       startIndex: string.length, stopIndexPlusOne: string.maxlength],
	      rs.options
	      ! Stream.EndOfStream => {
	          got ← 0; string.length ← string.length + nextIndex; CONTINUE};
                UNWIND => [] ← Space.Unmap[string]];
	    IF got = 0 THEN {
	      stringProc[string! UNWIND => [] ← Space.Unmap[string]];
	      [] ← Space.Unmap[string]; RETURN};
            string.length ← string.length + got;
            ENDLOOP;
          [] ← Space.Unmap[string];
          OthelloDefs.AbortingCommand["Command file too long!"L];
          ENDLOOP};
      rawWrite =>{
	buffer: LONG POINTER = Space.ScratchMap[1];
        done:  BOOLEAN ← FALSE;
        first: BOOLEAN ← TRUE;
	options: Stream.InputOptions ← rs.options;
	GetPage: PROC RETURNS [LONG POINTER] = {
          got:   CARDINAL; index: CARDINAL ← 0;
          IF first THEN {SIGNAL StartFeedback; first ← FALSE};
	  WHILE ~done DO
	    [bytesTransferred: got] ← rs.get[
	      sH: rs,
	      block: [blockPointer: buffer, startIndex: index,
	       stopIndexPlusOne: Environment.bytesPerPage],
	      options: options
	      ! Stream.EndOfStream => {got ← nextIndex; done ← TRUE; CONTINUE}];
            IF (index ← index + got) = Environment.bytesPerPage 
	       OR done THEN {OthelloDefs.FlipCursor[]; EXIT}
            ENDLOOP;
          RETURN[IF done AND index = 0 THEN NIL ELSE buffer]};
	options.signalEndOfStream ← TRUE;
        linkProc[GetPage ! UNWIND => [] ← Space.Unmap[buffer]];
        WHILE ~done DO [] ← GetPage[! UNWIND => [] ← Space.Unmap[buffer]] ENDLOOP;
        [] ← Space.Unmap[buffer]};
      ENDCASE => ERROR};
   
  -- Initial Ucode Fetch Command

  FetchInitialMicrocode: PUBLIC PROC [
    InstallProc: PROC [getPage: PROC RETURNS [LONG POINTER]]] = {
    CheckOpen[];
    OthelloDefs.GetName["File name: "L, @fileName];
    OthelloDefs.Confirm[];
    current.Retrieve[fileName, [rawWrite[InstallProc]]]};
    
  -- Command Files
  
  AlternateGetCMFile: PUBLIC PROC [s: STRING] = {
    z.FREE[@cmFile];
    cmFile ← z.NEW[StringBody[s.length+8]];
    FOR i: CARDINAL IN [1..s.length) DO
      String.AppendChar[cmFile, s[i]] ENDLOOP;
    DoIndirect[]};

  Indirect: PROC = {
    OthelloDefs.MyNameIs[
      myNameIs: "@", myHelpIs: "Run command file"L];
    OthelloDefs.GetName["Command file: "L, @cmFile
    ! OthelloDefs.Question => {
      OthelloDefs.WriteLine["[Host]<Dir>Filename"L]; RESUME}];
    DoIndirect[]};
  
  DoIndirect: PROC = {
    [] ← String.AppendExtensionIfNeeded[@cmFile, "othello"L, z];
    FOR h: Handle ← list, h.next UNTIL h = NIL DO
      IF h.DoIndirect[cmFile] THEN RETURN;
      ENDLOOP;
    OthelloDefs.AbortingCommand["Unrecognizable command file name"L]};
    

  -- Misc. commands
  
  CloseCmd: PROC = {
    OthelloDefs.MyNameIs[
      myNameIs: "Close"L, myHelpIs: "Close currently open connection"L];
    CloseFetch[]};
  
  CloseFetch: PUBLIC PROC = {
    Select[NIL]};
  
  ListCmd: PROC = {
    OthelloDefs.MyNameIs[
      myNameIs: "List Files"L, myHelpIs: "Enumerate files matching pattern"L];
    CheckOpen[];
    OthelloDefs.GetName["Pattern: "L, @fileName
    ! OthelloDefs.Question => {
      OthelloDefs.WriteLine["pattern to match"L]; RESUME}];
    current.List[fileName]};
  
  CheckOpen: PROC = {
    IF current = NIL THEN
      OthelloDefs.AbortingCommand["You must execute an Open command first"L]};
  
  SetLeaderPage: PUBLIC PROCEDURE [file: File.File, note: LONG STRING] =
    BEGIN
    lp: LONG POINTER TO OthelloDefs.LeaderPage ← Space.Map[[file, 0, OthelloDefs.leaderPages]].pointer;
    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];
    END;
   

  -- Commands for Interlisp-D
   CopyLisp: PROC = BEGIN
    fromVolume, toVolume: Volume.ID;
    fromFile, toFile: File.File;
    pvID: PhysicalVolume.ID;
    error: BOOLEAN ← FALSE;
    currentBase: File.PageNumber;
    nextCount: File.PageCount;
    fromWindow,toWindow: Space.Window;
    toPointer: LONG POINTER;
    fromPage1: LONG POINTER TO ARRAY[0..20] OF WORD; -- Lisp Interface Page
    fromFileSize: File.PageCount;
    fromFileActive: CARDINAL;
    tries: CARDINAL;
    toFileSize: File.PageCount;
    minFileSize: File.PageCount;
    OthelloDefs.MyNameIs[
      myNameIs: "Copy Lisp From Another Volume"L,
      myHelpIs: "Copies lisp from one volume to another"L];
      [pvID, fromVolume] ← OthelloDefs.GetLvIDFromUser[prompt:"Volume to copy from: "L];
      IF fromVolume = Volume.nullID THEN OthelloDefs.AbortingCommand["Invalid volume."L];
      toVolume ← OthelloDefs.GetLvIDFromUser[prompt:"Volume to copy to:  "L].lvID;
      IF toVolume = Volume.nullID THEN OthelloDefs.AbortingCommand["Invalid volume."L];
      -- Open up the volumes
      Volume.Open[fromVolume];
      Volume.Open[toVolume];
      -- Is there a source file?
      fromFile ← OthelloOps.GetVolumeBootFile[fromVolume,hardMicrocode].file;
      IF fromFile = File.nullFile THEN {
      		Volume.Close[fromVolume];
      		Volume.Close[toVolume];
      		OthelloDefs.AbortingCommand["No Lisp sysout on source volume"L];
		};
      -- Is there a dest file? Delete it if yes.
      toFile ← OthelloOps.GetVolumeBootFile[toVolume,hardMicrocode].file;
      IF toFile # File.nullFile THEN {
      	   OthelloOps.VoidVolumeBootFile[toVolume,hardMicrocode];
	   File.Delete[toFile];
	   IF OthelloOps.GetPhysicalVolumeBootFile[pvID,hardMicrocode].file = toFile
	   THEN OthelloOps.VoidPhysicalVolumeBootFile[pvID,hardMicrocode];
	   };
      -- Create the dest file
      fromWindow ← [file: fromFile,
             	    base: 1 + OthelloDefs.leaderPages,
		    count: 1];
      fromPage1 ← Space.Map[fromWindow].pointer;
      fromFileSize ← File.GetSize[fromFile];
      fromFileActive ← fromPage1[20];
      fromPage1 ← Space.Unmap[fromPage1];
      IF fromFileActive < 1000 THEN {Volume.Close[fromVolume];
      		     Volume.Close[toVolume];
      		     OthelloDefs.AbortingCommand["Source volume doesn't contain a Lisp system!"L];};
      IF fromFileActive > fromFileSize THEN {Volume.Close[fromVolume];
      		     Volume.Close[toVolume];
      		     OthelloDefs.AbortingCommand["Can't copy a VMEM that has been already expanded by Lisp!"L];};
      toFileSize ← fromFileActive;
      tries ← 1;
      toFile ← File.Create[toVolume, toFileSize, FileTypes.tUntypedFile
      			! Volume.InsufficientSpace => 
			    { tries ← tries - 1;
			      toFileSize ← toFileSize - 100;
			      OthelloDefs.WriteString["."L];
			      IF tries = 0 OR toFileSize < 0 THEN {error ← TRUE;
			    		CONTINUE}
			      ELSE RETRY;};];
      IF error THEN {Volume.Close[fromVolume];
      		     Volume.Close[toVolume];
      		     OthelloDefs.AbortingCommand["Destination volume does not contain sufficient space!"L];};
      File.MakePermanent[toFile];
      -- Create windows onto the file
      OthelloDefs.WriteString["Copying "L];
      OthelloDefs.WriteLongNumber[toFileSize];
      OthelloDefs.WriteString["pages... "L];

      currentBase ← 0;
      nextCount ← 100;
      DO -- until file has been copied
         nextCount ← MIN[100,fromFileActive-currentBase];
	 IF nextCount=0 THEN EXIT;
	 fromWindow ← [file: fromFile,
      		       base: currentBase,
		       count: nextCount];
         toWindow ← [file: toFile,
      		     base: currentBase,
		     count: nextCount];
         toPointer ← Space.Map[toWindow].pointer;
	 nextCount ← Space.CopyIn[toPointer,fromWindow];
	 toPointer ← Space.Unmap[toPointer];
	 currentBase ← currentBase + nextCount;
	 OthelloDefs.WriteString["."L];
         IF currentBase >= minFileSize THEN EXIT; 
      ENDLOOP;
      OthelloDefs.WriteString["Installing..."L];
      OthelloOps.MakeBootable[toFile, hardMicrocode, OthelloDefs.leaderPages
           ! TemporaryBooting.InvalidParameters => {
             OthelloDefs.WriteLine["Warning, trouble making bootable"L]; CONTINUE}];
      OthelloOps.SetVolumeBootFile[toFile,hardMicrocode,OthelloDefs.leaderPages];
      OthelloDefs.WriteLine["done"L];
      Volume.Close[fromVolume];
      Volume.Close[toVolume];
      END;
-- This kludge sets the file's size to be as big as Pilot will let it be, on 100 page increments.
  SetBootFileSize: PRIVATE PROC[file: File.File, lvID: Volume.ID] = BEGIN
    tries: CARDINAL; 
    oldSize: File.PageCount ← File.GetSize[file];
    newSize: File.PageCount ← oldSize + Volume.GetAttributes[lvID].freePageCount;
    tries ← 20;
    OthelloOps.MakeUnbootable[file, hardMicrocode, OthelloDefs.leaderPages];
    File.SetSize[file, newSize 
	  ! File.Unknown => {OthelloDefs.WriteLine["Warning: Trouble making file fill volume - File.Unknown"L];
	    CONTINUE};
	    Volume.InsufficientSpace => {OthelloDefs.WriteString["."L]; newSize ← newSize - 100;
	    tries ← tries - 1; 
	    IF tries = 0 OR newSize < oldSize THEN {OthelloDefs.WriteLine["Warning: Expanding volume failed!"]; CONTINUE;} ELSE RETRY};];
    OthelloOps.MakeBootable[file,hardMicrocode, OthelloDefs.leaderPages];
    END;
    
    
ExpandLisp: PROC = BEGIN
      pvID: PhysicalVolume.ID;
      exVolume: Volume.ID;
      exFile: File.File;
      OthelloDefs.MyNameIs[
         myNameIs: "Expand Lisp Virtual Memory File"L,
	 myHelpIs: "Expand the lisp virtual memory file on a volume"L];
      [pvID, exVolume] ← OthelloDefs.GetLvIDFromUser[prompt:"Volume to expand: "L];
      IF exVolume = Volume.nullID THEN OthelloDefs.AbortingCommand["Invalid volume."L];
      Volume.Open[exVolume];
      exFile ← OthelloOps.GetVolumeBootFile[exVolume, hardMicrocode].file;
      IF exFile = File.nullFile THEN OthelloDefs.AbortingCommand["No sysout on volume."L];
      OthelloDefs.WriteString["Expanding file..."L];
      SetBootFileSize[exFile,exVolume];
      OthelloDefs.WriteLine["Done."L];
      Volume.Close[exVolume];
      END;

  -- command processor

  commandProcessor: OthelloDefs.CommandProcessor ← [FetchCommands];

  FetchCommands: PROC [index: CARDINAL] = {
    SELECT index FROM
      0 => Indirect[];
      1 => ClearinghouseCmd[];
      2 => CloseCmd[];
      3 => Directory[];
      4 => FetchBoot[];
      5 => FetchDiagnosticMicrocode[];
      6 => FetchGerm[];
      7 => FetchPilotMicrocode[];
      8 => ListCmd[];
      9 => LoginCmd[];
      10 => FetchLisp[];
      11 => CopyLisp[];
      12 => ExpandLisp[];
      ENDCASE => OthelloDefs.IndexTooLarge};

  -- init

  OthelloDefs.RegisterCommandProc[@commandProcessor];

  END.