Version 6.4.0.3, tag libreoffice-6.4.0.3
[LibreOffice.git] / desktop / win32 / source / guistdio / guistdio.inc
blob85822a817d63e46ec4ec833cb2614eb57f21b660
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
20 #define WIN32_LEAN_AND_MEAN
21 #include <windows.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <systools/win32/uwinapi.h>
27 #include <stdio.h>
28 #include <sal/macros.h>
30 #include <memory>
32 #ifdef UNOPKG
34 DWORD passOutputToConsole(HANDLE readPipe, HANDLE console)
36     BYTE aBuffer[1024];
37     DWORD dwRead = 0;
38     HANDLE hReadPipe = readPipe;
39     DWORD dwWritten;
41     //Indicates that we read an odd number of bytes. That is, we only read half of the last
42     //wchar_t
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 ) )
53     {
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
56         //that one byte.
57         if (bIncompleteWchar)
58             dwRead++;
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;
62         if (bIncompleteWchar)
63         {
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
70             //next string
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;
77         }
78         else
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);
83             pBuffer = aBuffer;
84             (void) WriteConsoleW( console,
85                 aBuffer, dwRead / 2, &dwWritten, nullptr );
86         }
87     }
89     return 0;
92 #endif
94 #ifdef UNOPKG
95 DWORD WINAPI OutputThread( LPVOID pParam )
97     return passOutputToConsole(static_cast<HANDLE>(pParam), GetStdHandle( STD_OUTPUT_HANDLE ));
100 #else
101 DWORD WINAPI OutputThread( LPVOID pParam )
103     BYTE    aBuffer[256];
104     DWORD   dwRead = 0;
105     HANDLE  hReadPipe = (HANDLE)pParam;
106     while ( ReadFile( hReadPipe, &aBuffer, sizeof(aBuffer), &dwRead, NULL ) )
107     {
108         DWORD   dwWritten;
110         (void) WriteFile( GetStdHandle( STD_OUTPUT_HANDLE ), aBuffer, dwRead, &dwWritten, NULL );
111     }
113     return 0;
115 #endif
117 // Thread that reads from child process standard error pipe
120 #ifdef UNOPKG
121 DWORD WINAPI ErrorThread( LPVOID pParam )
123     return passOutputToConsole(static_cast<HANDLE>(pParam), GetStdHandle( STD_ERROR_HANDLE ));
126 #else
127 DWORD WINAPI ErrorThread( LPVOID pParam )
129     BYTE    aBuffer[256];
130     DWORD   dwRead = 0;
131     HANDLE  hReadPipe = (HANDLE)pParam;
133     while ( ReadFile( hReadPipe, &aBuffer, sizeof(aBuffer), &dwRead, NULL ) )
134     {
135         DWORD   dwWritten;
137         (void) WriteFile( GetStdHandle( STD_ERROR_HANDLE ), aBuffer, dwRead, &dwWritten, NULL );
138     }
140     return 0;
142 #endif
144 // Thread that writes to child process standard input pipe
146 #ifdef UNOPKG
148 DWORD WINAPI InputThread( LPVOID pParam )
150     DWORD   dwRead = 0;
151     HANDLE  hWritePipe = static_cast<HANDLE>(pParam);
152     char* readBuf = nullptr;
153     try
154     {
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);
166         WideCharToMultiByte(
167             GetConsoleCP(), 0, L"\r\n", 2, mbBuff.get(), cNewLine, nullptr, nullptr);
169         const DWORD dwBufferSize = 256;
170         readBuf = static_cast<char*>(malloc(dwBufferSize));
171         if (!readBuf)
172             throw std::bad_alloc();
173         int readAll = 0;
174         DWORD curBufSize = dwBufferSize;
176         while ( ReadFile( GetStdHandle( STD_INPUT_HANDLE ),
177                           readBuf + readAll,
178                           curBufSize - readAll, &dwRead, nullptr ) )
179         {
180             readAll += dwRead;
181             int lastBufSize = curBufSize;
182             //Grow the buffer if necessary
183             if (readAll > curBufSize * 0.7)
184             {
185                 curBufSize *= 2;
186                 if (auto p = static_cast<char *>(realloc(readBuf, curBufSize)))
187                     readBuf = p;
188                 else
189                 {
190                     throw std::bad_alloc();
191                 }
192             }
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)
203             {
204                 //The buffer was completely filled and the last byte(s) are no
205                 //new line, so there is more to come.
206                 continue;
207             }
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);
214             //Do the conversion.
215             MultiByteToWideChar(
216                 GetConsoleCP(), MB_PRECOMPOSED, readBuf, readAll, wideBuf.get(), sizeWBuf);
218             DWORD   dwWritten;
219             (void)WriteFile( hWritePipe, wideBuf.get(), sizeWBuf * 2, &dwWritten, nullptr );
221             readAll = 0;
222         }
223     }
224     catch (...)
225     {}
226     free(readBuf);
227     return 0;
229 #else
230 DWORD WINAPI InputThread( LPVOID pParam )
232     BYTE    aBuffer[256];
233     DWORD   dwRead = 0;
234     HANDLE  hWritePipe = (HANDLE)pParam;
236     while ( ReadFile( GetStdHandle( STD_INPUT_HANDLE ), &aBuffer, sizeof(aBuffer), &dwRead, NULL ) )
237     {
238         DWORD dwWritten;
239         (void) WriteFile( hWritePipe, aBuffer, dwRead, &dwWritten, NULL );
240     }
242     return 0;
244 #endif
247 // Thread that waits until child process reached input idle
250 DWORD WINAPI WaitForUIThread( LPVOID pParam )
252 #ifndef UNOPKG
253     HANDLE  hProcess = (HANDLE)pParam;
255     if ( !wgetenv( L"UNOPKG" ) )
256         WaitForInputIdle( hProcess, INFINITE );
257 #else
258     (void) pParam;
259 #endif
261     return 0;
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 );
276     return TRUE;
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 ) )
294     {
295         HANDLE  hTemp;
297         DuplicateHandle( GetCurrentProcess(), hOutputWrite, GetCurrentProcess(), &hTemp, 0, TRUE, DUPLICATE_SAME_ACCESS );
298         CloseHandle( hOutputWrite );
299         hOutputWrite = hTemp;
301         aStartupInfo.hStdOutput = hOutputWrite;
302     }
304     // Create an error pipe where the write end is inheritable
306     HANDLE  hErrorRead, hErrorWrite;
308     if ( CreatePipe( &hErrorRead, &hErrorWrite, nullptr, 0 ) )
309     {
310         HANDLE  hTemp;
312         DuplicateHandle( GetCurrentProcess(), hErrorWrite, GetCurrentProcess(), &hTemp, 0, TRUE, DUPLICATE_SAME_ACCESS );
313         CloseHandle( hErrorWrite );
314         hErrorWrite = hTemp;
316         aStartupInfo.hStdError = hErrorWrite;
317     }
319     // Create an input pipe where the read end is inheritable
321     HANDLE  hInputRead, hInputWrite;
323     if ( CreatePipe( &hInputRead, &hInputWrite, nullptr, 0 ) )
324     {
325         HANDLE  hTemp;
327         DuplicateHandle( GetCurrentProcess(), hInputRead, GetCurrentProcess(), &hTemp, 0, TRUE, DUPLICATE_SAME_ACCESS );
328         CloseHandle( hInputRead );
329         hInputRead = hTemp;
331         aStartupInfo.hStdInput = hInputRead;
332     }
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" ) )
341     {
342         size_t len = lpLastDot - szModuleFileName;
343         wcsncpy( szTargetFileName, szModuleFileName, len );
344         wcsncpy( szTargetFileName + len, L".EXE", SAL_N_ELEMENTS(szTargetFileName) - len );
345     }
347     // Create process with same command line, environment and stdio handles which
348     // are directed to the created pipes
350     BOOL fSuccess = CreateProcessW(
351         szTargetFileName,
352         GetCommandLineW(),
353         nullptr,
354         nullptr,
355         TRUE,
356         0,
357         nullptr,
358         nullptr,
359         &aStartupInfo,
360         &aProcessInfo );
362     if ( fSuccess )
363     {
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 );
386         HANDLE  hObjects[] =
387             {
388                 hTargetProcess,
389                 hWaitForUIThread,
390                 hOutputThread,
391                 hErrorThread
392             };
394  #ifdef UNOPKG
395         WaitForMultipleObjects( SAL_N_ELEMENTS(hObjects), hObjects, TRUE, INFINITE );
396  #else
397         bool    bDetach = false;
398         int     nOpenPipes = 2;
399         do
400         {
401             DWORD dwWaitResult = WaitForMultipleObjects( SAL_N_ELEMENTS(hObjects), hObjects, FALSE, INFINITE );
403             switch ( dwWaitResult )
404             {
405             case WAIT_OBJECT_0: // The child process has terminated
406             case WAIT_OBJECT_0 + 1: // The child process entered input idle
407                 bDetach = true;
408                 break;
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;
412                 break;
413             default: // Something went wrong
414                 bDetach = true;
415                 break;
416             }
417         } while( !bDetach );
419 #endif
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 );
431         return dwExitCode;
432     }
434     return -1;
437 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */