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
'"');
80 void AddEscapedArg(LPCWSTR sArg
, std::vector
<std::wstring
>& aEscapedArgs
,
81 std::size_t& iLengthAccumulator
)
83 std::wstring sEscapedArg
= EscapeArg(sArg
);
84 aEscapedArgs
.push_back(sEscapedArg
);
85 iLengthAccumulator
+= sEscapedArg
.length() + 1; // a space between args
88 bool HasWildCard(LPCWSTR sArg
)
90 while (*sArg
!= L
'\0')
92 if (*sArg
== L
'*' || *sArg
== L
'?')
101 namespace desktop_win32
{
103 void extendLoaderEnvironment(WCHAR
* binPath
, WCHAR
* iniDirectory
) {
104 if (!GetModuleFileNameW(nullptr, iniDirectory
, MAX_PATH
)) {
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
) {
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')))))
128 tools::buildPath(binPath
, iniDirectory
, iniDirEnd
, name
, nameEnd
- name
);
130 std::size_t const maxEnv
= 32767;
132 DWORD n
= GetEnvironmentVariableW(L
"PATH", env
, maxEnv
);
133 if ((n
>= maxEnv
|| n
== 0) && GetLastError() != ERROR_ENVVAR_NOT_FOUND
) {
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
';')
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);
146 for (DWORD i
= 0; i
<= n
; ++i
) {
152 if (!SetEnvironmentVariableW(L
"PATH", pad
)) {
158 int officeloader_impl(bool bAllowConsole
)
160 WCHAR szTargetFileName
[MAX_PATH
] = {};
161 WCHAR szIniDirectory
[MAX_PATH
];
162 STARTUPINFOW aStartupInfo
;
164 desktop_win32::extendLoaderEnvironment(szTargetFileName
, szIniDirectory
);
166 ZeroMemory(&aStartupInfo
, sizeof(aStartupInfo
));
167 aStartupInfo
.cb
= sizeof(aStartupInfo
);
169 // Create process with same command line, environment and stdio handles which
170 // are directed to the created pipes
171 GetStartupInfoW(&aStartupInfo
);
173 DWORD dwExitCode
= DWORD(-1);
175 bool fSuccess
= false;
176 LPWSTR lpCommandLine
= nullptr;
179 DWORD cwdLen
= GetCurrentDirectoryW(MAX_PATH
, cwd
);
180 if (cwdLen
>= MAX_PATH
)
184 std::vector
<std::wstring
> aEscapedArgs
;
186 // read limit values from bootstrap.ini
187 unsigned int nMaxMemoryInMB
= 0;
188 bool bExcludeChildProcesses
= true;
190 const WCHAR
* szIniFile
= L
"\\bootstrap.ini";
191 const size_t nDirLen
= wcslen(szIniDirectory
);
192 if (wcslen(szIniFile
) + nDirLen
< MAX_PATH
)
194 WCHAR szBootstrapIni
[MAX_PATH
];
195 wcscpy(szBootstrapIni
, szIniDirectory
);
196 wcscpy(&szBootstrapIni
[nDirLen
], szIniFile
);
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
);
212 // create a Windows JobObject with a memory limit
213 HANDLE hJobObject
= nullptr;
214 if (nMaxMemoryInMB
> 0)
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
));
232 LPWSTR
* argv
= GetCommandArgs(&argc
);
234 for (int i
= 0; i
< argc
; ++i
)
236 // check for wildCards in arguments- windows does not expand automatically
237 if (HasWildCard(argv
[i
]))
239 WIN32_FIND_DATAW aFindData
;
240 HANDLE h
= FindFirstFileW(argv
[i
], &aFindData
);
241 if (h
== INVALID_HANDLE_VALUE
)
243 AddEscapedArg(argv
[i
], aEscapedArgs
, n
);
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,
253 _wmakepath_s(path
, nPathSize
, drive
, dir
, aFindData
.cFileName
, nullptr);
254 AddEscapedArg(path
, aEscapedArgs
, n
);
256 while (FindNextFileW(h
, &aFindData
))
258 _wmakepath_s(path
, nPathSize
, drive
, dir
, aFindData
.cFileName
, nullptr);
259 AddEscapedArg(path
, aEscapedArgs
, n
);
266 AddEscapedArg(argv
[i
], aEscapedArgs
, n
);
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
273 lpCommandLine
= new WCHAR
[n
];
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
)
279 const std::wstring
& rArg
= aEscapedArgs
[i
];
280 if (bFirst
|| EXITHELPER_NORMAL_RESTART
== dwExitCode
281 || wcsncmp(rArg
.c_str(), MY_STRING(L
"\"-env:")) == 0)
283 p
= desktop_win32::commandLineAppend(p
, MY_STRING(L
" "));
284 p
= desktop_win32::commandLineAppend(p
, rArg
.c_str(), rArg
.length());
288 p
= desktop_win32::commandLineAppend(p
, MY_STRING(L
" \"-env:OOO_CWD="));
291 p
= desktop_win32::commandLineAppend(p
, MY_STRING(L
"0"));
295 p
= desktop_win32::commandLineAppend(p
, MY_STRING(L
"2"));
296 p
= desktop_win32::commandLineAppendEncoded(p
, cwd
);
298 desktop_win32::commandLineAppend(p
, MY_STRING(L
"\""));
301 WCHAR szParentProcessId
[64]; // This is more than large enough for a 128 bit decimal value
302 bool bHeadlessMode(false);
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.
311 LPWSTR
* argv2
= GetCommandArgs(&argc2
);
317 for (n
= 1; n
< argc2
; n
++)
319 if (0 == wcsnicmp(argv2
[n
], L
"-headless", 9)
320 || 0 == wcsnicmp(argv2
[n
], L
"--headless", 10))
322 bHeadlessMode
= true;
330 if (_ltow(static_cast<long>(GetCurrentProcessId()), szParentProcessId
, 10) && bHeadlessMode
)
331 SetEnvironmentVariableW(L
"ATTACHED_PARENT_PROCESSID", szParentProcessId
);
333 PROCESS_INFORMATION aProcessInfo
;
335 fSuccess
= CreateProcessW(szTargetFileName
, lpCommandLine
, nullptr, nullptr, TRUE
,
336 bAllowConsole
? 0 : DETACHED_PROCESS
, nullptr, szIniDirectory
,
337 &aStartupInfo
, &aProcessInfo
);
344 AssignProcessToJobObject(hJobObject
, aProcessInfo
.hProcess
);
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
351 dwWaitResult
= MsgWaitForMultipleObjects(1, &aProcessInfo
.hProcess
, FALSE
, INFINITE
,
354 if (WAIT_OBJECT_0
+ 1 == dwWaitResult
)
358 PeekMessageW(&msg
, nullptr, 0, 0, PM_REMOVE
);
360 } while (WAIT_OBJECT_0
+ 1 == dwWaitResult
);
363 GetExitCodeProcess(aProcessInfo
.hProcess
, &dwExitCode
);
365 CloseHandle(aProcessInfo
.hProcess
);
366 CloseHandle(aProcessInfo
.hThread
);
369 && (EXITHELPER_CRASH_WITH_RESTART
== dwExitCode
370 || EXITHELPER_NORMAL_RESTART
== dwExitCode
));
373 CloseHandle(hJobObject
);
375 delete[] lpCommandLine
;
377 return fSuccess
? dwExitCode
: -1;
380 int unopkgloader_impl(bool bAllowConsole
)
382 WCHAR szTargetFileName
[MAX_PATH
];
383 WCHAR szIniDirectory
[MAX_PATH
];
384 desktop_win32::extendLoaderEnvironment(szTargetFileName
, szIniDirectory
);
386 STARTUPINFOW aStartupInfo
{};
387 aStartupInfo
.cb
= sizeof(aStartupInfo
);
388 GetStartupInfoW(&aStartupInfo
);
390 DWORD dwExitCode
= DWORD(-1);
392 size_t iniDirLen
= wcslen(szIniDirectory
);
394 DWORD cwdLen
= GetCurrentDirectoryW(MAX_PATH
, cwd
);
395 if (cwdLen
>= MAX_PATH
) {
398 WCHAR redirect
[MAX_PATH
];
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
[
410 ? (MY_LENGTH(L
" \"-env:INIFILENAME=vnd.sun.star.pathname:") +
411 iniDirLen
+ MY_LENGTH(L
"redirect.ini\""))
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
416 WCHAR
* p
= desktop_win32::commandLineAppend(cl2
, cl1
);
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\""));
423 p
= desktop_win32::commandLineAppend(p
, MY_STRING(L
" \"-env:OOO_CWD="));
425 p
= desktop_win32::commandLineAppend(p
, MY_STRING(L
"0"));
428 p
= desktop_win32::commandLineAppend(p
, MY_STRING(L
"2"));
429 p
= desktop_win32::commandLineAppendEncoded(p
, cwd
);
431 desktop_win32::commandLineAppend(p
, MY_STRING(L
"\""));
433 PROCESS_INFORMATION aProcessInfo
;
435 bool fSuccess
= CreateProcessW(
441 bAllowConsole
? 0 : DETACHED_PROCESS
,
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
458 dwWaitResult
= MsgWaitForMultipleObjects(1, &aProcessInfo
.hProcess
, FALSE
, INFINITE
, QS_ALLEVENTS
);
460 if (WAIT_OBJECT_0
+ 1 == dwWaitResult
)
464 PeekMessageW(&msg
, nullptr, 0, 0, PM_REMOVE
);
466 } while (WAIT_OBJECT_0
+ 1 == dwWaitResult
);
469 GetExitCodeProcess(aProcessInfo
.hProcess
, &dwExitCode
);
471 CloseHandle(aProcessInfo
.hProcess
);
472 CloseHandle(aProcessInfo
.hThread
);
480 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */