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 .
22 #include <systools/win32/uwinapi.h>
26 #include <desktop/exithelper.h>
27 #include <tools/pathutils.hxx>
30 #include <boost/property_tree/ptree.hpp>
31 #include <boost/property_tree/ini_parser.hpp>
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
;
58 while ((posQuote
= std::find(lastPosQuote
, end
, L
'"')) != end
)
60 LPCWSTR posBackslash
= posQuote
;
61 while (posBackslash
!= lastPosQuote
&& *(posBackslash
- 1) == L
'\\')
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
'"');
82 namespace desktop_win32
{
84 void extendLoaderEnvironment(WCHAR
* binPath
, WCHAR
* iniDirectory
) {
85 if (!GetModuleFileNameW(nullptr, iniDirectory
, MAX_PATH
)) {
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
) {
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')))))
109 tools::buildPath(binPath
, iniDirectory
, iniDirEnd
, name
, nameEnd
- name
);
111 std::size_t const maxEnv
= 32767;
113 DWORD n
= GetEnvironmentVariableW(L
"PATH", env
, maxEnv
);
114 if ((n
>= maxEnv
|| n
== 0) && GetLastError() != ERROR_ENVVAR_NOT_FOUND
) {
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);
127 for (DWORD i
= 0; i
<= n
; ++i
) {
133 if (!SetEnvironmentVariableW(L
"PATH", pad
)) {
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;
160 DWORD cwdLen
= GetCurrentDirectoryW(MAX_PATH
, cwd
);
161 if (cwdLen
>= MAX_PATH
)
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
);
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
));
213 LPWSTR
* argv
= GetCommandArgs(&argc
);
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
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
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="));
243 p
= desktop_win32::commandLineAppend(p
, MY_STRING(L
"0"));
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
"\""));
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.
263 LPWSTR
* argv2
= GetCommandArgs(&argc2
);
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
;
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
);
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
,
306 if (WAIT_OBJECT_0
+ 1 == dwWaitResult
)
310 PeekMessageW(&msg
, nullptr, 0, 0, PM_REMOVE
);
312 } while (WAIT_OBJECT_0
+ 1 == dwWaitResult
);
315 GetExitCodeProcess(aProcessInfo
.hProcess
, &dwExitCode
);
317 CloseHandle(aProcessInfo
.hProcess
);
318 CloseHandle(aProcessInfo
.hThread
);
321 && (EXITHELPER_CRASH_WITH_RESTART
== dwExitCode
322 || EXITHELPER_NORMAL_RESTART
== dwExitCode
));
325 CloseHandle(hJobObject
);
327 delete[] lpCommandLine
;
329 return fSuccess
? dwExitCode
: -1;
334 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */