/* -*- 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 <sal/config.h>
#include <config_dbus.h>
#include <config_features.h>
#include <config_feature_desktop.h>
#include <app.hxx>
#include "officeipcthread.hxx"
#include "cmdlineargs.hxx"
#include "dispatchwatcher.hxx"
#include <com/sun/star/frame/TerminationVetoException.hpp>
#include <salhelper/thread.hxx>
#include <sal/log.hxx>
#include <unotools/bootstrap.hxx>
#include <utility>
#include <vcl/svapp.hxx>
#include <unotools/configmgr.hxx>
#include <osl/pipe.hxx>
#include <rtl/digest.h>
#include <rtl/ustrbuf.hxx>
#include <osl/conditn.hxx>
#include <unotools/moduleoptions.hxx>
#include <rtl/strbuf.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <osl/file.hxx>
#include <rtl/process.h>
#include <o3tl/string_view.hxx>
#include <cassert>
#include <cstdlib>
#include <memory>
#include <thread>
#if ENABLE_DBUS
#include <dbus/dbus.h>
#include <sys/socket.h>
#endif
using namespace desktop;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::frame;
namespace {
char const ARGUMENT_PREFIX[] = "InternalIPC::Arguments";
char const SEND_ARGUMENTS[] = "InternalIPC::SendArguments";
char const PROCESSING_DONE[] = "InternalIPC::ProcessingDone";
// Receives packets from the pipe until a packet ends in a NUL character (that
// will not be included in the returned string) or it cannot read anything (due
// to error or closed pipe, in which case an empty string will be returned to
// signal failure):
OString readStringFromPipe(osl::StreamPipe const & pipe) {
for (OStringBuffer str;;) {
char buf[1024];
sal_Int32 n = pipe.recv(buf, std::size(buf));
if (n <= 0) {
SAL_INFO("desktop.app", "read empty string");
return ""_ostr;
}
bool end = false;
if (buf[n - 1] == '\0') {
end = true;
--n;
}
str.append(buf, n);
//TODO: how does OStringBuffer.append handle overflow?
if (end) {
auto s = str.makeStringAndClear();
SAL_INFO("desktop.app", "read <" << s << ">");
return s;
}
}
}
}
namespace desktop
{
namespace {
class Parser: public CommandLineArgs::Supplier {
public:
explicit Parser(OString input): m_input(std::move(input)) {
if (!m_input.match(ARGUMENT_PREFIX) ||
m_input.getLength() == RTL_CONSTASCII_LENGTH(ARGUMENT_PREFIX))
{
throw CommandLineArgs::Supplier::Exception();
}
m_index = RTL_CONSTASCII_LENGTH(ARGUMENT_PREFIX);
switch (m_input[m_index++]) {
case '0':
break;
case '1':
{
OUString url;
if (!next(url, false)) {
throw CommandLineArgs::Supplier::Exception();
}
m_cwdUrl = url;
break;
}
case '2':
{
OUString path;
if (!next(path, false)) {
throw CommandLineArgs::Supplier::Exception();
}
OUString url;
if (osl::FileBase::getFileURLFromSystemPath(path, url) ==
osl::FileBase::E_None)
{
m_cwdUrl = url;
}
break;
}
default:
throw CommandLineArgs::Supplier::Exception();
}
}
virtual std::optional< OUString > getCwdUrl() override { return m_cwdUrl; }
virtual bool next(OUString& argument) override { return next(argument, true); }
private:
bool next(OUString& argument, bool prefix) {
if (m_index < m_input.getLength()) {
if (prefix) {
if (m_input[m_index] != ',') {
throw CommandLineArgs::Supplier::Exception();
}
++m_index;
}
OStringBuffer b;
while (m_index < m_input.getLength()) {
char c = m_input[m_index];
if (c == ',') {
break;
}
++m_index;
if (c == '\\') {
if (m_index >= m_input.getLength())
throw CommandLineArgs::Supplier::Exception();
c = m_input[m_index++];
switch (c) {
case '0':
c = '\0';
break;
case ',':
case '\\':
break;
default:
throw CommandLineArgs::Supplier::Exception();
}
}
b.append(c);
}
OString b2(b.makeStringAndClear());
if (!rtl_convertStringToUString(
&argument.pData, b2.getStr(), b2.getLength(),
RTL_TEXTENCODING_UTF8,
(RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR |
RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR |
RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR)))
{
throw CommandLineArgs::Supplier::Exception();
}
return true;
} else {
return false;
}
}
std::optional< OUString > m_cwdUrl;
OString m_input;
sal_Int32 m_index;
};
bool addArgument(OStringBuffer &rArguments, char prefix,
const OUString &rArgument)
{
OString utf8;
if (!rArgument.convertToString(
&utf8, RTL_TEXTENCODING_UTF8,
(RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR |
RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR)))
{
return false;
}
rArguments.append(prefix);
for (sal_Int32 i = 0; i < utf8.getLength(); ++i) {
char c = utf8[i];
switch (c) {
case '\0':
rArguments.append("\\0");
break;
case ',':
rArguments.append("\\,");
break;
case '\\':
rArguments.append("\\\\");
break;
default:
rArguments.append(c);
break;
}
}
return true;
}
}
rtl::Reference< RequestHandler > RequestHandler::pGlobal;
// Turns a string in aMsg such as file:///home/foo/.libreoffice/3
// Into a hex string of well known length ff132a86...
static OUString CreateMD5FromString( const OUString& aMsg )
{
SAL_INFO("desktop.app", "create md5 from '" << aMsg << "'");
rtlDigest handle = rtl_digest_create( rtl_Digest_AlgorithmMD5 );
if ( handle )
{
const sal_uInt8* pData = reinterpret_cast<const sal_uInt8*>(aMsg.getStr());
sal_uInt32 nSize = aMsg.getLength() * sizeof( sal_Unicode );
sal_uInt32 nMD5KeyLen = rtl_digest_queryLength( handle );
std::unique_ptr<sal_uInt8[]> pMD5KeyBuffer(new sal_uInt8[ nMD5KeyLen ]);
rtl_digest_init( handle, pData, nSize );
rtl_digest_update( handle, pData, nSize );
rtl_digest_get( handle, pMD5KeyBuffer.get(), nMD5KeyLen );
rtl_digest_destroy( handle );
// Create hex-value string from the MD5 value to keep the string size minimal
OUStringBuffer aBuffer( nMD5KeyLen * 2 + 1 );
for ( sal_uInt32 i = 0; i < nMD5KeyLen; i++ )
aBuffer.append( static_cast<sal_Int32>(pMD5KeyBuffer[i]), 16 );
return aBuffer.makeStringAndClear();
}
return OUString();
}
namespace {
class ProcessEventsClass_Impl
{
public:
DECL_STATIC_LINK( ProcessEventsClass_Impl, CallEvent, void*, void );
DECL_STATIC_LINK( ProcessEventsClass_Impl, ProcessDocumentsEvent, void*, void );
};
}
IMPL_STATIC_LINK( ProcessEventsClass_Impl, CallEvent, void*, pEvent, void )
{
// Application events are processed by the Desktop::HandleAppEvent implementation.
Desktop::HandleAppEvent( *static_cast<ApplicationEvent*>(pEvent) );
delete static_cast<ApplicationEvent*>(pEvent);
}
IMPL_STATIC_LINK( ProcessEventsClass_Impl, ProcessDocumentsEvent, void*, pEvent, void )
{
// Documents requests are processed by the RequestHandler implementation
ProcessDocumentsRequest* pDocsRequest = static_cast<ProcessDocumentsRequest*>(pEvent);
RequestHandler::ExecuteCmdLineRequests(*pDocsRequest, false);
delete pDocsRequest;
}
static void ImplPostForeignAppEvent( ApplicationEvent* pEvent )
{
Application::PostUserEvent( LINK( nullptr, ProcessEventsClass_Impl, CallEvent ), pEvent );
}
static void ImplPostProcessDocumentsEvent( std::unique_ptr<ProcessDocumentsRequest> pEvent )
{
Application::PostUserEvent( LINK( nullptr, ProcessEventsClass_Impl, ProcessDocumentsEvent ), pEvent.release() );
}
oslSignalAction SalMainPipeExchangeSignal_impl(SAL_UNUSED_PARAMETER void* /*pData*/, oslSignalInfo* pInfo)
{
if( pInfo->Signal == osl_Signal_Terminate )
RequestHandler::Disable();
return osl_Signal_ActCallNextHdl;
}
// The RequestHandlerController implementation is a bookkeeper for all pending requests
// that were created by the RequestHandler. The requests are waiting to be processed by
// our framework loadComponentFromURL function (e.g. open/print request).
// During shutdown the framework is asking RequestHandlerController about pending requests.
// If there are pending requests framework has to stop the shutdown process. It is waiting
// for these requests because framework is not able to handle shutdown and open a document
// concurrently.
// XServiceInfo
OUString SAL_CALL RequestHandlerController::getImplementationName()
{
return u"com.sun.star.comp.RequestHandlerController"_ustr;
}
sal_Bool RequestHandlerController::supportsService(
OUString const & ServiceName)
{
return cppu::supportsService(this, ServiceName);
}
Sequence< OUString > SAL_CALL RequestHandlerController::getSupportedServiceNames()
{
return { };
}
// XEventListener
void SAL_CALL RequestHandlerController::disposing( const EventObject& )
{
}
// XTerminateListener
void SAL_CALL RequestHandlerController::queryTermination( const EventObject& )
{
// Desktop ask about pending request through our office ipc pipe. We have to
// be sure that no pending request is waiting because framework is not able to
// handle shutdown and open a document concurrently.
if ( RequestHandler::AreRequestsPending() )
throw TerminationVetoException();
RequestHandler::SetDowning();
}
void SAL_CALL RequestHandlerController::notifyTermination( const EventObject& )
{
}
class IpcThread: public salhelper::Thread {
public:
void start(RequestHandler * handler) {
m_handler = handler;
launch();
}
virtual void close() = 0;
protected:
explicit IpcThread(char const * name): Thread(name), m_handler(nullptr) {}
virtual ~IpcThread() override {}
bool process(OString const & arguments, bool * waitProcessed);
RequestHandler * m_handler;
};
class PipeIpcThread: public IpcThread {
public:
static RequestHandler::Status enable(rtl::Reference<IpcThread> * thread);
private:
explicit PipeIpcThread(osl::Pipe pipe):
IpcThread("PipeIPC"), pipe_(std::move(pipe))
{}
virtual ~PipeIpcThread() override {}
void execute() override;
void close() override { pipe_.close(); }
osl::Pipe pipe_;
};
#if ENABLE_DBUS
namespace {
struct DbusConnectionHolder {
explicit DbusConnectionHolder(DBusConnection * theConnection):
connection(theConnection)
{}
DbusConnectionHolder(DbusConnectionHolder && other): connection(nullptr)
{ std::swap(connection, other.connection); }
~DbusConnectionHolder() {
if (connection != nullptr) {
dbus_connection_close(connection);
dbus_connection_unref(connection);
}
}
DBusConnection * connection;
};
struct DbusMessageHolder {
explicit DbusMessageHolder(DBusMessage * theMessage): message(theMessage) {}
~DbusMessageHolder() { clear(); }
void clear() {
if (message != nullptr) {
dbus_message_unref(message);
}
message = nullptr;
}
DBusMessage * message;
private:
DbusMessageHolder(DbusMessageHolder const &) = delete;
DbusMessageHolder& operator =(DbusMessageHolder const &) = delete;
};
}
class DbusIpcThread: public IpcThread {
public:
static RequestHandler::Status enable(rtl::Reference<IpcThread> * thread);
private:
explicit DbusIpcThread(DbusConnectionHolder && connection):
IpcThread("DbusIPC"), connection_(std::move(connection))
{}
virtual ~DbusIpcThread() override {}
void execute() override;
void close() override;
DbusConnectionHolder connection_;
};
RequestHandler::Status DbusIpcThread::enable(rtl::Reference<IpcThread> * thread)
{
assert(thread != nullptr);
if (!dbus_threads_init_default()) {
SAL_WARN("desktop.app", "dbus_threads_init_default failed");
return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
}
DBusError e;
dbus_error_init(&e);
DbusConnectionHolder con(dbus_bus_get_private(DBUS_BUS_SESSION, &e));
assert((con.connection == nullptr) == bool(dbus_error_is_set(&e)));
if (con.connection == nullptr) {
SAL_WARN(
"desktop.app",
"dbus_bus_get_private failed with: " << e.name << ": "
<< e.message);
dbus_error_free(&e);
return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
}
for (;;) {
int n = dbus_bus_request_name(
con.connection, "org.libreoffice.LibreOfficeIpc0",
DBUS_NAME_FLAG_DO_NOT_QUEUE, &e);
assert((n == -1) == bool(dbus_error_is_set(&e)));
switch (n) {
case -1:
SAL_WARN(
"desktop.app",
"dbus_bus_request_name failed with: " << e.name << ": "
<< e.message);
dbus_error_free(&e);
return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
*thread = new DbusIpcThread(std::move(con));
return RequestHandler::IPC_STATUS_OK;
case DBUS_REQUEST_NAME_REPLY_EXISTS:
{
OStringBuffer buf(ARGUMENT_PREFIX);
OUString arg;
if (!(utl::Bootstrap::getProcessWorkingDir(arg)
&& addArgument(buf, '1', arg)))
{
buf.append('0');
}
sal_uInt32 narg = rtl_getAppCommandArgCount();
for (sal_uInt32 i = 0; i != narg; ++i) {
rtl_getAppCommandArg(i, &arg.pData);
if (!addArgument(buf, ',', arg)) {
return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
}
}
char const * argstr = buf.getStr();
DbusMessageHolder msg(
dbus_message_new_method_call(
"org.libreoffice.LibreOfficeIpc0",
"/org/libreoffice/LibreOfficeIpc0",
"org.libreoffice.LibreOfficeIpcIfc0", "Execute"));
if (msg.message == nullptr) {
SAL_WARN(
"desktop.app", "dbus_message_new_method_call failed");
return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
}
DBusMessageIter it;
dbus_message_iter_init_append(msg.message, &it);
if (!dbus_message_iter_append_basic(
&it, DBUS_TYPE_STRING, &argstr))
{
SAL_WARN(
"desktop.app", "dbus_message_iter_append_basic failed");
return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
}
DbusMessageHolder repl(
dbus_connection_send_with_reply_and_block(
con.connection, msg.message, 0x7FFFFFFF, &e));
assert(
(repl.message == nullptr) == bool(dbus_error_is_set(&e)));
if (repl.message == nullptr) {
SAL_INFO(
"desktop.app",
"dbus_connection_send_with_reply_and_block failed"
" with: " << e.name << ": " << e.message);
dbus_error_free(&e);
break;
}
return RequestHandler::IPC_STATUS_2ND_OFFICE;
}
case DBUS_REQUEST_NAME_REPLY_IN_QUEUE:
case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER:
SAL_WARN(
"desktop.app",
"dbus_bus_request_name failed with unexpected " << +n);
return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
default:
for (;;) std::abort();
}
}
}
void DbusIpcThread::execute()
{
assert(m_handler != nullptr);
m_handler->cReady.wait();
for (;;) {
{
osl::MutexGuard g(RequestHandler::GetMutex());
if (m_handler->mState == RequestHandler::State::Downing) {
break;
}
}
if (!dbus_connection_read_write(connection_.connection, -1)) {
break;
}
for (;;) {
DbusMessageHolder msg(
dbus_connection_pop_message(connection_.connection));
if (msg.message == nullptr) {
break;
}
if (!dbus_message_is_method_call(
msg.message, "org.libreoffice.LibreOfficeIpcIfc0",
"Execute"))
{
SAL_INFO("desktop.app", "unknown DBus message ignored");
continue;
}
DBusMessageIter it;
if (!dbus_message_iter_init(msg.message, &it)) {
SAL_WARN(
"desktop.app", "DBus message without argument ignored");
continue;
}
if (dbus_message_iter_get_arg_type(&it) != DBUS_TYPE_STRING) {
SAL_WARN(
"desktop.app",
"DBus message with non-string argument ignored");
continue;
}
char const * argstr;
dbus_message_iter_get_basic(&it, &argstr);
bool waitProcessed = false;
{
osl::MutexGuard g(RequestHandler::GetMutex());
if (!process(argstr, &waitProcessed)) {
continue;
}
}
if (waitProcessed) {
m_handler->cProcessed.wait();
}
DbusMessageHolder repl(dbus_message_new_method_return(msg.message));
if (repl.message == nullptr) {
SAL_WARN(
"desktop.app", "dbus_message_new_method_return failed");
continue;
}
dbus_uint32_t serial = 0;
if (!dbus_connection_send(
connection_.connection, repl.message, &serial)) {
SAL_WARN("desktop.app", "dbus_connection_send failed");
continue;
}
dbus_connection_flush(connection_.connection);
}
}
}
void DbusIpcThread::close() {
assert(connection_.connection != nullptr);
// Make dbus_connection_read_write fall out of internal poll call blocking
// on POLLIN:
int fd;
if (!dbus_connection_get_socket(connection_.connection, &fd)) {
SAL_WARN("desktop.app", "dbus_connection_get_socket failed");
return;
}
if (shutdown(fd, SHUT_RD) == -1) {
auto const e = errno;
SAL_WARN("desktop.app", "shutdown failed with errno " << e);
}
}
#endif
::osl::Mutex& RequestHandler::GetMutex()
{
static ::osl::Mutex theRequestHandlerMutex;
return theRequestHandlerMutex;
}
void RequestHandler::SetDowning()
{
// We have the order to block all incoming requests. Framework
// wants to shutdown and we have to make sure that no loading/printing
// requests are executed anymore.
::osl::MutexGuard aGuard( GetMutex() );
if ( pGlobal.is() )
pGlobal->mState = State::Downing;
}
void RequestHandler::EnableRequests()
{
// switch between just queueing the requests and executing them
::osl::MutexGuard aGuard( GetMutex() );
if ( pGlobal.is() )
{
if (pGlobal->mState != State::Downing) {
pGlobal->mState = State::RequestsEnabled;
}
ProcessDocumentsRequest aEmptyReq(std::nullopt);
// trigger already queued requests
RequestHandler::ExecuteCmdLineRequests(aEmptyReq, true);
}
}
bool RequestHandler::AreRequestsPending()
{
// Give info about pending requests
::osl::MutexGuard aGuard( GetMutex() );
if ( pGlobal.is() )
return ( pGlobal->mnPendingRequests > 0 );
else
return false;
}
void RequestHandler::RequestsCompleted()
{
// Remove nCount pending requests from our internal counter
::osl::MutexGuard aGuard( GetMutex() );
if ( pGlobal.is() )
{
if ( pGlobal->mnPendingRequests > 0 )
pGlobal->mnPendingRequests --;
}
}
RequestHandler::Status RequestHandler::Enable(bool ipc)
{
::osl::MutexGuard aGuard( GetMutex() );
if( pGlobal.is() )
return IPC_STATUS_OK;
#if !HAVE_FEATURE_DESKTOP || HAVE_FEATURE_MACOSX_SANDBOX || defined(EMSCRIPTEN)
ipc = false;
#endif
if (!ipc) {
pGlobal = new RequestHandler;
return IPC_STATUS_OK;
}
enum class Kind { Pipe, Dbus };
Kind kind;
#if ENABLE_DBUS
kind = std::getenv("LIBO_FLATPAK") != nullptr ? Kind::Dbus : Kind::Pipe;
#else
kind = Kind::Pipe;
#endif
rtl::Reference<IpcThread> thread;
Status stat = Status(); // silence bogus potentially-uninitialized warnings
switch (kind) {
case Kind::Pipe:
stat = PipeIpcThread::enable(&thread);
break;
case Kind::Dbus:
#if ENABLE_DBUS
stat = DbusIpcThread::enable(&thread);
break;
#endif
default:
assert(false);
}
assert(thread.is() == (stat == IPC_STATUS_OK));
if (stat == IPC_STATUS_OK) {
pGlobal = new RequestHandler;
pGlobal->mIpcThread = std::move(thread);
pGlobal->mIpcThread->start(pGlobal.get());
}
return stat;
}
RequestHandler::Status PipeIpcThread::enable(rtl::Reference<IpcThread> * thread)
{
assert(thread != nullptr);
// The name of the named pipe is created with the hashcode of the user installation directory (without /user). We have to retrieve
// this information from a unotools implementation.
OUString aUserInstallPath;
::utl::Bootstrap::PathStatus aLocateResult = ::utl::Bootstrap::locateUserInstallation( aUserInstallPath );
if (aLocateResult != utl::Bootstrap::PATH_EXISTS
&& aLocateResult != utl::Bootstrap::PATH_VALID)
{
return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
}
// Try to determine if we are the first office or not! This should prevent multiple
// access to the user directory !
// First we try to create our pipe if this fails we try to connect. We have to do this
// in a loop because the other office can crash or shutdown between createPipe
// and connectPipe!!
auto aUserInstallPathHashCode = CreateMD5FromString(aUserInstallPath);
// Check result to create a hash code from the user install path
if ( aUserInstallPathHashCode.isEmpty() )
return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR; // Something completely broken, we cannot create a valid hash code!
osl::Pipe pipe;
enum PipeMode
{
PIPEMODE_DONTKNOW,
PIPEMODE_CREATED,
PIPEMODE_CONNECTED
};
PipeMode nPipeMode = PIPEMODE_DONTKNOW;
OUString aPipeIdent( "SingleOfficeIPC_" + aUserInstallPathHashCode );
do
{
osl::Security security;
// Try to create pipe
if ( pipe.create( aPipeIdent, osl_Pipe_CREATE, security ))
{
// Pipe created
nPipeMode = PIPEMODE_CREATED;
}
else if( pipe.create( aPipeIdent, osl_Pipe_OPEN, security )) // Creation not successful, now we try to connect
{
osl::StreamPipe aStreamPipe(pipe.getHandle());
if (readStringFromPipe(aStreamPipe) == SEND_ARGUMENTS)
{
// Pipe connected to first office
nPipeMode = PIPEMODE_CONNECTED;
}
else
{
// Pipe connection failed (other office exited or crashed)
std::this_thread::sleep_for( std::chrono::milliseconds(500) );
}
}
else
{
oslPipeError eReason = pipe.getError();
if ((eReason == osl_Pipe_E_ConnectionRefused) || (eReason == osl_Pipe_E_invalidError))
return RequestHandler::IPC_STATUS_PIPE_ERROR;
// Wait for second office to be ready
std::this_thread::sleep_for( std::chrono::milliseconds(10) );
}
} while ( nPipeMode == PIPEMODE_DONTKNOW );
if ( nPipeMode == PIPEMODE_CREATED )
{
// Seems we are the one and only, so create listening thread
*thread = new PipeIpcThread(std::move(pipe));
return RequestHandler::IPC_STATUS_OK;
}
else
{
// Seems another office is running. Pipe arguments to it and self terminate
osl::StreamPipe aStreamPipe(pipe.getHandle());
OStringBuffer aArguments(ARGUMENT_PREFIX);
OUString cwdUrl;
if (!(utl::Bootstrap::getProcessWorkingDir(cwdUrl) &&
addArgument(aArguments, '1', cwdUrl)))
{
aArguments.append('0');
}
sal_uInt32 nCount = rtl_getAppCommandArgCount();
for( sal_uInt32 i=0; i < nCount; i++ )
{
rtl_getAppCommandArg( i, &aUserInstallPath.pData );
if (!addArgument(aArguments, ',', aUserInstallPath)) {
return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
}
}
aArguments.append('\0');
// finally, write the string onto the pipe
SAL_INFO("desktop.app", "writing <" << aArguments.getStr() << ">");
sal_Int32 n = aStreamPipe.write(
aArguments.getStr(), aArguments.getLength());
if (n != aArguments.getLength()) {
SAL_INFO("desktop.app", "short write: " << n);
return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
}
if (readStringFromPipe(aStreamPipe) != PROCESSING_DONE)
{
// something went wrong
return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
}
return RequestHandler::IPC_STATUS_2ND_OFFICE;
}
}
void RequestHandler::Disable()
{
osl::ClearableMutexGuard aMutex( GetMutex() );
if( !pGlobal.is() )
return;
rtl::Reference< RequestHandler > handler(pGlobal);
pGlobal.clear();
handler->mState = State::Downing;
if (handler->mIpcThread.is()) {
handler->mIpcThread->close();
}
// release mutex to avoid deadlocks
aMutex.clear();
handler->cReady.set();
// exit gracefully and join
if (handler->mIpcThread.is())
{
handler->mIpcThread->join();
handler->mIpcThread.clear();
}
handler->cReady.reset();
}
RequestHandler::RequestHandler() :
mState( State::Starting ),
mnPendingRequests( 0 )
{
}
RequestHandler::~RequestHandler()
{
assert(!mIpcThread.is());
}
void RequestHandler::SetReady(bool bIsReady)
{
osl::MutexGuard g(GetMutex());
if (pGlobal.is())
{
if (bIsReady)
pGlobal->cReady.set();
else
pGlobal->cReady.reset();
}
}
void RequestHandler::WaitForReady()
{
rtl::Reference<RequestHandler> t;
{
osl::MutexGuard g(GetMutex());
t = pGlobal;
}
if (t.is())
{
t->cReady.wait();
}
}
bool IpcThread::process(OString const & arguments, bool * waitProcessed) {
assert(waitProcessed != nullptr);
std::unique_ptr< CommandLineArgs > aCmdLineArgs;
try
{
Parser p(arguments);
aCmdLineArgs.reset( new CommandLineArgs( p ) );
}
catch ( const CommandLineArgs::Supplier::Exception & )
{
SAL_WARN("desktop.app", "Error in received command line arguments");
return false;
}
bool bDocRequestSent = false;
OUString aUnknown( aCmdLineArgs->GetUnknown() );
if (aUnknown.isEmpty() && !aCmdLineArgs->IsHelp() && !aCmdLineArgs->IsVersion())
{
const CommandLineArgs &rCurrentCmdLineArgs = Desktop::GetCommandLineArgs();
if ( aCmdLineArgs->IsQuickstart() )
{
// we have to use application event, because we have to start quickstart service in main thread!!
ApplicationEvent* pAppEvent =
new ApplicationEvent(ApplicationEvent::Type::QuickStart);
ImplPostForeignAppEvent( pAppEvent );
}
// handle request for acceptor
std::vector< OUString > const & accept = aCmdLineArgs->GetAccept();
for (auto const& elem : accept)
{
ApplicationEvent* pAppEvent = new ApplicationEvent(
ApplicationEvent::Type::Accept, elem);
ImplPostForeignAppEvent( pAppEvent );
}
// handle acceptor removal
std::vector< OUString > const & unaccept = aCmdLineArgs->GetUnaccept();
for (auto const& elem : unaccept)
{
ApplicationEvent* pAppEvent = new ApplicationEvent(
ApplicationEvent::Type::Unaccept, elem);
ImplPostForeignAppEvent( pAppEvent );
}
std::unique_ptr<ProcessDocumentsRequest> pRequest(new ProcessDocumentsRequest(
aCmdLineArgs->getCwdUrl()));
m_handler->cProcessed.reset();
pRequest->pcProcessed = &m_handler->cProcessed;
m_handler->mbSuccess = false;
pRequest->mpbSuccess = &m_handler->mbSuccess;
// Print requests are not dependent on the --invisible cmdline argument as they are
// loaded with the "hidden" flag! So they are always checked.
pRequest->aPrintList = aCmdLineArgs->GetPrintList();
bDocRequestSent |= !pRequest->aPrintList.empty();
pRequest->aPrintToList = aCmdLineArgs->GetPrintToList();
pRequest->aPrinterName = aCmdLineArgs->GetPrinterName();
bDocRequestSent |= !( pRequest->aPrintToList.empty() || pRequest->aPrinterName.isEmpty() );
pRequest->aConversionList = aCmdLineArgs->GetConversionList();
pRequest->aConversionParams = aCmdLineArgs->GetConversionParams();
pRequest->aConversionOut = aCmdLineArgs->GetConversionOut();
pRequest->aImageConversionType = aCmdLineArgs->GetImageConversionType();
pRequest->aStartListParams = aCmdLineArgs->GetStartListParams();
pRequest->aInFilter = aCmdLineArgs->GetInFilter();
pRequest->bTextCat = aCmdLineArgs->IsTextCat();
pRequest->bScriptCat = aCmdLineArgs->IsScriptCat();
bDocRequestSent |= !pRequest->aConversionList.empty();
if ( !rCurrentCmdLineArgs.IsInvisible() )
{
// Read cmdline args that can open/create documents. As they would open a window
// they are only allowed if the "--invisible" is currently not used!
pRequest->aOpenList = aCmdLineArgs->GetOpenList();
bDocRequestSent |= !pRequest->aOpenList.empty();
pRequest->aViewList = aCmdLineArgs->GetViewList();
bDocRequestSent |= !pRequest->aViewList.empty();
pRequest->aStartList = aCmdLineArgs->GetStartList();
bDocRequestSent |= !pRequest->aStartList.empty();
pRequest->aForceOpenList = aCmdLineArgs->GetForceOpenList();
bDocRequestSent |= !pRequest->aForceOpenList.empty();
pRequest->aForceNewList = aCmdLineArgs->GetForceNewList();
bDocRequestSent |= !pRequest->aForceNewList.empty();
// Special command line args to create an empty document for a given module
// #i18338# (lo)
// we only do this if no document was specified on the command line,
// since this would be inconsistent with the behaviour of
// the first process, see OpenClients() (call to OpenDefault()) in app.cxx
if ( aCmdLineArgs->HasModuleParam() && !bDocRequestSent )
{
SvtModuleOptions aOpt;
SvtModuleOptions::EFactory eFactory = SvtModuleOptions::EFactory::WRITER;
if ( aCmdLineArgs->IsWriter() )
eFactory = SvtModuleOptions::EFactory::WRITER;
else if ( aCmdLineArgs->IsCalc() )
eFactory = SvtModuleOptions::EFactory::CALC;
else if ( aCmdLineArgs->IsDraw() )
eFactory = SvtModuleOptions::EFactory::DRAW;
else if ( aCmdLineArgs->IsImpress() )
eFactory = SvtModuleOptions::EFactory::IMPRESS;
else if ( aCmdLineArgs->IsBase() )
eFactory = SvtModuleOptions::EFactory::DATABASE;
else if ( aCmdLineArgs->IsMath() )
eFactory = SvtModuleOptions::EFactory::MATH;
else if ( aCmdLineArgs->IsGlobal() )
eFactory = SvtModuleOptions::EFactory::WRITERGLOBAL;
else if ( aCmdLineArgs->IsWeb() )
eFactory = SvtModuleOptions::EFactory::WRITERWEB;
if ( !pRequest->aOpenList.empty() )
pRequest->aModule = aOpt.GetFactoryName( eFactory );
else
pRequest->aOpenList.push_back( aOpt.GetFactoryEmptyDocumentURL( eFactory ) );
bDocRequestSent = true;
}
}
if ( !aCmdLineArgs->IsQuickstart() ) {
bool bShowHelp = false;
OUStringBuffer aHelpURLBuffer;
if (aCmdLineArgs->IsHelpWriter()) {
bShowHelp = true;
aHelpURLBuffer.append("vnd.sun.star.help://swriter/start");
} else if (aCmdLineArgs->IsHelpCalc()) {
bShowHelp = true;
aHelpURLBuffer.append("vnd.sun.star.help://scalc/start");
} else if (aCmdLineArgs->IsHelpDraw()) {
bShowHelp = true;
aHelpURLBuffer.append("vnd.sun.star.help://sdraw/start");
} else if (aCmdLineArgs->IsHelpImpress()) {
bShowHelp = true;
aHelpURLBuffer.append("vnd.sun.star.help://simpress/start");
} else if (aCmdLineArgs->IsHelpBase()) {
bShowHelp = true;
aHelpURLBuffer.append("vnd.sun.star.help://sdatabase/start");
} else if (aCmdLineArgs->IsHelpBasic()) {
bShowHelp = true;
aHelpURLBuffer.append("vnd.sun.star.help://sbasic/start");
} else if (aCmdLineArgs->IsHelpMath()) {
bShowHelp = true;
aHelpURLBuffer.append("vnd.sun.star.help://smath/start");
}
if (bShowHelp) {
aHelpURLBuffer.append("?Language="
+ utl::ConfigManager::getUILocale()
#if defined UNX
+ "&System=UNX");
#elif defined _WIN32
+ "&System=WIN");
#endif
ApplicationEvent* pAppEvent = new ApplicationEvent(
ApplicationEvent::Type::OpenHelpUrl,
aHelpURLBuffer.makeStringAndClear());
ImplPostForeignAppEvent( pAppEvent );
}
}
if ( bDocRequestSent )
{
// Send requests to dispatch watcher if we have at least one. The receiver
// is responsible to delete the request after processing it.
if ( aCmdLineArgs->HasModuleParam() )
{
SvtModuleOptions aOpt;
// Support command line parameters to start a module (as preselection)
if ( aCmdLineArgs->IsWriter() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER ) )
pRequest->aModule = aOpt.GetFactoryName( SvtModuleOptions::EFactory::WRITER );
else if ( aCmdLineArgs->IsCalc() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::CALC ) )
pRequest->aModule = aOpt.GetFactoryName( SvtModuleOptions::EFactory::CALC );
else if ( aCmdLineArgs->IsImpress() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::IMPRESS ) )
pRequest->aModule= aOpt.GetFactoryName( SvtModuleOptions::EFactory::IMPRESS );
else if ( aCmdLineArgs->IsDraw() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::DRAW ) )
pRequest->aModule= aOpt.GetFactoryName( SvtModuleOptions::EFactory::DRAW );
}
ImplPostProcessDocumentsEvent( std::move(pRequest) );
}
else
{
// delete not used request again
pRequest.reset();
}
if (aCmdLineArgs->IsEmpty())
{
// no document was sent, just bring Office to front
ApplicationEvent* pAppEvent =
new ApplicationEvent(ApplicationEvent::Type::Appear);
ImplPostForeignAppEvent( pAppEvent );
}
}
*waitProcessed = bDocRequestSent;
return true;
}
void PipeIpcThread::execute()
{
assert(m_handler != nullptr);
do
{
osl::StreamPipe aStreamPipe;
oslPipeError nError = pipe_.accept( aStreamPipe );
if( nError == osl_Pipe_E_None )
{
// if we receive a request while the office is displaying some dialog or error during
// bootstrap, that dialogs event loop might get events that are dispatched by this thread
// we have to wait for cReady to be set by the real main loop.
// only requests that don't dispatch events may be processed before cReady is set.
m_handler->cReady.wait();
// we might have decided to shutdown while we were sleeping
if (!RequestHandler::pGlobal.is()) return;
// only lock the mutex when processing starts, otherwise we deadlock when the office goes
// down during wait
osl::ClearableMutexGuard aGuard( RequestHandler::GetMutex() );
if (m_handler->mState == RequestHandler::State::Downing)
{
break;
}
// notify client we're ready to process its args:
SAL_INFO("desktop.app", "writing <" << SEND_ARGUMENTS << ">");
std::size_t n = aStreamPipe.write(
SEND_ARGUMENTS, std::size(SEND_ARGUMENTS));
// incl. terminating NUL
if (n != std::size(SEND_ARGUMENTS)) {
SAL_WARN("desktop.app", "short write: " << n);
continue;
}
OString aArguments = readStringFromPipe(aStreamPipe);
// Is this a lookup message from another application? if so, ignore
if (aArguments.isEmpty())
continue;
bool waitProcessed = false;
if (!process(aArguments, &waitProcessed)) {
continue;
}
// we don't need the mutex any longer...
aGuard.clear();
bool bSuccess = true;
// wait for processing to finish
if (waitProcessed)
{
m_handler->cProcessed.wait();
bSuccess = m_handler->mbSuccess;
}
if (bSuccess)
{
// processing finished, inform the requesting end:
SAL_INFO("desktop.app", "writing <" << PROCESSING_DONE << ">");
n = aStreamPipe.write(PROCESSING_DONE, std::size(PROCESSING_DONE));
// incl. terminating NUL
if (n != std::size(PROCESSING_DONE))
{
SAL_WARN("desktop.app", "short write: " << n);
continue;
}
}
}
else
{
{
osl::MutexGuard aGuard( RequestHandler::GetMutex() );
if (m_handler->mState == RequestHandler::State::Downing)
{
break;
}
}
SAL_WARN( "desktop.app", "Error on accept: " << static_cast<int>(nError));
std::this_thread::sleep_for( std::chrono::seconds(1) );
}
} while( schedule() );
}
static void AddToDispatchList(
std::vector<DispatchWatcher::DispatchRequest>& rDispatchList,
std::optional< OUString > const & cwdUrl,
std::vector< OUString > const & aRequestList,
DispatchWatcher::RequestType nType,
const OUString& aParam,
const OUString& aFactory )
{
for (auto const& request : aRequestList)
{
rDispatchList.push_back({nType, request, cwdUrl, aParam, aFactory});
}
}
static void AddConversionsToDispatchList(
std::vector<DispatchWatcher::DispatchRequest>& rDispatchList,
std::optional< OUString > const & cwdUrl,
std::vector< OUString > const & rRequestList,
const OUString& rParam,
const OUString& rPrinterName,
const OUString& rFactory,
const OUString& rParamOut,
std::u16string_view rImgOut,
const bool isTextCat,
const bool isScriptCat )
{
DispatchWatcher::RequestType nType;
OUString aParam( rParam );
if( !rParam.isEmpty() )
{
if ( isTextCat )
nType = DispatchWatcher::REQUEST_CAT;
else
nType = DispatchWatcher::REQUEST_CONVERSION;
}
else
{
if ( isScriptCat )
nType = DispatchWatcher::REQUEST_SCRIPT_CAT;
else
{
nType = DispatchWatcher::REQUEST_BATCHPRINT;
aParam = rPrinterName;
}
}
OUString aPWD;
if (cwdUrl)
{
aPWD = *cwdUrl;
}
else
{
utl::Bootstrap::getProcessWorkingDir( aPWD );
}
if (OUString aOutDir(rParamOut.trim()); !aOutDir.isEmpty())
{
if (osl::FileBase::getAbsoluteFileURL(aPWD, rParamOut, aOutDir) == osl::FileBase::E_None)
osl::FileBase::getSystemPathFromFileURL(aOutDir, aOutDir);
aParam += ";" + aOutDir;
}
else
{
::osl::FileBase::getSystemPathFromFileURL( aPWD, aPWD );
aParam += ";" + aPWD;
}
if( !rImgOut.empty() )
aParam += OUString::Concat("|") + o3tl::trim(rImgOut);
for (auto const& request : rRequestList)
{
rDispatchList.push_back({nType, request, cwdUrl, aParam, rFactory});
}
}
namespace {
struct ConditionSetGuard
{
osl::Condition* m_pCondition;
ConditionSetGuard(osl::Condition* pCondition) : m_pCondition(pCondition) {}
~ConditionSetGuard() { if (m_pCondition) m_pCondition->set(); }
};
}
bool RequestHandler::ExecuteCmdLineRequests(
ProcessDocumentsRequest& aRequest, bool noTerminate)
{
// protect the dispatch list
osl::ClearableMutexGuard aGuard( GetMutex() );
// ensure that Processed flag (if exists) is signaled in any outcome
ConditionSetGuard aSetGuard(aRequest.pcProcessed);
static std::vector<DispatchWatcher::DispatchRequest> aDispatchList;
// Create dispatch list for dispatch watcher
AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aInFilter, DispatchWatcher::REQUEST_INFILTER, u""_ustr, aRequest.aModule );
AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aOpenList, DispatchWatcher::REQUEST_OPEN, u""_ustr, aRequest.aModule );
AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aViewList, DispatchWatcher::REQUEST_VIEW, u""_ustr, aRequest.aModule );
AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aStartList, DispatchWatcher::REQUEST_START, aRequest.aStartListParams, aRequest.aModule );
AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aPrintList, DispatchWatcher::REQUEST_PRINT, u""_ustr, aRequest.aModule );
AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aPrintToList, DispatchWatcher::REQUEST_PRINTTO, aRequest.aPrinterName, aRequest.aModule );
AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aForceOpenList, DispatchWatcher::REQUEST_FORCEOPEN, u""_ustr, aRequest.aModule );
AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aForceNewList, DispatchWatcher::REQUEST_FORCENEW, u""_ustr, aRequest.aModule );
AddConversionsToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aConversionList, aRequest.aConversionParams, aRequest.aPrinterName, aRequest.aModule, aRequest.aConversionOut, aRequest.aImageConversionType, aRequest.bTextCat, aRequest.bScriptCat );
bool bShutdown( false );
if ( pGlobal.is() )
{
if( ! pGlobal->AreRequestsEnabled() )
{
// Either starting, or downing - do not process the request, just try to bring Office to front
ApplicationEvent* pAppEvent =
new ApplicationEvent(ApplicationEvent::Type::Appear);
ImplPostForeignAppEvent(pAppEvent);
return bShutdown;
}
pGlobal->mnPendingRequests += aDispatchList.size();
if ( !pGlobal->mpDispatchWatcher.is() )
{
pGlobal->mpDispatchWatcher = new DispatchWatcher;
}
rtl::Reference<DispatchWatcher> dispatchWatcher(
pGlobal->mpDispatchWatcher);
// copy for execute
std::vector<DispatchWatcher::DispatchRequest> aTempList;
aTempList.swap( aDispatchList );
aGuard.clear();
// Execute dispatch requests
bShutdown = dispatchWatcher->executeDispatchRequests( aTempList, noTerminate);
if (aRequest.mpbSuccess)
*aRequest.mpbSuccess = true; // signal that we have actually succeeded
}
return bShutdown;
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V690 The 'DbusConnectionHolder' class implements a move constructor, but lacks the move assignment operator. It is dangerous to use such a class.