    /* -*- 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 <app.hxx>
    #include "officeipcthread.hxx"
    #include "cmdlineargs.hxx"
    #include "dispatchwatcher.hxx"
    #include <stdio.h>
    #include <com/sun/star/frame/TerminationVetoException.hpp>
    #include <osl/process.h>
    #include <sal/log.hxx>
    #include <unotools/bootstrap.hxx>
    #include <vcl/svapp.hxx>
    #include <vcl/help.hxx>
    #include <unotools/configmgr.hxx>
    #include <osl/thread.hxx>
    #include <rtl/digest.h>
    #include <rtl/ustrbuf.hxx>
    #include <rtl/instance.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 <algorithm>
    #include <cassert>
    #include <cstdlib>
    #include <memory>

    #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 {

    static char const ARGUMENT_PREFIX[] = "InternalIPC::Arguments";
    static char const SEND_ARGUMENTS[] = "InternalIPC::SendArguments";
    static 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, SAL_N_ELEMENTS(buf));
            if (n <= 0) {
                SAL_INFO("desktop.app", "read empty string");
                return "";
            }
            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 const & input): m_input(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.reset(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.reset(url);
                    }
                    break;
                }
            default:
                throw CommandLineArgs::Supplier::Exception();
            }
        }

        virtual boost::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;
            }
        }

        boost::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...
    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();
    }

    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;
    }

    void ImplPostForeignAppEvent( ApplicationEvent* pEvent )
    {
        Application::PostUserEvent( LINK( nullptr, ProcessEventsClass_Impl, CallEvent ), pEvent );
    }

    void ImplPostProcessDocumentsEvent( ProcessDocumentsRequest* pEvent )
    {
        Application::PostUserEvent( LINK( nullptr, ProcessEventsClass_Impl, ProcessDocumentsEvent ), pEvent );
    }

    oslSignalAction SalMainPipeExchangeSignal_impl(SAL_UNUSED_PARAMETER void* /*pData*/, oslSignalInfo* pInfo)
    {
        if( pInfo->Signal == osl_Signal_Terminate )
            RequestHandler::SetDowning();
        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 OUString( "com.sun.star.comp.RequestHandlerController" );
    }

    sal_Bool RequestHandlerController::supportsService(
        OUString const & ServiceName)
    {
        return cppu::supportsService(this, ServiceName);
    }

    Sequence< OUString > SAL_CALL RequestHandlerController::getSupportedServiceNames()
    {
        Sequence< OUString > aSeq( 0 );
        return aSeq;
    }

    // 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 const & pipe):
            IpcThread("PipeIPC"), pipe_(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 &) = delete;
        void operator =(DbusMessageHolder) = 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

    namespace
    {
        class theRequestHandlerMutex
            : public rtl::Static<osl::Mutex, theRequestHandlerMutex> {};
    }

    ::osl::Mutex& RequestHandler::GetMutex()
    {
        return theRequestHandlerMutex::get();
    }

    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;
            }
            // hit the compiler over the head
            ProcessDocumentsRequest aEmptyReq = ProcessDocumentsRequest( boost::optional< OUString >() );
            // 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
        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)
                    TimeValue tval;
                    tval.Seconds = 0;
                    tval.Nanosec = 500000000;
                    salhelper::Thread::wait( tval );
                }
            }
            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
                TimeValue aTimeValue;
                aTimeValue.Seconds = 0;
                aTimeValue.Nanosec = 10000000; // 10ms
                salhelper::Thread::wait( aTimeValue );
            }

        } 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() )
        {
            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() )
        {
            ApplicationEvent* pAppEvent =
                new ApplicationEvent(ApplicationEvent::Type::Help, aUnknown);
            ImplPostForeignAppEvent( pAppEvent );
        }
        else if ( aCmdLineArgs->IsVersion() )
        {
            ApplicationEvent* pAppEvent =
                new ApplicationEvent(ApplicationEvent::Type::Version);
            ImplPostForeignAppEvent( pAppEvent );
        }
        else
        {
            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 );
            }

            ProcessDocumentsRequest* pRequest = new ProcessDocumentsRequest(
                aCmdLineArgs->getCwdUrl());
            m_handler->cProcessed.reset();
            pRequest->pcProcessed = &m_handler->cProcessed;

            // 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=");
                    aHelpURLBuffer.append(utl::ConfigManager::getLocale());
    #if defined UNX
                    aHelpURLBuffer.append("&System=UNX");
    #elif defined WNT
                    aHelpURLBuffer.append("&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( pRequest );
            }
            else
            {
                // delete not used request again
                delete pRequest;
                pRequest = nullptr;
            }
            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 << ">");
                sal_Int32 n = aStreamPipe.write(
                    SEND_ARGUMENTS, SAL_N_ELEMENTS(SEND_ARGUMENTS));
                    // incl. terminating NUL
                if (n != SAL_N_ELEMENTS(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();
                // wait for processing to finish
                if (waitProcessed)
                    m_handler->cProcessed.wait();
                // processing finished, inform the requesting end:
                SAL_INFO("desktop.app", "writing <" << PROCESSING_DONE << ">");
                n = aStreamPipe.write(
                    PROCESSING_DONE, SAL_N_ELEMENTS(PROCESSING_DONE));
                    // incl. terminating NUL
                if (n != SAL_N_ELEMENTS(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));
                TimeValue tval;
                tval.Seconds = 1;
                tval.Nanosec = 0;
                salhelper::Thread::wait( tval );
            }
        } while( schedule() );
    }

    static void AddToDispatchList(
        std::vector<DispatchWatcher::DispatchRequest>& rDispatchList,
        boost::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,
        boost::optional< OUString > const & cwdUrl,
        std::vector< OUString > const & rRequestList,
        const OUString& rParam,
        const OUString& rPrinterName,
        const OUString& rFactory,
        const OUString& rParamOut,
        const OUString& 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;
            aParam = rParam;
        }
        else
        {
            if ( isScriptCat )
                nType = DispatchWatcher::REQUEST_SCRIPT_CAT;
            else
            {
                nType = DispatchWatcher::REQUEST_BATCHPRINT;
                aParam = rPrinterName;
            }
        }

        OUString aOutDir( rParamOut.trim() );
        OUString aImgOut( rImgOut.trim() );
        OUString aPWD;
        if (cwdUrl)
        {
            aPWD = *cwdUrl;
        }
        else
        {
            utl::Bootstrap::getProcessWorkingDir( aPWD );
        }

        if( !::osl::FileBase::getAbsoluteFileURL( aPWD, rParamOut, aOutDir ) )
            ::osl::FileBase::getSystemPathFromFileURL( aOutDir, aOutDir );

        if( !rParamOut.trim().isEmpty() )
        {
            aParam += ";";
            aParam += aOutDir;
        }
        else
        {
            ::osl::FileBase::getSystemPathFromFileURL( aPWD, aPWD );
            aParam += ";" + aPWD;
        }

        if( !rImgOut.trim().isEmpty() )
            aParam += "|" + aImgOut;

        for (auto const& request : rRequestList)
        {
            rDispatchList.push_back({nType, request, cwdUrl, aParam, rFactory});
        }
    }

    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( aDispatchList );
            aDispatchList.clear();

            aGuard.clear();

            // Execute dispatch requests
            bShutdown = dispatchWatcher->executeDispatchRequests( aTempList, noTerminate);
        }

        return bShutdown;
    }

    }

    /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

↑ V785 Constant expression in switch statement.