ScriptForge (SF_Calc new Intersect() method
[LibreOffice.git] / desktop / source / app / officeipcthread.cxx
blob4029546fd666f20abe66a798516a25988ab6a436
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
22 #include <config_dbus.h>
23 #include <config_features.h>
24 #include <config_feature_desktop.h>
26 #include <app.hxx>
27 #include "officeipcthread.hxx"
28 #include "cmdlineargs.hxx"
29 #include "dispatchwatcher.hxx"
30 #include <com/sun/star/frame/TerminationVetoException.hpp>
31 #include <salhelper/thread.hxx>
32 #include <sal/log.hxx>
33 #include <unotools/bootstrap.hxx>
34 #include <utility>
35 #include <vcl/svapp.hxx>
36 #include <unotools/configmgr.hxx>
37 #include <osl/pipe.hxx>
38 #include <rtl/digest.h>
39 #include <rtl/ustrbuf.hxx>
40 #include <osl/conditn.hxx>
41 #include <unotools/moduleoptions.hxx>
42 #include <rtl/strbuf.hxx>
43 #include <cppuhelper/supportsservice.hxx>
44 #include <osl/file.hxx>
45 #include <rtl/process.h>
46 #include <o3tl/string_view.hxx>
48 #include <cassert>
49 #include <cstdlib>
50 #include <memory>
51 #include <thread>
53 #if ENABLE_DBUS
54 #include <dbus/dbus.h>
55 #include <sys/socket.h>
56 #endif
58 using namespace desktop;
59 using namespace ::com::sun::star::uno;
60 using namespace ::com::sun::star::lang;
61 using namespace ::com::sun::star::frame;
63 namespace {
65 char const ARGUMENT_PREFIX[] = "InternalIPC::Arguments";
66 char const SEND_ARGUMENTS[] = "InternalIPC::SendArguments";
67 char const PROCESSING_DONE[] = "InternalIPC::ProcessingDone";
69 // Receives packets from the pipe until a packet ends in a NUL character (that
70 // will not be included in the returned string) or it cannot read anything (due
71 // to error or closed pipe, in which case an empty string will be returned to
72 // signal failure):
73 OString readStringFromPipe(osl::StreamPipe const & pipe) {
74 for (OStringBuffer str;;) {
75 char buf[1024];
76 sal_Int32 n = pipe.recv(buf, std::size(buf));
77 if (n <= 0) {
78 SAL_INFO("desktop.app", "read empty string");
79 return ""_ostr;
81 bool end = false;
82 if (buf[n - 1] == '\0') {
83 end = true;
84 --n;
86 str.append(buf, n);
87 //TODO: how does OStringBuffer.append handle overflow?
88 if (end) {
89 auto s = str.makeStringAndClear();
90 SAL_INFO("desktop.app", "read <" << s << ">");
91 return s;
98 namespace desktop
101 namespace {
103 class Parser: public CommandLineArgs::Supplier {
104 public:
105 explicit Parser(OString input): m_input(std::move(input)) {
106 if (!m_input.match(ARGUMENT_PREFIX) ||
107 m_input.getLength() == RTL_CONSTASCII_LENGTH(ARGUMENT_PREFIX))
109 throw CommandLineArgs::Supplier::Exception();
111 m_index = RTL_CONSTASCII_LENGTH(ARGUMENT_PREFIX);
112 switch (m_input[m_index++]) {
113 case '0':
114 break;
115 case '1':
117 OUString url;
118 if (!next(url, false)) {
119 throw CommandLineArgs::Supplier::Exception();
121 m_cwdUrl = url;
122 break;
124 case '2':
126 OUString path;
127 if (!next(path, false)) {
128 throw CommandLineArgs::Supplier::Exception();
130 OUString url;
131 if (osl::FileBase::getFileURLFromSystemPath(path, url) ==
132 osl::FileBase::E_None)
134 m_cwdUrl = url;
136 break;
138 default:
139 throw CommandLineArgs::Supplier::Exception();
143 virtual std::optional< OUString > getCwdUrl() override { return m_cwdUrl; }
145 virtual bool next(OUString& argument) override { return next(argument, true); }
147 private:
148 bool next(OUString& argument, bool prefix) {
149 if (m_index < m_input.getLength()) {
150 if (prefix) {
151 if (m_input[m_index] != ',') {
152 throw CommandLineArgs::Supplier::Exception();
154 ++m_index;
156 OStringBuffer b;
157 while (m_index < m_input.getLength()) {
158 char c = m_input[m_index];
159 if (c == ',') {
160 break;
162 ++m_index;
163 if (c == '\\') {
164 if (m_index >= m_input.getLength())
165 throw CommandLineArgs::Supplier::Exception();
166 c = m_input[m_index++];
167 switch (c) {
168 case '0':
169 c = '\0';
170 break;
171 case ',':
172 case '\\':
173 break;
174 default:
175 throw CommandLineArgs::Supplier::Exception();
178 b.append(c);
180 OString b2(b.makeStringAndClear());
181 if (!rtl_convertStringToUString(
182 &argument.pData, b2.getStr(), b2.getLength(),
183 RTL_TEXTENCODING_UTF8,
184 (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR |
185 RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR |
186 RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR)))
188 throw CommandLineArgs::Supplier::Exception();
190 return true;
191 } else {
192 return false;
196 std::optional< OUString > m_cwdUrl;
197 OString m_input;
198 sal_Int32 m_index;
201 bool addArgument(OStringBuffer &rArguments, char prefix,
202 const OUString &rArgument)
204 OString utf8;
205 if (!rArgument.convertToString(
206 &utf8, RTL_TEXTENCODING_UTF8,
207 (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR |
208 RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR)))
210 return false;
212 rArguments.append(prefix);
213 for (sal_Int32 i = 0; i < utf8.getLength(); ++i) {
214 char c = utf8[i];
215 switch (c) {
216 case '\0':
217 rArguments.append("\\0");
218 break;
219 case ',':
220 rArguments.append("\\,");
221 break;
222 case '\\':
223 rArguments.append("\\\\");
224 break;
225 default:
226 rArguments.append(c);
227 break;
230 return true;
235 rtl::Reference< RequestHandler > RequestHandler::pGlobal;
237 // Turns a string in aMsg such as file:///home/foo/.libreoffice/3
238 // Into a hex string of well known length ff132a86...
239 static OUString CreateMD5FromString( const OUString& aMsg )
241 SAL_INFO("desktop.app", "create md5 from '" << aMsg << "'");
243 rtlDigest handle = rtl_digest_create( rtl_Digest_AlgorithmMD5 );
244 if ( handle )
246 const sal_uInt8* pData = reinterpret_cast<const sal_uInt8*>(aMsg.getStr());
247 sal_uInt32 nSize = aMsg.getLength() * sizeof( sal_Unicode );
248 sal_uInt32 nMD5KeyLen = rtl_digest_queryLength( handle );
249 std::unique_ptr<sal_uInt8[]> pMD5KeyBuffer(new sal_uInt8[ nMD5KeyLen ]);
251 rtl_digest_init( handle, pData, nSize );
252 rtl_digest_update( handle, pData, nSize );
253 rtl_digest_get( handle, pMD5KeyBuffer.get(), nMD5KeyLen );
254 rtl_digest_destroy( handle );
256 // Create hex-value string from the MD5 value to keep the string size minimal
257 OUStringBuffer aBuffer( nMD5KeyLen * 2 + 1 );
258 for ( sal_uInt32 i = 0; i < nMD5KeyLen; i++ )
259 aBuffer.append( static_cast<sal_Int32>(pMD5KeyBuffer[i]), 16 );
261 return aBuffer.makeStringAndClear();
264 return OUString();
267 namespace {
269 class ProcessEventsClass_Impl
271 public:
272 DECL_STATIC_LINK( ProcessEventsClass_Impl, CallEvent, void*, void );
273 DECL_STATIC_LINK( ProcessEventsClass_Impl, ProcessDocumentsEvent, void*, void );
278 IMPL_STATIC_LINK( ProcessEventsClass_Impl, CallEvent, void*, pEvent, void )
280 // Application events are processed by the Desktop::HandleAppEvent implementation.
281 Desktop::HandleAppEvent( *static_cast<ApplicationEvent*>(pEvent) );
282 delete static_cast<ApplicationEvent*>(pEvent);
285 IMPL_STATIC_LINK( ProcessEventsClass_Impl, ProcessDocumentsEvent, void*, pEvent, void )
287 // Documents requests are processed by the RequestHandler implementation
288 ProcessDocumentsRequest* pDocsRequest = static_cast<ProcessDocumentsRequest*>(pEvent);
289 RequestHandler::ExecuteCmdLineRequests(*pDocsRequest, false);
290 delete pDocsRequest;
293 static void ImplPostForeignAppEvent( ApplicationEvent* pEvent )
295 Application::PostUserEvent( LINK( nullptr, ProcessEventsClass_Impl, CallEvent ), pEvent );
298 static void ImplPostProcessDocumentsEvent( std::unique_ptr<ProcessDocumentsRequest> pEvent )
300 Application::PostUserEvent( LINK( nullptr, ProcessEventsClass_Impl, ProcessDocumentsEvent ), pEvent.release() );
303 oslSignalAction SalMainPipeExchangeSignal_impl(SAL_UNUSED_PARAMETER void* /*pData*/, oslSignalInfo* pInfo)
305 if( pInfo->Signal == osl_Signal_Terminate )
306 RequestHandler::Disable();
307 return osl_Signal_ActCallNextHdl;
311 // The RequestHandlerController implementation is a bookkeeper for all pending requests
312 // that were created by the RequestHandler. The requests are waiting to be processed by
313 // our framework loadComponentFromURL function (e.g. open/print request).
314 // During shutdown the framework is asking RequestHandlerController about pending requests.
315 // If there are pending requests framework has to stop the shutdown process. It is waiting
316 // for these requests because framework is not able to handle shutdown and open a document
317 // concurrently.
320 // XServiceInfo
321 OUString SAL_CALL RequestHandlerController::getImplementationName()
323 return u"com.sun.star.comp.RequestHandlerController"_ustr;
326 sal_Bool RequestHandlerController::supportsService(
327 OUString const & ServiceName)
329 return cppu::supportsService(this, ServiceName);
332 Sequence< OUString > SAL_CALL RequestHandlerController::getSupportedServiceNames()
334 return { };
337 // XEventListener
338 void SAL_CALL RequestHandlerController::disposing( const EventObject& )
342 // XTerminateListener
343 void SAL_CALL RequestHandlerController::queryTermination( const EventObject& )
345 // Desktop ask about pending request through our office ipc pipe. We have to
346 // be sure that no pending request is waiting because framework is not able to
347 // handle shutdown and open a document concurrently.
349 if ( RequestHandler::AreRequestsPending() )
350 throw TerminationVetoException();
351 RequestHandler::SetDowning();
354 void SAL_CALL RequestHandlerController::notifyTermination( const EventObject& )
358 class IpcThread: public salhelper::Thread {
359 public:
360 void start(RequestHandler * handler) {
361 m_handler = handler;
362 launch();
365 virtual void close() = 0;
367 protected:
368 explicit IpcThread(char const * name): Thread(name), m_handler(nullptr) {}
370 virtual ~IpcThread() override {}
372 bool process(OString const & arguments, bool * waitProcessed);
374 RequestHandler * m_handler;
377 class PipeIpcThread: public IpcThread {
378 public:
379 static RequestHandler::Status enable(rtl::Reference<IpcThread> * thread);
381 private:
382 explicit PipeIpcThread(osl::Pipe pipe):
383 IpcThread("PipeIPC"), pipe_(std::move(pipe))
386 virtual ~PipeIpcThread() override {}
388 void execute() override;
390 void close() override { pipe_.close(); }
392 osl::Pipe pipe_;
395 #if ENABLE_DBUS
397 namespace {
399 struct DbusConnectionHolder {
400 explicit DbusConnectionHolder(DBusConnection * theConnection):
401 connection(theConnection)
404 DbusConnectionHolder(DbusConnectionHolder && other): connection(nullptr)
405 { std::swap(connection, other.connection); }
407 ~DbusConnectionHolder() {
408 if (connection != nullptr) {
409 dbus_connection_close(connection);
410 dbus_connection_unref(connection);
414 DBusConnection * connection;
417 struct DbusMessageHolder {
418 explicit DbusMessageHolder(DBusMessage * theMessage): message(theMessage) {}
420 ~DbusMessageHolder() { clear(); }
422 void clear() {
423 if (message != nullptr) {
424 dbus_message_unref(message);
426 message = nullptr;
429 DBusMessage * message;
431 private:
432 DbusMessageHolder(DbusMessageHolder const &) = delete;
433 DbusMessageHolder& operator =(DbusMessageHolder const &) = delete;
438 class DbusIpcThread: public IpcThread {
439 public:
440 static RequestHandler::Status enable(rtl::Reference<IpcThread> * thread);
442 private:
443 explicit DbusIpcThread(DbusConnectionHolder && connection):
444 IpcThread("DbusIPC"), connection_(std::move(connection))
447 virtual ~DbusIpcThread() override {}
449 void execute() override;
451 void close() override;
453 DbusConnectionHolder connection_;
456 RequestHandler::Status DbusIpcThread::enable(rtl::Reference<IpcThread> * thread)
458 assert(thread != nullptr);
459 if (!dbus_threads_init_default()) {
460 SAL_WARN("desktop.app", "dbus_threads_init_default failed");
461 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
463 DBusError e;
464 dbus_error_init(&e);
465 DbusConnectionHolder con(dbus_bus_get_private(DBUS_BUS_SESSION, &e));
466 assert((con.connection == nullptr) == bool(dbus_error_is_set(&e)));
467 if (con.connection == nullptr) {
468 SAL_WARN(
469 "desktop.app",
470 "dbus_bus_get_private failed with: " << e.name << ": "
471 << e.message);
472 dbus_error_free(&e);
473 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
475 for (;;) {
476 int n = dbus_bus_request_name(
477 con.connection, "org.libreoffice.LibreOfficeIpc0",
478 DBUS_NAME_FLAG_DO_NOT_QUEUE, &e);
479 assert((n == -1) == bool(dbus_error_is_set(&e)));
480 switch (n) {
481 case -1:
482 SAL_WARN(
483 "desktop.app",
484 "dbus_bus_request_name failed with: " << e.name << ": "
485 << e.message);
486 dbus_error_free(&e);
487 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
488 case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
489 *thread = new DbusIpcThread(std::move(con));
490 return RequestHandler::IPC_STATUS_OK;
491 case DBUS_REQUEST_NAME_REPLY_EXISTS:
493 OStringBuffer buf(ARGUMENT_PREFIX);
494 OUString arg;
495 if (!(utl::Bootstrap::getProcessWorkingDir(arg)
496 && addArgument(buf, '1', arg)))
498 buf.append('0');
500 sal_uInt32 narg = rtl_getAppCommandArgCount();
501 for (sal_uInt32 i = 0; i != narg; ++i) {
502 rtl_getAppCommandArg(i, &arg.pData);
503 if (!addArgument(buf, ',', arg)) {
504 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
507 char const * argstr = buf.getStr();
508 DbusMessageHolder msg(
509 dbus_message_new_method_call(
510 "org.libreoffice.LibreOfficeIpc0",
511 "/org/libreoffice/LibreOfficeIpc0",
512 "org.libreoffice.LibreOfficeIpcIfc0", "Execute"));
513 if (msg.message == nullptr) {
514 SAL_WARN(
515 "desktop.app", "dbus_message_new_method_call failed");
516 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
518 DBusMessageIter it;
519 dbus_message_iter_init_append(msg.message, &it);
520 if (!dbus_message_iter_append_basic(
521 &it, DBUS_TYPE_STRING, &argstr))
523 SAL_WARN(
524 "desktop.app", "dbus_message_iter_append_basic failed");
525 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
527 DbusMessageHolder repl(
528 dbus_connection_send_with_reply_and_block(
529 con.connection, msg.message, 0x7FFFFFFF, &e));
530 assert(
531 (repl.message == nullptr) == bool(dbus_error_is_set(&e)));
532 if (repl.message == nullptr) {
533 SAL_INFO(
534 "desktop.app",
535 "dbus_connection_send_with_reply_and_block failed"
536 " with: " << e.name << ": " << e.message);
537 dbus_error_free(&e);
538 break;
540 return RequestHandler::IPC_STATUS_2ND_OFFICE;
542 case DBUS_REQUEST_NAME_REPLY_IN_QUEUE:
543 case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER:
544 SAL_WARN(
545 "desktop.app",
546 "dbus_bus_request_name failed with unexpected " << +n);
547 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
548 default:
549 for (;;) std::abort();
554 void DbusIpcThread::execute()
556 assert(m_handler != nullptr);
557 m_handler->cReady.wait();
558 for (;;) {
560 osl::MutexGuard g(RequestHandler::GetMutex());
561 if (m_handler->mState == RequestHandler::State::Downing) {
562 break;
565 if (!dbus_connection_read_write(connection_.connection, -1)) {
566 break;
568 for (;;) {
569 DbusMessageHolder msg(
570 dbus_connection_pop_message(connection_.connection));
571 if (msg.message == nullptr) {
572 break;
574 if (!dbus_message_is_method_call(
575 msg.message, "org.libreoffice.LibreOfficeIpcIfc0",
576 "Execute"))
578 SAL_INFO("desktop.app", "unknown DBus message ignored");
579 continue;
581 DBusMessageIter it;
582 if (!dbus_message_iter_init(msg.message, &it)) {
583 SAL_WARN(
584 "desktop.app", "DBus message without argument ignored");
585 continue;
587 if (dbus_message_iter_get_arg_type(&it) != DBUS_TYPE_STRING) {
588 SAL_WARN(
589 "desktop.app",
590 "DBus message with non-string argument ignored");
591 continue;
593 char const * argstr;
594 dbus_message_iter_get_basic(&it, &argstr);
595 bool waitProcessed = false;
597 osl::MutexGuard g(RequestHandler::GetMutex());
598 if (!process(argstr, &waitProcessed)) {
599 continue;
602 if (waitProcessed) {
603 m_handler->cProcessed.wait();
605 DbusMessageHolder repl(dbus_message_new_method_return(msg.message));
606 if (repl.message == nullptr) {
607 SAL_WARN(
608 "desktop.app", "dbus_message_new_method_return failed");
609 continue;
611 dbus_uint32_t serial = 0;
612 if (!dbus_connection_send(
613 connection_.connection, repl.message, &serial)) {
614 SAL_WARN("desktop.app", "dbus_connection_send failed");
615 continue;
617 dbus_connection_flush(connection_.connection);
622 void DbusIpcThread::close() {
623 assert(connection_.connection != nullptr);
624 // Make dbus_connection_read_write fall out of internal poll call blocking
625 // on POLLIN:
626 int fd;
627 if (!dbus_connection_get_socket(connection_.connection, &fd)) {
628 SAL_WARN("desktop.app", "dbus_connection_get_socket failed");
629 return;
631 if (shutdown(fd, SHUT_RD) == -1) {
632 auto const e = errno;
633 SAL_WARN("desktop.app", "shutdown failed with errno " << e);
637 #endif
639 ::osl::Mutex& RequestHandler::GetMutex()
641 static ::osl::Mutex theRequestHandlerMutex;
642 return theRequestHandlerMutex;
645 void RequestHandler::SetDowning()
647 // We have the order to block all incoming requests. Framework
648 // wants to shutdown and we have to make sure that no loading/printing
649 // requests are executed anymore.
650 ::osl::MutexGuard aGuard( GetMutex() );
652 if ( pGlobal.is() )
653 pGlobal->mState = State::Downing;
656 void RequestHandler::EnableRequests()
658 // switch between just queueing the requests and executing them
659 ::osl::MutexGuard aGuard( GetMutex() );
661 if ( pGlobal.is() )
663 if (pGlobal->mState != State::Downing) {
664 pGlobal->mState = State::RequestsEnabled;
666 ProcessDocumentsRequest aEmptyReq(std::nullopt);
667 // trigger already queued requests
668 RequestHandler::ExecuteCmdLineRequests(aEmptyReq, true);
672 bool RequestHandler::AreRequestsPending()
674 // Give info about pending requests
675 ::osl::MutexGuard aGuard( GetMutex() );
676 if ( pGlobal.is() )
677 return ( pGlobal->mnPendingRequests > 0 );
678 else
679 return false;
682 void RequestHandler::RequestsCompleted()
684 // Remove nCount pending requests from our internal counter
685 ::osl::MutexGuard aGuard( GetMutex() );
686 if ( pGlobal.is() )
688 if ( pGlobal->mnPendingRequests > 0 )
689 pGlobal->mnPendingRequests --;
693 RequestHandler::Status RequestHandler::Enable(bool ipc)
695 ::osl::MutexGuard aGuard( GetMutex() );
697 if( pGlobal.is() )
698 return IPC_STATUS_OK;
700 #if !HAVE_FEATURE_DESKTOP || HAVE_FEATURE_MACOSX_SANDBOX || defined(EMSCRIPTEN)
701 ipc = false;
702 #endif
704 if (!ipc) {
705 pGlobal = new RequestHandler;
706 return IPC_STATUS_OK;
709 enum class Kind { Pipe, Dbus };
710 Kind kind;
711 #if ENABLE_DBUS
712 kind = std::getenv("LIBO_FLATPAK") != nullptr ? Kind::Dbus : Kind::Pipe;
713 #else
714 kind = Kind::Pipe;
715 #endif
716 rtl::Reference<IpcThread> thread;
717 Status stat = Status(); // silence bogus potentially-uninitialized warnings
718 switch (kind) {
719 case Kind::Pipe:
720 stat = PipeIpcThread::enable(&thread);
721 break;
722 case Kind::Dbus:
723 #if ENABLE_DBUS
724 stat = DbusIpcThread::enable(&thread);
725 break;
726 #endif
727 default:
728 assert(false);
730 assert(thread.is() == (stat == IPC_STATUS_OK));
731 if (stat == IPC_STATUS_OK) {
732 pGlobal = new RequestHandler;
733 pGlobal->mIpcThread = std::move(thread);
734 pGlobal->mIpcThread->start(pGlobal.get());
736 return stat;
739 RequestHandler::Status PipeIpcThread::enable(rtl::Reference<IpcThread> * thread)
741 assert(thread != nullptr);
743 // The name of the named pipe is created with the hashcode of the user installation directory (without /user). We have to retrieve
744 // this information from a unotools implementation.
745 OUString aUserInstallPath;
746 ::utl::Bootstrap::PathStatus aLocateResult = ::utl::Bootstrap::locateUserInstallation( aUserInstallPath );
747 if (aLocateResult != utl::Bootstrap::PATH_EXISTS
748 && aLocateResult != utl::Bootstrap::PATH_VALID)
750 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
753 // Try to determine if we are the first office or not! This should prevent multiple
754 // access to the user directory !
755 // First we try to create our pipe if this fails we try to connect. We have to do this
756 // in a loop because the other office can crash or shutdown between createPipe
757 // and connectPipe!!
758 auto aUserInstallPathHashCode = CreateMD5FromString(aUserInstallPath);
760 // Check result to create a hash code from the user install path
761 if ( aUserInstallPathHashCode.isEmpty() )
762 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR; // Something completely broken, we cannot create a valid hash code!
764 osl::Pipe pipe;
765 enum PipeMode
767 PIPEMODE_DONTKNOW,
768 PIPEMODE_CREATED,
769 PIPEMODE_CONNECTED
771 PipeMode nPipeMode = PIPEMODE_DONTKNOW;
773 OUString aPipeIdent( "SingleOfficeIPC_" + aUserInstallPathHashCode );
776 osl::Security security;
778 // Try to create pipe
779 if ( pipe.create( aPipeIdent, osl_Pipe_CREATE, security ))
781 // Pipe created
782 nPipeMode = PIPEMODE_CREATED;
784 else if( pipe.create( aPipeIdent, osl_Pipe_OPEN, security )) // Creation not successful, now we try to connect
786 osl::StreamPipe aStreamPipe(pipe.getHandle());
787 if (readStringFromPipe(aStreamPipe) == SEND_ARGUMENTS)
789 // Pipe connected to first office
790 nPipeMode = PIPEMODE_CONNECTED;
792 else
794 // Pipe connection failed (other office exited or crashed)
795 std::this_thread::sleep_for( std::chrono::milliseconds(500) );
798 else
800 oslPipeError eReason = pipe.getError();
801 if ((eReason == osl_Pipe_E_ConnectionRefused) || (eReason == osl_Pipe_E_invalidError))
802 return RequestHandler::IPC_STATUS_PIPE_ERROR;
804 // Wait for second office to be ready
805 std::this_thread::sleep_for( std::chrono::milliseconds(10) );
808 } while ( nPipeMode == PIPEMODE_DONTKNOW );
810 if ( nPipeMode == PIPEMODE_CREATED )
812 // Seems we are the one and only, so create listening thread
813 *thread = new PipeIpcThread(std::move(pipe));
814 return RequestHandler::IPC_STATUS_OK;
816 else
818 // Seems another office is running. Pipe arguments to it and self terminate
819 osl::StreamPipe aStreamPipe(pipe.getHandle());
821 OStringBuffer aArguments(ARGUMENT_PREFIX);
822 OUString cwdUrl;
823 if (!(utl::Bootstrap::getProcessWorkingDir(cwdUrl) &&
824 addArgument(aArguments, '1', cwdUrl)))
826 aArguments.append('0');
828 sal_uInt32 nCount = rtl_getAppCommandArgCount();
829 for( sal_uInt32 i=0; i < nCount; i++ )
831 rtl_getAppCommandArg( i, &aUserInstallPath.pData );
832 if (!addArgument(aArguments, ',', aUserInstallPath)) {
833 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
836 aArguments.append('\0');
837 // finally, write the string onto the pipe
838 SAL_INFO("desktop.app", "writing <" << aArguments.getStr() << ">");
839 sal_Int32 n = aStreamPipe.write(
840 aArguments.getStr(), aArguments.getLength());
841 if (n != aArguments.getLength()) {
842 SAL_INFO("desktop.app", "short write: " << n);
843 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
846 if (readStringFromPipe(aStreamPipe) != PROCESSING_DONE)
848 // something went wrong
849 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
852 return RequestHandler::IPC_STATUS_2ND_OFFICE;
856 void RequestHandler::Disable()
858 osl::ClearableMutexGuard aMutex( GetMutex() );
860 if( !pGlobal.is() )
861 return;
863 rtl::Reference< RequestHandler > handler(pGlobal);
864 pGlobal.clear();
866 handler->mState = State::Downing;
867 if (handler->mIpcThread.is()) {
868 handler->mIpcThread->close();
871 // release mutex to avoid deadlocks
872 aMutex.clear();
874 handler->cReady.set();
876 // exit gracefully and join
877 if (handler->mIpcThread.is())
879 handler->mIpcThread->join();
880 handler->mIpcThread.clear();
883 handler->cReady.reset();
886 RequestHandler::RequestHandler() :
887 mState( State::Starting ),
888 mnPendingRequests( 0 )
892 RequestHandler::~RequestHandler()
894 assert(!mIpcThread.is());
897 void RequestHandler::SetReady(bool bIsReady)
899 osl::MutexGuard g(GetMutex());
900 if (pGlobal.is())
902 if (bIsReady)
903 pGlobal->cReady.set();
904 else
905 pGlobal->cReady.reset();
909 void RequestHandler::WaitForReady()
911 rtl::Reference<RequestHandler> t;
913 osl::MutexGuard g(GetMutex());
914 t = pGlobal;
916 if (t.is())
918 t->cReady.wait();
922 bool IpcThread::process(OString const & arguments, bool * waitProcessed) {
923 assert(waitProcessed != nullptr);
925 std::unique_ptr< CommandLineArgs > aCmdLineArgs;
928 Parser p(arguments);
929 aCmdLineArgs.reset( new CommandLineArgs( p ) );
931 catch ( const CommandLineArgs::Supplier::Exception & )
933 SAL_WARN("desktop.app", "Error in received command line arguments");
934 return false;
937 bool bDocRequestSent = false;
939 OUString aUnknown( aCmdLineArgs->GetUnknown() );
940 if (aUnknown.isEmpty() && !aCmdLineArgs->IsHelp() && !aCmdLineArgs->IsVersion())
942 const CommandLineArgs &rCurrentCmdLineArgs = Desktop::GetCommandLineArgs();
944 if ( aCmdLineArgs->IsQuickstart() )
946 // we have to use application event, because we have to start quickstart service in main thread!!
947 ApplicationEvent* pAppEvent =
948 new ApplicationEvent(ApplicationEvent::Type::QuickStart);
949 ImplPostForeignAppEvent( pAppEvent );
952 // handle request for acceptor
953 std::vector< OUString > const & accept = aCmdLineArgs->GetAccept();
954 for (auto const& elem : accept)
956 ApplicationEvent* pAppEvent = new ApplicationEvent(
957 ApplicationEvent::Type::Accept, elem);
958 ImplPostForeignAppEvent( pAppEvent );
960 // handle acceptor removal
961 std::vector< OUString > const & unaccept = aCmdLineArgs->GetUnaccept();
962 for (auto const& elem : unaccept)
964 ApplicationEvent* pAppEvent = new ApplicationEvent(
965 ApplicationEvent::Type::Unaccept, elem);
966 ImplPostForeignAppEvent( pAppEvent );
969 std::unique_ptr<ProcessDocumentsRequest> pRequest(new ProcessDocumentsRequest(
970 aCmdLineArgs->getCwdUrl()));
971 m_handler->cProcessed.reset();
972 pRequest->pcProcessed = &m_handler->cProcessed;
973 m_handler->mbSuccess = false;
974 pRequest->mpbSuccess = &m_handler->mbSuccess;
976 // Print requests are not dependent on the --invisible cmdline argument as they are
977 // loaded with the "hidden" flag! So they are always checked.
978 pRequest->aPrintList = aCmdLineArgs->GetPrintList();
979 bDocRequestSent |= !pRequest->aPrintList.empty();
980 pRequest->aPrintToList = aCmdLineArgs->GetPrintToList();
981 pRequest->aPrinterName = aCmdLineArgs->GetPrinterName();
982 bDocRequestSent |= !( pRequest->aPrintToList.empty() || pRequest->aPrinterName.isEmpty() );
983 pRequest->aConversionList = aCmdLineArgs->GetConversionList();
984 pRequest->aConversionParams = aCmdLineArgs->GetConversionParams();
985 pRequest->aConversionOut = aCmdLineArgs->GetConversionOut();
986 pRequest->aImageConversionType = aCmdLineArgs->GetImageConversionType();
987 pRequest->aStartListParams = aCmdLineArgs->GetStartListParams();
988 pRequest->aInFilter = aCmdLineArgs->GetInFilter();
989 pRequest->bTextCat = aCmdLineArgs->IsTextCat();
990 pRequest->bScriptCat = aCmdLineArgs->IsScriptCat();
991 bDocRequestSent |= !pRequest->aConversionList.empty();
993 if ( !rCurrentCmdLineArgs.IsInvisible() )
995 // Read cmdline args that can open/create documents. As they would open a window
996 // they are only allowed if the "--invisible" is currently not used!
997 pRequest->aOpenList = aCmdLineArgs->GetOpenList();
998 bDocRequestSent |= !pRequest->aOpenList.empty();
999 pRequest->aViewList = aCmdLineArgs->GetViewList();
1000 bDocRequestSent |= !pRequest->aViewList.empty();
1001 pRequest->aStartList = aCmdLineArgs->GetStartList();
1002 bDocRequestSent |= !pRequest->aStartList.empty();
1003 pRequest->aForceOpenList = aCmdLineArgs->GetForceOpenList();
1004 bDocRequestSent |= !pRequest->aForceOpenList.empty();
1005 pRequest->aForceNewList = aCmdLineArgs->GetForceNewList();
1006 bDocRequestSent |= !pRequest->aForceNewList.empty();
1008 // Special command line args to create an empty document for a given module
1010 // #i18338# (lo)
1011 // we only do this if no document was specified on the command line,
1012 // since this would be inconsistent with the behaviour of
1013 // the first process, see OpenClients() (call to OpenDefault()) in app.cxx
1014 if ( aCmdLineArgs->HasModuleParam() && !bDocRequestSent )
1016 SvtModuleOptions aOpt;
1017 SvtModuleOptions::EFactory eFactory = SvtModuleOptions::EFactory::WRITER;
1018 if ( aCmdLineArgs->IsWriter() )
1019 eFactory = SvtModuleOptions::EFactory::WRITER;
1020 else if ( aCmdLineArgs->IsCalc() )
1021 eFactory = SvtModuleOptions::EFactory::CALC;
1022 else if ( aCmdLineArgs->IsDraw() )
1023 eFactory = SvtModuleOptions::EFactory::DRAW;
1024 else if ( aCmdLineArgs->IsImpress() )
1025 eFactory = SvtModuleOptions::EFactory::IMPRESS;
1026 else if ( aCmdLineArgs->IsBase() )
1027 eFactory = SvtModuleOptions::EFactory::DATABASE;
1028 else if ( aCmdLineArgs->IsMath() )
1029 eFactory = SvtModuleOptions::EFactory::MATH;
1030 else if ( aCmdLineArgs->IsGlobal() )
1031 eFactory = SvtModuleOptions::EFactory::WRITERGLOBAL;
1032 else if ( aCmdLineArgs->IsWeb() )
1033 eFactory = SvtModuleOptions::EFactory::WRITERWEB;
1035 if ( !pRequest->aOpenList.empty() )
1036 pRequest->aModule = aOpt.GetFactoryName( eFactory );
1037 else
1038 pRequest->aOpenList.push_back( aOpt.GetFactoryEmptyDocumentURL( eFactory ) );
1039 bDocRequestSent = true;
1043 if ( !aCmdLineArgs->IsQuickstart() ) {
1044 bool bShowHelp = false;
1045 OUStringBuffer aHelpURLBuffer;
1046 if (aCmdLineArgs->IsHelpWriter()) {
1047 bShowHelp = true;
1048 aHelpURLBuffer.append("vnd.sun.star.help://swriter/start");
1049 } else if (aCmdLineArgs->IsHelpCalc()) {
1050 bShowHelp = true;
1051 aHelpURLBuffer.append("vnd.sun.star.help://scalc/start");
1052 } else if (aCmdLineArgs->IsHelpDraw()) {
1053 bShowHelp = true;
1054 aHelpURLBuffer.append("vnd.sun.star.help://sdraw/start");
1055 } else if (aCmdLineArgs->IsHelpImpress()) {
1056 bShowHelp = true;
1057 aHelpURLBuffer.append("vnd.sun.star.help://simpress/start");
1058 } else if (aCmdLineArgs->IsHelpBase()) {
1059 bShowHelp = true;
1060 aHelpURLBuffer.append("vnd.sun.star.help://sdatabase/start");
1061 } else if (aCmdLineArgs->IsHelpBasic()) {
1062 bShowHelp = true;
1063 aHelpURLBuffer.append("vnd.sun.star.help://sbasic/start");
1064 } else if (aCmdLineArgs->IsHelpMath()) {
1065 bShowHelp = true;
1066 aHelpURLBuffer.append("vnd.sun.star.help://smath/start");
1068 if (bShowHelp) {
1069 aHelpURLBuffer.append("?Language="
1070 + utl::ConfigManager::getUILocale()
1071 #if defined UNX
1072 + "&System=UNX");
1073 #elif defined _WIN32
1074 + "&System=WIN");
1075 #endif
1076 ApplicationEvent* pAppEvent = new ApplicationEvent(
1077 ApplicationEvent::Type::OpenHelpUrl,
1078 aHelpURLBuffer.makeStringAndClear());
1079 ImplPostForeignAppEvent( pAppEvent );
1083 if ( bDocRequestSent )
1085 // Send requests to dispatch watcher if we have at least one. The receiver
1086 // is responsible to delete the request after processing it.
1087 if ( aCmdLineArgs->HasModuleParam() )
1089 SvtModuleOptions aOpt;
1091 // Support command line parameters to start a module (as preselection)
1092 if (aCmdLineArgs->IsWriter() && aOpt.IsWriterInstalled())
1093 pRequest->aModule = aOpt.GetFactoryName( SvtModuleOptions::EFactory::WRITER );
1094 else if (aCmdLineArgs->IsCalc() && aOpt.IsCalcInstalled())
1095 pRequest->aModule = aOpt.GetFactoryName( SvtModuleOptions::EFactory::CALC );
1096 else if (aCmdLineArgs->IsImpress() && aOpt.IsImpressInstalled())
1097 pRequest->aModule= aOpt.GetFactoryName( SvtModuleOptions::EFactory::IMPRESS );
1098 else if (aCmdLineArgs->IsDraw() && aOpt.IsDrawInstalled())
1099 pRequest->aModule= aOpt.GetFactoryName( SvtModuleOptions::EFactory::DRAW );
1102 ImplPostProcessDocumentsEvent( std::move(pRequest) );
1104 else
1106 // delete not used request again
1107 pRequest.reset();
1109 if (aCmdLineArgs->IsEmpty())
1111 // no document was sent, just bring Office to front
1112 ApplicationEvent* pAppEvent =
1113 new ApplicationEvent(ApplicationEvent::Type::Appear);
1114 ImplPostForeignAppEvent( pAppEvent );
1117 *waitProcessed = bDocRequestSent;
1118 return true;
1121 void PipeIpcThread::execute()
1123 assert(m_handler != nullptr);
1126 osl::StreamPipe aStreamPipe;
1127 oslPipeError nError = pipe_.accept( aStreamPipe );
1130 if( nError == osl_Pipe_E_None )
1132 // if we receive a request while the office is displaying some dialog or error during
1133 // bootstrap, that dialogs event loop might get events that are dispatched by this thread
1134 // we have to wait for cReady to be set by the real main loop.
1135 // only requests that don't dispatch events may be processed before cReady is set.
1136 m_handler->cReady.wait();
1138 // we might have decided to shutdown while we were sleeping
1139 if (!RequestHandler::pGlobal.is()) return;
1141 // only lock the mutex when processing starts, otherwise we deadlock when the office goes
1142 // down during wait
1143 osl::ClearableMutexGuard aGuard( RequestHandler::GetMutex() );
1145 if (m_handler->mState == RequestHandler::State::Downing)
1147 break;
1150 // notify client we're ready to process its args:
1151 SAL_INFO("desktop.app", "writing <" << SEND_ARGUMENTS << ">");
1152 std::size_t n = aStreamPipe.write(
1153 SEND_ARGUMENTS, std::size(SEND_ARGUMENTS));
1154 // incl. terminating NUL
1155 if (n != std::size(SEND_ARGUMENTS)) {
1156 SAL_WARN("desktop.app", "short write: " << n);
1157 continue;
1160 OString aArguments = readStringFromPipe(aStreamPipe);
1162 // Is this a lookup message from another application? if so, ignore
1163 if (aArguments.isEmpty())
1164 continue;
1166 bool waitProcessed = false;
1167 if (!process(aArguments, &waitProcessed)) {
1168 continue;
1171 // we don't need the mutex any longer...
1172 aGuard.clear();
1173 bool bSuccess = true;
1174 // wait for processing to finish
1175 if (waitProcessed)
1177 m_handler->cProcessed.wait();
1178 bSuccess = m_handler->mbSuccess;
1180 if (bSuccess)
1182 // processing finished, inform the requesting end:
1183 SAL_INFO("desktop.app", "writing <" << PROCESSING_DONE << ">");
1184 n = aStreamPipe.write(PROCESSING_DONE, std::size(PROCESSING_DONE));
1185 // incl. terminating NUL
1186 if (n != std::size(PROCESSING_DONE))
1188 SAL_WARN("desktop.app", "short write: " << n);
1189 continue;
1193 else
1196 osl::MutexGuard aGuard( RequestHandler::GetMutex() );
1197 if (m_handler->mState == RequestHandler::State::Downing)
1199 break;
1203 SAL_WARN( "desktop.app", "Error on accept: " << static_cast<int>(nError));
1204 std::this_thread::sleep_for( std::chrono::seconds(1) );
1206 } while( schedule() );
1209 static void AddToDispatchList(
1210 std::vector<DispatchWatcher::DispatchRequest>& rDispatchList,
1211 std::optional< OUString > const & cwdUrl,
1212 std::vector< OUString > const & aRequestList,
1213 DispatchWatcher::RequestType nType,
1214 const OUString& aParam,
1215 const OUString& aFactory )
1217 for (auto const& request : aRequestList)
1219 rDispatchList.push_back({nType, request, cwdUrl, aParam, aFactory});
1223 static void AddConversionsToDispatchList(
1224 std::vector<DispatchWatcher::DispatchRequest>& rDispatchList,
1225 std::optional< OUString > const & cwdUrl,
1226 std::vector< OUString > const & rRequestList,
1227 const OUString& rParam,
1228 const OUString& rPrinterName,
1229 const OUString& rFactory,
1230 const OUString& rParamOut,
1231 std::u16string_view rImgOut,
1232 const bool isTextCat,
1233 const bool isScriptCat )
1235 DispatchWatcher::RequestType nType;
1236 OUString aParam( rParam );
1238 if( !rParam.isEmpty() )
1240 if ( isTextCat )
1241 nType = DispatchWatcher::REQUEST_CAT;
1242 else
1243 nType = DispatchWatcher::REQUEST_CONVERSION;
1245 else
1247 if ( isScriptCat )
1248 nType = DispatchWatcher::REQUEST_SCRIPT_CAT;
1249 else
1251 nType = DispatchWatcher::REQUEST_BATCHPRINT;
1252 aParam = rPrinterName;
1256 OUString aPWD;
1257 if (cwdUrl)
1259 aPWD = *cwdUrl;
1261 else
1263 utl::Bootstrap::getProcessWorkingDir( aPWD );
1266 if (OUString aOutDir(rParamOut.trim()); !aOutDir.isEmpty())
1268 if (osl::FileBase::getAbsoluteFileURL(aPWD, rParamOut, aOutDir) == osl::FileBase::E_None)
1269 osl::FileBase::getSystemPathFromFileURL(aOutDir, aOutDir);
1270 aParam += ";" + aOutDir;
1272 else
1274 ::osl::FileBase::getSystemPathFromFileURL( aPWD, aPWD );
1275 aParam += ";" + aPWD;
1278 if( !rImgOut.empty() )
1279 aParam += OUString::Concat("|") + o3tl::trim(rImgOut);
1281 for (auto const& request : rRequestList)
1283 rDispatchList.push_back({nType, request, cwdUrl, aParam, rFactory});
1287 namespace {
1289 struct ConditionSetGuard
1291 osl::Condition* m_pCondition;
1292 ConditionSetGuard(osl::Condition* pCondition) : m_pCondition(pCondition) {}
1293 ~ConditionSetGuard() { if (m_pCondition) m_pCondition->set(); }
1298 bool RequestHandler::ExecuteCmdLineRequests(
1299 ProcessDocumentsRequest& aRequest, bool noTerminate)
1301 // protect the dispatch list
1302 osl::ClearableMutexGuard aGuard( GetMutex() );
1304 // ensure that Processed flag (if exists) is signaled in any outcome
1305 ConditionSetGuard aSetGuard(aRequest.pcProcessed);
1307 static std::vector<DispatchWatcher::DispatchRequest> aDispatchList;
1309 // Create dispatch list for dispatch watcher
1310 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aInFilter, DispatchWatcher::REQUEST_INFILTER, u""_ustr, aRequest.aModule );
1311 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aOpenList, DispatchWatcher::REQUEST_OPEN, u""_ustr, aRequest.aModule );
1312 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aViewList, DispatchWatcher::REQUEST_VIEW, u""_ustr, aRequest.aModule );
1313 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aStartList, DispatchWatcher::REQUEST_START, aRequest.aStartListParams, aRequest.aModule );
1314 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aPrintList, DispatchWatcher::REQUEST_PRINT, u""_ustr, aRequest.aModule );
1315 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aPrintToList, DispatchWatcher::REQUEST_PRINTTO, aRequest.aPrinterName, aRequest.aModule );
1316 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aForceOpenList, DispatchWatcher::REQUEST_FORCEOPEN, u""_ustr, aRequest.aModule );
1317 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aForceNewList, DispatchWatcher::REQUEST_FORCENEW, u""_ustr, aRequest.aModule );
1318 AddConversionsToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aConversionList, aRequest.aConversionParams, aRequest.aPrinterName, aRequest.aModule, aRequest.aConversionOut, aRequest.aImageConversionType, aRequest.bTextCat, aRequest.bScriptCat );
1319 bool bShutdown( false );
1321 if ( pGlobal.is() )
1323 if( ! pGlobal->AreRequestsEnabled() )
1325 // Either starting, or downing - do not process the request, just try to bring Office to front
1326 ApplicationEvent* pAppEvent =
1327 new ApplicationEvent(ApplicationEvent::Type::Appear);
1328 ImplPostForeignAppEvent(pAppEvent);
1329 return bShutdown;
1332 pGlobal->mnPendingRequests += aDispatchList.size();
1333 if ( !pGlobal->mpDispatchWatcher.is() )
1335 pGlobal->mpDispatchWatcher = new DispatchWatcher;
1337 rtl::Reference<DispatchWatcher> dispatchWatcher(
1338 pGlobal->mpDispatchWatcher);
1340 // copy for execute
1341 std::vector<DispatchWatcher::DispatchRequest> aTempList;
1342 aTempList.swap( aDispatchList );
1344 aGuard.clear();
1346 // Execute dispatch requests
1347 bShutdown = dispatchWatcher->executeDispatchRequests( aTempList, noTerminate);
1348 if (aRequest.mpbSuccess)
1349 *aRequest.mpbSuccess = true; // signal that we have actually succeeded
1352 return bShutdown;
1357 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */