Detecting named pipe disconnects with I/O completion - winapi

I have a question about the correct approach for detecting client disconnects using named pipes with I/O completion ports. We have a server that creates child processes with stdin/stdout redirected to named pipes. The pipes are opened OVERLAPPED.
We've seen that after the client issues CreateFile() the I/O completion port
receives a packet with lpNumberOfBytes of zero -- which quite effectively indicates a connection from the client. But detecting when the child process has closed its' stdin/stdout and exited does not generate a similar event.
We've come up with two approaches to detecting the named pipe disconnects;
1) periodically poll the process HANDLE of the child process to detect when the process has ended,
OR
2) create a separate thread which blocks on WaitForSingleObject() on the child process's HANDLE and when it becomes signaled the process has ended, to then generate PostQueuedCompletionStatus() to the I/O completion port with a prearranged COMPLETION_KEY.
Neither of these is difficult -- but I wanted to make sure I wasn't missing something obvious. Has anyone found an alternative to being notified when a named pipe associated with IOCP has been closed?

Ok, I discovered why the IOCP was not delivering disconnect packets, and it had todo with how I was testing the issue. We had developed a unittest harness and our unittest was acting as both server and client. When the child process ended, the child's write-pipe handle was still open in the unittest, and therefore IOCP did not unblock any handler threads.
To effectively run a pipe server requires you create a new thread and within that thread to do the work of connecting to the pipe, creating the child process and waiting for the process to end. After the child ends to then close the pipe handle which causes IOCP to then deliver a dequeue packet with lpNumberOfBytes set to zero.
Here is a sample of how we did this from a thread created with _beginthread().
void __cdecl childproc(void* p) {
TCHAR* pipename = (TCHAR*)p;
/* make sure pipe handle is "inheritable" */
SECURITY_ATTRIBUTES sattr;
sattr.nLength = sizeof(SECURITY_ATTRIBUTES);
sattr.bInheritHandle = TRUE;
sattr.lpSecurityDescriptor = NULL;
HANDLE pipe = ::CreateFile(
pipename,
GENERIC_READ | GENERIC_WRITE,
0,
&sattr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (pipe == INVALID_HANDLE_VALUE) {
_tprintf(_T("connect to named pipe failed %ld\n", GetLastError());
_endthread();
}
/* redirect stdin/stdout/stderr to pipe */
PROCESS_INFORMATION procinfo;
STARTUPINFO startinfo;
memset(&procinfo, 0, sizeof(procinfo));
memset(&startinfo, 0, sizeof(startinfo));
startinfo.cb = sizeof(startinfo);
startinfo.hStdError = pipe;
startinfo.hStdOutput = pipe;
startinfo.hStdInput = pipe;
startinfo.dwFlags |= STARTF_USESTDHANDLES;
/* create child to do a simple "cmd.exe /c dir" */
DWORD rc = ::CreateProcess(
_T("C:\\Windows\\System32\\cmd.exe"),
_T("C:\\Windows\\System32\\cmd.exe /C dir"),
NULL,
NULL,
TRUE,
0,
NULL,
NULL,
&startinfo,
&procinfo);
if (rc == 0) {
_tprintf(_T("cannot create child process: %ld\n"), GetLastError());
_endthread();
}
if (::WaitForSingleObject(procinfo.hProcess, INFINITE) != WAIT_OBJECT_0) {
_tprintf(_T("error waiting for child to end: %ld\n"), GetLastError());
}
/* cleanup */
::CloseHandle(procinfo.hProcess);
::CloseHandle(procinfo.hThread);
::CloseHandle(pipe);
_endthread();
}

Related

Windows named pipe intermittant connection problems

I've got a pair of windows processes that communicate via named pipes. The server calls CreateNamedPipeW and the client calls CreateFileW to open the same named pipe. Some times this works fine, and then can send messages back and forth. Some times this fails with the error ERROR_PIPE_BUSY on both ends.
I'm pretty sure there is exactly one copy of each of the server and client process. I'm also sure that there is exactly one thread on each end that does the pipe management. i.e. I don't think my problem is that there are multiple producers or consumers.
server:
OVERLAPPED openServerOverlapped;
...
SECURITY_ATTRIBUTES sa;
// ...
server = CreateNamedPipeW(
pipeName.c_str(),
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
1,
PIPE_BUFFER_SIZE,
PIPE_BUFFER_SIZE,
NMPWAIT_USE_DEFAULT_WAIT,
&sa
);
if (server != INVALID_HANDLE_VALUE) {
auto lastError = ConnectNamedPipe(server, &openServerOverlapped) == FALSE
? GetLastError()
: ERROR_PIPE_CONNECTED;
if (lastError == ERROR_IO_PENDING) {
DWORD bytesTransferred = 0;
lastError = GetOverlappedResultEx(
server, &openServerOverlapped, &bytesTransferred,
CLIENT_CONNECT_TIMEOUT, FALSE
) == FALSE
? GetLastError()
: ERROR_PIPE_CONNECTED;
}
if (lastError == ERROR_PIPE_CONNECTED) {
// start reading messages
} else {
// error handling, looks like "ConnectNamedPipe error..."
}
} else {
// error handling, looks like "CreateNamedPipeW error..."
}
We sometimes get errors like the following
got error: CreateNamedPipeW error: LastError=000000e7, All pipe instances are busy.
e7 is ERROR_PIPE_BUSY, which matches the erorr message we got from FormatMessage
When this is happening we get similar errors out of our client. Client is actually c#, but uses marshalling to call the same win32 APIs.
client:
while (true) {
// ...
pipeHandle = CreateFileW(fileName,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
IntPtr.Zero,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
IntPtr.Zero
);
if (pipeHandle.ToInt32() == INVALID_HANDLE_VALUE) {
// error handling, looks like "Failed to create pipe client..."
Thread.Sleep(5000);
continue;
}
// ...
}
We get logging like:
Failed to create pipe client, win32 error: 231
231 = e7, so same error: ERROR_PIPE_BUSY
According to the documentation:
Named Pipe Client
A named pipe client uses the CreateFile function to open a handle to a named pipe. If the pipe exists but all of its instances are busy, CreateFile returns INVALID_HANDLE_VALUE and the GetLastError function returns ERROR_PIPE_BUSY.
The reason for the error is that all of its instances are busy.
I suggest you try to use the CreateNamedPipe() function to create another instance of a named pipe. And also try to use the DisconnectNamedPipe function to disconnect the server end of a named pipe instance from a client process, and then you can connect that instance to another client by using the ConnectNamedPipe() function.
I suggest you refer to the example: Multithreaded Pipe Server

Do I have to specify handles to be inherited with PROC_THREAD_ATTRIBUTE_LIST when using CreateProcess in threads?

I'm writing a program that spawns multiple child processes simultaneously and communicates to each of them by redirecting their stdin/stdout to anonymous pipes (as described here).
This is my actual code adapted from above example:
HANDLE h_child_stdout_r = NULL;
HANDLE h_child_stdout_w = NULL;
HANDLE h_child_stdin_r = NULL;
HANDLE h_child_stdin_w = NULL;
SECURITY_ATTRIBUTES saAttr;
BOOL bSuccess = TRUE;
// Set the bInheritHandle flag so pipe handles are inherited.
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDOUT.
bSuccess &= CreatePipe(&h_child_stdout_r, &h_child_stdout_w, &saAttr, 0);
// Ensure the read handle to the pipe for STDOUT is not inherited.
bSuccess &= SetHandleInformation(h_child_stdout_r, HANDLE_FLAG_INHERIT, 0);
// Create a pipe for the child process's STDIN.
bSuccess &= CreatePipe(&h_child_stdin_r, &h_child_stdin_w, &saAttr, 0);
// Ensure the write handle to the pipe for STDIN is not inherited.
bSuccess &= SetHandleInformation(h_child_stdin_w, HANDLE_FLAG_INHERIT, 0);
if(!bSuccess)
{
throw dlg_exception("Failed to create child process handles");
}
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );
ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
// Set up members of the STARTUPINFO structure.
// This structure specifies the STDIN and STDOUT handles for redirection.
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
siStartInfo.hStdError = h_child_stdout_w;
siStartInfo.hStdOutput = h_child_stdout_w;
siStartInfo.hStdInput = h_child_stdin_r;
DWORD processFlags = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT;
// Create the child process.
bSuccess = CreateProcess(
const_cast<wchar_t *>(utf8::widen(exeName).c_str()), // application name
const_cast<wchar_t *>(utf8::widen(cmd).c_str()), // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
processFlags, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
// If an error occurs, exit the application.
if (!bSuccess)
{
string msg = "Create process failed with error code ";
msg.append( std::to_string(GetLastError()) );
throw dlg_exception(msg.c_str());
}
// Close handles to the child process and its primary thread.
// Some applications might keep these handles to monitor the status
// of the child process, for example.
CloseHandle(piProcInfo.hProcess);
CloseHandle(piProcInfo.hThread);
// Close handles to the stdin and stdout pipes no longer needed by the child process.
// If they are not explicitly closed, there is no way to recognize that the child process has ended.
CloseHandle(h_child_stdout_w);
CloseHandle(h_child_stdin_r);
The above function will be called from multiple threads that are responsible for creating child processes and reading their output.
Everything works as expected but just to make sure I started searching about thread-safety of CreateProcess and I came across this article which in short says there will be an issue if multiple calls to CreateProcess are made from different threads because then each child process will inherit ALL handles from the parent process.
Now I have two questions:
1- Will this really be an issue for me? Since I am only redirecting the child process's stdin and stdout to the pipe handles, so I'm not sure if there will be a problem even if all the handles are inherited by the child. The child is merely writing to its own stdout and doesn't care about what handles it has inherited right?
2- I tried inspecting the child processes with Sysinternal's Process Explorer but as far as I could tell only two handles were inherited by each child not all of them.
Here's a screenshot of Process Explorer. As you can see the parent process has created 4 child processes. For each process I created, a new pair of handles would appear in parent process's list of handles (I have highlighted the first two):
As you can see there is four pairs of these, one for each child process.
And for each child process the handle list looks like this:
After reading the article what I was expecting was that the fourth child process would have all the handles listed because it's supposed to inherit all inheritable handles. But it only has two handles like all other childs.
So I'm wondering if I can get away with my current code without doing the PROC_THREAD_ATTRIBUTE_LIST which looks complicated and unfun.

Named Pipes server, how to interrupt or timeout the wait for client connection and for incoming data

I am writing a simple Named Pipes server for Windows, calling the Windows API (in Java with JNA but this is not relevant).
I am trying to figure out how to avoid that the server stays stuck forever waiting for a client to connect or for data to come from the client.
The server code does the following:
1) It creates the pipe by calling CreateNamedPipe, with PIPE_WAIT in the dwPipeMode argument.
2) It calls ConnectNamedPipe which doesn't return until a client has connected.
3) It enters a loop where it repeatedly reads a message from the client by calling ReadFile which doesn't return until data is read, and for each received message it sends a message back to the client in response by calling WriteFile.
4) After many such conversations the client and the server will disconnect from the pipe.
I just would like to be able to set timeouts in the wait for ConnectNamedPipe at step 2 and ReadFile at step 3, and I can't see where to set the timeouts. There is the nDefaultTimeOut argument in CreateNamedPipe function, but it doesn't really sound to be intended for that; the API doc says:
The default time-out value, in milliseconds, if the WaitNamedPipe function specifies NMPWAIT_USE_DEFAULT_WAIT.
So the nDefaultTimeOut arg in CreateNamedPipe sounds like the default timeout that the clients which would connect to the pipe would use for their operations and only if they call the WaitNamedPipe function. In fact in my tests values of 0 or 1000 don't make a difference, the call to ConnectNamedPipe never returns (unless a client connects). What I'm looking for is timeouts in the server instead, on the calls to ConnectNamedPipe and ReadFile.
As the doc of CreateNamedPipe, for the dwPipeMode argument with PIPE_WAIT says, Blocking mode is enabled. When the pipe handle is specified in the ReadFile, WriteFile, or ConnectNamedPipe function, the operations are not completed until there is data to read, all data is written, or a client is connected. Use of this mode can mean waiting indefinitely in some situations for a client process to perform an action.
So maybe the way to implement such timeouts is to create the pipe in non-blocking mode (with PIPE_NOWAIT instead of PIPE_WAIT) so that calls to ReadFile, WriteFile and ConnectNamedPipe return immediately, and then somehow monitor myself the event (client connected or data received) in a loop, and check myself within the loop whether a timeout elapsed or another interrupting event occurred (like the user clicking a Cancel button) ?
ADDED: It looks like for the ReadFile call I might be able to use PeekNamedPipe which returns immediately, to check if there is data to read, and only then call ReadFile. I will try that. But I still have the same problem for the call to ConnectNamedPipe.
ADDED: As I suspected and the answers confirmed, being a novice to pipes I was looking at them from a somehow skew angle, from which the need for timeouts appeared greater than it actually is.
F.ex. the reasoning behind wanting to timeout calls to ReadFile was that if I (the server) am inside it reading data from the client and the client suddenly shuts down, sometimes I might end up stuck inside the ReadFile. But now I know that if the ReadFile is reading from a pipe and the client shuts down, the ReadFile will always error out, so the execution won't be stuck inside it.
I suggest you set FILE_FLAG_OVERLAPPED and use an event to check/wait for completion.
Although this is originally intended for asynchronous IO, you can instead time the event to your predefined time to live.
If you then wanted to cancel the I/O operation, you can use the CancelIo() function. If you just wanted to do some work and then resume waiting, you can do that too - timing out the wait doesn't automatically cancel the I/O, so you would not need to call ConnectNamedPipe again.
You could also, as you yourself suggested set PIPE_NOWAIT and poll the connection until successfull, either way should bring the same result in this use case. Note however that this is legacy functionality and Microsoft discourage use of this option.
Some real-world code to demonstrate the asynchronous use of the server end of a pipe, in a GUI application:
void wait_for_object(HANDLE object)
{
DWORD dw;
MSG msg;
for (;;)
{
dw = MsgWaitForMultipleObjectsEx(1, &object, INFINITE, QS_ALLINPUT, 0);
if (dw == WAIT_OBJECT_0) break;
if (dw == WAIT_OBJECT_0 + 1)
{
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) DispatchMessage(&msg);
continue;
}
srvfail(L"sleep() messageloop", GetLastError());
}
}
HANDLE server_pipe;
HANDLE io_event;
void pipe_connection(void)
{
OVERLAPPED overlapped;
DWORD dw, err;
SecureZeroMemory(&overlapped, sizeof(overlapped));
overlapped.hEvent = io_event;
if (!ReadFile(server_pipe, input_buffer, sizeof(input_buffer) - 1, NULL, &overlapped))
{
err = GetLastError();
if (err == ERROR_IO_PENDING)
{
wait_for_object(io_event);
if (!GetOverlappedResult(server_pipe, &overlapped, &dw, FALSE))
{
srvfail(L"Read from pipe failed asynchronously.", GetLastError());
}
}
else
{
srvfail(L"Read from pipe failed synchronously.", GetLastError());
}
}
else
{
if (!GetOverlappedResult(server_pipe, &overlapped, &dw, FALSE))
{
srvfail(L"GetOverlappedResult failed reading from pipe.", GetLastError());
}
}
input_buffer[dw] = '\0';
process_command();
if (!WriteFile(server_pipe, &output_struct,
((char *)&output_struct.output_string - (char *)&output_struct) + output_struct.string_length,
NULL, &overlapped))
{
err = GetLastError();
if (err == ERROR_IO_PENDING)
{
wait_for_object(io_event);
if (!GetOverlappedResult(server_pipe, &overlapped, &dw, FALSE))
{
srvfail(L"Write to pipe failed asynchronously.", GetLastError());
}
}
else
{
srvfail(L"Write to pipe failed synchronously.", GetLastError());
}
}
else
{
if (!GetOverlappedResult(server_pipe, &overlapped, &dw, FALSE))
{
srvfail(L"GetOverlappedResult failed writing to pipe.", GetLastError());
}
}
if (!FlushFileBuffers(server_pipe)) srvfail(L"FlushFileBuffers failed.", GetLastError());
if (!DisconnectNamedPipe(server_pipe)) srvfail(L"DisconnectNamedPipe failed.", GetLastError());
}
void server(void)
{
OVERLAPPED overlapped;
DWORD err, dw;
// Create the named pipe
server_pipe = CreateNamedPipe(pipe_name, PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, 1, buffer_size, buffer_size, 0, NULL);
if (server_pipe == INVALID_HANDLE_VALUE) srvfail(L"CreateNamedPipe failed.", GetLastError());
// Wait for connections
io_event = CreateEvent(NULL, FALSE, FALSE, NULL);
if (io_event == NULL) srvfail(L"CreateEvent(io_event) failed.", GetLastError());
for (;;)
{
SecureZeroMemory(&overlapped, sizeof(overlapped));
overlapped.hEvent = io_event;
if (!ConnectNamedPipe(server_pipe, &overlapped))
{
err = GetLastError();
if (err == ERROR_PIPE_CONNECTED)
{
pipe_connection();
}
else if (err == ERROR_IO_PENDING)
{
wait_for_object(io_event);
if (!GetOverlappedResult(server_pipe, &overlapped, &dw, FALSE))
{
srvfail(L"Pipe connection failed asynchronously.", GetLastError());
}
pipe_connection();
}
else
{
srvfail(L"Pipe connection failed synchronously.", GetLastError());
}
}
else
{
if (!GetOverlappedResult(server_pipe, &overlapped, &dw, FALSE))
{
srvfail(L"GetOverlappedResult failed connecting pipe.", GetLastError());
}
pipe_connection();
}
}
}
(This code has been edited down from the original to remove extraneous logic. I haven't tried compiling the edited version, so there may be some minor problems. Also note the use of global variables, which is OK in my case because the application is very small, but should usually be avoided.)
The use of MsgWaitForMultipleObjectsEx() allows window messages to be processed while you are waiting for I/O to complete. If you were also waiting for something else to happen, you could pass it an array of handles rather than just a single handle - for example, if you wanted to monitor a child process and do something when it exited, you could pass an array containing both io_event and the process handle. And if you just had to do some other job periodically, you could set a timeout for the wait, or use a window timer.

Child process won't suicide if parent dies

I have a subprocess (running on MacOS) that I want to kill itself if the parent quits, exits, terminates, is killed or crashes. Having followed the advice from How to make child process die after parent exits? I can't get it to quietly kill itself if the parent program crashes. It will go to 100% CPU until I manually kill it.
Here are the key points of the code:
int main(int argc, char *argv[])
{
// Catch signals
signal(SIGINT, interruptHandler);
signal(SIGABRT, interruptHandler);
signal(SIGTERM, interruptHandler);
signal(SIGPIPE, interruptHandler);
// Create kqueue event filter
int kqueue_fd = kqueue();
struct kevent kev, recv_kev;
EV_SET(&kev, parent_pid, EVFILT_PROC, EV_ADD|EV_ENABLE, NOTE_EXIT, 0, NULL);
kevent(kqueue_fd, &kev, 1, NULL, 0, NULL);
struct pollfd kqpoll;
kqpoll.fd = kqueue_fd;
kqpoll.events = POLLIN;
// Start a run loop
while(processEvents())
{
if(kill(parent_pid, 0) == -1)
if(errno == ESRCH)
break;
if(poll(&kqpoll, 1, 0) == 1)
if(kevent(kqueue_fd, NULL, 0, &recv_kev, 1, NULL))
break;
parent_pid = getppid();
if(parent_pid == 1)
break;
sleep(a_short_time);
// (simple code here causes subprocess to sleep longer if it hasn't
// received any events recently)
}
}
Answering my own question here:
The reason for this problem was not down to detecting whether the parent process had died. In processEvents() I was polling the pipe from the parent process to see if there was any communication. When the parent died, poll() returned a value of 1 and the read loop thought there was infinite data waiting to be read.
The solution was to detect whether the pipe had been disconnected or not.

Waiting for grandchild processes in windows

Is it possible to wait for all processes launched by a child process in Windows? I can't modify the child or grandchild processes.
Specifically, here's what I want to do. My process launches uninstallA.exe. The process uninistallA.exe launches uninstallB.exe and immediately exits, and uninstallB.exe runs for a while. I'd like to wait for uninstallB.exe to exit so that I can know when the uninstall is finished.
Create a Job Object with CreateJobObject. Use CreateProcess to start UninstallA.exe in a suspended state. Assign that new process to your job object with AssignProcessToJobObject. Start UninstallA.exe running by calling ResumeThread on the handle of the thread you got back from CreateProcess.
Then the hard part: wait for the job object to complete its execution. Unfortunately, this is quite a bit more complex than anybody would reasonably hope for. The basic idea is that you create an I/O completion port, then you create the object object, associate it with the I/O completion port, and finally wait on the I/O completion port (getting its status with GetQueuedCompletionStatus). Raymond Chen has a demonstration (and explanation of how this came about) on his blog.
Here's a technique that, while not infallible, can be useful if for some reason you can't use a job object. The idea is to create an anonymous pipe and let the child process inherit the handle to the write end of the pipe.
Typically, grandchild processes will also inherit the write end of the pipe. In particular, processes launched by cmd.exe (e.g., from a batch file) will inherit handles.
Once the child process has exited, the parent process closes its handle to the write end of the pipe, and then attempts to read from the pipe. Since nobody is writing to the pipe, the read operation will block indefinitely. (Of course you can use threads or asynchronous I/O if you want to keep doing stuff while waiting for the grandchildren.)
When (and only when) the last handle to the write end of the pipe is closed, the write end of the pipe is automatically destroyed. This breaks the pipe and the read operation completes and reports an ERROR_BROKEN_PIPE failure.
I've been using this code (and earlier versions of the same code) in production for a number of years.
// pwatch.c
//
// Written in 2011 by Harry Johnston, University of Waikato, New Zealand.
// This code has been placed in the public domain. It may be freely
// used, modified, and distributed. However it is provided with no
// warranty, either express or implied.
//
// Launches a process with an inherited pipe handle,
// and doesn't exit until (a) the process has exited
// and (b) all instances of the pipe handle have been closed.
//
// This effectively waits for any child processes to exit,
// PROVIDED the child processes were created with handle
// inheritance enabled. This is usually but not always
// true.
//
// In particular if you launch a command shell (cmd.exe)
// any commands launched from that command shell will be
// waited on.
#include <windows.h>
#include <stdio.h>
void error(const wchar_t * message, DWORD err) {
wchar_t msg[512];
swprintf_s(msg, sizeof(msg)/sizeof(*msg), message, err);
printf("pwatch: %ws\n", msg);
MessageBox(NULL, msg, L"Error in pwatch utility", MB_OK | MB_ICONEXCLAMATION | MB_SYSTEMMODAL);
ExitProcess(err);
}
int main(int argc, char ** argv) {
LPWSTR lpCmdLine = GetCommandLine();
wchar_t ch;
DWORD dw, returncode;
HANDLE piperead, pipewrite;
STARTUPINFO si;
PROCESS_INFORMATION pi;
SECURITY_ATTRIBUTES sa;
char buffer[1];
while (ch = *(lpCmdLine++)) {
if (ch == '"') while (ch = *(lpCmdLine++)) if (ch == '"') break;
if (ch == ' ') break;
}
while (*lpCmdLine == ' ') lpCmdLine++;
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
if (!CreatePipe(&piperead, &pipewrite, &sa, 1)) error(L"Unable to create pipes: %u", GetLastError());
GetStartupInfo(&si);
if (!CreateProcess(NULL, lpCmdLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
error(L"Error %u creating process.", GetLastError());
if (WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_FAILED) error(L"Error %u waiting for process.", GetLastError());
if (!GetExitCodeProcess(pi.hProcess, &returncode)) error(L"Error %u getting exit code.", GetLastError());
CloseHandle(pipewrite);
if (ReadFile(piperead, buffer, 1, &dw, NULL)) {
error(L"Unexpected data received from pipe; bug in application being watched?", ERROR_INVALID_HANDLE);
}
dw = GetLastError();
if (dw != ERROR_BROKEN_PIPE) error(L"Unexpected error %u reading from pipe.", dw);
return returncode;
}
There is not a generic way to wait for all grandchildren but for your specific case you may be able to hack something together. You know you are looking for a specific process instance. I would first wait for uninstallA.exe to exit (using WaitForSingleObject) because at that point you know that uninstallB.exe has been started. Then use EnumProcesses and GetProcessImageFileName from PSAPI to find the running uninstallB.exe instance. If you don't find it you know it has already finished, otherwise you can wait for it.
An additional complication is that if you need to support versions of Windows older than XP you can't use GetProcessImageFileName, and for Windows NT you can't use PSAPI at all. For Windows 2000 you can use GetModuleFileNameEx but it has some caveats that mean it might fail sometimes (check docs). If you have to support NT then look up Toolhelp32.
Yes this is super ugly.
Use a named mutex.
One possibility is to install Cygwin and then use the ps command to watch for the grandchild to exit

Resources