0

So, on Windows, it seems execl() is broken in different ways across different versions of windows. I've spent a lot of time narrowing it down and debugging, and it doesn't really make sense, and I can only think there's something wrong with Microsoft's implementation of execl() (actually execlp() for me).

The only Windows version execlp() seems to work correctly on is 7.

On Windows 10, it works fine, until I compile with -mwindows in MinGW. Then it just makes my program terminate with a zero exit code.

On Windows XP, it interprets spaces in argument parameters as separate arguments, despite the actual number of arguments being clearly specified by the nature of the function's prototype...

So, looks like I'll have to use some Windows native function and wrap it in "#ifdef WIN32"s.

What I really need is execl() (execlp isn't necessary) like behavior on Windows, in that it replaces the current process image with a new one, and keeps network descriptors open like execl() will.

I'm really at a loss as to what good options are, and while CreateProcess seems somewhat able to do some of it, I can't find enough info on what I'm trying to do.

Any help is appreciated. Thanks!

Subsentient
  • 554
  • 3
  • 12
  • 3
    Windows doesn't really support this *nix style of coding. CreateProcess is how you create a new process. – David Heffernan May 22 '17 at 06:33
  • 1
    Could you please provide a code example that "works" on Windows 10 but breaks with MinGW? – rationalcoder May 22 '17 at 06:35
  • Behavior of "posix" functions in Windows doesn't match one of *nix system. there are also special path formats for example that are specific to Windows onlyand behavior changes\extends from version to version.. judging by that you say it works in Win7 you exactly got that problem. In MinGW you would use POSIX library, including `unistd.h` , also paths would be slashed, not backslashed ( e.g. "D:/Windows/System32") and you should specify 3rd parameter for execl – Swift - Friday Pie May 22 '17 at 06:36
  • The Windows XP behaviour is as-expected, and execl() should behave the same way in Windows 7 and Windows 10, [as documented](https://msdn.microsoft.com/en-us/library/431x4c1w.aspx). Windows processes receive a single string representing the command line, not an array of strings like *nix processes; if you want to pass an argument containing a space, you have to know what parsing logic the child process is going to use. – Harry Johnston May 22 '17 at 07:18
  • As for socket descriptors, they are inheritable by default, although [this question](http://stackoverflow.com/q/11847793/886887) says that some third-party products can cause this to break. That shouldn't be a major problem any more; LSPs are deprecated. So long as you tell CreateProcess to allow inheritance, you *should* be OK. – Harry Johnston May 22 '17 at 07:31

1 Answers1

3

Unfortunately, you will need a completely different code path on windows, because in the win32 subsystem, creating a process is coupled together with loading and running a new image in a single call: CreateProcess().

In a typical posix scenario, you would fork() your new process, set up things like e.g. file descriptors and then exec*() the new binary. To achieve something similar in Windows, you must rely on the possibilities you get from CreateProcess(). For open files (or sockets), win32 uses "handles" and these can be marked inheritable, for example I do the following for a pipe:

HANDLE pin, pout;
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = 0;
sa.bInheritHandle = 1;

if (!CreatePipe(&pin, &pout, &sa, 0))
{
    fprintf(stderr, "Error creating pipe: %lu\n", GetLastError());
    return;
}

This way, the pipe's handles are inheritable. Then, when calling CreateProcess(), by passing 1 (or TRUE) for bInheritHandles, the new process inherits all handles marked this way. In this example, I do the following:

STARTUPINFO si;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.hStdInput = nul;
si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
si.dwFlags |= STARTF_USESTDHANDLES;

// simple example, my production code looks different, e.g. quoting the command:
char cmdline[1024];
strcpy(cmdline, exename);
strcat(cmdline, " ");
snprintf(cmdline + strlen(cmdline), 1024 - strlen(cmdline), "%" PRIxPTR, (uintptr_t)pout);

// [...]

PROCESS_INFORMATION pi;
CreateProcess(0, cmdline, 0, 0, 1, 0, 0, 0, &si, &pi);

In the child, the pipe could be used like that:

uintptr_t testPipeHandleValue;
if (sscanf(argv[1], "%" SCNxPTR, &testPipeHandleValue) != 1)
{
    exit(EXIT_FAILURE);
}

int testPipeFd = _open_osfhandle(
            (intptr_t)testPipeHandleValue, _O_APPEND | _O_WRONLY);
FILE *testPipe = _fdopen(testPipeFd, "a");
setvbuf(testPipe, 0, _IONBF, 0);

Of course this will look different for a network socket, but I hope the general idea helps.