-- (C) Copyright International Business Machines Corporation 23 January 
-- 1990.  All Rights Reserved. 
--  
-- See the file USERAGREEMENT distributed with this software for full 
-- terms and conditions of use. 
-- SCCS Info: @(#)root.pp	1.21 2/14/92

-- This module is intended to be run in the initial process created
-- when the interpreter starts up.  It gathers a bunch of useful
-- things together and posts them (under user name "system") to a
-- resource manager, then runs the program named on the command line
-- using the sysMain or main interface, whichever succeeds.  If the
-- main interface is used, the resource manager provided in the main
-- interface uses the user's login ID as the user name on all
-- requests.

#include "typemark.h"

root: using (
  root, cload, initFindFile, findFile, unix, pathLoad, common, main, sysMain,
  pathreadobj, load, objectio, loadprogram, storeprogram, rManager,
  sysRManager, userRM, cwd, terminalIO
#ifndef CGBOOT
      , libinit, initCwd, stdio, check, initcheck, unixint
#endif
)

-- Enforce strict hierarchy in the xxBOOT cpp variables
#ifdef CGBOOT
#  ifndef TCBOOT
#    define TCBOOT
#  endif
#endif

#ifdef TCBOOT
#  ifndef TSBOOT
#    define TSBOOT
#  endif
#endif

#ifdef TSBOOT
#  define ANYBOOT
#endif

-- always use the cache on later stages of booting
#ifndef TCBOOT
#   define USECACHE
#endif

process (Q: rootInitMessageQ)
  
declare
  init: rootInitMessage;
  CFunc: polymorph;
  access: fileAccessFn;
  syspath: charstring;
  defpath: charstring;
  linkpath: charstring;		-- unused in this modeul but needed
				-- for getpaths.pp
  bindir: charstring;
  defpathFinder: findfile_func;
  syspathFinder: findfile_func;
  
  load: load_func;
  pathLoad: load_func;
  readObj: readObject_func;
  pathReadObj: readobject_func;
  writeObj: writeobject_func;
  libWriteObj: writeobject_func;
  store: storeFunc;
  libStore: storeFunc;
  getCwd: getCwdFn;
  setCwd: setCwdFn;
  terminal: terminalFunctions;
  userName: charstring;

  sysRM: sysRManager;
  RM: rManager;
  
#ifndef CGBOOT
  stdio: stdio;
  stdin: stdin;
  stdout: stdout;
#endif /* ndef CGBOOT */
  
#ifndef ANYBOOT
  initcheck: initcheckfn;
  checker: checkfn;
  killchecker: signalPort;
  initcheckdefinitions: initcheckdefinitionsfn;
  definitionschecker: checkdefinitionsfn;
  killdefinitionschecker: signalPort;
#endif /* ndef ANYBOOT */
  
begin

  block begin
    receive init from Q;	-- receive initial connection to the world
  on (disconnected)
    exit done;
  end block;
  
  CFunc <- polymorph#(init.cloader(S("Read Object")));
  unwrap readObj from CFunc {init};
  CFunc <- polymorph#(init.cloader(S("Write Object")));
  unwrap writeObj from CFunc {init};
  CFunc <- polymorph#(init.cloader(S("Access")));
  unwrap access from CFunc {init};

  -- set up path variables: syspath, defpath, linkpath, bindir
# define ENVIRON init.environ
# include "getpaths.pp"
  
  -- Create processes to load and store wrapped programs
  block declare
    p: program;
  begin
    unwrap p from polymorph#(readObj(S(bindir | S("/load_program.po"))))
	{init, checked};
    call (loadprogram_init_func#(create of p))(readObj, load);
    call (storeprogram_init_func#(create of program#(
	  load(S(bindir | S("/store_program.po"))))))(writeObj, store);
  end block;
  
  -- Create a process to maintain current working directory (not
  -- during codegen boot)
# ifdef CGBOOT
  getCwd <- getCwdFn#(procedure of program#(process (Q: getCwdQ)
      declare
	args: getCwd;
      begin
	receive args from Q;
	args.cwd <- S(".");
	return args;
	end process));
#else
  block declare
    initCwd: initCwdFn;
  begin
    initCwd <- initCwdFn#(create of program#(load(S(bindir | S("/cwd.po")))));
    call initCwd(init.directory, setCwd, getCwd);
  end block;
