Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / desktop / source / app / officeipcthread.cxx
blobe375c17f683c3def3f04997be0b007eb0564bf67
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 <stdio.h>
31 #include <com/sun/star/frame/TerminationVetoException.hpp>
32 #include <osl/process.h>
33 #include <sal/log.hxx>
34 #include <unotools/bootstrap.hxx>
35 #include <vcl/svapp.hxx>
36 #include <vcl/help.hxx>
37 #include <unotools/configmgr.hxx>
38 #include <osl/thread.hxx>
39 #include <rtl/digest.h>
40 #include <rtl/ustrbuf.hxx>
41 #include <rtl/instance.hxx>
42 #include <osl/conditn.hxx>
43 #include <unotools/moduleoptions.hxx>
44 #include <rtl/strbuf.hxx>
45 #include <cppuhelper/supportsservice.hxx>
46 #include <osl/file.hxx>
47 #include <rtl/process.h>
49 #include <algorithm>
50 #include <cassert>
51 #include <cstdlib>
52 #include <memory>
54 #if ENABLE_DBUS
55 #include <dbus/dbus.h>
56 #include <sys/socket.h>
57 #endif
59 using namespace desktop;
60 using namespace ::com::sun::star::uno;
61 using namespace ::com::sun::star::lang;
62 using namespace ::com::sun::star::frame;
64 namespace {
66 static char const ARGUMENT_PREFIX[] = "InternalIPC::Arguments";
67 static char const SEND_ARGUMENTS[] = "InternalIPC::SendArguments";
68 static char const PROCESSING_DONE[] = "InternalIPC::ProcessingDone";
70 // Receives packets from the pipe until a packet ends in a NUL character (that
71 // will not be included in the returned string) or it cannot read anything (due
72 // to error or closed pipe, in which case an empty string will be returned to
73 // signal failure):
74 OString readStringFromPipe(osl::StreamPipe const & pipe) {
75 for (OStringBuffer str;;) {
76 char buf[1024];
77 sal_Int32 n = pipe.recv(buf, SAL_N_ELEMENTS(buf));
78 if (n <= 0) {
79 SAL_INFO("desktop.app", "read empty string");
80 return "";
82 bool end = false;
83 if (buf[n - 1] == '\0') {
84 end = true;
85 --n;
87 str.append(buf, n);
88 //TODO: how does OStringBuffer.append handle overflow?
89 if (end) {
90 auto s = str.makeStringAndClear();
91 SAL_INFO("desktop.app", "read <" << s << ">");
92 return s;
99 namespace desktop
102 namespace {
104 class Parser: public CommandLineArgs::Supplier {
105 public:
106 explicit Parser(OString const & input): m_input(input) {
107 if (!m_input.match(ARGUMENT_PREFIX) ||
108 m_input.getLength() == RTL_CONSTASCII_LENGTH(ARGUMENT_PREFIX))
110 throw CommandLineArgs::Supplier::Exception();
112 m_index = RTL_CONSTASCII_LENGTH(ARGUMENT_PREFIX);
113 switch (m_input[m_index++]) {
114 case '0':
115 break;
116 case '1':
118 OUString url;
119 if (!next(&url, false)) {
120 throw CommandLineArgs::Supplier::Exception();
122 m_cwdUrl = url;
123 break;
125 case '2':
127 OUString path;
128 if (!next(&path, false)) {
129 throw CommandLineArgs::Supplier::Exception();
131 OUString url;
132 if (osl::FileBase::getFileURLFromSystemPath(path, url) ==
133 osl::FileBase::E_None)
135 m_cwdUrl = url;
137 break;
139 default:
140 throw CommandLineArgs::Supplier::Exception();
144 virtual boost::optional< OUString > getCwdUrl() override { return m_cwdUrl; }
146 virtual bool next(OUString * argument) override { return next(argument, true); }
148 private:
149 bool next(OUString * argument, bool prefix) {
150 OSL_ASSERT(argument != nullptr);
151 if (m_index < m_input.getLength()) {
152 if (prefix) {
153 if (m_input[m_index] != ',') {
154 throw CommandLineArgs::Supplier::Exception();
156 ++m_index;
158 OStringBuffer b;
159 while (m_index < m_input.getLength()) {
160 char c = m_input[m_index];
161 if (c == ',') {
162 break;
164 ++m_index;
165 if (c == '\\') {
166 if (m_index >= m_input.getLength())
167 throw CommandLineArgs::Supplier::Exception();
168 c = m_input[m_index++];
169 switch (c) {
170 case '0':
171 c = '\0';
172 break;
173 case ',':
174 case '\\':
175 break;
176 default:
177 throw CommandLineArgs::Supplier::Exception();
180 b.append(c);
182 OString b2(b.makeStringAndClear());
183 if (!rtl_convertStringToUString(
184 &argument->pData, b2.getStr(), b2.getLength(),
185 RTL_TEXTENCODING_UTF8,
186 (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR |
187 RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR |
188 RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR)))
190 throw CommandLineArgs::Supplier::Exception();
192 return true;
193 } else {
194 return false;
198 boost::optional< OUString > m_cwdUrl;
199 OString m_input;
200 sal_Int32 m_index;
203 bool addArgument(OStringBuffer &rArguments, char prefix,
204 const OUString &rArgument)
206 OString utf8;
207 if (!rArgument.convertToString(
208 &utf8, RTL_TEXTENCODING_UTF8,
209 (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR |
210 RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR)))
212 return false;
214 rArguments.append(prefix);
215 for (sal_Int32 i = 0; i < utf8.getLength(); ++i) {
216 char c = utf8[i];
217 switch (c) {
218 case '\0':
219 rArguments.append("\\0");
220 break;
221 case ',':
222 rArguments.append("\\,");
223 break;
224 case '\\':
225 rArguments.append("\\\\");
226 break;
227 default:
228 rArguments.append(c);
229 break;
232 return true;
237 rtl::Reference< RequestHandler > RequestHandler::pGlobal;
239 // Turns a string in aMsg such as file:///home/foo/.libreoffice/3
240 // Into a hex string of well known length ff132a86...
241 static OUString CreateMD5FromString( const OUString& aMsg )
243 SAL_INFO("desktop.app", "create md5 from '" << aMsg << "'");
245 rtlDigest handle = rtl_digest_create( rtl_Digest_AlgorithmMD5 );
246 if ( handle )
248 const sal_uInt8* pData = reinterpret_cast<const sal_uInt8*>(aMsg.getStr());
249 sal_uInt32 nSize = aMsg.getLength() * sizeof( sal_Unicode );
250 sal_uInt32 nMD5KeyLen = rtl_digest_queryLength( handle );
251 std::unique_ptr<sal_uInt8[]> pMD5KeyBuffer(new sal_uInt8[ nMD5KeyLen ]);
253 rtl_digest_init( handle, pData, nSize );
254 rtl_digest_update( handle, pData, nSize );
255 rtl_digest_get( handle, pMD5KeyBuffer.get(), nMD5KeyLen );
256 rtl_digest_destroy( handle );
258 // Create hex-value string from the MD5 value to keep the string size minimal
259 OUStringBuffer aBuffer( nMD5KeyLen * 2 + 1 );
260 for ( sal_uInt32 i = 0; i < nMD5KeyLen; i++ )
261 aBuffer.append( static_cast<sal_Int32>(pMD5KeyBuffer[i]), 16 );
263 return aBuffer.makeStringAndClear();
266 return OUString();
269 class ProcessEventsClass_Impl
271 public:
272 DECL_STATIC_LINK( ProcessEventsClass_Impl, CallEvent, void*, void );
273 DECL_STATIC_LINK( ProcessEventsClass_Impl, ProcessDocumentsEvent, void*, void );
276 IMPL_STATIC_LINK( ProcessEventsClass_Impl, CallEvent, void*, pEvent, void )
278 // Application events are processed by the Desktop::HandleAppEvent implementation.
279 Desktop::HandleAppEvent( *static_cast<ApplicationEvent*>(pEvent) );
280 delete static_cast<ApplicationEvent*>(pEvent);
283 IMPL_STATIC_LINK( ProcessEventsClass_Impl, ProcessDocumentsEvent, void*, pEvent, void )
285 // Documents requests are processed by the RequestHandler implementation
286 ProcessDocumentsRequest* pDocsRequest = static_cast<ProcessDocumentsRequest*>(pEvent);
287 RequestHandler::ExecuteCmdLineRequests(*pDocsRequest, false);
288 delete pDocsRequest;
291 static void ImplPostForeignAppEvent( ApplicationEvent* pEvent )
293 Application::PostUserEvent( LINK( nullptr, ProcessEventsClass_Impl, CallEvent ), pEvent );
296 static void ImplPostProcessDocumentsEvent( std::unique_ptr<ProcessDocumentsRequest> pEvent )
298 Application::PostUserEvent( LINK( nullptr, ProcessEventsClass_Impl, ProcessDocumentsEvent ), pEvent.release() );
301 oslSignalAction SalMainPipeExchangeSignal_impl(SAL_UNUSED_PARAMETER void* /*pData*/, oslSignalInfo* pInfo)
303 if( pInfo->Signal == osl_Signal_Terminate )
304 RequestHandler::SetDowning();
305 return osl_Signal_ActCallNextHdl;
309 // The RequestHandlerController implementation is a bookkeeper for all pending requests
310 // that were created by the RequestHandler. The requests are waiting to be processed by
311 // our framework loadComponentFromURL function (e.g. open/print request).
312 // During shutdown the framework is asking RequestHandlerController about pending requests.
313 // If there are pending requests framework has to stop the shutdown process. It is waiting
314 // for these requests because framework is not able to handle shutdown and open a document
315 // concurrently.
318 // XServiceInfo
319 OUString SAL_CALL RequestHandlerController::getImplementationName()
321 return "com.sun.star.comp.RequestHandlerController";
324 sal_Bool RequestHandlerController::supportsService(
325 OUString const & ServiceName)
327 return cppu::supportsService(this, ServiceName);
330 Sequence< OUString > SAL_CALL RequestHandlerController::getSupportedServiceNames()
332 return { };
335 // XEventListener
336 void SAL_CALL RequestHandlerController::disposing( const EventObject& )
340 // XTerminateListener
341 void SAL_CALL RequestHandlerController::queryTermination( const EventObject& )
343 // Desktop ask about pending request through our office ipc pipe. We have to
344 // be sure that no pending request is waiting because framework is not able to
345 // handle shutdown and open a document concurrently.
347 if ( RequestHandler::AreRequestsPending() )
348 throw TerminationVetoException();
349 RequestHandler::SetDowning();
352 void SAL_CALL RequestHandlerController::notifyTermination( const EventObject& )
356 class IpcThread: public salhelper::Thread {
357 public:
358 void start(RequestHandler * handler) {
359 m_handler = handler;
360 launch();
363 virtual void close() = 0;
365 protected:
366 explicit IpcThread(char const * name): Thread(name), m_handler(nullptr) {}
368 virtual ~IpcThread() override {}
370 bool process(OString const & arguments, bool * waitProcessed);
372 RequestHandler * m_handler;
375 class PipeIpcThread: public IpcThread {
376 public:
377 static RequestHandler::Status enable(rtl::Reference<IpcThread> * thread);
379 private:
380 explicit PipeIpcThread(osl::Pipe const & pipe):
381 IpcThread("PipeIPC"), pipe_(pipe)
384 virtual ~PipeIpcThread() override {}
386 void execute() override;
388 void close() override { pipe_.close(); }
390 osl::Pipe pipe_;
393 #if ENABLE_DBUS
395 namespace {
397 struct DbusConnectionHolder {
398 explicit DbusConnectionHolder(DBusConnection * theConnection):
399 connection(theConnection)
402 DbusConnectionHolder(DbusConnectionHolder && other): connection(nullptr)
403 { std::swap(connection, other.connection); }
405 ~DbusConnectionHolder() {
406 if (connection != nullptr) {
407 dbus_connection_close(connection);
408 dbus_connection_unref(connection);
412 DBusConnection * connection;
415 struct DbusMessageHolder {
416 explicit DbusMessageHolder(DBusMessage * theMessage): message(theMessage) {}
418 ~DbusMessageHolder() { clear(); }
420 void clear() {
421 if (message != nullptr) {
422 dbus_message_unref(message);
424 message = nullptr;
427 DBusMessage * message;
429 private:
430 DbusMessageHolder(DbusMessageHolder const &) = delete;
431 DbusMessageHolder& operator =(DbusMessageHolder const &) = delete;
436 class DbusIpcThread: public IpcThread {
437 public:
438 static RequestHandler::Status enable(rtl::Reference<IpcThread> * thread);
440 private:
441 explicit DbusIpcThread(DbusConnectionHolder && connection):
442 IpcThread("DbusIPC"), connection_(std::move(connection))
445 virtual ~DbusIpcThread() override {}
447 void execute() override;
449 void close() override;
451 DbusConnectionHolder connection_;
454 RequestHandler::Status DbusIpcThread::enable(rtl::Reference<IpcThread> * thread)
456 assert(thread != nullptr);
457 if (!dbus_threads_init_default()) {
458 SAL_WARN("desktop.app", "dbus_threads_init_default failed");
459 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
461 DBusError e;
462 dbus_error_init(&e);
463 DbusConnectionHolder con(dbus_bus_get_private(DBUS_BUS_SESSION, &e));
464 assert((con.connection == nullptr) == bool(dbus_error_is_set(&e)));
465 if (con.connection == nullptr) {
466 SAL_WARN(
467 "desktop.app",
468 "dbus_bus_get_private failed with: " << e.name << ": "
469 << e.message);
470 dbus_error_free(&e);
471 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
473 for (;;) {
474 int n = dbus_bus_request_name(
475 con.connection, "org.libreoffice.LibreOfficeIpc0",
476 DBUS_NAME_FLAG_DO_NOT_QUEUE, &e);
477 assert((n == -1) == bool(dbus_error_is_set(&e)));
478 switch (n) {
479 case -1:
480 SAL_WARN(
481 "desktop.app",
482 "dbus_bus_request_name failed with: " << e.name << ": "
483 << e.message);
484 dbus_error_free(&e);
485 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
486 case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
487 *thread = new DbusIpcThread(std::move(con));
488 return RequestHandler::IPC_STATUS_OK;
489 case DBUS_REQUEST_NAME_REPLY_EXISTS:
491 OStringBuffer buf(ARGUMENT_PREFIX);
492 OUString arg;
493 if (!(utl::Bootstrap::getProcessWorkingDir(arg)
494 && addArgument(buf, '1', arg)))
496 buf.append('0');
498 sal_uInt32 narg = rtl_getAppCommandArgCount();
499 for (sal_uInt32 i = 0; i != narg; ++i) {
500 rtl_getAppCommandArg(i, &arg.pData);
501 if (!addArgument(buf, ',', arg)) {
502 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
505 char const * argstr = buf.getStr();
506 DbusMessageHolder msg(
507 dbus_message_new_method_call(
508 "org.libreoffice.LibreOfficeIpc0",
509 "/org/libreoffice/LibreOfficeIpc0",
510 "org.libreoffice.LibreOfficeIpcIfc0", "Execute"));
511 if (msg.message == nullptr) {
512 SAL_WARN(
513 "desktop.app", "dbus_message_new_method_call failed");
514 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
516 DBusMessageIter it;
517 dbus_message_iter_init_append(msg.message, &it);
518 if (!dbus_message_iter_append_basic(
519 &it, DBUS_TYPE_STRING, &argstr))
521 SAL_WARN(
522 "desktop.app", "dbus_message_iter_append_basic failed");
523 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
525 DbusMessageHolder repl(
526 dbus_connection_send_with_reply_and_block(
527 con.connection, msg.message, 0x7FFFFFFF, &e));
528 assert(
529 (repl.message == nullptr) == bool(dbus_error_is_set(&e)));
530 if (repl.message == nullptr) {
531 SAL_INFO(
532 "desktop.app",
533 "dbus_connection_send_with_reply_and_block failed"
534 " with: " << e.name << ": " << e.message);
535 dbus_error_free(&e);
536 break;
538 return RequestHandler::IPC_STATUS_2ND_OFFICE;
540 case DBUS_REQUEST_NAME_REPLY_IN_QUEUE:
541 case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER:
542 SAL_WARN(
543 "desktop.app",
544 "dbus_bus_request_name failed with unexpected " << +n);
545 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
546 default:
547 for (;;) std::abort();
552 void DbusIpcThread::execute()
554 assert(m_handler != nullptr);
555 m_handler->cReady.wait();
556 for (;;) {
558 osl::MutexGuard g(RequestHandler::GetMutex());
559 if (m_handler->mState == RequestHandler::State::Downing) {
560 break;
563 if (!dbus_connection_read_write(connection_.connection, -1)) {
564 break;
566 for (;;) {
567 DbusMessageHolder msg(
568 dbus_connection_pop_message(connection_.connection));
569 if (msg.message == nullptr) {
570 break;
572 if (!dbus_message_is_method_call(
573 msg.message, "org.libreoffice.LibreOfficeIpcIfc0",
574 "Execute"))
576 SAL_INFO("desktop.app", "unknown DBus message ignored");
577 continue;
579 DBusMessageIter it;
580 if (!dbus_message_iter_init(msg.message, &it)) {
581 SAL_WARN(
582 "desktop.app", "DBus message without argument ignored");
583 continue;
585 if (dbus_message_iter_get_arg_type(&it) != DBUS_TYPE_STRING) {
586 SAL_WARN(
587 "desktop.app",
588 "DBus message with non-string argument ignored");
589 continue;
591 char const * argstr;
592 dbus_message_iter_get_basic(&it, &argstr);
593 bool waitProcessed = false;
595 osl::MutexGuard g(RequestHandler::GetMutex());
596 if (!process(argstr, &waitProcessed)) {
597 continue;
600 if (waitProcessed) {
601 m_handler->cProcessed.wait();
603 DbusMessageHolder repl(dbus_message_new_method_return(msg.message));
604 if (repl.message == nullptr) {
605 SAL_WARN(
606 "desktop.app", "dbus_message_new_method_return failed");
607 continue;
609 dbus_uint32_t serial = 0;
610 if (!dbus_connection_send(
611 connection_.connection, repl.message, &serial)) {
612 SAL_WARN("desktop.app", "dbus_connection_send failed");
613 continue;
615 dbus_connection_flush(connection_.connection);
620 void DbusIpcThread::close() {
621 assert(connection_.connection != nullptr);
622 // Make dbus_connection_read_write fall out of internal poll call blocking
623 // on POLLIN:
624 int fd;
625 if (!dbus_connection_get_socket(connection_.connection, &fd)) {
626 SAL_WARN("desktop.app", "dbus_connection_get_socket failed");
627 return;
629 if (shutdown(fd, SHUT_RD) == -1) {
630 auto const e = errno;
631 SAL_WARN("desktop.app", "shutdown failed with errno " << e);
635 #endif
637 namespace
639 class theRequestHandlerMutex
640 : public rtl::Static<osl::Mutex, theRequestHandlerMutex> {};
643 ::osl::Mutex& RequestHandler::GetMutex()
645 return theRequestHandlerMutex::get();
648 void RequestHandler::SetDowning()
650 // We have the order to block all incoming requests. Framework
651 // wants to shutdown and we have to make sure that no loading/printing
652 // requests are executed anymore.
653 ::osl::MutexGuard aGuard( GetMutex() );
655 if ( pGlobal.is() )
656 pGlobal->mState = State::Downing;
659 void RequestHandler::EnableRequests()
661 // switch between just queueing the requests and executing them
662 ::osl::MutexGuard aGuard( GetMutex() );
664 if ( pGlobal.is() )
666 if (pGlobal->mState != State::Downing) {
667 pGlobal->mState = State::RequestsEnabled;
669 // hit the compiler over the head
670 ProcessDocumentsRequest aEmptyReq { boost::optional< OUString >() };
671 // trigger already queued requests
672 RequestHandler::ExecuteCmdLineRequests(aEmptyReq, true);
676 bool RequestHandler::AreRequestsPending()
678 // Give info about pending requests
679 ::osl::MutexGuard aGuard( GetMutex() );
680 if ( pGlobal.is() )
681 return ( pGlobal->mnPendingRequests > 0 );
682 else
683 return false;
686 void RequestHandler::RequestsCompleted()
688 // Remove nCount pending requests from our internal counter
689 ::osl::MutexGuard aGuard( GetMutex() );
690 if ( pGlobal.is() )
692 if ( pGlobal->mnPendingRequests > 0 )
693 pGlobal->mnPendingRequests --;
697 RequestHandler::Status RequestHandler::Enable(bool ipc)
699 ::osl::MutexGuard aGuard( GetMutex() );
701 if( pGlobal.is() )
702 return IPC_STATUS_OK;
704 #if !HAVE_FEATURE_DESKTOP || HAVE_FEATURE_MACOSX_SANDBOX
705 ipc = false;
706 #endif
708 if (!ipc) {
709 pGlobal = new RequestHandler;
710 return IPC_STATUS_OK;
713 enum class Kind { Pipe, Dbus };
714 Kind kind;
715 #if ENABLE_DBUS
716 kind = std::getenv("LIBO_FLATPAK") != nullptr ? Kind::Dbus : Kind::Pipe;
717 #else
718 kind = Kind::Pipe;
719 #endif
720 rtl::Reference<IpcThread> thread;
721 Status stat = Status(); // silence bogus potentially-uninitialized warnings
722 switch (kind) {
723 case Kind::Pipe:
724 stat = PipeIpcThread::enable(&thread);
725 break;
726 case Kind::Dbus:
727 #if ENABLE_DBUS
728 stat = DbusIpcThread::enable(&thread);
729 break;
730 #endif
731 default:
732 assert(false);
734 assert(thread.is() == (stat == IPC_STATUS_OK));
735 if (stat == IPC_STATUS_OK) {
736 pGlobal = new RequestHandler;
737 pGlobal->mIpcThread = thread;
738 pGlobal->mIpcThread->start(pGlobal.get());
740 return stat;
743 RequestHandler::Status PipeIpcThread::enable(rtl::Reference<IpcThread> * thread)
745 assert(thread != nullptr);
747 // The name of the named pipe is created with the hashcode of the user installation directory (without /user). We have to retrieve
748 // this information from a unotools implementation.
749 OUString aUserInstallPath;
750 ::utl::Bootstrap::PathStatus aLocateResult = ::utl::Bootstrap::locateUserInstallation( aUserInstallPath );
751 if (aLocateResult != utl::Bootstrap::PATH_EXISTS
752 && aLocateResult != utl::Bootstrap::PATH_VALID)
754 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
757 // Try to determine if we are the first office or not! This should prevent multiple
758 // access to the user directory !
759 // First we try to create our pipe if this fails we try to connect. We have to do this
760 // in a loop because the other office can crash or shutdown between createPipe
761 // and connectPipe!!
762 auto aUserInstallPathHashCode = CreateMD5FromString(aUserInstallPath);
764 // Check result to create a hash code from the user install path
765 if ( aUserInstallPathHashCode.isEmpty() )
766 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR; // Something completely broken, we cannot create a valid hash code!
768 osl::Pipe pipe;
769 enum PipeMode
771 PIPEMODE_DONTKNOW,
772 PIPEMODE_CREATED,
773 PIPEMODE_CONNECTED
775 PipeMode nPipeMode = PIPEMODE_DONTKNOW;
777 OUString aPipeIdent( "SingleOfficeIPC_" + aUserInstallPathHashCode );
780 osl::Security security;
782 // Try to create pipe
783 if ( pipe.create( aPipeIdent, osl_Pipe_CREATE, security ))
785 // Pipe created
786 nPipeMode = PIPEMODE_CREATED;
788 else if( pipe.create( aPipeIdent, osl_Pipe_OPEN, security )) // Creation not successful, now we try to connect
790 osl::StreamPipe aStreamPipe(pipe.getHandle());
791 if (readStringFromPipe(aStreamPipe) == SEND_ARGUMENTS)
793 // Pipe connected to first office
794 nPipeMode = PIPEMODE_CONNECTED;
796 else
798 // Pipe connection failed (other office exited or crashed)
799 TimeValue tval;
800 tval.Seconds = 0;
801 tval.Nanosec = 500000000;
802 salhelper::Thread::wait( tval );
805 else
807 oslPipeError eReason = pipe.getError();
808 if ((eReason == osl_Pipe_E_ConnectionRefused) || (eReason == osl_Pipe_E_invalidError))
809 return RequestHandler::IPC_STATUS_PIPE_ERROR;
811 // Wait for second office to be ready
812 TimeValue aTimeValue;
813 aTimeValue.Seconds = 0;
814 aTimeValue.Nanosec = 10000000; // 10ms
815 salhelper::Thread::wait( aTimeValue );
818 } while ( nPipeMode == PIPEMODE_DONTKNOW );
820 if ( nPipeMode == PIPEMODE_CREATED )
822 // Seems we are the one and only, so create listening thread
823 *thread = new PipeIpcThread(pipe);
824 return RequestHandler::IPC_STATUS_OK;
826 else
828 // Seems another office is running. Pipe arguments to it and self terminate
829 osl::StreamPipe aStreamPipe(pipe.getHandle());
831 OStringBuffer aArguments(ARGUMENT_PREFIX);
832 OUString cwdUrl;
833 if (!(utl::Bootstrap::getProcessWorkingDir(cwdUrl) &&
834 addArgument(aArguments, '1', cwdUrl)))
836 aArguments.append('0');
838 sal_uInt32 nCount = rtl_getAppCommandArgCount();
839 for( sal_uInt32 i=0; i < nCount; i++ )
841 rtl_getAppCommandArg( i, &aUserInstallPath.pData );
842 if (!addArgument(aArguments, ',', aUserInstallPath)) {
843 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
846 aArguments.append('\0');
847 // finally, write the string onto the pipe
848 SAL_INFO("desktop.app", "writing <" << aArguments.getStr() << ">");
849 sal_Int32 n = aStreamPipe.write(
850 aArguments.getStr(), aArguments.getLength());
851 if (n != aArguments.getLength()) {
852 SAL_INFO("desktop.app", "short write: " << n);
853 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
856 if (readStringFromPipe(aStreamPipe) != PROCESSING_DONE)
858 // something went wrong
859 return RequestHandler::IPC_STATUS_BOOTSTRAP_ERROR;
862 return RequestHandler::IPC_STATUS_2ND_OFFICE;
866 void RequestHandler::Disable()
868 osl::ClearableMutexGuard aMutex( GetMutex() );
870 if( pGlobal.is() )
872 rtl::Reference< RequestHandler > handler(pGlobal);
873 pGlobal.clear();
875 handler->mState = State::Downing;
876 if (handler->mIpcThread.is()) {
877 handler->mIpcThread->close();
880 // release mutex to avoid deadlocks
881 aMutex.clear();
883 handler->cReady.set();
885 // exit gracefully and join
886 if (handler->mIpcThread.is())
888 handler->mIpcThread->join();
889 handler->mIpcThread.clear();
892 handler->cReady.reset();
896 RequestHandler::RequestHandler() :
897 mState( State::Starting ),
898 mnPendingRequests( 0 )
902 RequestHandler::~RequestHandler()
904 assert(!mIpcThread.is());
907 void RequestHandler::SetReady(bool bIsReady)
909 osl::MutexGuard g(GetMutex());
910 if (pGlobal.is())
912 if (bIsReady)
913 pGlobal->cReady.set();
914 else
915 pGlobal->cReady.reset();
919 void RequestHandler::WaitForReady()
921 rtl::Reference<RequestHandler> t;
923 osl::MutexGuard g(GetMutex());
924 t = pGlobal;
926 if (t.is())
928 t->cReady.wait();
932 bool IpcThread::process(OString const & arguments, bool * waitProcessed) {
933 assert(waitProcessed != nullptr);
935 std::unique_ptr< CommandLineArgs > aCmdLineArgs;
938 Parser p(arguments);
939 aCmdLineArgs.reset( new CommandLineArgs( p ) );
941 catch ( const CommandLineArgs::Supplier::Exception & )
943 SAL_WARN("desktop.app", "Error in received command line arguments");
944 return false;
947 bool bDocRequestSent = false;
949 OUString aUnknown( aCmdLineArgs->GetUnknown() );
950 if (aUnknown.isEmpty() && !aCmdLineArgs->IsHelp() && !aCmdLineArgs->IsVersion())
952 const CommandLineArgs &rCurrentCmdLineArgs = Desktop::GetCommandLineArgs();
954 if ( aCmdLineArgs->IsQuickstart() )
956 // we have to use application event, because we have to start quickstart service in main thread!!
957 ApplicationEvent* pAppEvent =
958 new ApplicationEvent(ApplicationEvent::Type::QuickStart);
959 ImplPostForeignAppEvent( pAppEvent );
962 // handle request for acceptor
963 std::vector< OUString > const & accept = aCmdLineArgs->GetAccept();
964 for (auto const& elem : accept)
966 ApplicationEvent* pAppEvent = new ApplicationEvent(
967 ApplicationEvent::Type::Accept, elem);
968 ImplPostForeignAppEvent( pAppEvent );
970 // handle acceptor removal
971 std::vector< OUString > const & unaccept = aCmdLineArgs->GetUnaccept();
972 for (auto const& elem : unaccept)
974 ApplicationEvent* pAppEvent = new ApplicationEvent(
975 ApplicationEvent::Type::Unaccept, elem);
976 ImplPostForeignAppEvent( pAppEvent );
979 std::unique_ptr<ProcessDocumentsRequest> pRequest(new ProcessDocumentsRequest(
980 aCmdLineArgs->getCwdUrl()));
981 m_handler->cProcessed.reset();
982 pRequest->pcProcessed = &m_handler->cProcessed;
983 m_handler->mbSuccess = false;
984 pRequest->mpbSuccess = &m_handler->mbSuccess;
986 // Print requests are not dependent on the --invisible cmdline argument as they are
987 // loaded with the "hidden" flag! So they are always checked.
988 pRequest->aPrintList = aCmdLineArgs->GetPrintList();
989 bDocRequestSent |= !pRequest->aPrintList.empty();
990 pRequest->aPrintToList = aCmdLineArgs->GetPrintToList();
991 pRequest->aPrinterName = aCmdLineArgs->GetPrinterName();
992 bDocRequestSent |= !( pRequest->aPrintToList.empty() || pRequest->aPrinterName.isEmpty() );
993 pRequest->aConversionList = aCmdLineArgs->GetConversionList();
994 pRequest->aConversionParams = aCmdLineArgs->GetConversionParams();
995 pRequest->aConversionOut = aCmdLineArgs->GetConversionOut();
996 pRequest->aImageConversionType = aCmdLineArgs->GetImageConversionType();
997 pRequest->aInFilter = aCmdLineArgs->GetInFilter();
998 pRequest->bTextCat = aCmdLineArgs->IsTextCat();
999 pRequest->bScriptCat = aCmdLineArgs->IsScriptCat();
1000 bDocRequestSent |= !pRequest->aConversionList.empty();
1002 if ( !rCurrentCmdLineArgs.IsInvisible() )
1004 // Read cmdline args that can open/create documents. As they would open a window
1005 // they are only allowed if the "--invisible" is currently not used!
1006 pRequest->aOpenList = aCmdLineArgs->GetOpenList();
1007 bDocRequestSent |= !pRequest->aOpenList.empty();
1008 pRequest->aViewList = aCmdLineArgs->GetViewList();
1009 bDocRequestSent |= !pRequest->aViewList.empty();
1010 pRequest->aStartList = aCmdLineArgs->GetStartList();
1011 bDocRequestSent |= !pRequest->aStartList.empty();
1012 pRequest->aForceOpenList = aCmdLineArgs->GetForceOpenList();
1013 bDocRequestSent |= !pRequest->aForceOpenList.empty();
1014 pRequest->aForceNewList = aCmdLineArgs->GetForceNewList();
1015 bDocRequestSent |= !pRequest->aForceNewList.empty();
1017 // Special command line args to create an empty document for a given module
1019 // #i18338# (lo)
1020 // we only do this if no document was specified on the command line,
1021 // since this would be inconsistent with the behaviour of
1022 // the first process, see OpenClients() (call to OpenDefault()) in app.cxx
1023 if ( aCmdLineArgs->HasModuleParam() && !bDocRequestSent )
1025 SvtModuleOptions aOpt;
1026 SvtModuleOptions::EFactory eFactory = SvtModuleOptions::EFactory::WRITER;
1027 if ( aCmdLineArgs->IsWriter() )
1028 eFactory = SvtModuleOptions::EFactory::WRITER;
1029 else if ( aCmdLineArgs->IsCalc() )
1030 eFactory = SvtModuleOptions::EFactory::CALC;
1031 else if ( aCmdLineArgs->IsDraw() )
1032 eFactory = SvtModuleOptions::EFactory::DRAW;
1033 else if ( aCmdLineArgs->IsImpress() )
1034 eFactory = SvtModuleOptions::EFactory::IMPRESS;
1035 else if ( aCmdLineArgs->IsBase() )
1036 eFactory = SvtModuleOptions::EFactory::DATABASE;
1037 else if ( aCmdLineArgs->IsMath() )
1038 eFactory = SvtModuleOptions::EFactory::MATH;
1039 else if ( aCmdLineArgs->IsGlobal() )
1040 eFactory = SvtModuleOptions::EFactory::WRITERGLOBAL;
1041 else if ( aCmdLineArgs->IsWeb() )
1042 eFactory = SvtModuleOptions::EFactory::WRITERWEB;
1044 if ( !pRequest->aOpenList.empty() )
1045 pRequest->aModule = aOpt.GetFactoryName( eFactory );
1046 else
1047 pRequest->aOpenList.push_back( aOpt.GetFactoryEmptyDocumentURL( eFactory ) );
1048 bDocRequestSent = true;
1052 if ( !aCmdLineArgs->IsQuickstart() ) {
1053 bool bShowHelp = false;
1054 OUStringBuffer aHelpURLBuffer;
1055 if (aCmdLineArgs->IsHelpWriter()) {
1056 bShowHelp = true;
1057 aHelpURLBuffer.append("vnd.sun.star.help://swriter/start");
1058 } else if (aCmdLineArgs->IsHelpCalc()) {
1059 bShowHelp = true;
1060 aHelpURLBuffer.append("vnd.sun.star.help://scalc/start");
1061 } else if (aCmdLineArgs->IsHelpDraw()) {
1062 bShowHelp = true;
1063 aHelpURLBuffer.append("vnd.sun.star.help://sdraw/start");
1064 } else if (aCmdLineArgs->IsHelpImpress()) {
1065 bShowHelp = true;
1066 aHelpURLBuffer.append("vnd.sun.star.help://simpress/start");
1067 } else if (aCmdLineArgs->IsHelpBase()) {
1068 bShowHelp = true;
1069 aHelpURLBuffer.append("vnd.sun.star.help://sdatabase/start");
1070 } else if (aCmdLineArgs->IsHelpBasic()) {
1071 bShowHelp = true;
1072 aHelpURLBuffer.append("vnd.sun.star.help://sbasic/start");
1073 } else if (aCmdLineArgs->IsHelpMath()) {
1074 bShowHelp = true;
1075 aHelpURLBuffer.append("vnd.sun.star.help://smath/start");
1077 if (bShowHelp) {
1078 aHelpURLBuffer.append("?Language=");
1079 aHelpURLBuffer.append(utl::ConfigManager::getUILocale());
1080 #if defined UNX
1081 aHelpURLBuffer.append("&System=UNX");
1082 #elif defined WNT
1083 aHelpURLBuffer.append("&System=WIN");
1084 #endif
1085 ApplicationEvent* pAppEvent = new ApplicationEvent(
1086 ApplicationEvent::Type::OpenHelpUrl,
1087 aHelpURLBuffer.makeStringAndClear());
1088 ImplPostForeignAppEvent( pAppEvent );
1092 if ( bDocRequestSent )
1094 // Send requests to dispatch watcher if we have at least one. The receiver
1095 // is responsible to delete the request after processing it.
1096 if ( aCmdLineArgs->HasModuleParam() )
1098 SvtModuleOptions aOpt;
1100 // Support command line parameters to start a module (as preselection)
1101 if ( aCmdLineArgs->IsWriter() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER ) )
1102 pRequest->aModule = aOpt.GetFactoryName( SvtModuleOptions::EFactory::WRITER );
1103 else if ( aCmdLineArgs->IsCalc() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::CALC ) )
1104 pRequest->aModule = aOpt.GetFactoryName( SvtModuleOptions::EFactory::CALC );
1105 else if ( aCmdLineArgs->IsImpress() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::IMPRESS ) )
1106 pRequest->aModule= aOpt.GetFactoryName( SvtModuleOptions::EFactory::IMPRESS );
1107 else if ( aCmdLineArgs->IsDraw() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::DRAW ) )
1108 pRequest->aModule= aOpt.GetFactoryName( SvtModuleOptions::EFactory::DRAW );
1111 ImplPostProcessDocumentsEvent( std::move(pRequest) );
1113 else
1115 // delete not used request again
1116 pRequest.reset();
1118 if (aCmdLineArgs->IsEmpty())
1120 // no document was sent, just bring Office to front
1121 ApplicationEvent* pAppEvent =
1122 new ApplicationEvent(ApplicationEvent::Type::Appear);
1123 ImplPostForeignAppEvent( pAppEvent );
1126 *waitProcessed = bDocRequestSent;
1127 return true;
1130 void PipeIpcThread::execute()
1132 assert(m_handler != nullptr);
1135 osl::StreamPipe aStreamPipe;
1136 oslPipeError nError = pipe_.accept( aStreamPipe );
1139 if( nError == osl_Pipe_E_None )
1141 // if we receive a request while the office is displaying some dialog or error during
1142 // bootstrap, that dialogs event loop might get events that are dispatched by this thread
1143 // we have to wait for cReady to be set by the real main loop.
1144 // only requests that don't dispatch events may be processed before cReady is set.
1145 m_handler->cReady.wait();
1147 // we might have decided to shutdown while we were sleeping
1148 if (!RequestHandler::pGlobal.is()) return;
1150 // only lock the mutex when processing starts, otherwise we deadlock when the office goes
1151 // down during wait
1152 osl::ClearableMutexGuard aGuard( RequestHandler::GetMutex() );
1154 if (m_handler->mState == RequestHandler::State::Downing)
1156 break;
1159 // notify client we're ready to process its args:
1160 SAL_INFO("desktop.app", "writing <" << SEND_ARGUMENTS << ">");
1161 sal_Int32 n = aStreamPipe.write(
1162 SEND_ARGUMENTS, SAL_N_ELEMENTS(SEND_ARGUMENTS));
1163 // incl. terminating NUL
1164 if (n != SAL_N_ELEMENTS(SEND_ARGUMENTS)) {
1165 SAL_WARN("desktop.app", "short write: " << n);
1166 continue;
1169 OString aArguments = readStringFromPipe(aStreamPipe);
1171 // Is this a lookup message from another application? if so, ignore
1172 if (aArguments.isEmpty())
1173 continue;
1175 bool waitProcessed = false;
1176 if (!process(aArguments, &waitProcessed)) {
1177 continue;
1180 // we don't need the mutex any longer...
1181 aGuard.clear();
1182 bool bSuccess = true;
1183 // wait for processing to finish
1184 if (waitProcessed)
1186 m_handler->cProcessed.wait();
1187 bSuccess = m_handler->mbSuccess;
1189 if (bSuccess)
1191 // processing finished, inform the requesting end:
1192 SAL_INFO("desktop.app", "writing <" << PROCESSING_DONE << ">");
1193 n = aStreamPipe.write(PROCESSING_DONE, SAL_N_ELEMENTS(PROCESSING_DONE));
1194 // incl. terminating NUL
1195 if (n != SAL_N_ELEMENTS(PROCESSING_DONE))
1197 SAL_WARN("desktop.app", "short write: " << n);
1198 continue;
1202 else
1205 osl::MutexGuard aGuard( RequestHandler::GetMutex() );
1206 if (m_handler->mState == RequestHandler::State::Downing)
1208 break;
1212 SAL_WARN( "desktop.app", "Error on accept: " << static_cast<int>(nError));
1213 TimeValue tval;
1214 tval.Seconds = 1;
1215 tval.Nanosec = 0;
1216 salhelper::Thread::wait( tval );
1218 } while( schedule() );
1221 static void AddToDispatchList(
1222 std::vector<DispatchWatcher::DispatchRequest>& rDispatchList,
1223 boost::optional< OUString > const & cwdUrl,
1224 std::vector< OUString > const & aRequestList,
1225 DispatchWatcher::RequestType nType,
1226 const OUString& aParam,
1227 const OUString& aFactory )
1229 for (auto const& request : aRequestList)
1231 rDispatchList.push_back({nType, request, cwdUrl, aParam, aFactory});
1235 static void AddConversionsToDispatchList(
1236 std::vector<DispatchWatcher::DispatchRequest>& rDispatchList,
1237 boost::optional< OUString > const & cwdUrl,
1238 std::vector< OUString > const & rRequestList,
1239 const OUString& rParam,
1240 const OUString& rPrinterName,
1241 const OUString& rFactory,
1242 const OUString& rParamOut,
1243 const OUString& rImgOut,
1244 const bool isTextCat,
1245 const bool isScriptCat )
1247 DispatchWatcher::RequestType nType;
1248 OUString aParam( rParam );
1250 if( !rParam.isEmpty() )
1252 if ( isTextCat )
1253 nType = DispatchWatcher::REQUEST_CAT;
1254 else
1255 nType = DispatchWatcher::REQUEST_CONVERSION;
1256 aParam = rParam;
1258 else
1260 if ( isScriptCat )
1261 nType = DispatchWatcher::REQUEST_SCRIPT_CAT;
1262 else
1264 nType = DispatchWatcher::REQUEST_BATCHPRINT;
1265 aParam = rPrinterName;
1269 OUString aOutDir( rParamOut.trim() );
1270 OUString aImgOut( rImgOut.trim() );
1271 OUString aPWD;
1272 if (cwdUrl)
1274 aPWD = *cwdUrl;
1276 else
1278 utl::Bootstrap::getProcessWorkingDir( aPWD );
1281 if( !::osl::FileBase::getAbsoluteFileURL( aPWD, rParamOut, aOutDir ) )
1282 ::osl::FileBase::getSystemPathFromFileURL( aOutDir, aOutDir );
1284 if( !rParamOut.trim().isEmpty() )
1286 aParam += ";" + aOutDir;
1288 else
1290 ::osl::FileBase::getSystemPathFromFileURL( aPWD, aPWD );
1291 aParam += ";" + aPWD;
1294 if( !rImgOut.trim().isEmpty() )
1295 aParam += "|" + aImgOut;
1297 for (auto const& request : rRequestList)
1299 rDispatchList.push_back({nType, request, cwdUrl, aParam, rFactory});
1303 struct ConditionSetGuard
1305 osl::Condition* m_pCondition;
1306 ConditionSetGuard(osl::Condition* pCondition) : m_pCondition(pCondition) {}
1307 ~ConditionSetGuard() { if (m_pCondition) m_pCondition->set(); }
1310 bool RequestHandler::ExecuteCmdLineRequests(
1311 ProcessDocumentsRequest& aRequest, bool noTerminate)
1313 // protect the dispatch list
1314 osl::ClearableMutexGuard aGuard( GetMutex() );
1316 // ensure that Processed flag (if exists) is signaled in any outcome
1317 ConditionSetGuard aSetGuard(aRequest.pcProcessed);
1319 static std::vector<DispatchWatcher::DispatchRequest> aDispatchList;
1321 // Create dispatch list for dispatch watcher
1322 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aInFilter, DispatchWatcher::REQUEST_INFILTER, "", aRequest.aModule );
1323 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aOpenList, DispatchWatcher::REQUEST_OPEN, "", aRequest.aModule );
1324 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aViewList, DispatchWatcher::REQUEST_VIEW, "", aRequest.aModule );
1325 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aStartList, DispatchWatcher::REQUEST_START, "", aRequest.aModule );
1326 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aPrintList, DispatchWatcher::REQUEST_PRINT, "", aRequest.aModule );
1327 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aPrintToList, DispatchWatcher::REQUEST_PRINTTO, aRequest.aPrinterName, aRequest.aModule );
1328 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aForceOpenList, DispatchWatcher::REQUEST_FORCEOPEN, "", aRequest.aModule );
1329 AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aForceNewList, DispatchWatcher::REQUEST_FORCENEW, "", aRequest.aModule );
1330 AddConversionsToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aConversionList, aRequest.aConversionParams, aRequest.aPrinterName, aRequest.aModule, aRequest.aConversionOut, aRequest.aImageConversionType, aRequest.bTextCat, aRequest.bScriptCat );
1331 bool bShutdown( false );
1333 if ( pGlobal.is() )
1335 if( ! pGlobal->AreRequestsEnabled() )
1337 // Either starting, or downing - do not process the request, just try to bring Office to front
1338 ApplicationEvent* pAppEvent =
1339 new ApplicationEvent(ApplicationEvent::Type::Appear);
1340 ImplPostForeignAppEvent(pAppEvent);
1341 return bShutdown;
1344 pGlobal->mnPendingRequests += aDispatchList.size();
1345 if ( !pGlobal->mpDispatchWatcher.is() )
1347 pGlobal->mpDispatchWatcher = new DispatchWatcher;
1349 rtl::Reference<DispatchWatcher> dispatchWatcher(
1350 pGlobal->mpDispatchWatcher);
1352 // copy for execute
1353 std::vector<DispatchWatcher::DispatchRequest> aTempList;
1354 aTempList.swap( aDispatchList );
1356 aGuard.clear();
1358 // Execute dispatch requests
1359 bShutdown = dispatchWatcher->executeDispatchRequests( aTempList, noTerminate);
1360 if (aRequest.mpbSuccess)
1361 *aRequest.mpbSuccess = true; // signal that we have actually succeeded
1364 return bShutdown;
1369 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */