libm3/src/os/WIN32/ProcessWin32.m3


 Copyright (C) 1994, Digital Equipment Corporation           
 All rights reserved.                                        
 See the file COPYRIGHT for a full description.              
                                                             
 Portions Copyright 1996-2000, Critical Mass, Inc.           
 See file COPYRIGHT-CMASS for details.                       
                                                             
 Last modified on Tue Dec 20 08:43:00 PST 1994 by kalsow     
      modified on Wed Sep 22 11:52:08 PDT 1993 by mcjones    
 Written by Mick Jordan, Winter-Spring 1993                  

UNSAFE MODULE ProcessWin32 EXPORTS Process;

IMPORT File, FileWin32, LazyConsole, M3toC, OSError, OSErrorWin32, Pathname,
  RTProcess, Text, WinDef, WinNT, WinBase, Word;

REVEAL T = BRANDED OBJECT
    waitOk := TRUE;
    info: WinBase.PROCESS_INFORMATION
  END;

EXCEPTION InternalError; WaitAlreadyCalled;
<* FATAL InternalError, WaitAlreadyCalled *>

PROCEDURE Create(
    cmd: Pathname.T;
    READONLY params: ARRAY  OF TEXT;
    env: REF ARRAY OF TEXT := NIL;
    wd: Pathname.T := NIL;
    stdin, stdout, stderr: File.T := NIL)
  : T RAISES {OSError.E} =
  VAR
    t := NEW(T);
    cmdLine := ConvertArgs(cmd, params);
    lpEnvironment: WinDef.LPVOID := NIL;
    wdAddr: WinNT.LPSTR := NIL;
    startupInfo := WinBase.STARTUPINFO{
      cb            := BYTESIZE(WinBase.STARTUPINFO),
      lpReserved    := NIL,
      lpDesktop     := NIL,
      lpTitle       := NIL,
      dwX := 0, dwY := 0, dwXSize := 0, dwYSize := 0,
      dwXCountChars := 0, dwYCountChars := 0, dwFillAttribute := 0,
      dwFlags       := WinBase.STARTF_USESTDHANDLES,
      wShowWindow   := 0,
      cbReserved2   := 0,
      lpReserved2   := NIL,
      hStdInput     := PrepHandle (stdin),
      hStdOutput    := PrepHandle (stdout),
      hStdError     := PrepHandle (stderr)};
  BEGIN
    TRY
      IF env # NIL THEN lpEnvironment := ADR(ConvertEnv(env)[0]) END;
      IF wd # NIL THEN wdAddr := M3toC.SharedTtoS(wd) END;
      IF WinBase.CreateProcess(
           lpApplicationName := NIL,
           lpCommandLine := cmdLine,
           lpProcessAttributes := NIL,
           lpThreadAttributes := NIL,
           bInheritHandles := 1,
           dwCreationFlags := WinBase.NORMAL_PRIORITY_CLASS,
           lpEnvironment := lpEnvironment,
           lpCurrentDirectory := wdAddr,
           lpStartupInfo := ADR(startupInfo),
           lpProcessInformation := ADR(t.info)) = 0
      THEN
        OSErrorWin32.Raise()
      END;
    FINALLY
      (* close the local copy of any duplicated handles *)
      CloseHandle (startupInfo.hStdInput);
      CloseHandle (startupInfo.hStdOutput);
      CloseHandle (startupInfo.hStdError);
      IF wd # NIL THEN M3toC.FreeSharedS(wd, wdAddr); END;
    END;
    RETURN t
  END Create;

PROCEDURE PrepHandle (f: File.T): WinNT.HANDLE
  RAISES {OSError.E} =
  VAR h, self: WinNT.HANDLE;
  BEGIN
    IF (f = NIL) THEN RETURN NIL; END;
    self := WinBase.GetCurrentProcess ();
    IF WinBase.DuplicateHandle (self, f.handle, self, ADR (h), 0, 1,
                                WinNT.DUPLICATE_SAME_ACCESS) = 0 THEN
      OSErrorWin32.Raise();
    END;
    RETURN h;
  END PrepHandle;

PROCEDURE CloseHandle (h: WinNT.HANDLE)
  RAISES {OSError.E} =
  BEGIN
    IF (h = NIL) THEN RETURN END;
    IF WinBase.CloseHandle (h) = 0 THEN
      OSErrorWin32.Raise();
    END;
  END CloseHandle;

PROCEDURE ConvertArgs(cmd: Pathname.T; READONLY params: ARRAY OF TEXT)
  : WinNT.LPCSTR =
  VAR
    l, k := 0;
    result: REF ARRAY OF CHAR;
    cmdL := Text.Length(cmd);
  BEGIN
    INC(l, cmdL+1);
    FOR i := 0 TO NUMBER(params)-1 DO
      INC(l, Text.Length(params[i])+1)
    END;
    result := NEW(REF ARRAY OF CHAR, l+1);
    k := l; l := 0; result[l] := '\000';
    Text.SetChars(result^, cmd);
    INC(l, cmdL); result[l] := ' '; INC(l);
    FOR i := 0 TO NUMBER(params)-1 DO
      Text.SetChars(SUBARRAY(result^, l, k-l), params[i]);
      INC(l, Text.Length(params[i]));
      result[l] := ' '; INC(l)
    END;
    RETURN ADR(result[0])
  END ConvertArgs;

PROCEDURE ConvertEnv(env: REF ARRAY OF TEXT): REF ARRAY OF CHAR =
  VAR k: CARDINAL; chars: REF ARRAY OF CHAR;
  BEGIN
    k := 0;
    FOR i := 0 TO LAST(env^) DO
      INC(k, Text.Length(env[i]) + 1) (* null-terminated string *)
    END;
    INC(k, 1); (* final null *)
    chars := NEW(REF ARRAY OF CHAR, k);
    k := 0;
    FOR i := 0 TO LAST(env^) DO
      WITH len = Text.Length(env[i]) DO
        Text.SetChars(SUBARRAY(chars^, k, len), env[i]);
        chars[k + len] := '\000';
        INC(k, len + 1)
      END
    END;
    chars[k] := '\000';
    RETURN chars
  END ConvertEnv;

PROCEDURE Wait(p: T): ExitCode =
  VAR status: WinDef.DWORD; error: WinDef.DWORD;
  BEGIN
    IF NOT p.waitOk THEN RAISE WaitAlreadyCalled END;
    p.waitOk := FALSE;
    TRY
      IF WinBase.WaitForSingleObject(p.info.hProcess, WinBase.INFINITE) #
	 WinBase.WAIT_OBJECT_0 THEN RAISE InternalError
      END;
      IF WinBase.GetExitCodeProcess(p.info.hProcess, ADR(status)) = 0 THEN
	error := WinBase.GetLastError();
	RAISE InternalError
      END;
    FINALLY
      TRY CloseHandle(p.info.hProcess) EXCEPT ELSE END;
      TRY CloseHandle(p.info.hThread) EXCEPT ELSE END;
    END;
    RETURN Word.And(status, LAST(ExitCode))
  END Wait;

PROCEDURE Exit(n: ExitCode) =
  BEGIN
    RTProcess.Exit(n)
  END Exit;

PROCEDURE Crash(msg: TEXT) =
  BEGIN
    RTProcess.Crash(msg)
  END Crash;

PROCEDURE RegisterExitor(p: PROCEDURE()) =
  BEGIN
    RTProcess.RegisterExitor(p)
  END RegisterExitor;

PROCEDURE GetID(p: T): ID =
  BEGIN RETURN LOOPHOLE(p.info.hProcess, ID) END GetID;

PROCEDURE GetMyID(): ID =
  BEGIN RETURN LOOPHOLE(WinBase.GetCurrentProcessId(), ID) END GetMyID;

VAR
  stdin_g  := GetFileHandle(WinBase.STD_INPUT_HANDLE,  FileWin32.Read);
  stdout_g := GetFileHandle(WinBase.STD_OUTPUT_HANDLE, FileWin32.Write);
  stderr_g := GetFileHandle(WinBase.STD_ERROR_HANDLE,  FileWin32.Write);

PROCEDURE GetFileHandle(hd: WinDef.DWORD; ds: FileWin32.DirectionSet): File.T =
  VAR h := WinBase.GetStdHandle(hd);
  BEGIN
    IF (h # NIL)
      AND (h # LOOPHOLE (WinBase.INVALID_HANDLE_VALUE, WinDef.HANDLE)) THEN
      TRY RETURN FileWin32.New(h, ds)
      EXCEPT OSError.E => (* not available *)
      END;
    END;
    (* if we can't get the standard handles, we might be a GUI program
       so we'll lazily allocate a console if needed. *)
    RETURN LazyConsole.New (hd, ds);
  END GetFileHandle;

PROCEDURE GetStandardFileHandles(VAR stdin, stdout, stderr: File.T) =
  BEGIN
    stdin := stdin_g; stdout := stdout_g; stderr := stderr_g
  END GetStandardFileHandles;

PROCEDURE GetWorkingDirectory(): Pathname.T RAISES {OSError.E} =
  VAR chars: ARRAY [0..63] OF CHAR; rc := SetCWD(chars);
  BEGIN
    IF rc < NUMBER(chars) THEN
      RETURN Text.FromChars(SUBARRAY(chars, 0, rc))
    END;
    WITH refChars = NEW(REF ARRAY OF CHAR, rc+1) DO
      rc := SetCWD(refChars^);
      IF rc > NUMBER(refChars^) THEN RAISE InternalError END;
      RETURN Text.FromChars(SUBARRAY(refChars^, 0, rc))
    END
 END GetWorkingDirectory;

PROCEDURE SetCWD(VAR chars: ARRAY OF CHAR): INTEGER RAISES {OSError.E} =
  VAR rc := WinBase.GetCurrentDirectory(NUMBER(chars), ADR(chars[0]));
  BEGIN
    IF rc <= 0 THEN OSErrorWin32.Raise() END;
    IF (1 < rc) AND (rc <= NUMBER(chars))
      AND (chars[rc-1] = '\134')
      AND (chars[rc-2] # ':')
      AND (chars[rc-2] # '\134') THEN
      DEC(rc);   (* delete the trailing backslash *)
    END;
    RETURN rc
  END SetCWD;

PROCEDURE SetWorkingDirectory(path: Pathname.T) RAISES {OSError.E}=
  VAR fname := M3toC.SharedTtoS(path);  err: INTEGER;
  BEGIN
    IF WinBase.SetCurrentDirectory(fname) = 0 THEN
      err := WinBase.GetLastError();
      M3toC.FreeSharedS(path, fname);
      OSErrorWin32.Raise0(err);
    END;
    M3toC.FreeSharedS(path, fname);
  END SetWorkingDirectory;

BEGIN
END ProcessWin32.