diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /desktop/source/app/officeipcthread.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'desktop/source/app/officeipcthread.cxx')
-rw-r--r-- | desktop/source/app/officeipcthread.cxx | 1357 |
1 files changed, 1357 insertions, 0 deletions
diff --git a/desktop/source/app/officeipcthread.cxx b/desktop/source/app/officeipcthread.cxx new file mode 100644 index 0000000000..9d342bf35a --- /dev/null +++ b/desktop/source/app/officeipcthread.cxx @@ -0,0 +1,1357 @@ +/* -*- 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) { + OSL_ASSERT(argument != nullptr); + 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 "com.sun.star.comp.RequestHandlerController"; +} + +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 = 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(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->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, "", aRequest.aModule ); + AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aOpenList, DispatchWatcher::REQUEST_OPEN, "", aRequest.aModule ); + AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aViewList, DispatchWatcher::REQUEST_VIEW, "", aRequest.aModule ); + AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aStartList, DispatchWatcher::REQUEST_START, "", aRequest.aModule ); + AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aPrintList, DispatchWatcher::REQUEST_PRINT, "", aRequest.aModule ); + AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aPrintToList, DispatchWatcher::REQUEST_PRINTTO, aRequest.aPrinterName, aRequest.aModule ); + AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aForceOpenList, DispatchWatcher::REQUEST_FORCEOPEN, "", aRequest.aModule ); + AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aForceNewList, DispatchWatcher::REQUEST_FORCENEW, "", 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: */ |