# endif  

  -- create the path finders, one for program loads and another for
  -- everything else (including definitions modules)
  block declare
    pathinit: initFindFileFn;
  begin
    pathinit <- initFindFileFn#(procedure of program#(
	load(S(bindir | S("/findfile.po")))));
    syspathFinder <- findfile_func#(pathinit(access, getCwd, syspath));
    defpathFinder <- findfile_func#(pathinit(access, getCwd, defpath));
  end block;
  
  -- create the path-based loaders, readers, writers, etc...
  block declare
    loadpath: charstring;
  begin
    loadpath <- S(syspathFinder(S("pathload.po"), fileAccessType#'read'));
    call (pathload_init_func#(create of program#(load(loadpath))))
	(load, syspathFinder, pathLoad);
    call (pathreadobj_init_func#
	  (create of program#(pathLoad(S("pathreadobj")))))
	(readObj, defpathFinder, pathReadObj);
    
#ifndef CGBOOT
    call (initLibWriteObjectFn#
	  (create of program#(pathLoad(S("libwriteobj")))))
	(writeObj, getCwd, libWriteObj);
    call (initLibStoreFn#(create of program#(pathLoad(S("libstore")))))
	(store, getCwd, libStore);
#endif CGBOOT
  end block;
  
  -- load stdio
#ifndef CGBOOT
  call (stdioInitFn#(create of program#(pathLoad(S("stdio")))))
	(init.cloader, stdio);
#endif CGBOOT
  
# ifndef CGBOOT
  -- make a convenient bundle of the most essental terminal I/O
  -- capabilities 
  
  new terminal;
  terminal.getString := stdio.stdin.gets;
  terminal.putString := stdio.stdout.fputs;
  terminal.putLine := stdio.stdout.puts;
  terminal.getChar := stdio.stdin.getc;
  terminal.putChar := stdio.stdout.putc;

  -- create the stdin and stdout resources for programs that want to
  -- retrieve them that way
  new stdin;
  stdin.getString := terminal.getString;
  stdin.getChar := terminal.getChar;
  new stdout;
  stdout.putString := terminal.putString;
  stdout.putLine := terminal.putLine;
  stdout.putChar := terminal.putChar;
  
# else CGBOOT
  -- For the boot case, fake out putstring and putline with a process
  -- that just prints its argument
  new terminal;
  terminal.putString <- putStringFunc#(procedure of program#(
      process (Q: putStringQ) declare
	args: putStringIntf;
      begin
	receive args from Q;
	print args.string;
	return args;
      end process));
  terminal.putLine := terminal.putString;
# endif CGBOOT
  
  -- Start up a resource manager and stuff it with all the things
  -- we've accumulated
  block declare
    ac: AccessFn;
    resource: polymorph;
  begin
    ac <- AccessFn#(create of program#(process (q: AccessQ)
	declare
	  args: rManager!Access;
	begin
	  while TRUE repeat
	    receive args from q;
	    args.resource <- args.postedResource;
	    return args;
	  end while;
	end process));

    call (rManagerInitFn#(create of program#(pathLoad(S("rmanager")))))
	(sysRM);

    wrap environ#(copy of init.environ) as resource;
    call sysRM.post(S("system"), S("environ"), resource, ac);
    wrap CLoadFn#(copy of init.cloader) as resource;
    call sysRM.post(S("system"), S("CLoader"), resource, ac);
#ifndef CGBOOT
    wrap stdio#(copy of stdio) as resource;
    call sysRM.post(S("system"), S("stdio"), resource, ac);
#endif
    wrap load_func#(copy of load) as resource;
    call sysRM.post(S("system"), S("load"), resource, ac);
    wrap load_func#(copy of pathLoad) as resource;
    call sysRM.post(S("system"), S("pathLoad"), resource, ac);
    wrap readobject_func#(copy of readObj) as resource;
    call sysRM.post(S("system"), S("readObj"), resource, ac);
    wrap readobject_func#(copy of pathReadObj) as resource;
    call sysRM.post(S("system"), S("pathReadObj"), resource, ac);
    wrap writeobject_func#(copy of writeObj) as resource;
    call sysRM.post(S("system"), S("writeObj"), resource, ac);
    wrap writeobject_func#(copy of libwriteObj) as resource;
    call sysRM.post(S("system"), S("libWriteObj"), resource, ac);
    wrap storeFunc#(copy of store) as resource;
    call sysRM.post(S("system"), S("store"), resource, ac);
    wrap storeFunc#(copy of libStore) as resource;
    call sysRM.post(S("system"), S("libStore"), resource, ac);
    wrap getCwdFn#(copy of getCwd) as resource;
    call sysRM.post(S("system"), S("getCwd"), resource, ac);
    wrap setCwdFn#(copy of setCwd) as resource;
    call sysRM.post(S("system"), S("setCwd"), resource, ac);

#ifndef CGBOOT
    wrap stdin#(copy of stdin) as resource;
    call sysRM.post(S("system"), S("stdin"), resource, ac);
    wrap stdout#(copy of stdout) as resource;
    call sysRM.post(S("system"), S("stdout"), resource, ac);
#endif CGBOOT
  end block;

  -- Add a resource that will return the requestor's user name (a
  -- useful utility for processes using filtered resource managers)
  block declare
    ac: accessFn;
    resource: polymorph;
    empty: empty;
  begin
    ac := accessFn#(create of program#(
	process (q: accessQ)
	declare
	  args: rManager!access;
	begin
	  while TRUE repeat
	    receive args from q;
	    wrap S(copy of args.userName) as args.resource;
	    return args;
	  end while;
	end process));
    wrap empty as resource;
    call sysRM.post(S("system"), S("username"), resource, ac);
  end block;
  
  -- figure out a user name to use for filtering RM requests
  block begin
    inspect entry in init.environ where (B(entry.variable = S("USER")))
    begin
      userName := entry.value;
    end inspect;
  on (NotFound)
    block begin
      inspect entry in init.environ where 
	    (B(entry.variable = S("LOGNAME"))) begin
	userName := entry.value;
      end inspect;
    on (NotFound)
      userName := S("anonymous");
    end block;
  end block;

#ifdef USECACHE
  -- load cache.  Note that cache will replace sysRM with cached version.
  -- Pray that cache.po exists by the time we need it here.
  block declare
      cache: sysMainFn;
      nullArgv: charstringList;
    begin
      cache <- sysMainFn#(create of program#
             (pathLoad(S("cache"))));
      
      new nullArgv;
      call cache(nullArgv, terminal, sysRM);
    end block;

#endif /* def USECACHE */

#ifndef ANYBOOT
  -- put program and definitions checkers into resource manager.  
  -- Of course, they need a resource manager for initialization, 
  -- so we have to build one here briefly.
  block declare
      ac: AccessFn;
      resource: polymorph;
    begin
      
      RM <- rManager#(userRMFn#(create of program#
                 (pathLoad(S("userrm"))))(userName, sysRM));
      initcheck <- initcheckfn#(create of program#(pathLoad(S("check"))));
      initcheckdefinitions <- initcheckdefinitionsfn#(create of program#(pathLoad(S("checkdefinitions"))));

      call initcheck(terminal, RM, checker, killchecker);
      call initcheckdefinitions(terminal, RM, definitionschecker, 
          killdefinitionschecker);
      
      -- ideally this should be an access function that loads and initializes
      -- the resource the first time it's used, so we don't have to do
      -- it above.
      ac <- AccessFn#(create of program#(process (q: AccessQ)
                declare
                  args: rManager!Access;
                begin
                  while TRUE repeat
                      receive args from q;
                      args.resource <- args.postedResource;
                      return args;
                    end while;
                end process));
      
      wrap checkfn#(copy of checker) as resource;
      call sysRM.post(S("system"), S("checker"), resource, ac);
      wrap checkdefinitionsfn#(copy of definitionschecker) as resource;
      call sysRM.post(S("system"), S("definitionschecker"), resource, ac);
    end block;
  
#endif /* ndef ANYBOOT */

-- execute commands separated by semicolons

  block declare
    prefix: charstring;
    thisArgv: charstringList;
    semiPos: integer;
    progName: charstring;
    prog: program;
    junk: charstring;
  begin
    remove prefix from a in init.argv where 
	(B(I(position of a) = ZERO));
    while B(I(size of init.argv) > ZERO) repeat
      block begin
	block begin
	  inspect a in init.argv where (B(a = S(";"))) begin
	    semiPos <- I(position of a);
	  end inspect;
	on (notFound)
	  semiPos <- I(size of init.argv);
	end block;
	extract thisArgv from a in init.argv where
	    (B(I(position of a) < semiPos));
	-- get rid of semicolon
	if B(I(size of init.argv) <> ZERO) then
	  remove junk from a in init.argv where
	      (B(I(position of a) = ZERO));
	end if;
	if B(I(size of thisArgv) = ZERO) then
	  exit continue;
	end if;
      
	-- program name is first argument
	progname := S(a in thisArgv where (B(I(position of a) = ZERO)));
	-- a prior sysMain program might have given us a new RM with a
	-- new (e.g. cached) pathLoad capability
	unwrap pathLoad from polymorph#(
	  sysRM.get(S("system"), S("pathLoad"), S(""))) {init};
	prog <- program#(pathLoad(progname));
	insert S(copy of prefix) into thisArgv at ZERO;
	block declare
	  proc: mainFn;
	begin
	  -- assume it's a main interface
	  proc <- mainFn#(create of prog);
	  
	  RM <- rManager#(userRMFn#(create of program#
		  (pathLoad(S("userrm"))))(userName, sysRM));
	  call proc(thisArgv, terminal, RM);
	on (main.discarded)
	  exit continue;
	on (InterfaceMismatch)
	  block declare
	    sysProc: sysMainFn;
	  begin
	    -- wasn't main interface.. try sysMain
	    sysProc <- sysMainFn#(create of prog);
	    call sysProc(thisArgv, terminal, sysRM);
	  on (sysMain.discarded)
	    exit continue;
	  on (InterfaceMismatch)
	    call terminal.putLine(S(S("Program ") | S(progname | 
		      S(" uses neither main nor sysMain interface."))));
	    exit done;
	  end block;
	end block;
      on (load_intf.file_not_found)
	call terminal.putLine(S(S("Program ") | S(progname
		  | S("could not be located."))));
	exit done;
      on (load_intf.CantLoadProgram)
	call terminal.putLine(S(S("Program ") | S(progname
		  | S("could not be loaded."))));
	exit done;
      on exit(continue)
      end block;
    end while;
  end block;

on exit(done)

on (others)
  print S("Root process encountered unexpected problems.");
end process

