1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #define WIN32_LEAN_AND_MEAN
25 #include <systools/win32/uwinapi.h>
28 #include <sal/macros.h>
32 DWORD passOutputToConsole(HANDLE readPipe, HANDLE console)
36 HANDLE hReadPipe = readPipe;
39 //Indicates that we read an odd number of bytes. That is, we only read half of the last
41 bool bIncompleteWchar = false;
42 //fprintf, fwprintf will both send char data without the terminating zero.
43 //fwprintf converts the unicode string first.
44 //We expect here to receive unicode without the terminating zero.
45 //unopkg and the extension manager code MUST
46 //use dp_misc::writeConsole instead of using fprintf, etc.
48 DWORD dwToRead = sizeof(aBuffer);
49 BYTE * pBuffer = aBuffer;
50 while ( ReadFile( hReadPipe, pBuffer, dwToRead, &dwRead, nullptr ) )
52 //If the previous ReadFile call read an odd number of bytes, then the last one was
53 //put at the front of the buffer. We increase the number of read bytes by one to reflect
57 //We must make sure that only complete wchar_t|s are written. WriteConsolse takes
58 //the number of wchar_t|s as argument. ReadFile, however, reads bytes.
59 bIncompleteWchar = (dwRead % 2) != 0;
62 //To test this case, give aBuffer a small odd size, e.g. aBuffer[3]
63 //The last byte, which is the incomplete wchar_t (half of it), will not be written.
64 (void) WriteConsoleW( console, aBuffer,
65 (dwRead - 1) / 2, &dwWritten, nullptr );
67 //Move the last byte to the front of the buffer, so that it is the start of the
69 aBuffer[0] = aBuffer[dwRead - 1];
71 //Make sure that ReadFile does not overwrite the first byte the next time
72 dwToRead = sizeof(aBuffer) - 1;
73 pBuffer = aBuffer + 1;
77 { //We have read an even number of bytes. Therefore, we do not put the last incomplete
78 //wchar_t at the front of the buffer. We will use the complete buffer the next time
79 //when ReadFile is called.
80 dwToRead = sizeof(aBuffer);
82 (void) WriteConsoleW( console,
83 aBuffer, dwRead / 2, &dwWritten, nullptr );
93 DWORD WINAPI OutputThread( LPVOID pParam )
95 return passOutputToConsole(static_cast<HANDLE>(pParam), GetStdHandle( STD_OUTPUT_HANDLE ));
99 DWORD WINAPI OutputThread( LPVOID pParam )
103 HANDLE hReadPipe = (HANDLE)pParam;
104 while ( ReadFile( hReadPipe, &aBuffer, sizeof(aBuffer), &dwRead, NULL ) )
108 (void) WriteFile( GetStdHandle( STD_OUTPUT_HANDLE ), aBuffer, dwRead, &dwWritten, NULL );
115 // Thread that reads from child process standard error pipe
119 DWORD WINAPI ErrorThread( LPVOID pParam )
121 return passOutputToConsole(static_cast<HANDLE>(pParam), GetStdHandle( STD_ERROR_HANDLE ));
125 DWORD WINAPI ErrorThread( LPVOID pParam )
129 HANDLE hReadPipe = (HANDLE)pParam;
131 while ( ReadFile( hReadPipe, &aBuffer, sizeof(aBuffer), &dwRead, NULL ) )
135 (void) WriteFile( GetStdHandle( STD_ERROR_HANDLE ), aBuffer, dwRead, &dwWritten, NULL );
142 // Thread that writes to child process standard input pipe
146 DWORD WINAPI InputThread( LPVOID pParam )
149 HANDLE hWritePipe = static_cast<HANDLE>(pParam);
151 //We need to read in the complete input until we encounter a new line before
152 //converting to Unicode. This is necessary because the input string can use
153 //characters of one, two, and more bytes. If the last character is not
154 //complete, then it will not be converted properly.
156 //Find out how a new line (0xd 0xa) looks like with the used code page.
157 //Characters may have one or multiple bytes and different byte ordering
158 //can be used (little and big endian);
159 int cNewLine = WideCharToMultiByte(
160 GetConsoleCP(), 0, L"\r\n", 2, nullptr, 0, nullptr, nullptr);
161 char * mbBuff = new char[cNewLine];
163 GetConsoleCP(), 0, L"\r\n", 2, mbBuff, cNewLine, nullptr, nullptr);
165 const DWORD dwBufferSize = 256;
166 char* readBuf = static_cast<char*>(malloc(dwBufferSize));
168 DWORD curBufSize = dwBufferSize;
170 while ( ReadFile( GetStdHandle( STD_INPUT_HANDLE ),
172 curBufSize - readAll, &dwRead, nullptr ) )
175 int lastBufSize = curBufSize;
176 //Grow the buffer if necessary
177 if (readAll > curBufSize * 0.7)
180 readBuf = static_cast<char *>(realloc(readBuf, curBufSize));
183 //If the buffer was filled completely then
184 //there could be more input coming. But if we read from the console
185 //and the console input fits exactly in the buffer, then the next
186 //ReadFile would block until the users presses return, etc.
187 //Therefore we check if last character is a new line.
188 //To test this, set dwBufferSize to 4 and enter "no". This should produce
189 //4 bytes with most code pages.
190 if ( readAll == lastBufSize
191 && memcmp(readBuf + lastBufSize - cNewLine, mbBuff, cNewLine) != 0)
193 //The buffer was completely filled and the last byte(s) are no
194 //new line, so there is more to come.
197 //Obtain the size of the buffer for the converted string.
198 int sizeWBuf = MultiByteToWideChar(
199 GetConsoleCP(), MB_PRECOMPOSED, readBuf, readAll, nullptr, 0);
201 wchar_t * wideBuf = new wchar_t[sizeWBuf];
205 GetConsoleCP(), MB_PRECOMPOSED, readBuf, readAll, wideBuf, sizeWBuf);
208 (void)WriteFile( hWritePipe, wideBuf, sizeWBuf * 2, &dwWritten, nullptr );
218 DWORD WINAPI InputThread( LPVOID pParam )
222 HANDLE hWritePipe = (HANDLE)pParam;
224 while ( ReadFile( GetStdHandle( STD_INPUT_HANDLE ), &aBuffer, sizeof(aBuffer), &dwRead, NULL ) )
227 (void) WriteFile( hWritePipe, aBuffer, dwRead, &dwWritten, NULL );
235 // Thread that waits until child process reached input idle
238 DWORD WINAPI WaitForUIThread( LPVOID pParam )
241 HANDLE hProcess = (HANDLE)pParam;
243 if ( !wgetenv( L"UNOPKG" ) )
244 WaitForInputIdle( hProcess, INFINITE );
254 // Ctrl-Break handler that terminates the child process if Ctrl-C was pressed
257 HANDLE hTargetProcess = INVALID_HANDLE_VALUE;
259 BOOL WINAPI CtrlBreakHandler(
260 DWORD // control signal type
263 TerminateProcess( hTargetProcess, 255 );
267 int wmain( int, wchar_t** )
269 WCHAR szTargetFileName[MAX_PATH] = L"";
270 STARTUPINFOW aStartupInfo;
271 PROCESS_INFORMATION aProcessInfo;
273 ZeroMemory( &aStartupInfo, sizeof(aStartupInfo) );
274 aStartupInfo.cb = sizeof(aStartupInfo);
275 aStartupInfo.dwFlags = STARTF_USESTDHANDLES;
277 // Create an output pipe where the write end is inheritable
279 HANDLE hOutputRead, hOutputWrite;
281 if ( CreatePipe( &hOutputRead, &hOutputWrite, nullptr, 0 ) )
285 DuplicateHandle( GetCurrentProcess(), hOutputWrite, GetCurrentProcess(), &hTemp, 0, TRUE, DUPLICATE_SAME_ACCESS );
286 CloseHandle( hOutputWrite );
287 hOutputWrite = hTemp;
289 aStartupInfo.hStdOutput = hOutputWrite;
292 // Create an error pipe where the write end is inheritable
294 HANDLE hErrorRead, hErrorWrite;
296 if ( CreatePipe( &hErrorRead, &hErrorWrite, nullptr, 0 ) )
300 DuplicateHandle( GetCurrentProcess(), hErrorWrite, GetCurrentProcess(), &hTemp, 0, TRUE, DUPLICATE_SAME_ACCESS );
301 CloseHandle( hErrorWrite );
304 aStartupInfo.hStdError = hErrorWrite;
307 // Create an input pipe where the read end is inheritable
309 HANDLE hInputRead, hInputWrite;
311 if ( CreatePipe( &hInputRead, &hInputWrite, nullptr, 0 ) )
315 DuplicateHandle( GetCurrentProcess(), hInputRead, GetCurrentProcess(), &hTemp, 0, TRUE, DUPLICATE_SAME_ACCESS );
316 CloseHandle( hInputRead );
319 aStartupInfo.hStdInput = hInputRead;
322 // Get image path with same name but with .exe extension
324 WCHAR szModuleFileName[MAX_PATH];
326 GetModuleFileNameW( nullptr, szModuleFileName, MAX_PATH );
327 WCHAR *lpLastDot = wcsrchr( szModuleFileName, '.' );
328 if ( lpLastDot && 0 == wcsicmp( lpLastDot, L".COM" ) )
330 size_t len = lpLastDot - szModuleFileName;
331 wcsncpy( szTargetFileName, szModuleFileName, len );
332 wcsncpy( szTargetFileName + len, L".EXE", SAL_N_ELEMENTS(szTargetFileName) - len );
335 // Create process with same command line, environment and stdio handles which
336 // are directed to the created pipes
338 BOOL fSuccess = CreateProcessW(
352 // These pipe ends are inherited by the child process and no longer used
353 CloseHandle( hOutputWrite );
354 CloseHandle( hErrorWrite );
355 CloseHandle( hInputRead );
357 // Set the Ctrl-Break handler
358 hTargetProcess = aProcessInfo.hProcess;
359 SetConsoleCtrlHandler( CtrlBreakHandler, TRUE );
361 // Create threads that redirect remote pipe io to current process's console stdio
363 DWORD dwOutputThreadId, dwErrorThreadId, dwInputThreadId;
365 HANDLE hOutputThread = CreateThread( nullptr, 0, OutputThread, static_cast<LPVOID>(hOutputRead), 0, &dwOutputThreadId );
366 HANDLE hErrorThread = CreateThread( nullptr, 0, OutputThread, static_cast<LPVOID>(hErrorRead), 0, &dwErrorThreadId );
367 HANDLE hInputThread = CreateThread( nullptr, 0, InputThread, static_cast<LPVOID>(hInputWrite), 0, &dwInputThreadId );
369 // Create thread that wait until child process entered input idle
371 DWORD dwWaitForUIThreadId;
372 HANDLE hWaitForUIThread = CreateThread( nullptr, 0, WaitForUIThread, static_cast<LPVOID>(aProcessInfo.hProcess), 0, &dwWaitForUIThreadId );
383 WaitForMultipleObjects( SAL_N_ELEMENTS(hObjects), hObjects, TRUE, INFINITE );
385 bool bDetach = false;
389 DWORD dwWaitResult = WaitForMultipleObjects( SAL_N_ELEMENTS(hObjects), hObjects, FALSE, INFINITE );
391 switch ( dwWaitResult )
393 case WAIT_OBJECT_0: // The child process has terminated
394 case WAIT_OBJECT_0 + 1: // The child process entered input idle
397 case WAIT_OBJECT_0 + 2: // The remote end of stdout pipe was closed
398 case WAIT_OBJECT_0 + 3: // The remote end of stderr pipe was closed
399 bDetach = --nOpenPipes <= 0;
401 default: // Something went wrong
409 CloseHandle( hOutputThread );
410 CloseHandle( hErrorThread );
411 CloseHandle( hInputThread );
412 CloseHandle( hWaitForUIThread );
414 DWORD dwExitCode = 0;
415 GetExitCodeProcess( aProcessInfo.hProcess, &dwExitCode );
416 CloseHandle( aProcessInfo.hProcess );
417 CloseHandle( aProcessInfo.hThread );
425 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */