This is the mail archive of the
cygwin
mailing list for the Cygwin project.
Question about redirecting Cygwin process's I/O to the client side of an Win32 named pipe.
- From: Chiheng Xu <chiheng dot xu at gmail dot com>
- To: cygwin at cygwin dot com
- Date: Fri, 20 Jul 2012 02:14:06 +0800
- Subject: Question about redirecting Cygwin process's I/O to the client side of an Win32 named pipe.
Hi, list
I'm working on a very small software component, that can create a
Cygwin process, and redirect it's I/O to the client side of an Win32
named pipe.
The creating process now have a handle of the server side of the Win32
named pipe. The creating process can then write to/read from the
sub-process through the pipe handle.
This is pretty much like mintty, which spawn bash.exe by redirecting
bash.exe's I/O to the slave side of a pty.
But because the component is intended to be used by Win32 native apps,
like MFC apps, JNI, etc, it is not plausible to use the pty
facilities provided by cygwin1.dll.
It is also not appropriate to dynamically load cygwin1.dll in the
creating process.
The method is below:
1. Win32 native A.exe create a named pipe and own the handle to the
server side.
2. A.exe create a "detached" starter.exe process. This starter.exe get
the name of the pipe and the argvs of the B.exe(the to be created
Cygwin process) through its command line.
3. starter.exe get handle to client side of the pipe, and create
"detached" B.exe process redirecting its I/O to the client side of
pipe.
The idea comes from another open source project.
The use of the intermediate starter.exe is because, Cygwin process
B.exe will inherit all inheritable handles in parent process,
including the client side handle of the named pipe, but we don't know
whether there exist other inheritable handles in parent process. So, a
"clean" process starter.exe that have no handles is created. The
starter.exe executable is even not linked with C run time, and have no
main(). It's entry is specified by compiler command line option. So,
starter.exe is considered clean.
The problem here is that, this method works for "simple" executable
like Windows's cmd.exe and Cygwin's gdb.exe, but does not work for
Cygwin's bash.exe or python.exe.
bash.exe process can be created, but I can't read data from the pipe.
So, what's wrong ?
What's the difference between bash.exe and gdb.exe or cmd.exe ?
What's the difference between Win32 named pipe and Cygwin pty ?
How can I make it work ?
Some code pieces is attached.
Thanks.
--
Chiheng Xu
#include "stdafx.h"
#include "VG-process.h"
bool CommandLine2Argv(CString & cmdline, CArray<CString> & argv)
{
//TODO: consider space and double quote
argv.RemoveAll();
cmdline.Trim();
int prev_index = 0;
int current_index;
while(1){
current_index = cmdline.Find(_T(" "), prev_index);
if(current_index != -1){
CString arg = cmdline.Mid(prev_index, current_index - prev_index);
argv.Add(arg);
prev_index = current_index + 1;
}else{
CString arg = cmdline.Mid(prev_index);
argv.Add(arg);
break;
}
}
return true;
}
bool Argv2CommandLine(CArray<CString> & argv, CString & cmdline)
{
//TODO: consider space and double quote
cmdline = _T("");
int array_size = argv.GetSize();
if(array_size){
cmdline += argv.GetAt(0);
int i;
for(i = 1; i < array_size; i++){
cmdline += _T(" ");
cmdline += argv.GetAt(i);
}
}
return true;
}
bool EnvironmentBlock2Evnp(CString & envblk, CArray<CString> & envp)
{
return true;
}
bool Evnp2EnvironmentBlock(CArray<CString> & envp, CString & envblk)
{
return true;
}
CString GetModulePath()
{
TCHAR zsFileName[_MAX_PATH];
//DWORD dwRes = ::GetModuleFileName(NULL, zsFileName, _MAX_PATH);
DWORD dwRes = ::GetModuleFileName(AfxGetInstanceHandle(), zsFileName, _MAX_PATH);
ASSERT(dwRes);
CString csFilePath = zsFileName;
int nFLs = csFilePath.ReverseFind(_T('\\'));
if (nFLs > 0)
{
csFilePath.ReleaseBuffer(nFLs + 1);
}
return csFilePath;
}
bool CreateAndConnectProcess(CArray<CString> & argv, CArray<CString> & envp, CString & strPipeName, HANDLE & hpipeProcess)
{
CString cmdline;
Argv2CommandLine(argv, cmdline);
CString starter_cmdline;
starter_cmdline += _T('"');
starter_cmdline += GetModulePath();
starter_cmdline += _T("\\starter.exe");
starter_cmdline += _T('"');
starter_cmdline += _T(' ');
starter_cmdline += strPipeName;
starter_cmdline += _T(' ');
starter_cmdline += cmdline;
//create namedpipe server side
hpipeProcess = CreateNamedPipe(
strPipeName,
PIPE_ACCESS_DUPLEX,// | FILE_FLAG_FIRST_PIPE_INSTANCE,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
4096,
4096,
10000,
NULL);
if(hpipeProcess == INVALID_HANDLE_VALUE){
//error
TRACE(_T("CreateNamedPipe failed\n"));
goto fail;
}
SetHandleInformation(hpipeProcess, HANDLE_FLAG_INHERIT, 0);
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
BOOL bFuncRetn = FALSE;
// Set up members of the PROCESS_INFORMATION structure.
ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );
// Set up members of the STARTUPINFO structure.
ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
siStartInfo.cb = sizeof(STARTUPINFO);
// Create the child process.
TCHAR * p_temp_cmdline = new TCHAR[starter_cmdline.GetLength() + 1];
StrCpy(p_temp_cmdline, (LPCTSTR)starter_cmdline);
bFuncRetn = CreateProcess(NULL,
p_temp_cmdline, // command line
NULL, // process security attributes
NULL, // primary thread security attributes
FALSE, // handles are not inherited
DETACHED_PROCESS, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
delete p_temp_cmdline;
if (bFuncRetn == 0){
TRACE(_T("CreateProcess failed\n"));
goto fail_close_pipe;
}
//don't need WaitForSingleObject(hProcess, INFINITE);
CloseHandle(piProcInfo.hThread);
CloseHandle(piProcInfo.hProcess);
return true;
fail_close_pipe:
CloseHandle(hpipeProcess);
hpipeProcess = NULL;
fail:
return false;
}
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
int starter_entry()
{
int i;
STARTUPINFOW si;
PROCESS_INFORMATION pi;
int cmdline_length;
WCHAR * p;
int argc;
int * start_index_of_arguments;
int * end_index_of_arguments;
WCHAR * cmdline_copy;
WCHAR ** argv;
WCHAR * subprocess_cmdline = NULL;
WCHAR * PipeName;
HANDLE hpipeClient;
SECURITY_ATTRIBUTES sa;
BOOL f;
for(i = 0; i < sizeof(STARTUPINFOW); i++){
*((volatile char *)(&si) + i ) = 0;
}
si.cb = sizeof(STARTUPINFOW);
for(i = 0; i < sizeof(PROCESS_INFORMATION); i++){
*((volatile char *)(&pi) + i ) = 0;
}
LPWSTR cmdline = GetCommandLineW();
//get argc and length of command line string
p = cmdline;
i = 0;
while(*p){
if(*p == L'"'){
i++;
p++;
while(*p != L'"' && *p){
p++;
}
if(!*p){
//error : unbalanced double quote
goto error_ret;
break;
}else{
//*p == L'"'
p++;
continue;
}
}else if(*p != L' '){
i++;
while(*p != L' ' && *p){
p++;
}
if(!*p){
//end of string
break;
}else{
//*p == L' '
continue;
}
}else{
//*p == L' '
p++;
continue;
}
}
argc = i;
cmdline_length = p - cmdline;
//get argv
start_index_of_arguments = (int *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, argc * sizeof(int));
end_index_of_arguments = (int *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, argc * sizeof(int));
p = cmdline;
i = 0;
while(*p){
if(*p == L'"'){
i++;
p++;
start_index_of_arguments[i - 1] = p - cmdline;
while(*p != L'"' && *p){
p++;
}
end_index_of_arguments[i - 1] = p - cmdline;
if(!*p){
//error : unbalanced double quote
goto error_ret;
break;
}else{
//*p == L'"'
p++;
continue;
}
}else if(*p != L' '){
i++;
start_index_of_arguments[i - 1] = p - cmdline;
while(*p != L' ' && *p){
p++;
}
end_index_of_arguments[i - 1] = p - cmdline;
if(!*p){
//end of string
break;
}else{
//*p == L' '
continue;
}
}else{
//*p == L' '
p++;
continue;
}
}
cmdline_copy = (WCHAR * )HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WCHAR) * (cmdline_length + 1));
for(i = 0; i < cmdline_length + 1; i++){
cmdline_copy[i] = cmdline[i];
}
for(i = 0; i < argc; i++){
cmdline_copy[end_index_of_arguments[i]] = L'\0';
}
argv = (WCHAR **)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WCHAR *) * argc);
for(i = 0; i < argc; i++){
argv[i] = cmdline_copy + start_index_of_arguments[i];
}
//get subprocess command line
p = cmdline;
i = 0;
while(*p){
if(*p == L'"'){
if(i == 2){
subprocess_cmdline = p;
break;
}
i++;
p++;
while(*p != L'"' && *p){
p++;
}
if(!*p){
goto error_ret;
//error : unbalanced double quote
break;
}else{
//*p == L'"'
p++;
continue;
}
}else if(*p != L' '){
if(i == 2){
subprocess_cmdline = p;
break;
}
i++;
while(*p != L' ' && *p){
p++;
}
if(!*p){
//end of string
break;
}else{
//*p == L' '
continue;
}
}else{
//*p == L' '
p++;
continue;
}
}
if(!subprocess_cmdline){
//error :
goto error_ret;
}
//create client side of the named pipe
PipeName = argv[1];
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
hpipeClient = CreateFileW(
PipeName,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
&sa,
OPEN_EXISTING,
0,
NULL);
if(hpipeClient == INVALID_HANDLE_VALUE){
//error
goto error_ret;
}
SetHandleInformation(hpipeClient, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
si.hStdError = hpipeClient;
si.hStdOutput = hpipeClient;
si.hStdInput = hpipeClient;
si.dwFlags |= STARTF_USESTDHANDLES;
//
f = CreateProcessW(NULL,
subprocess_cmdline, // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
DETACHED_PROCESS, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&si, // STARTUPINFO pointer
&pi); // receives PROCESS_INFORMATION
if(!f){
//error
goto error_ret;
}
error_ret:
return 0;
}
--
Problem reports: http://cygwin.com/problems.html
FAQ: http://cygwin.com/faq/
Documentation: http://cygwin.com/docs.html
Unsubscribe info: http://cygwin.com/ml/#unsubscribe-simple