office-gobmx/desktop/win32/source/loader.cxx
Stephan Bergmann 250f35740d Revert "Fall back to old bootstrap.ini [Win32] section, for backwards compatibility"
This reverts commit ebd3f0971b.
Conflicts:
	desktop/win32/source/loader.cxx
	vcl/win/window/salframe.cxx

Reason for revert:  This fallback functionality was documented as deprecated at
<https://wiki.documentfoundation.org/index.php?title=ReleaseNotes/24.8&oldid=738731>
"fundamental.override.ini", to "be removed in a later version", which is
happening now for LO 25.2.

Change-Id: I40a1800f6a05fc7fd7188917083b269ce6a9f1d2
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/163394
Tested-by: Jenkins
Reviewed-by: Stephan Bergmann <stephan.bergmann@allotropia.de>
2024-09-30 09:44:34 +02:00

428 lines
14 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include "loader.hxx"
#include <algorithm>
#include <cassert>
#include <numeric>
#include <stdlib.h>
#include <string>
#include <string_view>
#include <vector>
#include <desktop/exithelper.h>
#include <systools/win32/extended_max_path.hxx>
#include <systools/win32/uwinapi.h>
#include <tools/pathutils.hxx>
#include <fstream>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>
namespace {
void fail()
{
LPWSTR buf = nullptr;
FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,
GetLastError(), 0, reinterpret_cast< LPWSTR >(&buf), 0, nullptr);
MessageBoxW(nullptr, buf, nullptr, MB_OK | MB_ICONERROR);
HeapFree(GetProcessHeap(), 0, buf);
TerminateProcess(GetCurrentProcess(), 255);
}
struct CommandArgs
{
LPWSTR* argv;
int argc;
CommandArgs() { argv = CommandLineToArgvW(GetCommandLineW(), &argc); }
~CommandArgs() { LocalFree(argv); }
auto begin() const { return argv; }
auto end() const { return begin() + argc; }
};
// tdf#120249: quotes in arguments need to be escaped; backslashes before quotes need doubling. See
// https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-commandlinetoargvw
std::wstring EscapeArg(std::wstring_view sArg)
{
std::wstring sResult(L"\"");
for (size_t lastPosQuote = 0; lastPosQuote <= sArg.size();)
{
const size_t posQuote = std::min(sArg.find(L'"', lastPosQuote), sArg.size());
size_t posBackslash = posQuote;
while (posBackslash != lastPosQuote && sArg[posBackslash - 1] == L'\\')
--posBackslash;
// 2n+1 '\' to escape internal '"'; 2n '\' before closing '"'
const size_t nEscapes = (posQuote - posBackslash) * 2 + (posQuote < sArg.size() ? 1 : 0);
sResult.append(sArg.begin() + lastPosQuote, sArg.begin() + posBackslash);
sResult.append(nEscapes, L'\\');
sResult.append(1, L'"');
lastPosQuote = posQuote + 1;
}
return sResult;
}
std::wstring getCWDarg()
{
std::wstring s(L" \"-env:OOO_CWD=");
DWORD cwdLen = GetCurrentDirectoryW(0, nullptr);
std::vector<WCHAR> cwd(cwdLen);
cwdLen = GetCurrentDirectoryW(cwdLen, cwd.data());
if (cwdLen == 0 || cwdLen >= cwd.size())
{
s += L'0';
}
else
{
s += L'2';
size_t n = 0; // number of trailing backslashes
for (auto* p = cwd.data(); *p; ++p)
{
WCHAR c = *p;
if (c == L'$')
{
s += L"\\$";
n = 0;
}
else if (c == L'\\')
{
s += L"\\\\";
n += 2;
}
else
{
s += c;
n = 0;
}
}
// The command line will continue with a double quote, so double any
// preceding backslashes as required by Windows:
s.append(n, L'\\');
}
s += L'"';
return s;
}
WCHAR* commandLineAppend(WCHAR* buffer, std::wstring_view text)
{
auto ret = std::copy_n(text.begin(), text.size(), buffer);
*ret = 0; // trailing null
return ret;
}
// Set the PATH environment variable in the current (loader) process, so that a
// following CreateProcess has the necessary environment:
// returns a pair of strings { binPath, iniDirectory }
// * binPath is the full path to the "bin" file corresponding to the current executable.
// * iniDirectory is the full directory path (ending in "\") to the "ini" file corresponding to the
// current executable.
[[nodiscard]] std::pair<std::wstring, std::wstring> extendLoaderEnvironment()
{
std::vector<wchar_t> executable_path(EXTENDED_MAX_PATH);
DWORD exe_len;
for (;;)
{
exe_len = GetModuleFileNameW(nullptr, executable_path.data(), executable_path.size());
if (!exe_len)
fail();
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
executable_path.resize(exe_len + 4); // to accommodate a possible ".bin" in the end
break;
}
executable_path.resize(executable_path.size() * 2);
}
WCHAR* iniDirEnd = tools::filename(executable_path.data());
std::wstring_view iniDirView(executable_path.data(), iniDirEnd);
WCHAR* nameEnd = executable_path.data() + exe_len;
if (!(nameEnd - iniDirEnd >= 4 && nameEnd[-4] == L'.' &&
(((nameEnd[-3] == L'E' || nameEnd[-3] == L'e') &&
(nameEnd[-2] == L'X' || nameEnd[-2] == L'x') &&
(nameEnd[-1] == L'E' || nameEnd[-1] == L'e')) ||
((nameEnd[-3] == L'C' || nameEnd[-3] == L'c') &&
(nameEnd[-2] == L'O' || nameEnd[-2] == L'o') &&
(nameEnd[-1] == L'M' || nameEnd[-1] == L'm')))))
{
*nameEnd = L'.';
nameEnd += 4;
}
nameEnd[-3] = 'b';
nameEnd[-2] = 'i';
nameEnd[-1] = 'n';
std::wstring_view nameView(iniDirEnd, nameEnd);
WCHAR env[32767];
DWORD n = GetEnvironmentVariableW(L"PATH", env, std::size(env));
if ((n >= std::size(env) || n == 0) && GetLastError() != ERROR_ENVVAR_NOT_FOUND) {
fail();
}
std::wstring_view envView(env, n);
// must be first in PATH to override other entries
assert(iniDirView.back() == L'\\'); // hence -1 below
std::wstring_view iniDirView1(iniDirView.substr(0, iniDirView.size() - 1));
if (!envView.starts_with(iniDirView1) || env[iniDirView1.size()] != L';')
{
std::wstring pad(iniDirView1);
if (n != 0) {
pad += L';';
pad += envView;
}
if (!SetEnvironmentVariableW(L"PATH", pad.data())) {
fail();
}
}
return { tools::buildPath(iniDirView, nameView), std::wstring(iniDirView) };
}
}
namespace desktop_win32 {
int officeloader_impl(bool bAllowConsole)
{
const auto& [szTargetFileName, szIniDirectory] = extendLoaderEnvironment();
STARTUPINFOW aStartupInfo;
ZeroMemory(&aStartupInfo, sizeof(aStartupInfo));
aStartupInfo.cb = sizeof(aStartupInfo);
// Create process with same command line, environment and stdio handles which
// are directed to the created pipes
GetStartupInfoW(&aStartupInfo);
DWORD dwExitCode = DWORD(-1);
bool fSuccess = false;
bool bFirst = true;
// read limit values from fundamental.override.ini
unsigned int nMaxMemoryInMB = 0;
bool bExcludeChildProcesses = true;
try
{
boost::property_tree::ptree pt;
std::ifstream aFile(szIniDirectory + L"\\fundamental.override.ini");
boost::property_tree::ini_parser::read_ini(aFile, pt);
nMaxMemoryInMB = pt.get("Bootstrap.LimitMaximumMemoryInMB", nMaxMemoryInMB);
bExcludeChildProcesses = pt.get("Bootstrap.ExcludeChildProcessesFromLimit", bExcludeChildProcesses);
}
catch (...)
{
nMaxMemoryInMB = 0;
}
// create a Windows JobObject with a memory limit
HANDLE hJobObject = nullptr;
if (nMaxMemoryInMB > 0)
{
JOBOBJECT_EXTENDED_LIMIT_INFORMATION aJobLimit;
aJobLimit.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_JOB_MEMORY;
if (bExcludeChildProcesses)
aJobLimit.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
aJobLimit.JobMemoryLimit = nMaxMemoryInMB * 1024 * 1024;
hJobObject = CreateJobObjectW(nullptr, nullptr);
if (hJobObject != nullptr)
SetInformationJobObject(hJobObject, JobObjectExtendedLimitInformation, &aJobLimit,
sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
}
std::vector<std::wstring> aEscapedArgs;
bool bHeadlessMode = false;
for (std::wstring_view arg : CommandArgs())
{
// Check command line arguments for "--headless" parameter. We only set the environment
// variable "ATTACHED_PARENT_PROCESSID" for the headless mode as self-destruction of the
// soffice.bin process can lead to certain side-effects (log-off can result in data-loss,
// ".lock" is not deleted). See 138244 for more information.
if (arg == L"-headless" || arg == L"--headless")
bHeadlessMode = true;
// check for wildcards in arguments - Windows does not expand automatically
else if (arg.find_first_of(L"*?") != std::wstring_view::npos)
{
WIN32_FIND_DATAW aFindData;
HANDLE h = FindFirstFileW(arg.data(), &aFindData);
if (h != INVALID_HANDLE_VALUE)
{
const int nPathSize = 32 * 1024;
wchar_t drive[3];
wchar_t dir[nPathSize];
wchar_t path[nPathSize];
_wsplitpath_s(arg.data(), drive, std::size(drive), dir, std::size(dir), nullptr, 0,
nullptr, 0);
do
{
_wmakepath_s(path, std::size(path), drive, dir, aFindData.cFileName, nullptr);
aEscapedArgs.push_back(EscapeArg(path));
} while (FindNextFileW(h, &aFindData));
FindClose(h);
continue;
}
}
aEscapedArgs.push_back(EscapeArg(arg));
}
size_t n = std::accumulate(aEscapedArgs.begin(), aEscapedArgs.end(), aEscapedArgs.size(),
[](size_t a, const std::wstring& s) { return a + s.size(); });
std::wstring sCWDarg = getCWDarg();
n += sCWDarg.size() + 1;
LPWSTR lpCommandLine = new WCHAR[n];
if (bHeadlessMode)
{
WCHAR szParentProcessId[64]; // This is more than large enough for a 128 bit decimal value
if (_ltow(static_cast<long>(GetCurrentProcessId()), szParentProcessId, 10))
SetEnvironmentVariableW(L"ATTACHED_PARENT_PROCESSID", szParentProcessId);
}
do
{
WCHAR* p = commandLineAppend(lpCommandLine, aEscapedArgs[0]);
for (size_t i = 1; i < aEscapedArgs.size(); ++i)
{
const std::wstring& rArg = aEscapedArgs[i];
if (bFirst || EXITHELPER_NORMAL_RESTART == dwExitCode || rArg.starts_with(L"\"-env:"))
{
p = commandLineAppend(p, L" ");
p = commandLineAppend(p, rArg);
}
}
commandLineAppend(p, sCWDarg);
bFirst = false;
PROCESS_INFORMATION aProcessInfo;
fSuccess = CreateProcessW(szTargetFileName.data(), lpCommandLine, nullptr, nullptr, TRUE,
bAllowConsole ? 0 : DETACHED_PROCESS, nullptr,
szIniDirectory.data(), &aStartupInfo, &aProcessInfo);
if (fSuccess)
{
DWORD dwWaitResult;
if (hJobObject)
AssignProcessToJobObject(hJobObject, aProcessInfo.hProcess);
do
{
// On Windows XP it seems as the desktop calls WaitForInputIdle after "OpenWith" so
// we have to do so as if we were processing any messages
dwWaitResult = MsgWaitForMultipleObjects(1, &aProcessInfo.hProcess, FALSE, INFINITE,
QS_ALLEVENTS);
if (WAIT_OBJECT_0 + 1 == dwWaitResult)
{
MSG msg;
PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE);
}
} while (WAIT_OBJECT_0 + 1 == dwWaitResult);
dwExitCode = 0;
GetExitCodeProcess(aProcessInfo.hProcess, &dwExitCode);
CloseHandle(aProcessInfo.hProcess);
CloseHandle(aProcessInfo.hThread);
}
} while (fSuccess
&& (EXITHELPER_CRASH_WITH_RESTART == dwExitCode
|| EXITHELPER_NORMAL_RESTART == dwExitCode));
if (hJobObject)
CloseHandle(hJobObject);
delete[] lpCommandLine;
return fSuccess ? dwExitCode : -1;
}
int unopkgloader_impl(bool bAllowConsole)
{
const auto& [szTargetFileName, szIniDirectory] = extendLoaderEnvironment();
STARTUPINFOW aStartupInfo{};
aStartupInfo.cb = sizeof(aStartupInfo);
GetStartupInfoW(&aStartupInfo);
DWORD dwExitCode = DWORD(-1);
std::wstring sCWDarg = getCWDarg();
DWORD dummy;
std::wstring redirect = tools::buildPath(szIniDirectory, L"redirect.ini");
bool hasRedirect = !redirect.empty() &&
(GetBinaryTypeW(redirect.data(), &dummy) || // cheaper check for file existence?
GetLastError() != ERROR_FILE_NOT_FOUND);
LPWSTR cl1 = GetCommandLineW();
std::wstring cl2 = cl1;
if (hasRedirect)
cl2 += L" \"-env:INIFILENAME=vnd.sun.star.pathname:" + redirect + L"\"";
cl2 += sCWDarg;
PROCESS_INFORMATION aProcessInfo;
bool fSuccess = CreateProcessW(
szTargetFileName.data(),
cl2.data(),
nullptr,
nullptr,
TRUE,
bAllowConsole ? 0 : DETACHED_PROCESS,
nullptr,
szIniDirectory.data(),
&aStartupInfo,
&aProcessInfo);
if (fSuccess)
{
DWORD dwWaitResult;
do
{
// On Windows XP it seems as the desktop calls WaitForInputIdle after "OpenWidth" so we have to do so
// as if we were processing any messages
dwWaitResult = MsgWaitForMultipleObjects(1, &aProcessInfo.hProcess, FALSE, INFINITE, QS_ALLEVENTS);
if (WAIT_OBJECT_0 + 1 == dwWaitResult)
{
MSG msg;
PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE);
}
} while (WAIT_OBJECT_0 + 1 == dwWaitResult);
dwExitCode = 0;
GetExitCodeProcess(aProcessInfo.hProcess, &dwExitCode);
CloseHandle(aProcessInfo.hProcess);
CloseHandle(aProcessInfo.hThread);
}
return dwExitCode;
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */