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 */
19
20 #include "loader.hxx"
21 #include <cassert>
22 #include <systools/win32/uwinapi.h>
23 #include <stdlib.h>
24 #include <string>
25 #include <vector>
26 #include <desktop/exithelper.h>
27 #include <tools/pathutils.hxx>
28
29 #include <fstream>
30 #include <boost/property_tree/ptree.hpp>
31 #include <boost/property_tree/ini_parser.hpp>
32
33 namespace {
34
fail()35 void fail()
36 {
37 LPWSTR buf = nullptr;
38 FormatMessageW(
39 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,
40 GetLastError(), 0, reinterpret_cast< LPWSTR >(&buf), 0, nullptr);
41 MessageBoxW(nullptr, buf, nullptr, MB_OK | MB_ICONERROR);
42 HeapFree(GetProcessHeap(), 0, buf);
43 TerminateProcess(GetCurrentProcess(), 255);
44 }
45
GetCommandArgs(int * pArgc)46 LPWSTR* GetCommandArgs(int* pArgc) { return CommandLineToArgvW(GetCommandLineW(), pArgc); }
47
48 // tdf#120249: quotes in arguments need to be escaped; backslashes before quotes need doubling. See
49 // https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-commandlinetoargvw
EscapeArg(LPCWSTR sArg)50 std::wstring EscapeArg(LPCWSTR sArg)
51 {
52 const size_t nOrigSize = wcslen(sArg);
53 LPCWSTR const end = sArg + nOrigSize;
54 std::wstring sResult(L"\"");
55
56 LPCWSTR lastPosQuote = sArg;
57 LPCWSTR posQuote;
58 while ((posQuote = std::find(lastPosQuote, end, L'"')) != end)
59 {
60 LPCWSTR posBackslash = posQuote;
61 while (posBackslash != lastPosQuote && *(posBackslash - 1) == L'\\')
62 --posBackslash;
63
64 sResult.append(lastPosQuote, posBackslash);
65 sResult.append((posQuote - posBackslash) * 2 + 1, L'\\'); // 2n+1 '\' to escape the '"'
66 sResult.append(1, L'"');
67 lastPosQuote = posQuote + 1;
68 }
69
70 LPCWSTR posTrailingBackslashSeq = end;
71 while (posTrailingBackslashSeq != lastPosQuote && *(posTrailingBackslashSeq - 1) == L'\\')
72 --posTrailingBackslashSeq;
73 sResult.append(lastPosQuote, posTrailingBackslashSeq);
74 sResult.append((end - posTrailingBackslashSeq) * 2, L'\\'); // 2n '\' before closing '"'
75 sResult.append(1, L'"');
76
77 return sResult;
78 }
79
AddEscapedArg(LPCWSTR sArg,std::vector<std::wstring> & aEscapedArgs,std::size_t & iLengthAccumulator)80 void AddEscapedArg(LPCWSTR sArg, std::vector<std::wstring>& aEscapedArgs,
81 std::size_t& iLengthAccumulator)
82 {
83 std::wstring sEscapedArg = EscapeArg(sArg);
84 aEscapedArgs.push_back(sEscapedArg);
85 iLengthAccumulator += sEscapedArg.length() + 1; // a space between args
86 }
87
HasWildCard(LPCWSTR sArg)88 bool HasWildCard(LPCWSTR sArg)
89 {
90 while (*sArg != L'\0')
91 {
92 if (*sArg == L'*' || *sArg == L'?')
93 return true;
94 sArg++;
95 }
96 return false;
97 }
98
99 }
100
101 namespace desktop_win32 {
102
extendLoaderEnvironment(WCHAR * binPath,WCHAR * iniDirectory)103 void extendLoaderEnvironment(WCHAR * binPath, WCHAR * iniDirectory) {
104 if (!GetModuleFileNameW(nullptr, iniDirectory, MAX_PATH)) {
105 fail();
106 }
107 WCHAR * iniDirEnd = tools::filename(iniDirectory);
108 WCHAR name[MAX_PATH + MY_LENGTH(L".bin")];
109 // hopefully std::size_t is large enough to not overflow
110 WCHAR * nameEnd = name;
111 for (WCHAR * p = iniDirEnd; *p != L'\0'; ++p) {
112 *nameEnd++ = *p;
113 }
114 if (!(nameEnd - name >= 4 && nameEnd[-4] == L'.' &&
115 (((nameEnd[-3] == L'E' || nameEnd[-3] == L'e') &&
116 (nameEnd[-2] == L'X' || nameEnd[-2] == L'x') &&
117 (nameEnd[-1] == L'E' || nameEnd[-1] == L'e')) ||
118 ((nameEnd[-3] == L'C' || nameEnd[-3] == L'c') &&
119 (nameEnd[-2] == L'O' || nameEnd[-2] == L'o') &&
120 (nameEnd[-1] == L'M' || nameEnd[-1] == L'm')))))
121 {
122 *nameEnd = L'.';
123 nameEnd += 4;
124 }
125 nameEnd[-3] = 'b';
126 nameEnd[-2] = 'i';
127 nameEnd[-1] = 'n';
128 tools::buildPath(binPath, iniDirectory, iniDirEnd, name, nameEnd - name);
129 *iniDirEnd = L'\0';
130 std::size_t const maxEnv = 32767;
131 WCHAR env[maxEnv];
132 DWORD n = GetEnvironmentVariableW(L"PATH", env, maxEnv);
133 if ((n >= maxEnv || n == 0) && GetLastError() != ERROR_ENVVAR_NOT_FOUND) {
134 fail();
135 }
136 // must be first in PATH to override other entries
137 assert(*(iniDirEnd - 1) == L'\\'); // hence -1 below
138 if (wcsncmp(env, iniDirectory, iniDirEnd - iniDirectory - 1) != 0
139 || env[iniDirEnd - iniDirectory - 1] != L';')
140 {
141 WCHAR pad[MAX_PATH + maxEnv];
142 // hopefully std::size_t is large enough to not overflow
143 WCHAR * p = commandLineAppend(pad, iniDirectory, iniDirEnd - iniDirectory - 1);
144 if (n != 0) {
145 *p++ = L';';
146 for (DWORD i = 0; i <= n; ++i) {
147 *p++ = env[i];
148 }
149 } else {
150 *p++ = L'\0';
151 }
152 if (!SetEnvironmentVariableW(L"PATH", pad)) {
153 fail();
154 }
155 }
156 }
157
officeloader_impl(bool bAllowConsole)158 int officeloader_impl(bool bAllowConsole)
159 {
160 WCHAR szTargetFileName[MAX_PATH] = {};
161 WCHAR szIniDirectory[MAX_PATH];
162 STARTUPINFOW aStartupInfo;
163
164 desktop_win32::extendLoaderEnvironment(szTargetFileName, szIniDirectory);
165
166 ZeroMemory(&aStartupInfo, sizeof(aStartupInfo));
167 aStartupInfo.cb = sizeof(aStartupInfo);
168
169 // Create process with same command line, environment and stdio handles which
170 // are directed to the created pipes
171 GetStartupInfoW(&aStartupInfo);
172
173 DWORD dwExitCode = DWORD(-1);
174
175 bool fSuccess = false;
176 LPWSTR lpCommandLine = nullptr;
177 bool bFirst = true;
178 WCHAR cwd[MAX_PATH];
179 DWORD cwdLen = GetCurrentDirectoryW(MAX_PATH, cwd);
180 if (cwdLen >= MAX_PATH)
181 {
182 cwdLen = 0;
183 }
184 std::vector<std::wstring> aEscapedArgs;
185
186 // read limit values from bootstrap.ini
187 unsigned int nMaxMemoryInMB = 0;
188 bool bExcludeChildProcesses = true;
189
190 const WCHAR* szIniFile = L"\\bootstrap.ini";
191 const size_t nDirLen = wcslen(szIniDirectory);
192 if (wcslen(szIniFile) + nDirLen < MAX_PATH)
193 {
194 WCHAR szBootstrapIni[MAX_PATH];
195 wcscpy(szBootstrapIni, szIniDirectory);
196 wcscpy(&szBootstrapIni[nDirLen], szIniFile);
197
198 try
199 {
200 boost::property_tree::ptree pt;
201 std::ifstream aFile(szBootstrapIni);
202 boost::property_tree::ini_parser::read_ini(aFile, pt);
203 nMaxMemoryInMB = pt.get("Win32.LimitMaximumMemoryInMB", nMaxMemoryInMB);
204 bExcludeChildProcesses = pt.get("Win32.ExcludeChildProcessesFromLimit", bExcludeChildProcesses);
205 }
206 catch (...)
207 {
208 nMaxMemoryInMB = 0;
209 }
210 }
211
212 // create a Windows JobObject with a memory limit
213 HANDLE hJobObject = nullptr;
214 if (nMaxMemoryInMB > 0)
215 {
216 JOBOBJECT_EXTENDED_LIMIT_INFORMATION aJobLimit;
217 aJobLimit.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_JOB_MEMORY;
218 if (bExcludeChildProcesses)
219 aJobLimit.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
220 aJobLimit.JobMemoryLimit = nMaxMemoryInMB * 1024 * 1024;
221 hJobObject = CreateJobObjectW(nullptr, nullptr);
222 if (hJobObject != nullptr)
223 SetInformationJobObject(hJobObject, JobObjectExtendedLimitInformation, &aJobLimit,
224 sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
225 }
226
227 do
228 {
229 if (bFirst)
230 {
231 int argc = 0;
232 LPWSTR* argv = GetCommandArgs(&argc);
233 std::size_t n = 0;
234 for (int i = 0; i < argc; ++i)
235 {
236 // check for wildCards in arguments- windows does not expand automatically
237 if (HasWildCard(argv[i]))
238 {
239 WIN32_FIND_DATAW aFindData;
240 HANDLE h = FindFirstFileW(argv[i], &aFindData);
241 if (h == INVALID_HANDLE_VALUE)
242 {
243 AddEscapedArg(argv[i], aEscapedArgs, n);
244 }
245 else
246 {
247 const int nPathSize = 32 * 1024;
248 wchar_t drive[nPathSize];
249 wchar_t dir[nPathSize];
250 wchar_t path[nPathSize];
251 _wsplitpath_s(argv[i], drive, nPathSize, dir, nPathSize, nullptr, 0,
252 nullptr, 0);
253 _wmakepath_s(path, nPathSize, drive, dir, aFindData.cFileName, nullptr);
254 AddEscapedArg(path, aEscapedArgs, n);
255
256 while (FindNextFileW(h, &aFindData))
257 {
258 _wmakepath_s(path, nPathSize, drive, dir, aFindData.cFileName, nullptr);
259 AddEscapedArg(path, aEscapedArgs, n);
260 }
261 FindClose(h);
262 }
263 }
264 else
265 {
266 AddEscapedArg(argv[i], aEscapedArgs, n);
267 }
268 }
269 LocalFree(argv);
270 n += MY_LENGTH(L" \"-env:OOO_CWD=2") + 4 * cwdLen + MY_LENGTH(L"\"") + 1;
271 // 4 * cwdLen: each char preceded by backslash, each trailing
272 // backslash doubled
273 lpCommandLine = new WCHAR[n];
274 }
275 WCHAR* p = desktop_win32::commandLineAppend(lpCommandLine, aEscapedArgs[0].c_str(),
276 aEscapedArgs[0].length());
277 for (size_t i = 1; i < aEscapedArgs.size(); ++i)
278 {
279 const std::wstring& rArg = aEscapedArgs[i];
280 if (bFirst || EXITHELPER_NORMAL_RESTART == dwExitCode
281 || wcsncmp(rArg.c_str(), MY_STRING(L"\"-env:")) == 0)
282 {
283 p = desktop_win32::commandLineAppend(p, MY_STRING(L" "));
284 p = desktop_win32::commandLineAppend(p, rArg.c_str(), rArg.length());
285 }
286 }
287
288 p = desktop_win32::commandLineAppend(p, MY_STRING(L" \"-env:OOO_CWD="));
289 if (cwdLen == 0)
290 {
291 p = desktop_win32::commandLineAppend(p, MY_STRING(L"0"));
292 }
293 else
294 {
295 p = desktop_win32::commandLineAppend(p, MY_STRING(L"2"));
296 p = desktop_win32::commandLineAppendEncoded(p, cwd);
297 }
298 desktop_win32::commandLineAppend(p, MY_STRING(L"\""));
299 bFirst = false;
300
301 WCHAR szParentProcessId[64]; // This is more than large enough for a 128 bit decimal value
302 bool bHeadlessMode(false);
303
304 {
305 // Check command line arguments for "--headless" parameter. We only
306 // set the environment variable "ATTACHED_PARENT_PROCESSID" for the headless
307 // mode as self-destruction of the soffice.bin process can lead to
308 // certain side-effects (log-off can result in data-loss, ".lock" is not deleted.
309 // See 138244 for more information.
310 int argc2;
311 LPWSTR* argv2 = GetCommandArgs(&argc2);
312
313 if (argc2 > 1)
314 {
315 int n;
316
317 for (n = 1; n < argc2; n++)
318 {
319 if (0 == wcsnicmp(argv2[n], L"-headless", 9)
320 || 0 == wcsnicmp(argv2[n], L"--headless", 10))
321 {
322 bHeadlessMode = true;
323 }
324 }
325 }
326
327 LocalFree(argv2);
328 }
329
330 if (_ltow(static_cast<long>(GetCurrentProcessId()), szParentProcessId, 10) && bHeadlessMode)
331 SetEnvironmentVariableW(L"ATTACHED_PARENT_PROCESSID", szParentProcessId);
332
333 PROCESS_INFORMATION aProcessInfo;
334
335 fSuccess = CreateProcessW(szTargetFileName, lpCommandLine, nullptr, nullptr, TRUE,
336 bAllowConsole ? 0 : DETACHED_PROCESS, nullptr, szIniDirectory,
337 &aStartupInfo, &aProcessInfo);
338
339 if (fSuccess)
340 {
341 DWORD dwWaitResult;
342
343 if (hJobObject)
344 AssignProcessToJobObject(hJobObject, aProcessInfo.hProcess);
345
346 do
347 {
348 // On Windows XP it seems as the desktop calls WaitForInputIdle after "OpenWith" so
349 // we have to do so as if we were processing any messages
350
351 dwWaitResult = MsgWaitForMultipleObjects(1, &aProcessInfo.hProcess, FALSE, INFINITE,
352 QS_ALLEVENTS);
353
354 if (WAIT_OBJECT_0 + 1 == dwWaitResult)
355 {
356 MSG msg;
357
358 PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE);
359 }
360 } while (WAIT_OBJECT_0 + 1 == dwWaitResult);
361
362 dwExitCode = 0;
363 GetExitCodeProcess(aProcessInfo.hProcess, &dwExitCode);
364
365 CloseHandle(aProcessInfo.hProcess);
366 CloseHandle(aProcessInfo.hThread);
367 }
368 } while (fSuccess
369 && (EXITHELPER_CRASH_WITH_RESTART == dwExitCode
370 || EXITHELPER_NORMAL_RESTART == dwExitCode));
371
372 if (hJobObject)
373 CloseHandle(hJobObject);
374
375 delete[] lpCommandLine;
376
377 return fSuccess ? dwExitCode : -1;
378 }
379
unopkgloader_impl(bool bAllowConsole)380 int unopkgloader_impl(bool bAllowConsole)
381 {
382 WCHAR szTargetFileName[MAX_PATH];
383 WCHAR szIniDirectory[MAX_PATH];
384 desktop_win32::extendLoaderEnvironment(szTargetFileName, szIniDirectory);
385
386 STARTUPINFOW aStartupInfo{};
387 aStartupInfo.cb = sizeof(aStartupInfo);
388 GetStartupInfoW(&aStartupInfo);
389
390 DWORD dwExitCode = DWORD(-1);
391
392 size_t iniDirLen = wcslen(szIniDirectory);
393 WCHAR cwd[MAX_PATH];
394 DWORD cwdLen = GetCurrentDirectoryW(MAX_PATH, cwd);
395 if (cwdLen >= MAX_PATH) {
396 cwdLen = 0;
397 }
398 WCHAR redirect[MAX_PATH];
399 DWORD dummy;
400 bool hasRedirect =
401 tools::buildPath(
402 redirect, szIniDirectory, szIniDirectory + iniDirLen,
403 MY_STRING(L"redirect.ini")) != nullptr &&
404 (GetBinaryTypeW(redirect, &dummy) || // cheaper check for file existence?
405 GetLastError() != ERROR_FILE_NOT_FOUND);
406 LPWSTR cl1 = GetCommandLineW();
407 WCHAR* cl2 = new WCHAR[
408 wcslen(cl1) +
409 (hasRedirect
410 ? (MY_LENGTH(L" \"-env:INIFILENAME=vnd.sun.star.pathname:") +
411 iniDirLen + MY_LENGTH(L"redirect.ini\""))
412 : 0) +
413 MY_LENGTH(L" \"-env:OOO_CWD=2") + 4 * cwdLen + MY_LENGTH(L"\"") + 1];
414 // 4 * cwdLen: each char preceded by backslash, each trailing backslash
415 // doubled
416 WCHAR* p = desktop_win32::commandLineAppend(cl2, cl1);
417 if (hasRedirect) {
418 p = desktop_win32::commandLineAppend(
419 p, MY_STRING(L" \"-env:INIFILENAME=vnd.sun.star.pathname:"));
420 p = desktop_win32::commandLineAppend(p, szIniDirectory);
421 p = desktop_win32::commandLineAppend(p, MY_STRING(L"redirect.ini\""));
422 }
423 p = desktop_win32::commandLineAppend(p, MY_STRING(L" \"-env:OOO_CWD="));
424 if (cwdLen == 0) {
425 p = desktop_win32::commandLineAppend(p, MY_STRING(L"0"));
426 }
427 else {
428 p = desktop_win32::commandLineAppend(p, MY_STRING(L"2"));
429 p = desktop_win32::commandLineAppendEncoded(p, cwd);
430 }
431 desktop_win32::commandLineAppend(p, MY_STRING(L"\""));
432
433 PROCESS_INFORMATION aProcessInfo;
434
435 bool fSuccess = CreateProcessW(
436 szTargetFileName,
437 cl2,
438 nullptr,
439 nullptr,
440 TRUE,
441 bAllowConsole ? 0 : DETACHED_PROCESS,
442 nullptr,
443 szIniDirectory,
444 &aStartupInfo,
445 &aProcessInfo);
446
447 delete[] cl2;
448
449 if (fSuccess)
450 {
451 DWORD dwWaitResult;
452
453 do
454 {
455 // On Windows XP it seems as the desktop calls WaitForInputIdle after "OpenWidth" so we have to do so
456 // as if we were processing any messages
457
458 dwWaitResult = MsgWaitForMultipleObjects(1, &aProcessInfo.hProcess, FALSE, INFINITE, QS_ALLEVENTS);
459
460 if (WAIT_OBJECT_0 + 1 == dwWaitResult)
461 {
462 MSG msg;
463
464 PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE);
465 }
466 } while (WAIT_OBJECT_0 + 1 == dwWaitResult);
467
468 dwExitCode = 0;
469 GetExitCodeProcess(aProcessInfo.hProcess, &dwExitCode);
470
471 CloseHandle(aProcessInfo.hProcess);
472 CloseHandle(aProcessInfo.hThread);
473 }
474
475 return dwExitCode;
476 }
477
478 }
479
480 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
481