bump product version to 7.2.5.1
[LibreOffice.git] / desktop / source / app / officeipcthread.cxx
bloba0e6b98cc006a4518dbe13d7c02655da251b7494
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 <vcl/svapp.hxx>
35 #include <unotools/configmgr.hxx>
36 #include <osl/pipe.hxx>
37 #include <rtl/digest.h>
38 #include <rtl/ustrbuf.hxx>
39 #include <rtl/instance.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>
47 #include <cassert>
48 #include <cstdlib>
49 #include <memory>
51 #if ENABLE_DBUS
52 #include <dbus/dbus.h>
53 #include <sys/socket.h>
54 #endif
56 using namespace desktop;
57 using namespace ::com::sun::star::uno;
58 using namespace ::com::sun::star::lang;
59 using namespace ::com::sun::star::frame;
61 namespace {
63 char const ARGUMENT_PREFIX[] = "InternalIPC::Arguments";
64 char const SEND_ARGUMENTS[] = "InternalIPC::SendArguments";
65 char const PROCESSING_DONE[] = "InternalIPC::ProcessingDone";
67 // Receives packets from the pipe until a packet ends in a NUL character (that
68 // will not be included in the returned string) or it cannot read anything (due
69 // to error or closed pipe, in which case an empty string will be returned to
70 // signal failure):
71 OString readStringFromPipe(osl::StreamPipe const & pipe) {
72 for (OStringBuffer str;;) {
73 char buf[1024];
74 sal_Int32 n = pipe.recv(buf, SAL_N_ELEMENTS(buf));
75 if (n <= 0) {
76 SAL_INFO("desktop.app", "read empty string");
77 return "";
79 bool end = false;
80 if (buf[n - 1] == '\0') {
81 end = true;
82 --n;
84 str.append(buf, n);
85 //TODO: how does OStringBuffer.append handle overflow?
86 if (end) {
87 auto s = str.makeStringAndClear();
88 SAL_INFO("desktop.app", "read <" << s << ">");
89 return s;
96 namespace desktop
99 namespace {
101 class Parser: public CommandLineArgs::Supplier {
102 public:
103 explicit Parser(OString const & input): m_input(input) {
104 if (!m_input.match(ARGUMENT_PREFIX) ||
105 m_input.getLength() == RTL_CONSTASCII_LENGTH(ARGUMENT_PREFIX))
107 throw CommandLineArgs::Supplier::Exception();
109 m_index = RTL_CONSTASCII_LENGTH(ARGUMENT_PREFIX);
110 switch (m_input[m_index++]) {
111 case '0':
112 break;
113 case '1':
115 OUString url;
116 if (!next(&url, false)) {
117 throw CommandLineArgs::Supplier::Exception();
119 m_cwdUrl = url;
120 break;
122 case '2':
124 OUString path;
125 if (!next(&path, false)) {
126 throw CommandLineArgs::Supplier::Exception();
128 OUString url;
129 if (osl::FileBase::getFileURLFromSystemPath(path, url) ==
130 osl::FileBase::E_None)
132 m_cwdUrl = url;
134 break;
136 default:
137 throw CommandLineArgs::Supplier::Exception();
141 virtual std::optional< OUString > getCwdUrl() override { return m_cwdUrl; }
143 virtual bool next(OUString * argument) override { return next(argument, true); }
145 private:
146 bool next(OUString * argument, bool prefix) {
147 OSL_ASSERT(argument != nullptr);
148 if (m_index < m_input.getLength()) {
149 if (prefix) {
150 if (m_input[m_index] != ',') {
151 throw CommandLineArgs::Supplier::Exception();
153 ++m_index;
155 OStringBuffer b;
156 while (m_index < m_input.getLength()) {
157 char c = m_input[m_index];
158 if (c == ',') {
159 break;
161 ++m_index;
162 if (c == '\\') {
163 if (m_index >= m_input.getLength())
164 throw CommandLineArgs::Supplier::Exception();
165 c = m_input[m_index++];
166 switch (c) {
167 case '0':
168 c = '\0';
169 break;
170 case ',':
171 case '\\':
172 break;
173 default:
174 throw CommandLineArgs::Supplier::Exception();
177 b.append(c);
179 OString b2(b.makeStringAndClear());
180 if (!rtl_convertStringToUString(
181 &argument->pData, b2.getStr(), b2.getLength(),
182 RTL_TEXTENCODING_UTF8,
183 (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR |
184 RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR |
185 RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR)))
187 throw CommandLineArgs::Supplier::Exception();
189 return true;
190 } else {
191 return false;
195 std::optional< OUString > m_cwdUrl;
196 OString m_input;
197 sal_Int32 m_index;
200 bool addArgument(OStringBuffer &rArguments, char prefix,
201 const OUString &rArgument)
203 OString utf8;
204 if (!rArgument.convertToString(
205 &utf8, RTL_TEXTENCODING_UTF8,
206 (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR |
207 RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR)))
209 return false;
211 rArguments.append(prefix);
212 for (sal_Int32 i = 0; i < utf8.getLength(); ++i) {
213 char c = utf8[i];
214 switch (c) {
215 case '\0':
216 rArguments.append("\\0");
217 break;
218 case ',':
219 rArguments.append("\\,");
220 break;
221 case '\\':
222 rArguments.append("\\\\");
223 break;
224 default:
225 rArguments.append(c);
226 break;
229 return true;
234 rtl::Reference< RequestHandler > RequestHandler::pGlobal;
236 // Turns a string in aMsg such as file:///home/foo/.libreoffice/3
237 // Into a hex string of well known length ff132a86...
238 static OUString CreateMD5FromString( const OUString& aMsg )
240 SAL_INFO("desktop.app", "create md5 from '" << aMsg << "'");
242 rtlDigest handle = rtl_digest_create( rtl_Digest_AlgorithmMD5 );
243 if ( handle )
245 const sal_uInt8* pData = reinterpret_cast<const sal_uInt8*>(aMsg.getStr());
246 sal_uInt32 nSize = aMsg.getLength() * sizeof( sal_Unicode );
247 sal_uInt32 nMD5KeyLen = rtl_digest_queryLength( handle );
248 std::unique_ptr<sal_uInt8[]> pMD5KeyBuffer(new sal_uInt8[ nMD5KeyLen ]);
250 rtl_digest_init( handle, pData, nSize );
251 rtl_digest_update( handle, pData, nSize );
252 rtl_digest_get( handle, pMD5KeyBuffer.get(), nMD5KeyLen );
253 rtl_digest_destroy( handle );
255 // Create hex-value string from the MD5 value to keep the string size minimal
256 OUStringBuffer aBuffer( nMD5KeyLen * 2 + 1 );
257 for ( sal_uInt32 i = 0; i < nMD5KeyLen; i++ )
258 aBuffer.append( static_cast<sal_Int32>(pMD5KeyBuffer[i]), 16 );
260 return aBuffer.makeStringAndClear();
263 return OUString();
266 namespace {
268 class ProcessEventsClass_Impl
270 public:
271 DECL_STATIC_LINK( ProcessEventsClass_Impl, CallEvent, void*, void );
272 DECL_STATIC_LINK( ProcessEventsClass_Impl, ProcessDocumentsEvent, void*, void );
277 IMPL_STATIC_LINK( ProcessEventsClass_Impl, CallEvent, void*, pEvent, void )
279 // Application events are processed by the Desktop::HandleAppEvent implementation.
280 Desktop::HandleAppEvent( *static_cast<ApplicationEvent*>(pEvent) );
281 delete static_cast<ApplicationEvent*>(pEvent);
284 IMPL_STATIC_LINK( ProcessEventsClass_Impl, ProcessDocumentsEvent, void*, pEvent, void )
286 // Documents requests are processed by the RequestHandler implementation
287 ProcessDocumentsRequest* pDocsRequest = static_cast<ProcessDocumentsRequest*>(pEvent);
288 RequestHandler::ExecuteCmdLineRequests(*pDocsRequest, false);
289 delete pDocsRequest;
292 static void ImplPostForeignAppEvent( ApplicationEvent* pEvent )
294 Application::PostUserEvent( LINK( nullptr, ProcessEventsClass_Impl, CallEvent ), pEvent );
297 static void ImplPostProcessDocumentsEvent( std::unique_ptr<ProcessDocumentsRequest> pEvent )
299 Application::PostUserEvent( LINK( nullptr, ProcessEventsClass_Impl, ProcessDocumentsEvent ), pEvent.release() );
302 oslSignalAction SalMainPipeExchangeSignal_impl(SAL_UNUSED_PARAMETER void* /*pData*/, oslSignalInfo* pInfo)
304 if( pInfo->Signal == osl_Signal_Terminate )
305 RequestHandler::SetDowning();
306 return osl_Signal_ActCallNextHdl;
310 // The RequestHandlerController implementation is a bookkeeper for all pending requests
311 // that were created by the RequestHandler. The requests are waiting to be processed by
312 // our framework loadComponentFromURL function (e.g. open/print request).
313 // During shutdown the framework is asking RequestHandlerController about pending requests.
314 // If there are pending requests framework has to stop the shutdown process. It is waiting
315 // for these requests because framework is not able to handle shutdown and open a document
316 // concurrently.
319 // XServiceInfo
320 OUString SAL_CALL RequestHandlerController::getImplementationName()
322 return "com.sun.star.comp.RequestHandlerController";
325 sal_Bool RequestHandlerController::supportsService(
326 OUString const & ServiceName)
328 return cppu::supportsService(this, ServiceName);
331 Sequence< OUString > SAL_CALL RequestHandlerController::getSupportedServiceNames()
333 return { };
336 // XEventListener
337 void SAL_CALL RequestHandlerController::disposing( const EventObject& )
341 // XTerminateListener
342 void SAL_CALL RequestHandlerController::queryTermination( const EventObject& )
344 // Desktop ask about pending request through our office ipc pipe. We have to
345 // be sure that no pending request is waiting because framework is not able to
346 // handle shutdown and open a document concurrently.
348 if ( RequestHandler::AreRequestsPending() )
349 throw TerminationVetoException();
350 RequestHandler::SetDowning();
353 void SAL_CALL RequestHandlerController::notifyTermination( const EventObject& )
357 class IpcThread: public salhelper::Thread {
358 public:
359 void start(RequestHandler * handler) {
360 m_handler = handler;
361 launch();
364 virtual void close() = 0;
366 protected:
367 explicit IpcThread(char const * name): Thread(name), m_handler(nullptr) {}
369 virtual ~IpcThread() override {}
371 bool process(OString const & arguments, bool * waitProcessed);
373 RequestHandler * m_handler;
376 class PipeIpcThread: public IpcThread {
377 public:
378 static RequestHandler::Status enable(rtl::Reference<IpcThread> * thread);
380 private:
381 explicit PipeIpcThread(osl::Pipe const & pipe):
382 IpcThread("PipeIPC"), pipe_(pipe)
385 virtual ~PipeIpcThread() override {}
387 void execute() override;
389 void close() override { pipe_.close(); }
391 osl::Pipe pipe_;
394 #if ENABLE_DBUS
396 namespace {
398 struct DbusConnectionHolder {
399 explicit DbusConnectionHolder(DBusConnection * theConnection):
400 connection(theConnection)
403 DbusConnectionHolder(DbusConnectionHolder && other): connection(nullptr)
404 { std::swap(connection, other.connection); }
406 ~DbusConnectionHolder() {
407 if (connection != nullptr) {
408 dbus_connection_close(connection);
409 dbus_connection_unref(connection);
413 DBusConnection * connection;
416 struct DbusMessageHolder {
417 explicit DbusMessageHolder(DBusMessage * theMessage): message(theMessage) {}
419 ~DbusMessageHolder() { clear(); }
421 void clear() {
422 if (message != nullptr) {
423 dbus_message_unref(message);
425 message = nullptr;
428 DBusMessage * message;
430 private:
431 DbusMessageHolder(DbusMessageHolder const &) = delete;
432 DbusMessageHolder& operator =(DbusMessageHolder const &) = delete;
437 class DbusIpcThread: public IpcThread {
438 public:
439 static RequestHandler::Status enable(rtl::Reference<IpcThread> * thread);
441 private:
442 explicit DbusIpcThread(DbusConnectionHolder && connection):
443 IpcThread("DbusIPC"), connection_(std::move(connection))
446 virtual ~DbusIpcThread() override {}
448 void execute() override;
450 void close() override;
452 DbusConnectionHolder connection_;
455 RequestHandler::Status DbusIpcThread::enable(rtl::Reference<IpcThread> * thread)
457 assert(thread != nullptr);
458 if (!dbus_threads_init_default()) {
459 SAL_WARN("desktop.app", "dbus_threads_init_default failed");
460 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
462 DBusError e;
463 dbus_error_init(&e);
464 DbusConnectionHolder con(dbus_bus_get_private(DBUS_BUS_SESSION, &e));
465 assert((con.connection == nullptr) == bool(dbus_error_is_set(&e)));
466 if (con.connection == nullptr) {
467 SAL_WARN(
468 "desktop.app",
469 "dbus_bus_get_private failed with: " << e.name << ": "
470 << e.message);
471 dbus_error_free(&e);
472 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
474 for (;;) {
475 int n = dbus_bus_request_name(
476 con.connection, "org.libreoffice.LibreOfficeIpc0",
477 DBUS_NAME_FLAG_DO_NOT_QUEUE, &e);
478 assert((n == -1) == bool(dbus_error_is_set(&e)));
479 switch (n) {
480 case -1:
481 SAL_WARN(
482 "desktop.app",
483 "dbus_bus_request_name failed with: " << e.name << ": "
484 << e.message);
485 dbus_error_free(&e);
486 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
487 case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
488 *thread = new DbusIpcThread(std::move(con));
489 return RequestHandler::IPC_STATUS_OK;
490 case DBUS_REQUEST_NAME_REPLY_EXISTS:
492 OStringBuffer buf(ARGUMENT_PREFIX);
493 OUString arg;
494 if (!(utl::Bootstrap::getProcessWorkingDir(arg)
495 && addArgument(buf, '1', arg)))
497 buf.append('0');
499 sal_uInt32 narg = rtl_getAppCommandArgCount();
500 for (sal_uInt32 i = 0; i != narg; ++i) {
501 rtl_getAppCommandArg(i, &arg.pData);
502 if (!addArgument(buf, ',', arg)) {
503 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
506 char const * argstr = buf.getStr();
507 DbusMessageHolder msg(
508 dbus_message_new_method_call(
509 "org.libreoffice.LibreOfficeIpc0",
510 "/org/libreoffice/LibreOfficeIpc0",
511 "org.libreoffice.LibreOfficeIpcIfc0", "Execute"));
512 if (msg.message == nullptr) {
513 SAL_WARN(
514 "desktop.app", "dbus_message_new_method_call failed");
515 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
517 DBusMessageIter it;
518 dbus_message_iter_init_append(msg.message, &it);
519 if (!dbus_message_iter_append_basic(
520 &it, DBUS_TYPE_STRING, &argstr))
522 SAL_WARN(
523 "desktop.app", "dbus_message_iter_append_basic failed");
524 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
526 DbusMessageHolder repl(
527 dbus_connection_send_with_reply_and_block(
528 con.connection, msg.message, 0x7FFFFFFF, &e));
529 assert(
530 (repl.message == nullptr) == bool(dbus_error_is_set(&e)));
531 if (repl.message == nullptr) {
532 SAL_INFO(
533 "desktop.app",
534 "dbus_connection_send_with_reply_and_block failed"
535 " with: " << e.name << ": " << e.message);
536 dbus_error_free(&e);
537 break;
539 return RequestHandler::IPC_STATUS_2ND_OFFICE;
541 case DBUS_REQUEST_NAME_REPLY_IN_QUEUE:
542 case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER:
543 SAL_WARN(
544 "desktop.app",
545 "dbus_bus_request_name failed with unexpected " << +n);
546 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
547 default:
548 for (;;) std::abort();
553 void DbusIpcThread::execute()
555 assert(m_handler != nullptr);
556 m_handler->cReady.wait();
557 for (;;) {
559 osl::MutexGuard g(RequestHandler::GetMutex());
560 if (m_handler->mState == RequestHandler::State::Downing) {
561 break;
564 if (!dbus_connection_read_write(connection_.connection, -1)) {
565 break;
567 for (;;) {
568 DbusMessageHolder msg(
569 dbus_connection_pop_message(connection_.connection));
570 if (msg.message == nullptr) {
571 break;
573 if (!dbus_message_is_method_call(
574 msg.message, "org.libreoffice.LibreOfficeIpcIfc0",
575 "Execute"))
577 SAL_INFO("desktop.app", "unknown DBus message ignored");
578 continue;
580 DBusMessageIter it;
581 if (!dbus_message_iter_init(msg.message, &it)) {
582 SAL_WARN(
583 "desktop.app", "DBus message without argument ignored");
584 continue;
586 if (dbus_message_iter_get_arg_type(&it) != DBUS_TYPE_STRING) {
587 SAL_WARN(
588 "desktop.app",
589 "DBus message with non-string argument ignored");
590 continue;
592 char const * argstr;
593 dbus_message_iter_get_basic(&it, &argstr);
594 bool waitProcessed = false;
596 osl::MutexGuard g(RequestHandler::GetMutex());
597 if (!process(argstr, &waitProcessed)) {
598 continue;
601 if (waitProcessed) {
602 m_handler->cProcessed.wait();
604 DbusMessageHolder repl(dbus_message_new_method_return(msg.message));
605 if (repl.message == nullptr) {
606 SAL_WARN(
607 "desktop.app", "dbus_message_new_method_return failed");
608 continue;
610 dbus_uint32_t serial = 0;
611 if (!dbus_connection_send(
612 connection_.connection, repl.message, &serial)) {
613 SAL_WARN("desktop.app", "dbus_connection_send failed");
614 continue;
616 dbus_connection_flush(connection_.connection);
621 void DbusIpcThread::close() {
622 assert(connection_.connection != nullptr);
623 // Make dbus_connection_read_write fall out of internal poll call blocking
624 // on POLLIN:
625 int fd;
626 if (!dbus_connection_get_socket(connection_.connection, &fd)) {
627 SAL_WARN("desktop.app", "dbus_connection_get_socket failed");
628 return;
630 if (shutdown(fd, SHUT_RD) == -1) {
631 auto const e = errno;
632 SAL_WARN("desktop.app", "shutdown failed with errno " << e);
636 #endif
638 namespace
640 class theRequestHandlerMutex
641 : public rtl::Static<osl::Mutex, theRequestHandlerMutex> {};
644 ::osl::Mutex& RequestHandler::GetMutex()
646 return theRequestHandlerMutex::get();
649 void RequestHandler::SetDowning()
651 // We have the order to block all incoming requests. Framework
652 // wants to shutdown and we have to make sure that no loading/printing
653 // requests are executed anymore.
654 ::osl::MutexGuard aGuard( GetMutex() );
656 if ( pGlobal.is() )
657 pGlobal->mState = State::Downing;
660 void RequestHandler::EnableRequests()
662 // switch between just queueing the requests and executing them
663 ::osl::MutexGuard aGuard( GetMutex() );
665 if ( pGlobal.is() )
667 if (pGlobal->mState != State::Downing) {
668 pGlobal->mState = State::RequestsEnabled;
670 // hit the compiler over the head - this avoids GCC -Werror=maybe-uninitialized
671 std::optional<OUString> tmp;
672 ProcessDocumentsRequest aEmptyReq(tmp);
673 // trigger already queued requests
674 RequestHandler::ExecuteCmdLineRequests(aEmptyReq, true);
678 bool RequestHandler::AreRequestsPending()
680 // Give info about pending requests
681 ::osl::MutexGuard aGuard( GetMutex() );
682 if ( pGlobal.is() )
683 return ( pGlobal->mnPendingRequests > 0 );
684 else
685 return false;
688 void RequestHandler::RequestsCompleted()
690 // Remove nCount pending requests from our internal counter
691 ::osl::MutexGuard aGuard( GetMutex() );
692 if ( pGlobal.is() )
694 if ( pGlobal->mnPendingRequests > 0 )
695 pGlobal->mnPendingRequests --;
699 RequestHandler::Status RequestHandler::Enable(bool ipc)
701 ::osl::MutexGuard aGuard( GetMutex() );
703 if( pGlobal.is() )
704 return IPC_STATUS_OK;
706 #if !HAVE_FEATURE_DESKTOP || HAVE_FEATURE_MACOSX_SANDBOX
707 ipc = false;
708 #endif
710 if (!ipc) {
711 pGlobal = new RequestHandler;
712 return IPC_STATUS_OK;
715 enum class Kind { Pipe, Dbus };
716 Kind kind;
717 #if ENABLE_DBUS
718 kind = std::getenv("LIBO_FLATPAK") != nullptr ? Kind::Dbus : Kind::Pipe;
719 #else
720 kind = Kind::Pipe;
721 #endif
722 rtl::Reference<IpcThread> thread;
723 Status stat = Status(); // silence bogus potentially-uninitialized warnings
724 switch (kind) {
725 case Kind::Pipe:
726 stat = PipeIpcThread::enable(&thread);
727 break;
728 case Kind::Dbus:
729 #if ENABLE_DBUS
730 stat = DbusIpcThread::enable(&thread);
731 break;
732 #endif
733 default:
734 assert(false);
736 assert(thread.is() == (stat == IPC_STATUS_OK));
737 if (stat == IPC_STATUS_OK) {
738 pGlobal = new RequestHandler;
739 pGlobal->mIpcThread = thread;
740 pGlobal->mIpcThread->start(pGlobal.get());
742 return stat;
745 RequestHandler::Status PipeIpcThread::enable(rtl::Reference<IpcThread> * thread)
747 assert(thread != nullptr);
749 // The name of the named pipe is created with the hashcode of the user installation directory (without /user). We have to retrieve
750 // this information from a unotools implementation.
751 OUString aUserInstallPath;
752 ::utl::Bootstrap::PathStatus aLocateResult = ::utl::Bootstrap::locateUserInstallation( aUserInstallPath );
753 if (aLocateResult != utl::Bootstrap::PATH_EXISTS
754 && aLocateResult != utl::Bootstrap::PATH_VALID)
756 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
759 // Try to determine if we are the first office or not! This should prevent multiple
760 // access to the user directory !
761 // First we try to create our pipe if this fails we try to connect. We have to do this
762 // in a loop because the other office can crash or shutdown between createPipe
763 // and connectPipe!!
764 auto aUserInstallPathHashCode = CreateMD5FromString(aUserInstallPath);
766 // Check result to create a hash code from the user install path
767 if ( aUserInstallPathHashCode.isEmpty() )
768 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR; // Something completely broken, we cannot create a valid hash code!
770 osl::Pipe pipe;
771 enum PipeMode
773 PIPEMODE_DONTKNOW,
774 PIPEMODE_CREATED,
775 PIPEMODE_CONNECTED
777 PipeMode nPipeMode = PIPEMODE_DONTKNOW;
779 OUString aPipeIdent( "SingleOfficeIPC_" + aUserInstallPathHashCode );
782 osl::Security security;
784 // Try to create pipe
785 if ( pipe.create( aPipeIdent, osl_Pipe_CREATE, security ))
787 // Pipe created
788 nPipeMode = PIPEMODE_CREATED;
790 else if( pipe.create( aPipeIdent, osl_Pipe_OPEN, security )) // Creation not successful, now we try to connect
792 osl::StreamPipe aStreamPipe(pipe.getHandle());
793 if (readStringFromPipe(aStreamPipe) == SEND_ARGUMENTS)
795 // Pipe connected to first office
796 nPipeMode = PIPEMODE_CONNECTED;
798 else
800 // Pipe connection failed (other office exited or crashed)
801 TimeValue tval;
802 tval.Seconds = 0;
803 tval.Nanosec = 500000000;
804 salhelper::Thread::wait( tval );
807 else
809 oslPipeError eReason = pipe.getError();
810 if ((eReason == osl_Pipe_E_ConnectionRefused) || (eReason == osl_Pipe_E_invalidError))
811 return RequestHandler::IPC_STATUS_PIPE_ERROR;
813 // Wait for second office to be ready
814 TimeValue aTimeValue;
815 aTimeValue.Seconds = 0;
816 aTimeValue.Nanosec = 10000000; // 10ms
817 salhelper::Thread::wait( aTimeValue );
820 } while ( nPipeMode == PIPEMODE_DONTKNOW );
822 if ( nPipeMode == PIPEMODE_CREATED )
824 // Seems we are the one and only, so create listening thread
825 *thread = new PipeIpcThread(pipe);
826 return RequestHandler::IPC_STATUS_OK;
828 else
830 // Seems another office is running. Pipe arguments to it and self terminate
831 osl::StreamPipe aStreamPipe(pipe.getHandle());
833 OStringBuffer aArguments(ARGUMENT_PREFIX);
834 OUString cwdUrl;
835 if (!(utl::Bootstrap::getProcessWorkingDir(cwdUrl) &&
836 addArgument(aArguments, '1', cwdUrl)))
838 aArguments.append('0');
840 sal_uInt32 nCount = rtl_getAppCommandArgCount();
841 for( sal_uInt32 i=0; i < nCount; i++ )
843 rtl_getAppCommandArg( i, &aUserInstallPath.pData );
844 if (!addArgument(aArguments, ',', aUserInstallPath)) {
845 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
848 aArguments.append('\0');
849 // finally, write the string onto the pipe
850 SAL_INFO("desktop.app", "writing <" << aArguments.getStr() << ">");
851 sal_Int32 n = aStreamPipe.write(
852 aArguments.getStr(), aArguments.getLength());
853 if (n != aArguments.getLength()) {
854 SAL_INFO("desktop.app", "short write: " << n);
855 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
858 if (readStringFromPipe(aStreamPipe) != PROCESSING_DONE)
860 // something went wrong
861 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
864 return RequestHandler::IPC_STATUS_2ND_OFFICE;
868 void RequestHandler::Disable()
870 osl::ClearableMutexGuard aMutex( GetMutex() );
872 if( !pGlobal.is() )
873 return;
875 rtl::Reference< RequestHandler > handler(pGlobal);
876 pGlobal.clear();
878 handler->mState = State::Downing;
879 if (handler->mIpcThread.is()) {
880 handler->mIpcThread->close();
883 // release mutex to avoid deadlocks
884 aMutex.clear();
886 handler->cReady.set();
888 // exit gracefully and join
889 if (handler->mIpcThread.is())
891 handler->mIpcThread->join();
892 handler->mIpcThread.clear();
895 handler->cReady.reset();
898 RequestHandler::RequestHandler() :
899 mState( State::Starting ),
900 mnPendingRequests( 0 )
904 RequestHandler::~RequestHandler()
906 assert(!mIpcThread.is());
909 void RequestHandler::SetReady(bool bIsReady)
911 osl::MutexGuard g(GetMutex());
912 if (pGlobal.is())
914 if (bIsReady)
915 pGlobal->cReady.set();
916 else
917 pGlobal->cReady.reset();
921 void RequestHandler::WaitForReady()
923 rtl::Reference<RequestHandler> t;
925 osl::MutexGuard g(GetMutex());
926 t = pGlobal;
928 if (t.is())
930 t->cReady.wait();
934 bool IpcThread::process(OString const & arguments, bool * waitProcessed) {
935 assert(waitProcessed != nullptr);
937 std::unique_ptr< CommandLineArgs > aCmdLineArgs;
940 Parser p(arguments);
941 aCmdLineArgs.reset( new CommandLineArgs( p ) );
943 catch ( const CommandLineArgs::Supplier::Exception & )
945 SAL_WARN("desktop.app", "Error in received command line arguments");
946 return false;
949 bool bDocRequestSent = false;
951 OUString aUnknown( aCmdLineArgs->GetUnknown() );
952 if (aUnknown.isEmpty() && !aCmdLineArgs->IsHelp() && !aCmdLineArgs->IsVersion())
954 const CommandLineArgs &rCurrentCmdLineArgs = Desktop::GetCommandLineArgs();
956 if ( aCmdLineArgs->IsQuickstart() )
958 // we have to use application event, because we have to start quickstart service in main thread!!
959 ApplicationEvent* pAppEvent =
960 new ApplicationEvent(ApplicationEvent::Type::QuickStart);
961 ImplPostForeignAppEvent( pAppEvent );
964 // handle request for acceptor
965 std::vector< OUString > const & accept = aCmdLineArgs->GetAccept();
966 for (auto const& elem : accept)
968 ApplicationEvent* pAppEvent = new ApplicationEvent(
969 ApplicationEvent::Type::Accept, elem);
970 ImplPostForeignAppEvent( pAppEvent );
972 // handle acceptor removal
973 std::vector< OUString > const & unaccept = aCmdLineArgs->GetUnaccept();
974 for (auto const& elem : unaccept)
976 ApplicationEvent* pAppEvent = new ApplicationEvent(
977 ApplicationEvent::Type::Unaccept, elem);
978 ImplPostForeignAppEvent( pAppEvent );
981 std::unique_ptr<ProcessDocumentsRequest> pRequest(new ProcessDocumentsRequest(
982 aCmdLineArgs->getCwdUrl()));
983 m_handler->cProcessed.reset();
984 pRequest->pcProcessed = &m_handler->cProcessed;
985 m_handler->mbSuccess = false;
986 pRequest->mpbSuccess = &m_handler->mbSuccess;
988 // Print requests are not dependent on the --invisible cmdline argument as they are
989 // loaded with the "hidden" flag! So they are always checked.
990 pRequest->aPrintList = aCmdLineArgs->GetPrintList();
991 bDocRequestSent |= !pRequest->aPrintList.empty();
992 pRequest->aPrintToList = aCmdLineArgs->GetPrintToList();
993 pRequest->aPrinterName = aCmdLineArgs->GetPrinterName();
994 bDocRequestSent |= !( pRequest->aPrintToList.empty() || pRequest->aPrinterName.isEmpty() );
995 pRequest->aConversionList = aCmdLineArgs->GetConversionList();
996 pRequest->aConversionParams = aCmdLineArgs->GetConversionParams();
997 pRequest->aConversionOut = aCmdLineArgs->GetConversionOut();
998 pRequest->aImageConversionType = aCmdLineArgs->GetImageConversionType();
999 pRequest->aInFilter = aCmdLineArgs->GetInFilter();
1000 pRequest->bTextCat = aCmdLineArgs->IsTextCat();
1001 pRequest->bScriptCat = aCmdLineArgs->IsScriptCat();
1002 bDocRequestSent |= !pRequest->aConversionList.empty();
1004 if ( !rCurrentCmdLineArgs.IsInvisible() )
1006 // Read cmdline args that can open/create documents. As they would open a window
1007 // they are only allowed if the "--invisible" is currently not used!
1008 pRequest->aOpenList = aCmdLineArgs->GetOpenList();
1009 bDocRequestSent |= !pRequest->aOpenList.empty();
1010 pRequest->aViewList = aCmdLineArgs->GetViewList();
1011 bDocRequestSent |= !pRequest->aViewList.empty();
1012 pRequest->aStartList = aCmdLineArgs->GetStartList();
1013 bDocRequestSent |= !pRequest->aStartList.empty();
1014 pRequest->aForceOpenList = aCmdLineArgs->GetForceOpenList();
1015 bDocRequestSent |= !pRequest->aForceOpenList.empty();
1016 pRequest->aForceNewList = aCmdLineArgs->GetForceNewList();
1017 bDocRequestSent |= !pRequest->aForceNewList.empty();
1019 // Special command line args to create an empty document for a given module
1021 // #i18338# (lo)
1022 // we only do this if no document was specified on the command line,
1023 // since this would be inconsistent with the behaviour of
1024 // the first process, see OpenClients() (call to OpenDefault()) in app.cxx
1025 if ( aCmdLineArgs->HasModuleParam() && !bDocRequestSent )
1027 SvtModuleOptions aOpt;
1028 SvtModuleOptions::EFactory eFactory = SvtModuleOptions::EFactory::WRITER;
1029 if ( aCmdLineArgs->IsWriter() )
1030 eFactory = SvtModuleOptions::EFactory::WRITER;
1031 else if ( aCmdLineArgs->IsCalc() )
1032 eFactory = SvtModuleOptions::EFactory::CALC;
1033 else if ( aCmdLineArgs->IsDraw() )
1034 eFactory = SvtModuleOptions::EFactory::DRAW;
1035 else if ( aCmdLineArgs->IsImpress() )
1036 eFactory = SvtModuleOptions::EFactory::IMPRESS;
1037 else if ( aCmdLineArgs->IsBase() )
1038 eFactory = SvtModuleOptions::EFactory::DATABASE;
1039 else if ( aCmdLineArgs->IsMath() )
1040 eFactory = SvtModuleOptions::EFactory::MATH;
1041 else if ( aCmdLineArgs->IsGlobal() )
1042 eFactory = SvtModuleOptions::EFactory::WRITERGLOBAL;
1043 else if ( aCmdLineArgs->IsWeb() )
1044 eFactory = SvtModuleOptions::EFactory::WRITERWEB;
1046 if ( !pRequest->aOpenList.empty() )
1047 pRequest->aModule = aOpt.GetFactoryName( eFactory );
1048 else
1049 pRequest->aOpenList.push_back( aOpt.GetFactoryEmptyDocumentURL( eFactory ) );
1050 bDocRequestSent = true;
1054 if ( !aCmdLineArgs->IsQuickstart() ) {
1055 bool bShowHelp = false;
1056 OUStringBuffer aHelpURLBuffer;
1057 if (aCmdLineArgs->IsHelpWriter()) {
1058 bShowHelp = true;
1059 aHelpURLBuffer.append("vnd.sun.star.help://swriter/start");
1060 } else if (aCmdLineArgs->IsHelpCalc()) {
1061 bShowHelp = true;
1062 aHelpURLBuffer.append("vnd.sun.star.help://scalc/start");
1063 } else if (aCmdLineArgs->IsHelpDraw()) {
1064 bShowHelp = true;
1065 aHelpURLBuffer.append("vnd.sun.star.help://sdraw/start");
1066 } else if (aCmdLineArgs->IsHelpImpress()) {
1067 bShowHelp = true;
1068 aHelpURLBuffer.append("vnd.sun.star.help://simpress/start");
1069 } else if (aCmdLineArgs->IsHelpBase()) {
1070 bShowHelp = true;
1071 aHelpURLBuffer.append("vnd.sun.star.help://sdatabase/start");
1072 } else if (aCmdLineArgs->IsHelpBasic()) {
1073 bShowHelp = true;
1074 aHelpURLBuffer.append("vnd.sun.star.help://sbasic/start");
1075 } else if (aCmdLineArgs->IsHelpMath()) {
1076 bShowHelp = true;
1077 aHelpURLBuffer.append("vnd.sun.star.help://smath/start");
1079 if (bShowHelp) {
1080 aHelpURLBuffer.append("?Language=");
1081 aHelpURLBuffer.append(utl::ConfigManager::getUILocale());
1082 #if defined UNX
1083 aHelpURLBuffer.append("&System=UNX");
1084 #elif defined _WIN32
1085 aHelpURLBuffer.append("&System=WIN");
1086 #endif
1087 ApplicationEvent* pAppEvent = new ApplicationEvent(
1088 ApplicationEvent::Type::OpenHelpUrl,
1089 aHelpURLBuffer.makeStringAndClear());
1090 ImplPostForeignAppEvent( pAppEvent );
1094 if ( bDocRequestSent )
1096 // Send requests to dispatch watcher if we have at least one. The receiver
1097 // is responsible to delete the request after processing it.
1098 if ( aCmdLineArgs->HasModuleParam() )
1100 SvtModuleOptions aOpt;
1102 // Support command line parameters to start a module (as preselection)
1103 if ( aCmdLineArgs->IsWriter() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER ) )
1104 pRequest->aModule = aOpt.GetFactoryName( SvtModuleOptions::EFactory::WRITER );
1105 else if ( aCmdLineArgs->IsCalc() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::CALC ) )
1106 pRequest->aModule = aOpt.GetFactoryName( SvtModuleOptions::EFactory::CALC );
1107 else if ( aCmdLineArgs->IsImpress() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::IMPRESS ) )
1108 pRequest->aModule= aOpt.GetFactoryName( SvtModuleOptions::EFactory::IMPRESS );
1109 else if ( aCmdLineArgs->IsDraw() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::DRAW ) )
1110 pRequest->aModule= aOpt.GetFactoryName( SvtModuleOptions::EFactory::DRAW );
1113 ImplPostProcessDocumentsEvent( std::move(pRequest) );
1115 else
1117 // delete not used request again
1118 pRequest.reset();
1120 if (aCmdLineArgs->IsEmpty())
1122 // no document was sent, just bring Office to front
1123 ApplicationEvent* pAppEvent =
1124 new ApplicationEvent(ApplicationEvent::Type::Appear);
1125 ImplPostForeignAppEvent( pAppEvent );
1128 *waitProcessed = bDocRequestSent;
1129 return true;
1132 void PipeIpcThread::execute()
1134 assert(m_handler != nullptr);
1137 osl::StreamPipe aStreamPipe;
1138 oslPipeError nError = pipe_.accept( aStreamPipe );
1141 if( nError == osl_Pipe_E_None )
1143 // if we receive a request while the office is displaying some dialog or error during
1144 // bootstrap, that dialogs event loop might get events that are dispatched by this thread
1145 // we have to wait for cReady to be set by the real main loop.
1146 // only requests that don't dispatch events may be processed before cReady is set.
1147 m_handler->cReady.wait();
1149 // we might have decided to shutdown while we were sleeping
1150 if (!RequestHandler::pGlobal.is()) return;
1152 // only lock the mutex when processing starts, otherwise we deadlock when the office goes
1153 // down during wait
1154 osl::ClearableMutexGuard aGuard( RequestHandler::GetMutex() );
1156 if (m_handler->mState == RequestHandler::State::Downing)
1158 break;
1161 // notify client we're ready to process its args:
1162 SAL_INFO("desktop.app", "writing <" << SEND_ARGUMENTS << ">");
1163 sal_Int32 n = aStreamPipe.write(
1164 SEND_ARGUMENTS, SAL_N_ELEMENTS(SEND_ARGUMENTS));
1165 // incl. terminating NUL
1166 if (n != SAL_N_ELEMENTS(SEND_ARGUMENTS)) {
1167 SAL_WARN("desktop.app", "short write: " << n);
1168 continue;
1171 OString aArguments = readStringFromPipe(aStreamPipe);
1173 // Is this a lookup message from another application? if so, ignore
1174 if (aArguments.isEmpty())
1175 continue;
1177 bool waitProcessed = false;
1178 if (!process(aArguments, &waitProcessed)) {
1179 continue;
1182 // we don't need the mutex any longer...
1183 aGuard.clear();
1184 bool bSuccess = true;
1185 // wait for processing to finish
1186 if (waitProcessed)
1188 m_handler->cProcessed.wait();
1189 bSuccess = m_handler->mbSuccess;
1191 if (bSuccess)
1193 // processing finished, inform the requesting end:
1194 SAL_INFO("desktop.app", "writing <" << PROCESSING_DONE << ">");
1195 n = aStreamPipe.write(PROCESSING_DONE, SAL_N_ELEMENTS(PROCESSING_DONE));
1196 // incl. terminating NUL
1197 if (n != SAL_N_ELEMENTS(PROCESSING_DONE))
1199 SAL_WARN("desktop.app", "short write: " << n);
1200 continue;
1204 else
1207 osl::MutexGuard aGuard( RequestHandler::GetMutex() );
1208 if (m_handler->mState == RequestHandler::State::Downing)
1210 break;
1214 SAL_WARN( "desktop.app", "Error on accept: " << static_cast<int>(nError));
1215 TimeValue tval;
1216 tval.Seconds = 1;
1217 tval.Nanosec = 0;
1218 salhelper::Thread::wait( tval );
1220 } while( schedule() );
1223 static void AddToDispatchList(
1224 std::vector<DispatchWatcher::DispatchRequest>& rDispatchList,
1225 std::optional< OUString > const & cwdUrl,
1226 std::vector< OUString > const & aRequestList,
1227 DispatchWatcher::RequestType nType,
1228 const OUString& aParam,
1229 const OUString& aFactory )
1231 for (auto const& request : aRequestList)
1233 rDispatchList.push_back({nType, request, cwdUrl, aParam, aFactory});
1237 static void AddConversionsToDispatchList(
1238 std::vector<DispatchWatcher::DispatchRequest>& rDispatchList,
1239 std::optional< OUString > const & cwdUrl,
1240 std::vector< OUString > const & rRequestList,
1241 const OUString& rParam,
1242 const OUString& rPrinterName,
1243 const OUString& rFactory,
1244 const OUString& rParamOut,
1245 const OUString& rImgOut,
1246 const bool isTextCat,
1247 const bool isScriptCat )
1249 DispatchWatcher::RequestType nType;
1250 OUString aParam( rParam );
1252 if( !rParam.isEmpty() )
1254 if ( isTextCat )
1255 nType = DispatchWatcher::REQUEST_CAT;
1256 else
1257 nType = DispatchWatcher::REQUEST_CONVERSION;
1258 aParam = rParam;
1260 else
1262 if ( isScriptCat )
1263 nType = DispatchWatcher::REQUEST_SCRIPT_CAT;
1264 else
1266 nType = DispatchWatcher::REQUEST_BATCHPRINT;
1267 aParam = rPrinterName;
1271 OUString aOutDir( rParamOut.trim() );
1272 OUString aImgOut( rImgOut.trim() );
1273 OUString aPWD;
1274 if (cwdUrl)
1276 aPWD = *cwdUrl;
1278 else
1280 utl::Bootstrap::getProcessWorkingDir( aPWD );
1283 if( !::osl::FileBase::getAbsoluteFileURL( aPWD, rParamOut, aOutDir ) )
1284 ::osl::FileBase::getSystemPathFromFileURL( aOutDir, aOutDir );
1286 if( !rParamOut.trim().isEmpty() )
1288 aParam += ";" + aOutDir;
1290 else
1292 ::osl::FileBase::getSystemPathFromFileURL( aPWD, aPWD );
1293 aParam += ";" + aPWD;
1296 if( !rImgOut.trim().isEmpty() )
1297 aParam += "|" + aImgOut;
1299 for (auto const& request : rRequestList)
1301 rDispatchList.push_back({nType, request, cwdUrl, aParam, rFactory});
1305 namespace {
1307 struct ConditionSetGuard
1309 osl::Condition* m_pCondition;
1310 ConditionSetGuard(osl::Condition* pCondition) : m_pCondition(pCondition) {}
1311 ~ConditionSetGuard() { if (m_pCondition) m_pCondition->set(); }
1316 bool RequestHandler::ExecuteCmdLineRequests(
1317 ProcessDocumentsRequest& aRequest, bool noTerminate)
1319 // protect the dispatch list
1320 osl::ClearableMutexGuard aGuard( GetMutex() );
1322 // ensure that Processed flag (if exists) is signaled in any outcome
1323 ConditionSetGuard aSetGuard(aRequest.pcProcessed);
1325 static std::vector<DispatchWatcher::DispatchRequest> aDispatchList;
1327 // Create dispatch list for dispatch watcher
1328 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aInFilter, DispatchWatcher::REQUEST_INFILTER, "", aRequest.aModule );
1329 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aOpenList, DispatchWatcher::REQUEST_OPEN, "", aRequest.aModule );
1330 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aViewList, DispatchWatcher::REQUEST_VIEW, "", aRequest.aModule );
1331 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aStartList, DispatchWatcher::REQUEST_START, "", aRequest.aModule );
1332 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aPrintList, DispatchWatcher::REQUEST_PRINT, "", aRequest.aModule );
1333 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aPrintToList, DispatchWatcher::REQUEST_PRINTTO, aRequest.aPrinterName, aRequest.aModule );
1334 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aForceOpenList, DispatchWatcher::REQUEST_FORCEOPEN, "", aRequest.aModule );
1335 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aForceNewList, DispatchWatcher::REQUEST_FORCENEW, "", aRequest.aModule );
1336 AddConversionsToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aConversionList, aRequest.aConversionParams, aRequest.aPrinterName, aRequest.aModule, aRequest.aConversionOut, aRequest.aImageConversionType, aRequest.bTextCat, aRequest.bScriptCat );
1337 bool bShutdown( false );
1339 if ( pGlobal.is() )
1341 if( ! pGlobal->AreRequestsEnabled() )
1343 // Either starting, or downing - do not process the request, just try to bring Office to front
1344 ApplicationEvent* pAppEvent =
1345 new ApplicationEvent(ApplicationEvent::Type::Appear);
1346 ImplPostForeignAppEvent(pAppEvent);
1347 return bShutdown;
1350 pGlobal->mnPendingRequests += aDispatchList.size();
1351 if ( !pGlobal->mpDispatchWatcher.is() )
1353 pGlobal->mpDispatchWatcher = new DispatchWatcher;
1355 rtl::Reference<DispatchWatcher> dispatchWatcher(
1356 pGlobal->mpDispatchWatcher);
1358 // copy for execute
1359 std::vector<DispatchWatcher::DispatchRequest> aTempList;
1360 aTempList.swap( aDispatchList );
1362 aGuard.clear();
1364 // Execute dispatch requests
1365 bShutdown = dispatchWatcher->executeDispatchRequests( aTempList, noTerminate);
1366 if (aRequest.mpbSuccess)
1367 *aRequest.mpbSuccess = true; // signal that we have actually succeeded
1370 return bShutdown;
1375 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */