This is the mail archive of the cygwin mailing list for the Cygwin project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

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

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]