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>
34 DWORD passOutputToConsole(HANDLE readPipe, HANDLE console)
38 HANDLE hReadPipe = readPipe;
41 //Indicates that we read an odd number of bytes. That is, we only read half of the last
43 bool bIncompleteWchar = false;
44 //fprintf, fwprintf will both send char data without the terminating zero.
45 //fwprintf converts the unicode string first.
46 //We expect here to receive unicode without the terminating zero.
47 //unopkg and the extension manager code MUST
48 //use dp_misc::writeConsole instead of using fprintf, etc.
50 DWORD dwToRead = sizeof(aBuffer);
51 BYTE * pBuffer = aBuffer;
52 while ( ReadFile( hReadPipe, pBuffer, dwToRead, &dwRead, nullptr ) )
54 //If the previous ReadFile call read an odd number of bytes, then the last one was
55 //put at the front of the buffer. We increase the number of read bytes by one to reflect
59 //We must make sure that only complete wchar_t|s are written. WriteConsolse takes
60 //the number of wchar_t|s as argument. ReadFile, however, reads bytes.
61 bIncompleteWchar = (dwRead % 2) != 0;
64 //To test this case, give aBuffer a small odd size, e.g. aBuffer[3]
65 //The last byte, which is the incomplete wchar_t (half of it), will not be written.
66 (void) WriteConsoleW( console, aBuffer,
67 (dwRead - 1) / 2, &dwWritten, nullptr );
69 //Move the last byte to the front of the buffer, so that it is the start of the
71 aBuffer[0] = aBuffer[dwRead - 1];
73 //Make sure that ReadFile does not overwrite the first byte the next time
74 dwToRead = sizeof(aBuffer) - 1;
75 pBuffer = aBuffer + 1;
79 { //We have read an even number of bytes. Therefore, we do not put the last incomplete
80 //wchar_t at the front of the buffer. We will use the complete buffer the next time
81 //when ReadFile is called.
82 dwToRead = sizeof(aBuffer);
84 (void) WriteConsoleW( console,
85 aBuffer, dwRead / 2, &dwWritten, nullptr );
95 DWORD WINAPI OutputThread( LPVOID pParam )
97 return passOutputToConsole(static_cast<HANDLE>(pParam), GetStdHandle( STD_OUTPUT_HANDLE ));
101 DWORD WINAPI OutputThread( LPVOID pParam )
105 HANDLE hReadPipe = (HANDLE)pParam;
106 while ( ReadFile( hReadPipe, &aBuffer, sizeof(aBuffer), &dwRead, NULL ) )
110 (void) WriteFile( GetStdHandle( STD_OUTPUT_HANDLE ), aBuffer, dwRead, &dwWritten, NULL );
117 // Thread that reads from child process standard error pipe
121 DWORD WINAPI ErrorThread( LPVOID pParam )
123 return passOutputToConsole(static_cast<HANDLE>(pParam), GetStdHandle( STD_ERROR_HANDLE ));
127 DWORD WINAPI ErrorThread( LPVOID pParam )
131 HANDLE hReadPipe = (HANDLE)pParam;
133 while ( ReadFile( hReadPipe, &aBuffer, sizeof(aBuffer), &dwRead, NULL ) )
137 (void) WriteFile( GetStdHandle( STD_ERROR_HANDLE ), aBuffer, dwRead, &dwWritten, NULL );
144 // Thread that writes to child process standard input pipe
148 DWORD WINAPI InputThread( LPVOID pParam )
151 HANDLE hWritePipe = static_cast<HANDLE>(pParam);
152 char* readBuf = nullptr;
155 //We need to read in the complete input until we encounter a new line before
156 //converting to Unicode. This is necessary because the input string can use
157 //characters of one, two, and more bytes. If the last character is not
158 //complete, then it will not be converted properly.
160 //Find out how a new line (0xd 0xa) looks like with the used code page.
161 //Characters may have one or multiple bytes and different byte ordering
162 //can be used (little and big endian);
163 int cNewLine = WideCharToMultiByte(
164 GetConsoleCP(), 0, L"\r\n", 2, nullptr, 0, nullptr, nullptr);
165 auto mbBuff = std::make_unique<char[]>(cNewLine);
167 GetConsoleCP(), 0, L"\r\n", 2, mbBuff.get(), cNewLine, nullptr, nullptr);
169 const DWORD dwBufferSize = 256;
170 readBuf = static_cast<char*>(malloc(dwBufferSize));
172 throw std::bad_alloc();
174 DWORD curBufSize = dwBufferSize;
176 while ( ReadFile( GetStdHandle( STD_INPUT_HANDLE ),
178 curBufSize - readAll, &dwRead, nullptr ) )
181 int lastBufSize = curBufSize;
182 //Grow the buffer if necessary
183 if (readAll > curBufSize * 0.7)
186 if (auto p = static_cast<char *>(realloc(readBuf, curBufSize)))
190 throw std::bad_alloc();
194 //If the buffer was filled completely then
195 //there could be more input coming. But if we read from the console
196 //and the console input fits exactly in the buffer, then the next
197 //ReadFile would block until the users presses return, etc.
198 //Therefore we check if last character is a new line.
199 //To test this, set dwBufferSize to 4 and enter "no". This should produce
200 //4 bytes with most code pages.
201 if ( readAll == lastBufSize
202 && memcmp(readBuf + lastBufSize - cNewLine, mbBuff.get(), cNewLine) != 0)
204 //The buffer was completely filled and the last byte(s) are no
205 //new line, so there is more to come.
208 //Obtain the size of the buffer for the converted string.
209 int sizeWBuf = MultiByteToWideChar(
210 GetConsoleCP(), MB_PRECOMPOSED, readBuf, readAll, nullptr, 0);
212 auto wideBuf = std::make_unique<wchar_t[]>(sizeWBuf);
216 GetConsoleCP(), MB_PRECOMPOSED, readBuf, readAll, wideBuf.get(), sizeWBuf);
219 (void)WriteFile( hWritePipe, wideBuf.get(), sizeWBuf * 2, &dwWritten, nullptr );
230 DWORD WINAPI InputThread( LPVOID pParam )
234 HANDLE hWritePipe = (HANDLE)pParam;
236 while ( ReadFile( GetStdHandle( STD_INPUT_HANDLE ), &aBuffer, sizeof(aBuffer), &dwRead, NULL ) )
239 (void) WriteFile( hWritePipe, aBuffer, dwRead, &dwWritten, NULL );
247 // Thread that waits until child process reached input idle
250 DWORD WINAPI WaitForUIThread( LPVOID pParam )
253 HANDLE hProcess = (HANDLE)pParam;
255 if ( !wgetenv( L"UNOPKG" ) )
256 WaitForInputIdle( hProcess, INFINITE );
266 // Ctrl-Break handler that terminates the child process if Ctrl-C was pressed
269 HANDLE hTargetProcess = INVALID_HANDLE_VALUE;
271 BOOL WINAPI CtrlBreakHandler(
272 DWORD // control signal type
275 TerminateProcess( hTargetProcess, 255 );
279 int wmain( int, wchar_t** )
281 WCHAR szTargetFileName[MAX_PATH] = L"";
282 STARTUPINFOW aStartupInfo;
283 PROCESS_INFORMATION aProcessInfo;
285 ZeroMemory( &aStartupInfo, sizeof(aStartupInfo) );
286 aStartupInfo.cb = sizeof(aStartupInfo);
287 aStartupInfo.dwFlags = STARTF_USESTDHANDLES;
289 // Create an output pipe where the write end is inheritable
291 HANDLE hOutputRead, hOutputWrite;
293 if ( CreatePipe( &hOutputRead, &hOutputWrite, nullptr, 0 ) )
297 DuplicateHandle( GetCurrentProcess(), hOutputWrite, GetCurrentProcess(), &hTemp, 0, TRUE, DUPLICATE_SAME_ACCESS );
298 CloseHandle( hOutputWrite );
299 hOutputWrite = hTemp;
301 aStartupInfo.hStdOutput = hOutputWrite;
304 // Create an error pipe where the write end is inheritable
306 HANDLE hErrorRead, hErrorWrite;
308 if ( CreatePipe( &hErrorRead, &hErrorWrite, nullptr, 0 ) )
312 DuplicateHandle( GetCurrentProcess(), hErrorWrite, GetCurrentProcess(), &hTemp, 0, TRUE, DUPLICATE_SAME_ACCESS );
313 CloseHandle( hErrorWrite );
316 aStartupInfo.hStdError = hErrorWrite;
319 // Create an input pipe where the read end is inheritable
321 HANDLE hInputRead, hInputWrite;
323 if ( CreatePipe( &hInputRead, &hInputWrite, nullptr, 0 ) )
327 DuplicateHandle( GetCurrentProcess(), hInputRead, GetCurrentProcess(), &hTemp, 0, TRUE, DUPLICATE_SAME_ACCESS );
328 CloseHandle( hInputRead );
331 aStartupInfo.hStdInput = hInputRead;
334 // Get image path with same name but with .exe extension
336 WCHAR szModuleFileName[MAX_PATH];
338 GetModuleFileNameW( nullptr, szModuleFileName, MAX_PATH );
339 WCHAR *lpLastDot = wcsrchr( szModuleFileName, '.' );
340 if ( lpLastDot && 0 == wcsicmp( lpLastDot, L".COM" ) )
342 size_t len = lpLastDot - szModuleFileName;
343 wcsncpy( szTargetFileName, szModuleFileName, len );
344 wcsncpy( szTargetFileName + len, L".EXE", SAL_N_ELEMENTS(szTargetFileName) - len );
347 // Create process with same command line, environment and stdio handles which
348 // are directed to the created pipes
350 BOOL fSuccess = CreateProcessW(
364 // These pipe ends are inherited by the child process and no longer used
365 CloseHandle( hOutputWrite );
366 CloseHandle( hErrorWrite );
367 CloseHandle( hInputRead );
369 // Set the Ctrl-Break handler
370 hTargetProcess = aProcessInfo.hProcess;
371 SetConsoleCtrlHandler( CtrlBreakHandler, TRUE );
373 // Create threads that redirect remote pipe io to current process's console stdio
375 DWORD dwOutputThreadId, dwErrorThreadId, dwInputThreadId;
377 HANDLE hOutputThread = CreateThread( nullptr, 0, OutputThread, static_cast<LPVOID>(hOutputRead), 0, &dwOutputThreadId );
378 HANDLE hErrorThread = CreateThread( nullptr, 0, OutputThread, static_cast<LPVOID>(hErrorRead), 0, &dwErrorThreadId );
379 HANDLE hInputThread = CreateThread( nullptr, 0, InputThread, static_cast<LPVOID>(hInputWrite), 0, &dwInputThreadId );
381 // Create thread that wait until child process entered input idle
383 DWORD dwWaitForUIThreadId;
384 HANDLE hWaitForUIThread = CreateThread( nullptr, 0, WaitForUIThread, static_cast<LPVOID>(aProcessInfo.hProcess), 0, &dwWaitForUIThreadId );
395 WaitForMultipleObjects( SAL_N_ELEMENTS(hObjects), hObjects, TRUE, INFINITE );
397 bool bDetach = false;
401 DWORD dwWaitResult = WaitForMultipleObjects( SAL_N_ELEMENTS(hObjects), hObjects, FALSE, INFINITE );
403 switch ( dwWaitResult )
405 case WAIT_OBJECT_0: // The child process has terminated
406 case WAIT_OBJECT_0 + 1: // The child process entered input idle
409 case WAIT_OBJECT_0 + 2: // The remote end of stdout pipe was closed
410 case WAIT_OBJECT_0 + 3: // The remote end of stderr pipe was closed
411 bDetach = --nOpenPipes <= 0;
413 default: // Something went wrong
421 CloseHandle( hOutputThread );
422 CloseHandle( hErrorThread );
423 CloseHandle( hInputThread );
424 CloseHandle( hWaitForUIThread );
426 DWORD dwExitCode = 0;
427 GetExitCodeProcess( aProcessInfo.hProcess, &dwExitCode );
428 CloseHandle( aProcessInfo.hProcess );
429 CloseHandle( aProcessInfo.hThread );
437 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */