Bump version to 6.4-15
[LibreOffice.git] / desktop / win32 / source / loader.cxx
blob40cef9e3fd4fed6c58b967cd44caaf8c5c4b8467
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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 #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>
29 #include <fstream>
30 #include <boost/property_tree/ptree.hpp>
31 #include <boost/property_tree/ini_parser.hpp>
33 namespace {
35 void fail()
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);
46 LPWSTR* GetCommandArgs(int* pArgc) { return CommandLineToArgvW(GetCommandLineW(), pArgc); }
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
50 std::wstring EscapeArg(LPCWSTR sArg)
52 const size_t nOrigSize = wcslen(sArg);
53 LPCWSTR const end = sArg + nOrigSize;
54 std::wstring sResult(L"\"");
56 LPCWSTR lastPosQuote = sArg;
57 LPCWSTR posQuote;
58 while ((posQuote = std::find(lastPosQuote, end, L'"')) != end)
60 LPCWSTR posBackslash = posQuote;
61 while (posBackslash != lastPosQuote && *(posBackslash - 1) == L'\\')
62 --posBackslash;
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;
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'"');
77 return sResult;
82 namespace desktop_win32 {
84 void extendLoaderEnvironment(WCHAR * binPath, WCHAR * iniDirectory) {
85 if (!GetModuleFileNameW(nullptr, iniDirectory, MAX_PATH)) {
86 fail();
88 WCHAR * iniDirEnd = tools::filename(iniDirectory);
89 WCHAR name[MAX_PATH + MY_LENGTH(L".bin")];
90 // hopefully std::size_t is large enough to not overflow
91 WCHAR * nameEnd = name;
92 for (WCHAR * p = iniDirEnd; *p != L'\0'; ++p) {
93 *nameEnd++ = *p;
95 if (!(nameEnd - name >= 4 && nameEnd[-4] == L'.' &&
96 (((nameEnd[-3] == L'E' || nameEnd[-3] == L'e') &&
97 (nameEnd[-2] == L'X' || nameEnd[-2] == L'x') &&
98 (nameEnd[-1] == L'E' || nameEnd[-1] == L'e')) ||
99 ((nameEnd[-3] == L'C' || nameEnd[-3] == L'c') &&
100 (nameEnd[-2] == L'O' || nameEnd[-2] == L'o') &&
101 (nameEnd[-1] == L'M' || nameEnd[-1] == L'm')))))
103 *nameEnd = L'.';
104 nameEnd += 4;
106 nameEnd[-3] = 'b';
107 nameEnd[-2] = 'i';
108 nameEnd[-1] = 'n';
109 tools::buildPath(binPath, iniDirectory, iniDirEnd, name, nameEnd - name);
110 *iniDirEnd = L'\0';
111 std::size_t const maxEnv = 32767;
112 WCHAR env[maxEnv];
113 DWORD n = GetEnvironmentVariableW(L"PATH", env, maxEnv);
114 if ((n >= maxEnv || n == 0) && GetLastError() != ERROR_ENVVAR_NOT_FOUND) {
115 fail();
117 // must be first in PATH to override other entries
118 assert(*(iniDirEnd - 1) == L'\\'); // hence -1 below
119 if (wcsncmp(env, iniDirectory, iniDirEnd - iniDirectory - 1) != 0
120 || env[iniDirEnd - iniDirectory - 1] != L';')
122 WCHAR pad[MAX_PATH + maxEnv];
123 // hopefully std::size_t is large enough to not overflow
124 WCHAR * p = commandLineAppend(pad, iniDirectory, iniDirEnd - iniDirectory - 1);
125 if (n != 0) {
126 *p++ = L';';
127 for (DWORD i = 0; i <= n; ++i) {
128 *p++ = env[i];
130 } else {
131 *p++ = L'\0';
133 if (!SetEnvironmentVariableW(L"PATH", pad)) {
134 fail();
139 int officeloader_impl(bool bAllowConsole)
141 WCHAR szTargetFileName[MAX_PATH] = {};
142 WCHAR szIniDirectory[MAX_PATH];
143 STARTUPINFOW aStartupInfo;
145 desktop_win32::extendLoaderEnvironment(szTargetFileName, szIniDirectory);
147 ZeroMemory(&aStartupInfo, sizeof(aStartupInfo));
148 aStartupInfo.cb = sizeof(aStartupInfo);
150 // Create process with same command line, environment and stdio handles which
151 // are directed to the created pipes
152 GetStartupInfoW(&aStartupInfo);
154 DWORD dwExitCode = DWORD(-1);
156 BOOL fSuccess = FALSE;
157 LPWSTR lpCommandLine = nullptr;
158 bool bFirst = true;
159 WCHAR cwd[MAX_PATH];
160 DWORD cwdLen = GetCurrentDirectoryW(MAX_PATH, cwd);
161 if (cwdLen >= MAX_PATH)
163 cwdLen = 0;
165 std::vector<std::wstring> aEscapedArgs;
167 // read limit values from bootstrap.ini
168 unsigned int nMaxMemoryInMB = 0;
169 bool bExcludeChildProcesses = true;
171 const WCHAR* szIniFile = L"\\bootstrap.ini";
172 const size_t nDirLen = wcslen(szIniDirectory);
173 if (wcslen(szIniFile) + nDirLen < MAX_PATH)
175 WCHAR szBootstrapIni[MAX_PATH];
176 wcscpy(szBootstrapIni, szIniDirectory);
177 wcscpy(&szBootstrapIni[nDirLen], szIniFile);
181 boost::property_tree::ptree pt;
182 std::ifstream aFile(szBootstrapIni);
183 boost::property_tree::ini_parser::read_ini(aFile, pt);
184 nMaxMemoryInMB = pt.get("Win32.LimitMaximumMemoryInMB", nMaxMemoryInMB);
185 bExcludeChildProcesses = pt.get("Win32.ExcludeChildProcessesFromLimit", bExcludeChildProcesses);
187 catch (...)
189 nMaxMemoryInMB = 0;
193 // create a Windows JobObject with a memory limit
194 HANDLE hJobObject = nullptr;
195 if (nMaxMemoryInMB > 0)
197 JOBOBJECT_EXTENDED_LIMIT_INFORMATION aJobLimit;
198 aJobLimit.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_JOB_MEMORY;
199 if (bExcludeChildProcesses)
200 aJobLimit.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
201 aJobLimit.JobMemoryLimit = nMaxMemoryInMB * 1024 * 1024;
202 hJobObject = CreateJobObjectW(nullptr, nullptr);
203 if (hJobObject != nullptr)
204 SetInformationJobObject(hJobObject, JobObjectExtendedLimitInformation, &aJobLimit,
205 sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
210 if (bFirst)
212 int argc = 0;
213 LPWSTR* argv = GetCommandArgs(&argc);
214 std::size_t n = 0;
215 for (int i = 0; i < argc; ++i)
217 std::wstring sEscapedArg = EscapeArg(argv[i]);
218 aEscapedArgs.push_back(sEscapedArg);
219 n += sEscapedArg.length() + 1; // a space between args
221 LocalFree(argv);
222 n += MY_LENGTH(L" \"-env:OOO_CWD=2") + 4 * cwdLen + MY_LENGTH(L"\"") + 1;
223 // 4 * cwdLen: each char preceded by backslash, each trailing
224 // backslash doubled
225 lpCommandLine = new WCHAR[n];
227 WCHAR* p = desktop_win32::commandLineAppend(lpCommandLine, aEscapedArgs[0].c_str(),
228 aEscapedArgs[0].length());
229 for (size_t i = 1; i < aEscapedArgs.size(); ++i)
231 const std::wstring& rArg = aEscapedArgs[i];
232 if (bFirst || EXITHELPER_NORMAL_RESTART == dwExitCode
233 || wcsncmp(rArg.c_str(), MY_STRING(L"\"-env:")) == 0)
235 p = desktop_win32::commandLineAppend(p, MY_STRING(L" "));
236 p = desktop_win32::commandLineAppend(p, rArg.c_str(), rArg.length());
240 p = desktop_win32::commandLineAppend(p, MY_STRING(L" \"-env:OOO_CWD="));
241 if (cwdLen == 0)
243 p = desktop_win32::commandLineAppend(p, MY_STRING(L"0"));
245 else
247 p = desktop_win32::commandLineAppend(p, MY_STRING(L"2"));
248 p = desktop_win32::commandLineAppendEncoded(p, cwd);
250 desktop_win32::commandLineAppend(p, MY_STRING(L"\""));
251 bFirst = false;
253 WCHAR szParentProcessId[64]; // This is more than large enough for a 128 bit decimal value
254 BOOL bHeadlessMode(FALSE);
257 // Check command line arguments for "--headless" parameter. We only
258 // set the environment variable "ATTACHED_PARENT_PROCESSID" for the headless
259 // mode as self-destruction of the soffice.bin process can lead to
260 // certain side-effects (log-off can result in data-loss, ".lock" is not deleted.
261 // See 138244 for more information.
262 int argc2;
263 LPWSTR* argv2 = GetCommandArgs(&argc2);
265 if (argc2 > 1)
267 int n;
269 for (n = 1; n < argc2; n++)
271 if (0 == wcsnicmp(argv2[n], L"-headless", 9)
272 || 0 == wcsnicmp(argv2[n], L"--headless", 10))
274 bHeadlessMode = TRUE;
279 LocalFree(argv2);
282 if (_ltow(static_cast<long>(GetCurrentProcessId()), szParentProcessId, 10) && bHeadlessMode)
283 SetEnvironmentVariableW(L"ATTACHED_PARENT_PROCESSID", szParentProcessId);
285 PROCESS_INFORMATION aProcessInfo;
287 fSuccess = CreateProcessW(szTargetFileName, lpCommandLine, nullptr, nullptr, TRUE,
288 bAllowConsole ? 0 : DETACHED_PROCESS, nullptr, szIniDirectory,
289 &aStartupInfo, &aProcessInfo);
291 if (fSuccess)
293 DWORD dwWaitResult;
295 if (hJobObject)
296 AssignProcessToJobObject(hJobObject, aProcessInfo.hProcess);
300 // On Windows XP it seems as the desktop calls WaitForInputIdle after "OpenWith" so
301 // we have to do so as if we where processing any messages
303 dwWaitResult = MsgWaitForMultipleObjects(1, &aProcessInfo.hProcess, FALSE, INFINITE,
304 QS_ALLEVENTS);
306 if (WAIT_OBJECT_0 + 1 == dwWaitResult)
308 MSG msg;
310 PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE);
312 } while (WAIT_OBJECT_0 + 1 == dwWaitResult);
314 dwExitCode = 0;
315 GetExitCodeProcess(aProcessInfo.hProcess, &dwExitCode);
317 CloseHandle(aProcessInfo.hProcess);
318 CloseHandle(aProcessInfo.hThread);
320 } while (fSuccess
321 && (EXITHELPER_CRASH_WITH_RESTART == dwExitCode
322 || EXITHELPER_NORMAL_RESTART == dwExitCode));
324 if (hJobObject)
325 CloseHandle(hJobObject);
327 delete[] lpCommandLine;
329 return fSuccess ? dwExitCode : -1;
334 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */