[MLton] Re: [MLton-commit] r6699

Matthew Fluet fluet at tti-c.org
Wed Jun 17 08:45:20 PDT 2009


On Wed, 17 Jun 2009, Wesley W. Terpstra wrote:
> On Wed, Jun 17, 2009 at 2:25 PM, Matthew Fluet<fluet at tti-c.org> wrote:
>> It seems that there are multiple, independent(?) issues.  Certainly one is
>> that a Cygwin program doesn't like to be started via CreateProcess with a
>> non-NULL lpEnvironment argument; I can't find any documentation for this,
>> but I can definitely demonstrate it with some short C programs.
>
> ... great. Is this a cygwin bug? This seems a pretty serious issue.
>
> At any rate, the solution seems clear except that lpEnvironment =>
> cygwin implosion. One possible work-around would be to temporarily
> change the calling process' environment before invoking CreateProcess.
> I don't find anyone else reporting this problem on google, though. Are
> you sure?

Here's my testing:

[fluet at winxp-cygwin tmp]$ cat child.c
#include <stdlib.h>
#include <stdio.h>

int main(int argc, const char* argv[]) {
   fprintf(stderr, "HW! [stderr]\n");
   fprintf(stdout, "HW! [stdout]\n");
   exit (5);
}
[fluet at winxp-cygwin tmp]$ gcc -Wall -o child.exe child.c

[fluet at winxp-cygwin tmp]$ cat parent.c
#include <windows.h>
#include <stdio.h>

#define BUFSIZE 4096

int main (int argc, const char* argv[]) {
   LPCTSTR modnamep = NULL;
   TCHAR modname[BUFSIZE];
   if (argc > 1) {
     lstrcpy(modname, argv[1]);
     printf("modname = %s\n", modname);
     modnamep = modname;
   } else { return 0; }
   LPTSTR cmdlinep = NULL;
   TCHAR cmdline[BUFSIZE];
   if (argc > 2) {
     lstrcpy(cmdline, argv[2]);
     printf("cmdline = %s\n", cmdline);
     cmdlinep = cmdline;
   }

   LPTSTR envp = NULL;
   TCHAR env[BUFSIZE];
   if (argc > 3) {
     envp = env;
     int i = 3;
     LPSTR curp = env;
     while (argc > i) {
       lstrcpy (curp, argv[i]);
       printf("env[%i] = %s\n", i - 3, argv[i]);
       curp += lstrlen(curp) + 1;
       i++;
     }
     *curp = (TCHAR)0;
   }

   STARTUPINFO siStartInfo;
   ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
   siStartInfo.cb = sizeof(STARTUPINFO);

   PROCESS_INFORMATION piProcInfo;
   ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));

   BOOL bSuccess = FALSE;
   printf("CreateProcess\n");
   bSuccess = CreateProcess(modnamep,      // module name
                            cmdlinep,      // command line
                            NULL,          // process security attributes
                            NULL,          // primary thread security attributes
                            TRUE,          // handles are inherited
                            0,             // creation flags
                            envp,          // use parent's environment
                            NULL,          // use parent's current directory
                            &siStartInfo,  // STARTUPINFO pointer
                            &piProcInfo);  // receives PROCESS_INFORMATION
   if ( ! bSuccess ) {
     printf("CreateProcess failed (%ld).\n", (long int)(GetLastError()));
     return 0;
   }
   printf("piProcInfo.hProcess = %ld\n", (long int)(piProcInfo.hProcess));

   DWORD status = 0;
   WaitForSingleObject(piProcInfo.hProcess, INFINITE);
   GetExitCodeProcess(piProcInfo.hProcess, &status);
   printf("status: %ld\n", status);

   CloseHandle(piProcInfo.hProcess);
   CloseHandle(piProcInfo.hThread);
   return 1;
}
[fluet at winxp-cygwin tmp]$ gcc -Wall -o parent.exe parent.c

[fluet at winxp-cygwin tmp]$ ./parent.exe 'C:\WINDOWS\system32\finger.exe' 'C:\WINDOWS\system32\finger.exe -l fluet at mlton.org'
modname = C:\WINDOWS\system32\finger.exe
cmdline = C:\WINDOWS\system32\finger.exe -l fluet at mlton.org
CreateProcess
piProcInfo.hProcess = 1856
[mlton.org]

> Finger: connect::Connection refused
status: 0
[fluet at winxp-cygwin tmp]$ ./parent.exe 'C:\WINDOWS\system32\finger.exe' 'C:\WINDOWS\system32\finger.exe -l fluet at mlton.org' 'FOO=bar'
modname = C:\WINDOWS\system32\finger.exe
cmdline = C:\WINDOWS\system32\finger.exe -l fluet at mlton.org
env[0] = FOO=bar
CreateProcess
piProcInfo.hProcess = 1856
Unknown host: mlton.org

status: 0

So, finger.exe is unhappy about not having the full environment (or, 
rather, the DNS resolver is unhappy), but the command runs as expected.

[fluet at winxp-cygwin tmp]$ ./parent.exe 'C:\WINDOWS\system32\cmd.exe' 'C:\WINDOWS\system32\cmd.exe /c set'
modname = C:\WINDOWS\system32\cmd.exe
cmdline = C:\WINDOWS\system32\cmd.exe /c set
CreateProcess
piProcInfo.hProcess = 1856
COMSPEC=C:\WINDOWS\system32\cmd.exe
PATH=C:\cygwin\home\fluet\bin;...elided...;c:\WINDOWS\system32\WindowsPowerShell\v1.0;C:\cygwin\bin
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS
PROMPT=$P$G
SYSTEMDRIVE=C:
SYSTEMROOT=C:\WINDOWS
WINDIR=C:\WINDOWS
status: 0
[fluet at winxp-cygwin tmp]$ ./parent.exe 'C:\WINDOWS\system32\cmd.exe' 'C:\WINDOWS\system32\cmd.exe /c set' 'FOO=bar'
modname = C:\WINDOWS\system32\cmd.exe
cmdline = C:\WINDOWS\system32\cmd.exe /c set
env[0] = FOO=bar
CreateProcess
piProcInfo.hProcess = 1856
COMSPEC=C:\WINDOWS\system32\cmd.exe
FOO=bar
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS
PROMPT=$P$G
status: 0

So, cmd.exe sees the variables named in an explicit lpEnvironment (and 
loses the environment of the parent; I suspect that COMSPEC, PATHEXT, and 
PROPMT are baked into cmd.exe).

On the other hand:

[fluet at winxp-cygwin tmp]$ ./parent.exe 'C:\cygwin\home\fluet\tmp\child.exe' 'C:\cygwin\home\fluet\tmp\child.exe 1 2 3'
modname = C:\cygwin\home\fluet\tmp\child.exe
cmdline = C:\cygwin\home\fluet\tmp\child.exe 1 2 3
CreateProcess
piProcInfo.hProcess = 1848
HW! [stderr]
HW! [stdout]
status: 5
[fluet at winxp-cygwin tmp]$ ./parent.exe 'C:\cygwin\home\fluet\tmp\child.exe' 'C:\cygwin\home\fluet\tmp\child.exe 1 2 3' 'FOO=bar'
modname = C:\cygwin\home\fluet\tmp\child.exe
cmdline = C:\cygwin\home\fluet\tmp\child.exe 1 2 3
env[0] = FOO=bar
CreateProcess
piProcInfo.hProcess = 1848
status: -1073741515

A little more investigation reveals that a cygwin program is unhappy 
unless C:\cygwin\bin is in the PATH environment variable:

[fluet at winxp-cygwin tmp]$ ./parent.exe 'C:\cygwin\home\fluet\tmp\child.exe' 'C:\cygwin\home\fluet\tmp\child.exe 1 2 3' 'PATH=C:\cygwin\bin'
modname = C:\cygwin\home\fluet\tmp\child.exe
cmdline = C:\cygwin\home\fluet\tmp\child.exe 1 2 3
env[0] = PATH=C:\cygwin\bin
CreateProcess
piProcInfo.hProcess = 1844
HW! [stderr]
HW! [stdout]
status: 5
[fluet at winxp-cygwin tmp]$ ./parent.exe 'C:\cygwin\home\fluet\tmp\child.exe' 'C:\cygwin\home\fluet\tmp\child.exe 1 2 3' 'PATH=C:\WINDOWS\system32'
modname = C:\cygwin\home\fluet\tmp\child.exe
cmdline = C:\cygwin\home\fluet\tmp\child.exe 1 2 3
env[0] = PATH=C:\WINDOWS\system32
CreateProcess
piProcInfo.hProcess = 1848
status: -1073741515

And, with the exception of needing C:\cygwin\bin in the PATH, we are able 
to manage the environment:

[fluet at winxp-cygwin tmp]$ cat environ.sml
val () =
    List.app
    (fn s => (print (String.toString s); print "\n"))
    (Posix.ProcEnv.environ ())
[fluet at winxp-cygwin tmp]$ mlton -output environ.exe environ.sml
[fluet at winxp-cygwin tmp]$ ./parent.exe 'C:\cygwin\home\fluet\tmp\environ.exe' 'C:\cygwin\home\fluet\tmp\environ.exe'
modname = C:\cygwin\home\fluet\tmp\environ.exe
cmdline = C:\cygwin\home\fluet\tmp\environ.exe
CreateProcess
piProcInfo.hProcess = 1848
PATH=/home/fluet/bin:...elided...:/cygdrive/c/WINDOWS/system32/WindowsPowerShell/v1.0:/usr/bin
SYSTEMDRIVE=C:
SYSTEMROOT=C:\\WINDOWS
WINDIR=C:\\WINDOWS
TERM=cygwin
HOME=/home/fluet
status: 0
[fluet at winxp-cygwin tmp]$ ./parent.exe 'C:\cygwin\home\fluet\tmp\environ.exe' 'C:\cygwin\home\fluet\tmp\environ.exe' 'PATH=C:\cygwin\bin' 'FOO=bar' 'BAR=foo'
modname = C:\cygwin\home\fluet\tmp\environ.exe
cmdline = C:\cygwin\home\fluet\tmp\environ.exe
env[0] = PATH=C:\cygwin\bin
env[1] = FOO=bar
env[2] = BAR=foo
CreateProcess
piProcInfo.hProcess = 1848
PATH=/usr/bin
FOO=bar
BAR=foo
TERM=cygwin
HOME=/home/fluet
status: 0

This also explains why the current implementation of create, that copies 
the whole environment via Posix.ProcEnv.environ (which would appear to 
have a PATH entry with the appropriate directory) fails.  The 'problem' is 
that the PATH environment variable returned by Posix.ProcEnv.environ has 
been cygwinified: it has '/usr/bin', not 'C:\cygwin\bin'.  Of course, 
'/usr/bin' means nothing to CreateProcess/Windows, which I'm guessing is 
using the PATH to find cygwin1.dll (in C:\cygwin\bin) for a cygwin 
program.  So, I suspect that the exit status -1073741515 corresponds to a 
dll load failure.  finger.exe and cmd.exe are only dependent upon core 
system dlls, which presumably can be found even in the absence of a PATH.

All in all, I think the way forward is clear:

  * create {env = NONE, ...} should result in calling CreateProcess with a
    NULL lpEnvironment; this is, by far, the most common case I would
    think.

  * create {env = SOME [...], ...} should behave as currently implemented;
    the user is responsible for establishing the correct environment (it is
    just the case that the correct environment for a Cygwin program is
    subtle)


More information about the MLton mailing list