Cancel IO for pipe - winapi

I 'm using the CreatePipe to redirect stdin/out from a process to my process.
http://msdn.microsoft.com/en-us/library/windows/desktop/aa365152(v=vs.85).aspx
This works ok so far. The problem is when I want to terminate the thread that waits for the client process to write something.
I can use CancelIoEx() but this only works in Vista+, and I also want an XP solution. Without CancelIoEx(), ReadFile() in the other thread never returns.
I cannot also use OVERLAPPED ReadFile, for pipes created with CreatePipe do not support it.
Any options?

Save a handle to the write end of the stdout pipe when creating the child process. You can then write a character to this to unblock the thread that has called ReadFile (that is reading from the read end of the stdout pipe). In order not to interpret this as data, create an Event (CreateEvent) that is set (SetEvent) in the thread that writes the dummy character, and is checked after ReadFile returns. A bit messy, but seems to work.
/* Init */
stdout_closed_event = CreateEvent(NULL, TRUE, FALSE, NULL);
/* Read thread */
read_result = ReadFile(stdout_read, data, buf_len, &bytes_read, NULL);
if (!read_result)
ret = -1;
else
ret = bytes_read;
if ((bytes_read > 0) && (WAIT_OBJECT_0 == WaitForSingleObject(stdout_closed_event, 0))) {
if (data[bytes_read-1] == eot) {
if (bytes_read > 1) {
/* Discard eot character, but return the rest of the read data that should be valid. */
ret--;
} else {
/* No data. */
ret = -1;
}
}
}
/* Cancel thread */
HMODULE mod = LoadLibrary (L"Kernel32.dll");
BOOL WINAPI (*cancel_io_ex) (HANDLE, LPOVERLAPPED) = NULL;
if (mod != NULL) {
cancel_io_ex = (BOOL WINAPI (*) (HANDLE, LPOVERLAPPED)) GetProcAddress (mod, "CancelIoEx");
}
if (cancel_io_ex != NULL) {
cancel_io_ex(stdout_write_pipe, NULL);
} else {
SetEvent(stdout_closed_event);
WriteFile(stdout_write_pipe, &eot, 1, &written, NULL);
}

Related

Why is my file with redirected stderr empty? [duplicate]

In the following program I print to the console using two different functions
#include <windows.h>
int main() {
HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD byteswritten;
WriteConsole(h, "WriteConsole", 12, &byteswritten, NULL);
WriteFile(h, "WriteFile", 9, &byteswritten, NULL);
}
If when I execute this program and redirect it's output using a > out.txt or a 1> out.txt nothing gets printed to the console (as expected) but the contents of out.txt are only
WriteFile
What is different between the two that allows calls to WriteFile to be redirected to the file and calls to WriteConsole to go to ... nowhere
Tested with gcc and msvc on windows 10
WriteConsole only works with console screen handles, not files nor pipes.
If you are only writing ASCII content you can use WriteFile for everything.
If you need to write Unicode characters you can use GetConsoleMode to detect the handle type, it fails for everything that is not a console handle.
When doing raw output like this you also have to deal with the BOM if the handle is redirected to a file.
This blog post is a good starting point for dealing with Unicode in the Windows console...
Edit 2021:
Windows 10 now has the ConPTY API (aka pseudo-console), which basically allows any program to act like the console for another program, thus enables capturing output that is directly written to the console.
This renders my original answer obsolete for Windows versions that support ConPTY.
Original answer:
From the reference:
WriteConsole fails if it is used with a standard handle that is
redirected to a file. If an application processes multilingual output
that can be redirected, determine whether the output handle is a
console handle (one method is to call the GetConsoleMode function and
check whether it succeeds). If the handle is a console handle, call
WriteConsole. If the handle is not a console handle, the output is
redirected and you should call WriteFile to perform the I/O.
This is only applicable if you control the source code of the application that you want to redirect. I recently had to redirect output from a closed-source application that unconditionally called WriteConsole() so it could not be redirected normally.
Reading the console screen buffer (as suggested by this answer) prooved to be unreliable, so I used Microsoft Detours library to hook the WriteConsole() API in the target process and call WriteFile() if necessary. Otherwise call the original WriteConsole() function.
I created a hook DLL based on the example of Using Detours:
#include <windows.h>
#include <detours.h>
// Target pointer for the uninstrumented WriteConsoleW API.
//
auto WriteConsoleW_orig = &WriteConsoleW;
// Detour function that replaces the WriteConsoleW API.
//
BOOL WINAPI WriteConsoleW_hooked(
_In_ HANDLE hConsoleOutput,
_In_ const VOID *lpBuffer,
_In_ DWORD nNumberOfCharsToWrite,
_Out_ LPDWORD lpNumberOfCharsWritten,
_Reserved_ LPVOID lpReserved
)
{
// Check if this actually is a console screen buffer handle.
DWORD mode;
if( GetConsoleMode( hConsoleOutput, &mode ) )
{
// Forward to the original WriteConsoleW() function.
return WriteConsoleW_orig( hConsoleOutput, lpBuffer, nNumberOfCharsToWrite, lpNumberOfCharsWritten, lpReserved );
}
else
{
// This is a redirected handle (e. g. a file or a pipe). We multiply with sizeof(WCHAR), because WriteFile()
// expects the number of bytes, but WriteConsoleW() gets passed the number of characters.
BOOL result = WriteFile( hConsoleOutput, lpBuffer, nNumberOfCharsToWrite * sizeof(WCHAR), lpNumberOfCharsWritten, nullptr );
// WriteFile() returns number of bytes written, but WriteConsoleW() has to return the number of characters written.
if( lpNumberOfCharsWritten )
*lpNumberOfCharsWritten /= sizeof(WCHAR);
return result;
}
}
// DllMain function attaches and detaches the WriteConsoleW_hooked detour to the
// WriteConsoleW target function. The WriteConsoleW target function is referred to
// through the WriteConsoleW_orig target pointer.
//
BOOL WINAPI DllMain(HINSTANCE hinst, DWORD dwReason, LPVOID reserved)
{
if (DetourIsHelperProcess()) {
return TRUE;
}
if (dwReason == DLL_PROCESS_ATTACH) {
DetourRestoreAfterWith();
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)WriteConsoleW_orig, WriteConsoleW_hooked);
DetourTransactionCommit();
}
else if (dwReason == DLL_PROCESS_DETACH) {
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)WriteConsoleW_orig, WriteConsoleW_hooked);
DetourTransactionCommit();
}
return TRUE;
}
Note: In the WriteFile() branch I don't write a BOM (byte order mark), because it is not always wanted (e. g. when redirecting to a pipe instead of a file or when appending to an existing file). An application that is using the DLL to redirect process output to a file can simply write the UTF-16 LE BOM on its own before launching the redirected process.
The target process is created using DetourCreateProcessWithDllExW(), specifying the name of our hook DLL as argument for the lpDllName parameter. The other arguments are identical to how you create a redirected process via the CreateProcessW() API. I won't go into detail, because these are all well documented.
The code below can be used to redirect console output if the other party uses WriteConsole. The code reads the output via a hidden console screen buffer. I've written this code to intercept debug output some directshow drivers write to the console. Directshow drivers have the habit of doing things drivers should not do, like writing unwanted logfiles, writing to console and crashing.
// info to redirected console output
typedef struct tagRedConInfo
{
// hidden console
HANDLE hCon;
// old console handles
HANDLE hOldConOut;
HANDLE hOldConErr;
// buffer to read screen content
CHAR_INFO *BufData;
INT BufSize;
//
} TRedConInfo;
//------------------------------------------------------------------------------
// GLOBALS
//------------------------------------------------------------------------------
// initial handles
HANDLE gv_hOldConOut;
HANDLE gv_hOldConErr;
//------------------------------------------------------------------------------
// PROTOTYPES
//------------------------------------------------------------------------------
/* init redirecting the console output */
BOOL Shell_InitRedirectConsole(BOOL,TRedConInfo*);
/* done redirecting the console output */
BOOL Shell_DoneRedirectConsole(TRedConInfo*);
/* read string from hidden console, then clear */
BOOL Shell_ReadRedirectConsole(TRedConInfo*,TCHAR*,INT);
/* clear buffer of hidden console */
BOOL Shell_ClearRedirectConsole(TRedConInfo*);
//------------------------------------------------------------------------------
// IMPLEMENTATIONS
//------------------------------------------------------------------------------
/***************************************/
/* init redirecting the console output */
/***************************************/
BOOL Shell_InitRedirectConsole(BOOL in_SetStdHandles, TRedConInfo *out_RcInfo)
{
/* locals */
HANDLE lv_hCon;
SECURITY_ATTRIBUTES lv_SecAttr;
// preclear structure
memset(out_RcInfo, 0, sizeof(TRedConInfo));
// prepare inheritable handle just in case an api spans an external process
memset(&lv_SecAttr, 0, sizeof(SECURITY_ATTRIBUTES));
lv_SecAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
lv_SecAttr.bInheritHandle = TRUE;
// create hidden console buffer
lv_hCon = CreateConsoleScreenBuffer(
GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE,
&lv_SecAttr, CONSOLE_TEXTMODE_BUFFER, 0);
// failed to create console buffer?
if (lv_hCon == INVALID_HANDLE_VALUE)
return FALSE;
// store
out_RcInfo->hCon = lv_hCon;
// set as standard handles for own process?
if (in_SetStdHandles)
{
// mutex the globals
WaitForGlobalVarMutex();
// remember the old handles
out_RcInfo->hOldConOut = GetStdHandle(STD_OUTPUT_HANDLE);
out_RcInfo->hOldConErr = GetStdHandle(STD_ERROR_HANDLE);
// set hidden console as std output
SetStdHandle(STD_OUTPUT_HANDLE, lv_hCon);
SetStdHandle(STD_ERROR_HANDLE, lv_hCon);
// is this the first instance?
if (!gv_hOldConOut)
{
// inform our own console output code about the old handles so our own
// console will be writing to the real console, only console output from
// other parties will write to the hidden console
gv_hOldConOut = out_RcInfo->hOldConOut;
gv_hOldConErr = out_RcInfo->hOldConErr;
}
// release mutex
ReleaseGlobalVarMutex();
}
// done
return TRUE;
}
/***************************************/
/* done redirecting the console output */
/***************************************/
BOOL Shell_DoneRedirectConsole(TRedConInfo *in_RcInfo)
{
// validate
if (!in_RcInfo->hCon)
return FALSE;
// restore original handles?
if (in_RcInfo->hOldConOut)
{
// mutex the globals
WaitForGlobalVarMutex();
// restore original handles
SetStdHandle(STD_OUTPUT_HANDLE, in_RcInfo->hOldConOut);
SetStdHandle(STD_ERROR_HANDLE, in_RcInfo->hOldConErr);
// was this the first instance?
if (in_RcInfo->hOldConOut == gv_hOldConOut)
{
// clear
gv_hOldConOut = NULL;
gv_hOldConErr = NULL;
}
// release mutex
ReleaseGlobalVarMutex();
}
// close the console handle
CloseHandle(in_RcInfo->hCon);
// free read buffer
if (in_RcInfo->BufData)
MemFree(in_RcInfo->BufData);
// clear structure
memset(in_RcInfo, 0, sizeof(TRedConInfo));
// done
return TRUE;
}
/***********************************************/
/* read string from hidden console, then clear */
/***********************************************/
BOOL Shell_ReadRedirectConsole(TRedConInfo *in_RcInfo, TCHAR *out_Str, INT in_MaxLen)
{
/* locals */
TCHAR lv_C;
INT lv_X;
INT lv_Y;
INT lv_W;
INT lv_H;
INT lv_N;
INT lv_Len;
INT lv_Size;
INT lv_PrvLen;
COORD lv_DstSze;
COORD lv_DstOfs;
DWORD lv_Written;
SMALL_RECT lv_SrcRect;
CHAR_INFO *lv_BufData;
CONSOLE_SCREEN_BUFFER_INFO lv_Info;
// preclear output
out_Str[0] = 0;
// validate
if (!in_RcInfo->hCon)
return FALSE;
// reserve character for eos
--in_MaxLen;
// get current buffer info
if (!GetConsoleScreenBufferInfo(in_RcInfo->hCon, &lv_Info))
return FALSE;
// check whether there is something at all
if (!lv_Info.dwSize.X || !lv_Info.dwSize.Y)
return FALSE;
// limit the buffer passed onto read call otherwise it
// will fail with out-of-resources error
lv_DstSze.X = (INT16)(lv_Info.dwSize.X);
lv_DstSze.Y = (INT16)(lv_Info.dwSize.Y < 8 ? lv_Info.dwSize.Y : 8);
// size of buffer needed
lv_Size = lv_DstSze.X * lv_DstSze.Y * sizeof(CHAR_INFO);
// is previous buffer too small?
if (!in_RcInfo->BufData || in_RcInfo->BufSize < lv_Size)
{
// free old buffer
if (in_RcInfo->BufData)
MemFree(in_RcInfo->BufData);
// allocate read buffer
if ((in_RcInfo->BufData = (CHAR_INFO*)MemAlloc(lv_Size)) == NULL)
return FALSE;
// store new size
in_RcInfo->BufSize = lv_Size;
}
// always write to (0,0) in buffer
lv_DstOfs.X = 0;
lv_DstOfs.Y = 0;
// init src rectangle
lv_SrcRect.Left = 0;
lv_SrcRect.Top = 0;
lv_SrcRect.Right = lv_DstSze.X;
lv_SrcRect.Bottom = lv_DstSze.Y;
// buffer to local
lv_BufData = in_RcInfo->BufData;
// start at first string position in output
lv_Len = 0;
// loop until no more rows to read
do
{
// read buffer load
if (!ReadConsoleOutput(in_RcInfo->hCon, lv_BufData, lv_DstSze, lv_DstOfs, &lv_SrcRect))
return FALSE;
// w/h of actually read content
lv_W = lv_SrcRect.Right - lv_SrcRect.Left + 1;
lv_H = lv_SrcRect.Bottom - lv_SrcRect.Top + 1;
// remember previous position
lv_PrvLen = lv_Len;
// loop through rows of buffer
for (lv_Y = 0; lv_Y < lv_H; ++lv_Y)
{
// reset output position of current row
lv_N = 0;
// loop through columns
for (lv_X = 0; lv_X < lv_W; ++lv_X)
{
// is output full?
if (lv_Len + lv_N > in_MaxLen)
break;
// get character from screen buffer, ignore attributes
lv_C = lv_BufData[lv_Y * lv_DstSze.X + lv_X].Char.UnicodeChar;
// append character
out_Str[lv_Len + lv_N++] = lv_C;
}
// remove spaces at the end of the line
while (lv_N > 0 && out_Str[lv_Len+lv_N-1] == ' ')
--lv_N;
// if row was not blank
if (lv_N > 0)
{
// update output position
lv_Len += lv_N;
// is output not full?
if (lv_Len + 2 < in_MaxLen)
{
// append cr/lf
out_Str[lv_Len++] = '\r';
out_Str[lv_Len++] = '\n';
}
}
}
// update screen position
lv_SrcRect.Top = (INT16)(lv_SrcRect.Top + lv_H);
lv_SrcRect.Bottom = (INT16)(lv_SrcRect.Bottom + lv_H);
// until nothing is added or no more screen rows
} while (lv_PrvLen != lv_Len && lv_SrcRect.Bottom < lv_Info.dwSize.Y);
// remove last cr/lf
if (lv_Len > 2)
lv_Len -= 2;
// append eos
out_Str[lv_Len] = 0;
// total screen buffer size in characters
lv_Size = lv_Info.dwSize.X * lv_Info.dwSize.Y;
// clear the buffer with spaces
FillConsoleOutputCharacter(in_RcInfo->hCon, ' ', lv_Size, lv_DstOfs, &lv_Written);
// reset cursor position to (0,0)
SetConsoleCursorPosition(in_RcInfo->hCon, lv_DstOfs);
// done
return TRUE;
}
/**********************************/
/* clear buffer of hidden console */
/**********************************/
BOOL Shell_ClearRedirectConsole(TRedConInfo *in_RcInfo)
{
/* locals */
INT lv_Size;
COORD lv_ClrOfs;
DWORD lv_Written;
CONSOLE_SCREEN_BUFFER_INFO lv_Info;
// validate
if (!in_RcInfo->hCon)
return FALSE;
// get current buffer info
if (!GetConsoleScreenBufferInfo(in_RcInfo->hCon, &lv_Info))
return FALSE;
// clear from (0,0) onward
lv_ClrOfs.X = 0;
lv_ClrOfs.Y = 0;
// total screen buffer size in characters
lv_Size = lv_Info.dwSize.X * lv_Info.dwSize.Y;
// clear the buffer with spaces
FillConsoleOutputCharacter(in_RcInfo->hCon, ' ', lv_Size, lv_ClrOfs, &lv_Written);
// reset cursor position to (0,0)
SetConsoleCursorPosition(in_RcInfo->hCon, lv_ClrOfs);
// done
return TRUE;
}

ReadFile does not return while reading stdout from a child process after it ends

I am working on my library which needs to capture and process the standard output (and err) of a child process as it runs. The problem arises when ReadFile is used to read the output, it does not return once the process ends (gets killed or exits).
It looks like ReadFile is not able to detect that the other end of the pipe (the write handle) is closed. According to the documentation it should return FALSE and set the last error to ERROR_BROKEN_PIPE:
If an anonymous pipe is being used and the write handle has been closed, when ReadFile attempts to read using the pipe's corresponding read handle, the function returns FALSE and GetLastError returns ERROR_BROKEN_PIPE.
Here is my code, I have stripped out the irrelevant bits: (NOTE: I have updated the allium_start to follow the suggested changes, I am keeping the original for reference, please use the newer function code to find flaws)
bool allium_start(struct TorInstance *instance, char *config, allium_pipe *output_pipes) {
// Prepare startup info with appropriate information
SecureZeroMemory(&instance->startup_info, sizeof instance->startup_info);
instance->startup_info.dwFlags = STARTF_USESTDHANDLES;
SECURITY_ATTRIBUTES pipe_secu_attribs = {sizeof(SECURITY_ATTRIBUTES), NULL, true};
HANDLE pipes[2];
if (output_pipes == NULL) {
CreatePipe(&pipes[0], &pipes[1], &pipe_secu_attribs, 0);
output_pipes = pipes;
}
instance->startup_info.hStdOutput = output_pipes[1];
instance->startup_info.hStdError = output_pipes[1];
instance->stdout_pipe = output_pipes[0]; // Stored for internal reference
// Create the process
bool success = CreateProcessA(
NULL,
cmd,
NULL,
NULL,
config ? true : false,
0,
NULL,
NULL,
&instance->startup_info,
SecureZeroMemory(&instance->process, sizeof instance->process)
);
// Return on failure
if (!success) return false;
}
char *allium_read_stdout_line(struct TorInstance *instance) {
char *buffer = instance->buffer.data;
// Process the input
unsigned int read_len = 0;
while (true) {
// Read data
unsigned long bytes_read;
if (ReadFile(instance->stdout_pipe, buffer, 1, &bytes_read, NULL) == false || bytes_read == 0) return NULL;
// Check if we have reached end of line
if (buffer[0] == '\n') break;
// Proceed to the next character
++buffer; ++read_len;
}
// Terminate the new line with null character and return
// Special handling for Windows, terminate at CR if present
buffer[read_len >= 2 && buffer[-1] == '\r' ? -1 : 0] = '\0';
return instance->buffer.data;
}
The allium_start creates the pipe for output redirection (it uses the same pipe for both stdout and stderr to get merged streams) and then creates the child process. The other allium_read_stdout_line function is responsible for reading the output from the pipe and returning it when it encounters a new line.
The issue occurs at the ReadFile function call, it never returns if there is nothing to read after the process exits, from my understanding all the handles of a process are closed by Windows when it ends, so it looks like ReadFile is not able to detect the fact that the pipe (write handle) at the other end has been closed.
How do I fix this? I have been searching for a solution but I have found none so far, one potential option is to use multi-threading and put ReadFile in a separate thread so that it doesn't block the whole program, by using that method I can check if the process still exists periodically while I wait for the reading to finish... or kill/stop the thread if the process is gone.
I do prefer fixing the issue instead of opting for a workaround, but I am open to any other solutions to make it work. Thanks in advance!
Edit: After reading #RemyLebeau's answer and #RbMm's comments in that answer, it is pretty clear that my understand of how handle inheritance works is fundamentally flawed. So I incorporated their suggestions (SetHandleInformation to disable inheritance of read handle and closing it after creating the child process) into my allium_start function:
bool allium_start(struct TorInstance *instance, char *config, allium_pipe *output_pipes) {
// Prepare startup info with appropriate information
SecureZeroMemory(&instance->startup_info, sizeof instance->startup_info);
instance->startup_info.dwFlags = STARTF_USESTDHANDLES;
SECURITY_ATTRIBUTES pipe_secu_attribs = {sizeof(SECURITY_ATTRIBUTES), NULL, true};
HANDLE pipes[2];
if (output_pipes == NULL) {
CreatePipe(&pipes[0], &pipes[1], &pipe_secu_attribs, 0);
output_pipes = pipes;
}
SetHandleInformation(output_pipes[0], HANDLE_FLAG_INHERIT, 0);
instance->startup_info.hStdOutput = output_pipes[1];
instance->startup_info.hStdError = output_pipes[1];
instance->stdout_pipe = output_pipes[0]; // Stored for internal reference
// Create the process
bool success = CreateProcessA(
NULL,
cmd,
NULL,
NULL,
config ? true : false,
0,
NULL,
NULL,
&instance->startup_info,
SecureZeroMemory(&instance->process, sizeof instance->process)
);
// Close the write end of our stdout handle
CloseHandle(output_pipes[1]);
// Return on failure
if (!success) return false;
}
(The below text was originally here before edit 2)
But sadly it still doesn't work :(
Edit 2 (after accepting answer): It does work! See my last comment on the accepted answer.
You are not managing your pipes correctly, or more specifically, you are not controlling the inheritance of your pipe handles. DO NOT let the child process inherit the reading handle of your pipe (output_pipes[0]), otherwise the pipe will not break correctly when the child process ends.
Read MSDN for more details:
Creating a Child Process with Redirected Input and Output
The case of the redirected standard handles that won’t close even though the child process has exited
Use SetHandleInformation() or PROC_THREAD_ATTRIBUTE_LIST to prevent CreateProcess() from passing output_pipes[0] to the child process as an inheritable handle. The child process does not need access to that handle, so there is no need to pass it over the process boundary anyway. It only needs access to the writing handle of your pipe (output_pipes[1]).
For anonymous pipelines, the read process and the write process will have the handler of hRead and hWrite, each of process has its own handler(copy after inheritance). So after your child process exit and close the handler in it, anther hWrite still in parent process. We must pay attention to close hRead in the write process, close hWrite in the read process.
I can reproduce this ReadFile issue, and if closing write handler after setting child's hStdOutput and hStdError, the ReadFile will return 0 after the child process exit.
Here is my code sample,
Parent.cpp:
#include <windows.h>
#include <iostream>
#include <stdio.h>
HANDLE childInRead = NULL;
HANDLE W1 = NULL;
HANDLE W2 = NULL;
HANDLE R2 = NULL;
HANDLE R1 = NULL;
#define BUFSIZE 4096
void CreateChildProcess() {
TCHAR applicationName[] = TEXT("kids.exe");
PROCESS_INFORMATION pi;
STARTUPINFO si;
BOOL success = FALSE;
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.hStdError = W1;
si.hStdOutput = W1;
si.hStdInput = R2;
si.dwFlags |= STARTF_USESTDHANDLES;
success = CreateProcess(NULL, applicationName, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
if (!success) {
printf("Error creating child process \n");
}
else {
printf("Child process successfuly created \n");
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
}
int main()
{
printf("Parent process running.... \n");
DWORD dRead, dWritten;
CHAR chBuf[BUFSIZE] = { 0 };
BOOL bSuccess = FALSE;
SECURITY_ATTRIBUTES secAttr;
secAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
secAttr.bInheritHandle = TRUE;
secAttr.lpSecurityDescriptor = NULL;
printf("Creating first pipe \n");
if (!CreatePipe(&R1, &W1, &secAttr, 0)) {
printf("\n error creating first pipe \n");
}
printf("Creating second pipe \n");
if (!CreatePipe(&R2, &W2, &secAttr, 0)) {
printf("\n error creating second pipe \n");
}
if (!SetHandleInformation(R1, HANDLE_FLAG_INHERIT, 0)) {
printf("\n R1 SetHandleInformation \n");
}
if (!SetHandleInformation(W2, HANDLE_FLAG_INHERIT, 0)) {
printf("\n W1 SetHandleInformation \n");
}
printf("\n Creating child process..... \n");
HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
CreateChildProcess();
CloseHandle(W1);
CloseHandle(R2);
for (;;) {
printf("Inside for loop \n");
//1. read from stdin
printf("read from stdin:\n");
bSuccess = ReadFile(hStdIn, chBuf, BUFSIZE, &dRead, NULL);
if (!bSuccess) {
printf("error reading \n");
break;
}
//2. write to Pipe2
printf("write to Pipe2...\n");
bSuccess = WriteFile(W2, chBuf, 100, &dWritten, NULL);
if (!bSuccess) {
printf("error reading \n");
break;
}
//3. read from Pipe1
printf("read from Pipe1...\n");
bSuccess = ReadFile(R1, chBuf, BUFSIZE, &dRead, NULL);
if (!bSuccess)
{
printf("error reading :%d \n", GetLastError());
break;
}
//4. write to stdout
printf("write to stdout:\n");
bSuccess = WriteFile(hStdOut, chBuf, 100, &dWritten, NULL);
if (!bSuccess) {
printf("error reading \n");
break;
}
}
getchar();
return 0;
}
Kids.cpp:
#include <windows.h>
#include <stdio.h>
#define BUFSIZE 4096
int main()
{
DWORD dRead, dWritten;
CHAR chBuf[BUFSIZE];
BOOL success = FALSE;
HANDLE stdIn = GetStdHandle(STD_INPUT_HANDLE);
HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE);
printf("Child process running....");
if (stdIn == INVALID_HANDLE_VALUE || stdOut == INVALID_HANDLE_VALUE) {
ExitProcess(1);
}
//for (;;) {
success = ReadFile(stdIn, chBuf, BUFSIZE, &dRead, NULL);
//if (!success || dRead == 0) break;
success = WriteFile(stdOut, chBuf, dRead, &dWritten, NULL);
//if (!success) break;
//}
return 0;
}

How to CancelSynchronousIo() on WaitForSingleObject() waiting on stdin?

On Windows 10, I'm waiting for input from the console using
WaitForSingleObject( GetStdHandle(STD_INPUT_HANDLE), ... )
and to cancel this waiting using CancelSynchronousIo().
But the cancellation does nothing (returns 0 and GetLastError() is ERROR_NOT_FOUND).
Any idea what I could be doing wrong?
Should I be able to cancel this waiting for new input on stdin?
(I actually want to do this with any HANDLE whose GetFileType() is FILE_TYPE_CHAR, not only stdin, but stdin is certainly the most important use case and the simplest to test with).
Related discussions I've found:
Synchronous ReadFile() on stdin cannot be unblocked by CancelSynchronousIo()
win32: how stop ReadFile (stdin|pipe)
But unfortunately they only discuss ReadFile(), not WaitForSingleObject(). I've also tried WaitForMultipleObjects() (with just a single object in the array), same problem.
(Background: I'm trying to improve input handling in the GHC Haskell compiler runtime.)
CancelSynchronousIo cancel I/O operations that are issued by the specified thread. more concrete it cancel IRP packets which associated with specified thread via call IoCancelIrp. if use undocumented NtCancelSynchronousIoFile (CancelSynchronousIo internally call it with IoRequestToCancel = 0) we can be more selective - cancel only i/o request which used specified IoRequestToCancel (system check that Irp->UserIosb == IoRequestToCancel and cancel only this requests)
but WaitForSingleObject this is not I/O request. this call not create any IRP which can be canceled. so - no way do this.
however if you use WaitForSingleObjectEx with bAlertable set to TRUE - you can break wait by queue apc to thread by using QueueUserAPC . also if use NtWaitForSingleObject instead WaitForSingleObjectEx we can also alert thread by using undocumented call NtAlertThread. in this case NtWaitForSingleObject will break with STATUS_ALERTED (note that WaitForSingleObjectEx which internally call NtWaitForSingleObject do special check for STATUS_ALERTED and in case this status - again run NtWaitForSingleObject - as result we can not break WaitForSingleObjectEx by call NtAlertThread, but NtWaitForSingleObject will be breaked.
so if you need break waiting for std input - create additional thread, which must call not CancelSynchronousIo (this senseless) but QueueUserAPC or NtAlertThread (only if you use NtWaitForSingleObject for wait). and input thread must wait in alertable state. so demo code can look like:
extern "C" NTSYSCALLAPI NTSTATUS NTAPI NtAlertThread(HANDLE ThreadHandle);
VOID NTAPI OnApc(ULONG_PTR Parameter)
{
DbgPrint("OnApc(%p)\n", Parameter);
}
DWORD CALLBACK BreakWaitThread(HANDLE hThread)
{
switch (LONG status = MessageBoxW(0, L"Use Apc(yes) or Alert(No) ?", L"BreakWaitThread",
MB_ICONQUESTION|MB_YESNOCANCEL|MB_DEFBUTTON3))
{
case IDYES:
if (!QueueUserAPC(OnApc, hThread, 0))
{
DbgPrint("QueueUserAPC=%u\n", GetLastError());
}
break;
case IDNO:
if (0 > (status = NtAlertThread(hThread)))
{
DbgPrint("AlertThread=%x\n", status);
}
break;
case IDCANCEL:
DbgPrint("canceled\n");
break;
default:
DbgPrint("MessageBox=%x\n", status);
}
CloseHandle(hThread);
return 0;
}
void ConsoleLoop(HANDLE hStdIn)
{
ULONG NumberOfEvents, NumberOfEventsRead, n;
INPUT_RECORD buf[8], *p;
for (;;)
{
switch (ZwWaitForSingleObject(hStdIn, TRUE, 0))
//switch (WaitForSingleObjectEx(hStdIn, INFINITE, TRUE))
{
case WAIT_OBJECT_0:
while (GetNumberOfConsoleInputEvents(hStdIn, &NumberOfEvents) && NumberOfEvents)
{
do
{
NumberOfEventsRead = min(RTL_NUMBER_OF(buf), NumberOfEvents);
if (ReadConsoleInput(hStdIn, buf, NumberOfEventsRead, &NumberOfEventsRead) && NumberOfEventsRead)
{
n = NumberOfEventsRead;
p = buf;
do
{
if (p->EventType == KEY_EVENT)
{
DbgPrint("%u(%u) %C %x %x %x\n",
p->Event.KeyEvent.bKeyDown,
p->Event.KeyEvent.wRepeatCount,
p->Event.KeyEvent.uChar.UnicodeChar,
p->Event.KeyEvent.wVirtualKeyCode,
p->Event.KeyEvent.wVirtualScanCode,
p->Event.KeyEvent.dwControlKeyState);
if (VK_OEM_PERIOD == p->Event.KeyEvent.wVirtualKeyCode)
{
return ;//if user type '.' return for demo
}
}
} while (p++, --n);
}
else
{
FlushConsoleInputBuffer(hStdIn);
break;
}
} while (NumberOfEvents -= NumberOfEventsRead);
}
continue;
case STATUS_USER_APC:
DbgPrint("\nUSER_APC\n");
return;
case STATUS_ALERTED:
DbgPrint("\nALERTED\n");
return;
case WAIT_FAILED :
DbgPrint("\nWAIT_FAILED=%u\n", GetLastError());
return;
default:
__debugbreak();
return;
}
}
}
void SimpleDemo()
{
if (HANDLE hCurrentThread = OpenThread(THREAD_ALERT|THREAD_SET_CONTEXT , FALSE, GetCurrentThreadId()))
{
ULONG dwThreadId;
HANDLE hThread = CreateThread(0, 0, BreakWaitThread, hCurrentThread, 0, &dwThreadId);
if (hThread)
{
ConsoleLoop(GetStdHandle(STD_INPUT_HANDLE));
PostThreadMessage(dwThreadId, WM_QUIT, 0, 0);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
}
else
{
CloseHandle(hCurrentThread);
}
}
}
Console I/O is difficult to use asynchronously, it is simply not designed for it. See IO Completion Ports (IOCP) and Asynchronous I/O through STDIN, STDOUT and STDERR for some possible workarounds.
If that is not an option for you, then you will have to either:
use WaitForSingleObject() in a loop with a short timeout. Create a flag variable that your loop can look at on each iteration to break the loop if the flag is set.
use WaitForMutipleObjects(), giving it 2 HANDLEs to wait on - one for the console (or whatever), and one for an event object from CreateEvent(). Then you can signal the event with SetEvent() when you want to break the wait. The return value of WaitForMutipleObjects() will tell you which HANDLE was signaled.

Binary Pipe in Windows

I'm able to create a child process and pipe its stdin and stdout. It works fine when it is in text mode.
However, when I try to set the I/O in the child process to be in the binary format (i.e. no 0x0A to 0x0D 0x0A translation) the child process fails. _setmode returns -1 which has been documented to indicate failure. Why is that and how can it be fixed?
Parent code resembles the following:
const std::string path; // = "path_to.exe"
PROCESS_INFORMATION info;
SECURITY_ATTRIBUTES sec_attr;
//in-out from the CHILD process' perspective
HANDLE out_r = nullptr;
HANDLE out_w = nullptr;
HANDLE in_r = nullptr;
HANDLE in_w = nullptr;
sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES);
sec_attr.lpSecurityDescriptor = NULL;
sec_attr.bInheritHandle = TRUE; //inherit by child processes
if (!CreatePipe(&out_r, &out_w, &sec_attr, 0))
throw std::exception();
if (!SetHandleInformation(out_r, HANDLE_FLAG_INHERIT, 0))
throw std::exception();
if (!CreatePipe(&in_r, &in_w, &sec_attr, 0))
throw std::exception();
if (!SetHandleInformation(in_r, HANDLE_FLAG_INHERIT, 0))
throw std::exception();
if (out_r && out_w && in_r && in_w)
{
startup_info.hStdError = out_w;
startup_info.hStdOutput = out_w;
startup_info.hStdInput = in_r;
startup_info.dwFlags = STARTF_USESTDHANDLES;
}
if (CreateProcessA(path.c_str(), (char*)cmd, NULL, NULL, TRUE, 0, NULL, NULL, &startup_info, &info)
!= TRUE)
{
DWORD error = GetLastError();
//error handling
}
// ... read data using ReadFile
Child code resembles the following:
int status = _setmode(_fileno(stdin), _O_BINARY);
if (status == -1)
throw std::exception();
status = _setmode(_fileno(stdout), _O_BINARY);
if (status == -1)
throw std::exception();
puts("hello from the child process");
I'm looking at the MSDN article Creating a Child Process with Redirected Input and Output that provides a reference implementation for nearly what you're trying to do here.
One subtle difference I see is, where in your parent code is written (the second call to SetHandleInformation()):
if (!SetHandleInformation(in_r, HANDLE_FLAG_INHERIT, 0))
the MSDN example (translated to use your variable names) would be written:
if (!SetHandleInformation(in_w, HANDLE_FLAG_INHERIT, 0))
Here are a couple more SO pages that would be worth looking at as well, if this small change doesn't help:
What is the simplest way to write to stdout in binary mode?
Win32 changing to binary mode child's Stdout (pipe)

WINSDK: Determining whether an arbitrary pid identifies a running process on Windows

Attempting to implement a poor man's test of whether a process is still running or not (essentially an equivalent of the trivial kill(pid, 0).)
Hoped to be able to simply call OpenProcess with some minimal desired access then test for either GetLastError() == ERROR_INVALID_PARAMETER or GetExitCodeProcess(...) != STILL_ACTIVE.
Nice try... Running on Windows XP, as administrator:
HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
if (!hProc) {
DWORD dwLastError = GetLastError();
}
...fails miserably with dwLastError == ERROR_ACCESS_DENIED when pid is owned by a different (not SYSTEM) user. Moreover, if pid was originally owned by a different user but has since terminated, OpenProcess also fails with ERROR_ACCESS_DENIED (not ERROR_INVALID_PARAMETER.)
Do I have to use Process32First/Process32Next or EnumProcesses?
I absolutely do not want to use SeDebugPrivilege.
Thanks,
V
If you have a process ID:
// this should succeed even when a medium integrity process
// requests access to a high integrity process
if (HANDLE h = OpenProcess(SYNCHRONIZE, FALSE, pid))
{
// do a wait, if the handle is signaled: not running
DWORD wait = WaitForSingleObject(h, 0);
if (wait == WAIT_OBJECT_0) return FALSE;
}
// cannot get a handle to the process:
// probably running at system integrity level
// I'm not sure how reliable this check is, but it seems to work:
// if access is denied: running
// if invalid parameter: not running
else if (GetLastError() != ERROR_ACCESS_DENIED) return FALSE;
If you have a window handle that should be valid for as long as the process is running, this is a good alternative:
if (hWnd && !IsWindow(hWnd)) return FALSE;
static BOOL
isProcessAlive(DWORD th32ProcessID) {
BOOL bSuccess = FALSE;
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap != INVALID_HANDLE_VALUE) {
PROCESSENTRY32 pe32 = { sizeof(pe32), 0 };
if (Process32First(hSnap, &pe32)) {
while (pe32.th32ProcessID != pid && Process32Next(hSnap, &pe32));
_ASSERT(GetLastError() == 0 || GetLastError() == ERROR_NO_MORE_FILES);
bSuccess = (pe32.th32ProcessID == th32ProcessID);
}
CloseHandle(hSnap);
}
return bSuccess;
}

Resources