Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
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/.
8 : *
9 : * This file incorporates work covered by the following license notice:
10 : *
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 .
18 : */
19 :
20 : #include <config_features.h>
21 : #include <config_version.h>
22 :
23 : #include "app.hxx"
24 : #include "officeipcthread.hxx"
25 : #include "cmdlineargs.hxx"
26 : #include "dispatchwatcher.hxx"
27 : #include <boost/scoped_ptr.hpp>
28 : #include <stdio.h>
29 : #include <osl/process.h>
30 : #include <sal/log.hxx>
31 : #include <unotools/bootstrap.hxx>
32 : #include <vcl/svapp.hxx>
33 : #include <vcl/help.hxx>
34 : #include <unotools/configmgr.hxx>
35 : #include <osl/thread.hxx>
36 : #include <rtl/digest.h>
37 : #include <rtl/ustrbuf.hxx>
38 : #include <rtl/instance.hxx>
39 : #include <osl/conditn.hxx>
40 : #include <unotools/moduleoptions.hxx>
41 : #include <rtl/bootstrap.hxx>
42 : #include <rtl/strbuf.hxx>
43 : #include <comphelper/lok.hxx>
44 : #include <comphelper/processfactory.hxx>
45 : #include <cppuhelper/supportsservice.hxx>
46 : #include <osl/file.hxx>
47 : #include <rtl/process.h>
48 : #include <tools/getprocessworkingdir.hxx>
49 : #include <boost/scoped_array.hpp>
50 :
51 : using namespace desktop;
52 : using namespace ::com::sun::star::uno;
53 : using namespace ::com::sun::star::lang;
54 : using namespace ::com::sun::star::frame;
55 :
56 : namespace {
57 :
58 : #if HAVE_FEATURE_DESKTOP || defined(ANDROID)
59 :
60 : static char const ARGUMENT_PREFIX[] = "InternalIPC::Arguments";
61 : static char const SEND_ARGUMENTS[] = "InternalIPC::SendArguments";
62 : static char const PROCESSING_DONE[] = "InternalIPC::ProcessingDone";
63 :
64 : // Receives packets from the pipe until a packet ends in a NUL character (that
65 : // will not be included in the returned string) or it cannot read anything (due
66 : // to error or closed pipe, in which case an empty string will be returned to
67 : // signal failure):
68 0 : OString readStringFromPipe(osl::StreamPipe & pipe) {
69 0 : for (OStringBuffer str;;) {
70 : char buf[1024];
71 0 : sal_Int32 n = pipe.recv(buf, SAL_N_ELEMENTS(buf));
72 0 : if (n <= 0) {
73 0 : return "";
74 : }
75 0 : bool end = false;
76 0 : if (buf[n - 1] == '\0') {
77 0 : end = true;
78 0 : --n;
79 : }
80 0 : str.append(buf, n);
81 : //TODO: how does OStringBuffer.append handle overflow?
82 0 : if (end) {
83 0 : return str.makeStringAndClear();
84 : }
85 0 : }
86 : }
87 :
88 : #endif
89 :
90 : }
91 :
92 : // Type of pipe we use
93 : enum PipeMode
94 : {
95 : PIPEMODE_DONTKNOW,
96 : PIPEMODE_CREATED,
97 : PIPEMODE_CONNECTED
98 : };
99 :
100 : namespace desktop
101 : {
102 :
103 : namespace {
104 :
105 : #if HAVE_FEATURE_DESKTOP || defined(ANDROID)
106 :
107 : class Parser: public CommandLineArgs::Supplier {
108 : public:
109 0 : explicit Parser(OString const & input): m_input(input) {
110 0 : if (!m_input.match(ARGUMENT_PREFIX) ||
111 0 : m_input.getLength() == RTL_CONSTASCII_LENGTH(ARGUMENT_PREFIX))
112 : {
113 0 : throw CommandLineArgs::Supplier::Exception();
114 : }
115 0 : m_index = RTL_CONSTASCII_LENGTH(ARGUMENT_PREFIX);
116 0 : switch (m_input[m_index++]) {
117 : case '0':
118 0 : break;
119 : case '1':
120 : {
121 0 : OUString url;
122 0 : if (!next(&url, false)) {
123 0 : throw CommandLineArgs::Supplier::Exception();
124 : }
125 0 : m_cwdUrl.reset(url);
126 0 : break;
127 : }
128 : case '2':
129 : {
130 0 : OUString path;
131 0 : if (!next(&path, false)) {
132 0 : throw CommandLineArgs::Supplier::Exception();
133 : }
134 0 : OUString url;
135 0 : if (osl::FileBase::getFileURLFromSystemPath(path, url) ==
136 : osl::FileBase::E_None)
137 : {
138 0 : m_cwdUrl.reset(url);
139 : }
140 0 : break;
141 : }
142 : default:
143 0 : throw CommandLineArgs::Supplier::Exception();
144 : }
145 0 : }
146 :
147 0 : virtual ~Parser() {}
148 :
149 0 : virtual boost::optional< OUString > getCwdUrl() SAL_OVERRIDE { return m_cwdUrl; }
150 :
151 0 : virtual bool next(OUString * argument) SAL_OVERRIDE { return next(argument, true); }
152 :
153 : private:
154 0 : bool next(OUString * argument, bool prefix) {
155 : OSL_ASSERT(argument != NULL);
156 0 : if (m_index < m_input.getLength()) {
157 0 : if (prefix) {
158 0 : if (m_input[m_index] != ',') {
159 0 : throw CommandLineArgs::Supplier::Exception();
160 : }
161 0 : ++m_index;
162 : }
163 0 : OStringBuffer b;
164 0 : while (m_index < m_input.getLength()) {
165 0 : char c = m_input[m_index];
166 0 : if (c == ',') {
167 0 : break;
168 : }
169 0 : ++m_index;
170 0 : if (c == '\\') {
171 0 : if (m_index < m_input.getLength()) {
172 0 : c = m_input[m_index++];
173 0 : switch (c) {
174 : case '0':
175 0 : c = '\0';
176 0 : break;
177 : case ',':
178 : case '\\':
179 0 : break;
180 : default:
181 0 : throw CommandLineArgs::Supplier::Exception();
182 : }
183 : } else {
184 0 : throw CommandLineArgs::Supplier::Exception();
185 : }
186 : }
187 0 : b.append(c);
188 : }
189 0 : OString b2(b.makeStringAndClear());
190 0 : if (!rtl_convertStringToUString(
191 : &argument->pData, b2.getStr(), b2.getLength(),
192 : RTL_TEXTENCODING_UTF8,
193 : (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR |
194 : RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR |
195 0 : RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR)))
196 : {
197 0 : throw CommandLineArgs::Supplier::Exception();
198 : }
199 0 : return true;
200 : } else {
201 0 : return false;
202 : }
203 : }
204 :
205 : boost::optional< OUString > m_cwdUrl;
206 : OString m_input;
207 : sal_Int32 m_index;
208 : };
209 :
210 0 : bool addArgument(OStringBuffer &rArguments, char prefix,
211 : const OUString &rArgument)
212 : {
213 0 : OString utf8;
214 0 : if (!rArgument.convertToString(
215 : &utf8, RTL_TEXTENCODING_UTF8,
216 : (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR |
217 0 : RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR)))
218 : {
219 0 : return false;
220 : }
221 0 : rArguments.append(prefix);
222 0 : for (sal_Int32 i = 0; i < utf8.getLength(); ++i) {
223 0 : char c = utf8[i];
224 0 : switch (c) {
225 : case '\0':
226 0 : rArguments.append("\\0");
227 0 : break;
228 : case ',':
229 0 : rArguments.append("\\,");
230 0 : break;
231 : case '\\':
232 0 : rArguments.append("\\\\");
233 0 : break;
234 : default:
235 0 : rArguments.append(c);
236 0 : break;
237 : }
238 : }
239 0 : return true;
240 : }
241 :
242 : #endif
243 :
244 : }
245 :
246 116 : rtl::Reference< OfficeIPCThread > OfficeIPCThread::pGlobalOfficeIPCThread;
247 : namespace { struct Security : public rtl::Static<osl::Security, Security> {}; }
248 :
249 : // Turns a string in aMsg such as file:///home/foo/.libreoffice/3
250 : // Into a hex string of well known length ff132a86...
251 115 : OUString CreateMD5FromString( const OUString& aMsg )
252 : {
253 : #if (OSL_DEBUG_LEVEL > 2)
254 : fprintf( stderr, "create md5 from '%s'\n",
255 : OUStringToOString (aMsg, RTL_TEXTENCODING_UTF8).getStr() );
256 : #endif
257 :
258 115 : rtlDigest handle = rtl_digest_create( rtl_Digest_AlgorithmMD5 );
259 115 : if ( handle )
260 : {
261 115 : const sal_uInt8* pData = reinterpret_cast<const sal_uInt8*>(aMsg.getStr());
262 115 : sal_uInt32 nSize = ( aMsg.getLength() * sizeof( sal_Unicode ));
263 115 : sal_uInt32 nMD5KeyLen = rtl_digest_queryLength( handle );
264 115 : boost::scoped_array<sal_uInt8> pMD5KeyBuffer(new sal_uInt8[ nMD5KeyLen ]);
265 :
266 115 : rtl_digest_init( handle, pData, nSize );
267 115 : rtl_digest_update( handle, pData, nSize );
268 115 : rtl_digest_get( handle, pMD5KeyBuffer.get(), nMD5KeyLen );
269 115 : rtl_digest_destroy( handle );
270 :
271 : // Create hex-value string from the MD5 value to keep the string size minimal
272 230 : OUStringBuffer aBuffer( nMD5KeyLen * 2 + 1 );
273 1955 : for ( sal_uInt32 i = 0; i < nMD5KeyLen; i++ )
274 1840 : aBuffer.append( (sal_Int32)pMD5KeyBuffer[i], 16 );
275 :
276 230 : return aBuffer.makeStringAndClear();
277 : }
278 :
279 0 : return OUString();
280 : }
281 :
282 : class ProcessEventsClass_Impl
283 : {
284 : public:
285 : DECL_STATIC_LINK( ProcessEventsClass_Impl, CallEvent, void* pEvent );
286 : DECL_STATIC_LINK( ProcessEventsClass_Impl, ProcessDocumentsEvent, void* pEvent );
287 : };
288 :
289 0 : IMPL_STATIC_LINK( ProcessEventsClass_Impl, CallEvent, void*, pEvent )
290 : {
291 : // Application events are processed by the Desktop::HandleAppEvent implementation.
292 0 : Desktop::HandleAppEvent( *static_cast<ApplicationEvent*>(pEvent) );
293 0 : delete static_cast<ApplicationEvent*>(pEvent);
294 0 : return 0;
295 : }
296 :
297 0 : IMPL_STATIC_LINK( ProcessEventsClass_Impl, ProcessDocumentsEvent, void*, pEvent )
298 : {
299 : // Documents requests are processed by the OfficeIPCThread implementation
300 0 : ProcessDocumentsRequest* pDocsRequest = static_cast<ProcessDocumentsRequest*>(pEvent);
301 :
302 0 : if ( pDocsRequest )
303 : {
304 0 : OfficeIPCThread::ExecuteCmdLineRequests( *pDocsRequest );
305 0 : delete pDocsRequest;
306 : }
307 0 : return 0;
308 : }
309 :
310 0 : void ImplPostForeignAppEvent( ApplicationEvent* pEvent )
311 : {
312 0 : Application::PostUserEvent( LINK( NULL, ProcessEventsClass_Impl, CallEvent ), pEvent );
313 0 : }
314 :
315 0 : void ImplPostProcessDocumentsEvent( ProcessDocumentsRequest* pEvent )
316 : {
317 0 : Application::PostUserEvent( LINK( NULL, ProcessEventsClass_Impl, ProcessDocumentsEvent ), pEvent );
318 0 : }
319 :
320 0 : oslSignalAction SAL_CALL SalMainPipeExchangeSignal_impl(void* /*pData*/, oslSignalInfo* pInfo)
321 : {
322 0 : if( pInfo->Signal == osl_Signal_Terminate )
323 0 : OfficeIPCThread::DisableOfficeIPCThread(false);
324 0 : return osl_Signal_ActCallNextHdl;
325 : }
326 :
327 :
328 :
329 : // The OfficeIPCThreadController implementation is a bookkeeper for all pending requests
330 : // that were created by the OfficeIPCThread. The requests are waiting to be processed by
331 : // our framework loadComponentFromURL function (e.g. open/print request).
332 : // During shutdown the framework is asking OfficeIPCThreadController about pending requests.
333 : // If there are pending requests framework has to stop the shutdown process. It is waiting
334 : // for these requests because framework is not able to handle shutdown and open a document
335 : // concurrently.
336 :
337 :
338 : // XServiceInfo
339 64 : OUString SAL_CALL OfficeIPCThreadController::getImplementationName()
340 : throw ( RuntimeException, std::exception )
341 : {
342 64 : return OUString( "com.sun.star.comp.OfficeIPCThreadController" );
343 : }
344 :
345 0 : sal_Bool OfficeIPCThreadController::supportsService(
346 : OUString const & ServiceName) throw (css::uno::RuntimeException, std::exception)
347 : {
348 0 : return cppu::supportsService(this, ServiceName);
349 : }
350 :
351 0 : Sequence< OUString > SAL_CALL OfficeIPCThreadController::getSupportedServiceNames()
352 : throw ( RuntimeException, std::exception )
353 : {
354 0 : Sequence< OUString > aSeq( 0 );
355 0 : return aSeq;
356 : }
357 :
358 : // XEventListener
359 0 : void SAL_CALL OfficeIPCThreadController::disposing( const EventObject& )
360 : throw( RuntimeException, std::exception )
361 : {
362 0 : }
363 :
364 : // XTerminateListener
365 63 : void SAL_CALL OfficeIPCThreadController::queryTermination( const EventObject& )
366 : throw( TerminationVetoException, RuntimeException, std::exception )
367 : {
368 : // Desktop ask about pending request through our office ipc pipe. We have to
369 : // be sure that no pending request is waiting because framework is not able to
370 : // handle shutdown and open a document concurrently.
371 :
372 63 : if ( OfficeIPCThread::AreRequestsPending() )
373 0 : throw TerminationVetoException();
374 : else
375 63 : OfficeIPCThread::SetDowning();
376 63 : }
377 :
378 63 : void SAL_CALL OfficeIPCThreadController::notifyTermination( const EventObject& )
379 : throw( RuntimeException, std::exception )
380 : {
381 63 : }
382 :
383 : namespace
384 : {
385 : class theOfficeIPCThreadMutex
386 : : public rtl::Static<osl::Mutex, theOfficeIPCThreadMutex> {};
387 : }
388 :
389 605 : ::osl::Mutex& OfficeIPCThread::GetMutex()
390 : {
391 605 : return theOfficeIPCThreadMutex::get();
392 : }
393 :
394 63 : void OfficeIPCThread::SetDowning()
395 : {
396 : // We have the order to block all incoming requests. Framework
397 : // wants to shutdown and we have to make sure that no loading/printing
398 : // requests are executed anymore.
399 63 : ::osl::MutexGuard aGuard( GetMutex() );
400 :
401 63 : if ( pGlobalOfficeIPCThread.is() )
402 63 : pGlobalOfficeIPCThread->mbDowning = true;
403 63 : }
404 :
405 : static bool s_bInEnableRequests = false;
406 :
407 64 : void OfficeIPCThread::EnableRequests( bool i_bEnable )
408 : {
409 : // switch between just queueing the requests and executing them
410 64 : ::osl::MutexGuard aGuard( GetMutex() );
411 :
412 64 : if ( pGlobalOfficeIPCThread.is() )
413 : {
414 64 : s_bInEnableRequests = true;
415 64 : pGlobalOfficeIPCThread->mbRequestsEnabled = i_bEnable;
416 64 : if( i_bEnable )
417 : {
418 : // hit the compiler over the head
419 64 : ProcessDocumentsRequest aEmptyReq = ProcessDocumentsRequest( boost::optional< OUString >() );
420 : // trigger already queued requests
421 64 : OfficeIPCThread::ExecuteCmdLineRequests( aEmptyReq );
422 : }
423 64 : s_bInEnableRequests = false;
424 64 : }
425 64 : }
426 :
427 63 : bool OfficeIPCThread::AreRequestsPending()
428 : {
429 : // Give info about pending requests
430 63 : ::osl::MutexGuard aGuard( GetMutex() );
431 63 : if ( pGlobalOfficeIPCThread.is() )
432 63 : return ( pGlobalOfficeIPCThread->mnPendingRequests > 0 );
433 : else
434 0 : return false;
435 : }
436 :
437 1 : void OfficeIPCThread::RequestsCompleted( int nCount )
438 : {
439 : // Remove nCount pending requests from our internal counter
440 1 : ::osl::MutexGuard aGuard( GetMutex() );
441 1 : if ( pGlobalOfficeIPCThread.is() )
442 : {
443 1 : if ( pGlobalOfficeIPCThread->mnPendingRequests > 0 )
444 1 : pGlobalOfficeIPCThread->mnPendingRequests -= nCount;
445 1 : }
446 1 : }
447 :
448 117 : OfficeIPCThread::Status OfficeIPCThread::EnableOfficeIPCThread()
449 : {
450 117 : ::osl::MutexGuard aGuard( GetMutex() );
451 :
452 117 : if( pGlobalOfficeIPCThread.is() )
453 1 : return IPC_STATUS_OK;
454 :
455 : #if HAVE_FEATURE_DESKTOP || defined(ANDROID)
456 232 : OUString aUserInstallPath;
457 232 : OUString aDummy;
458 :
459 232 : rtl::Reference< OfficeIPCThread > pThread(new OfficeIPCThread);
460 :
461 116 : PipeMode nPipeMode = PIPEMODE_DONTKNOW;
462 :
463 : #ifndef ANDROID // On Android it might be that we still for some reason need the pipe?
464 :
465 : // In LibreOfficeKit-based programs we want to be totally independent from any other LibreOffice
466 : // instance or LOKit-using program. Certainly no need for any IPC pipes by definition, as we
467 : // don't have any reason to do any IPC. Why we even call this EnableOfficeIPCThread function
468 : // from LibreOfficeKit's lo_initialize() I am not completely sure, but that code, and this, is
469 : // such horrible crack that I don't want to change it too much.
470 :
471 116 : if (comphelper::LibreOfficeKit::isActive())
472 : {
473 : // Setting nPipeMode to PIPEMODE_CREATED causes the trivial path to be taken below, starting
474 : // the listeing thread. (Which will immediately finish, see the execute() function, but what
475 : // the heck...)
476 1 : nPipeMode = PIPEMODE_CREATED;
477 : }
478 : else
479 : #endif
480 : {
481 : // The name of the named pipe is created with the hashcode of the user installation directory (without /user). We have to retrieve
482 : // this information from a unotools implementation.
483 115 : ::utl::Bootstrap::PathStatus aLocateResult = ::utl::Bootstrap::locateUserInstallation( aUserInstallPath );
484 115 : if ( aLocateResult == ::utl::Bootstrap::PATH_EXISTS || aLocateResult == ::utl::Bootstrap::PATH_VALID)
485 115 : aDummy = aUserInstallPath;
486 : else
487 : {
488 0 : return IPC_STATUS_BOOTSTRAP_ERROR;
489 : }
490 :
491 : // Try to determine if we are the first office or not! This should prevent multiple
492 : // access to the user directory !
493 : // First we try to create our pipe if this fails we try to connect. We have to do this
494 : // in a loop because the other office can crash or shutdown between createPipe
495 : // and connectPipe!!
496 :
497 115 : OUString aIniName;
498 :
499 115 : osl_getExecutableFile( &aIniName.pData );
500 :
501 115 : sal_uInt32 lastIndex = aIniName.lastIndexOf('/');
502 115 : if ( lastIndex > 0 )
503 : {
504 115 : aIniName = aIniName.copy( 0, lastIndex+1 );
505 115 : aIniName += "perftune";
506 : #if defined(WNT)
507 : aIniName += ".ini";
508 : #else
509 115 : aIniName += "rc";
510 : #endif
511 : }
512 :
513 230 : ::rtl::Bootstrap aPerfTuneIniFile( aIniName );
514 :
515 230 : OUString aDefault( "0" );
516 230 : OUString aPreloadData;
517 :
518 115 : aPerfTuneIniFile.getFrom( OUString( "FastPipeCommunication" ), aPreloadData, aDefault );
519 :
520 :
521 230 : OUString aUserInstallPathHashCode;
522 :
523 115 : if ( aPreloadData == "1" )
524 : {
525 : sal_Char szBuffer[32];
526 0 : sprintf( szBuffer, "%d", LIBO_VERSION_MAJOR * 10000 + LIBO_VERSION_MINOR * 100 + LIBO_VERSION_MICRO * 1 );
527 0 : aUserInstallPathHashCode = OUString( szBuffer, strlen(szBuffer), osl_getThreadTextEncoding() );
528 : }
529 : else
530 115 : aUserInstallPathHashCode = CreateMD5FromString( aDummy );
531 :
532 :
533 : // Check result to create a hash code from the user install path
534 115 : if ( aUserInstallPathHashCode.isEmpty() )
535 0 : return IPC_STATUS_BOOTSTRAP_ERROR; // Something completely broken, we cannot create a valid hash code!
536 :
537 230 : OUString aPipeIdent( "SingleOfficeIPC_" + aUserInstallPathHashCode );
538 :
539 115 : do
540 : {
541 115 : osl::Security &rSecurity = Security::get();
542 :
543 : // Try to create pipe
544 115 : if ( pThread->maPipe.create( aPipeIdent.getStr(), osl_Pipe_CREATE, rSecurity ))
545 : {
546 : // Pipe created
547 115 : nPipeMode = PIPEMODE_CREATED;
548 : }
549 0 : else if( pThread->maPipe.create( aPipeIdent.getStr(), osl_Pipe_OPEN, rSecurity )) // Creation not successful, now we try to connect
550 : {
551 0 : osl::StreamPipe aStreamPipe(pThread->maPipe.getHandle());
552 0 : if (readStringFromPipe(aStreamPipe) == SEND_ARGUMENTS)
553 : {
554 : // Pipe connected to first office
555 0 : nPipeMode = PIPEMODE_CONNECTED;
556 : }
557 : else
558 : {
559 : // Pipe connection failed (other office exited or crashed)
560 : TimeValue tval;
561 0 : tval.Seconds = 0;
562 0 : tval.Nanosec = 500000000;
563 0 : salhelper::Thread::wait( tval );
564 0 : }
565 : }
566 : else
567 : {
568 0 : oslPipeError eReason = pThread->maPipe.getError();
569 0 : if ((eReason == osl_Pipe_E_ConnectionRefused) || (eReason == osl_Pipe_E_invalidError))
570 0 : return IPC_STATUS_PIPE_ERROR;
571 :
572 : // Wait for second office to be ready
573 : TimeValue aTimeValue;
574 0 : aTimeValue.Seconds = 0;
575 0 : aTimeValue.Nanosec = 10000000; // 10ms
576 0 : salhelper::Thread::wait( aTimeValue );
577 : }
578 :
579 115 : } while ( nPipeMode == PIPEMODE_DONTKNOW );
580 : }
581 :
582 116 : if ( nPipeMode == PIPEMODE_CREATED )
583 : {
584 : // Seems we are the one and only, so start listening thread
585 116 : pGlobalOfficeIPCThread = pThread;
586 116 : pThread->launch();
587 : }
588 : else
589 : {
590 : // Seems another office is running. Pipe arguments to it and self terminate
591 0 : osl::StreamPipe aStreamPipe(pThread->maPipe.getHandle());
592 :
593 0 : OStringBuffer aArguments(ARGUMENT_PREFIX);
594 0 : OUString cwdUrl;
595 0 : if (!(tools::getProcessWorkingDir(cwdUrl) &&
596 0 : addArgument(aArguments, '1', cwdUrl)))
597 : {
598 0 : aArguments.append('0');
599 : }
600 0 : sal_uInt32 nCount = rtl_getAppCommandArgCount();
601 0 : for( sal_uInt32 i=0; i < nCount; i++ )
602 : {
603 0 : rtl_getAppCommandArg( i, &aDummy.pData );
604 0 : if (!addArgument(aArguments, ',', aDummy)) {
605 0 : return IPC_STATUS_BOOTSTRAP_ERROR;
606 : }
607 : }
608 0 : aArguments.append('\0');
609 : // finally, write the string onto the pipe
610 : sal_Int32 n = aStreamPipe.write(
611 0 : aArguments.getStr(), aArguments.getLength());
612 0 : if (n != aArguments.getLength()) {
613 : SAL_INFO("desktop", "short write: " << n);
614 0 : return IPC_STATUS_BOOTSTRAP_ERROR;
615 : }
616 :
617 0 : if (readStringFromPipe(aStreamPipe) != PROCESSING_DONE)
618 : {
619 : // something went wrong
620 0 : return IPC_STATUS_BOOTSTRAP_ERROR;
621 : }
622 :
623 0 : return IPC_STATUS_2ND_OFFICE;
624 : }
625 : #else
626 : rtl::Reference< OfficeIPCThread > pThread(new OfficeIPCThread);
627 : pGlobalOfficeIPCThread = pThread;
628 : pThread->launch();
629 : #endif
630 233 : return IPC_STATUS_OK;
631 : }
632 :
633 116 : void OfficeIPCThread::DisableOfficeIPCThread(bool join)
634 : {
635 116 : osl::ClearableMutexGuard aMutex( GetMutex() );
636 :
637 116 : if( pGlobalOfficeIPCThread.is() )
638 : {
639 : rtl::Reference< OfficeIPCThread > pOfficeIPCThread(
640 116 : pGlobalOfficeIPCThread);
641 116 : pGlobalOfficeIPCThread.clear();
642 :
643 116 : pOfficeIPCThread->mbDowning = true;
644 116 : pOfficeIPCThread->maPipe.close();
645 :
646 : // release mutex to avoid deadlocks
647 116 : aMutex.clear();
648 :
649 116 : OfficeIPCThread::SetReady(pOfficeIPCThread);
650 :
651 : // exit gracefully and join
652 116 : if (join)
653 : {
654 116 : pOfficeIPCThread->join();
655 116 : }
656 116 : }
657 116 : }
658 :
659 116 : OfficeIPCThread::OfficeIPCThread() :
660 : Thread( "OfficeIPCThread" ),
661 : mbDowning( false ),
662 : mbRequestsEnabled( false ),
663 : mnPendingRequests( 0 ),
664 116 : mpDispatchWatcher( 0 )
665 : {
666 116 : }
667 :
668 348 : OfficeIPCThread::~OfficeIPCThread()
669 : {
670 116 : ::osl::ClearableMutexGuard aGuard( GetMutex() );
671 :
672 116 : if ( mpDispatchWatcher )
673 64 : mpDispatchWatcher->release();
674 116 : maPipe.close();
675 116 : pGlobalOfficeIPCThread.clear();
676 232 : }
677 :
678 180 : void OfficeIPCThread::SetReady(
679 : rtl::Reference< OfficeIPCThread > const & pThread)
680 : {
681 : rtl::Reference< OfficeIPCThread > const & t(
682 180 : pThread.is() ? pThread : pGlobalOfficeIPCThread);
683 180 : if (t.is())
684 : {
685 180 : t->cReady.set();
686 : }
687 180 : }
688 :
689 1 : void OfficeIPCThread::WaitForReady(
690 : rtl::Reference< OfficeIPCThread > const & pThread)
691 :
692 : {
693 : rtl::Reference< OfficeIPCThread > const & t(
694 1 : pThread.is() ? pThread : pGlobalOfficeIPCThread);
695 1 : if (t.is())
696 : {
697 1 : t->cReady.wait();
698 : }
699 1 : }
700 :
701 1 : bool OfficeIPCThread::IsEnabled()
702 : {
703 1 : return pGlobalOfficeIPCThread.is();
704 : }
705 :
706 116 : void OfficeIPCThread::execute()
707 : {
708 : #if HAVE_FEATURE_DESKTOP || defined(ANDROID)
709 :
710 : #ifndef ANDROID
711 116 : if (comphelper::LibreOfficeKit::isActive())
712 1 : return;
713 : #endif
714 :
715 0 : do
716 : {
717 115 : osl::StreamPipe aStreamPipe;
718 115 : oslPipeError nError = maPipe.accept( aStreamPipe );
719 :
720 :
721 115 : if( nError == osl_Pipe_E_None )
722 : {
723 : // if we receive a request while the office is displaying some dialog or error during
724 : // bootstrap, that dialogs event loop might get events that are dispatched by this thread
725 : // we have to wait for cReady to be set by the real main loop.
726 : // only reqests that dont dispatch events may be processed before cReady is set.
727 115 : cReady.wait();
728 :
729 : // we might have decided to shutdown while we were sleeping
730 115 : if (!pGlobalOfficeIPCThread.is()) return;
731 :
732 : // only lock the mutex when processing starts, othewise we deadlock when the office goes
733 : // down during wait
734 0 : osl::ClearableMutexGuard aGuard( GetMutex() );
735 :
736 0 : if ( mbDowning )
737 : {
738 0 : break;
739 : }
740 :
741 : // notify client we're ready to process its args:
742 : sal_Int32 n = aStreamPipe.write(
743 0 : SEND_ARGUMENTS, SAL_N_ELEMENTS(SEND_ARGUMENTS));
744 : // incl. terminating NUL
745 0 : if (n != SAL_N_ELEMENTS(SEND_ARGUMENTS)) {
746 : SAL_WARN("desktop", "short write: " << n);
747 0 : continue;
748 : }
749 :
750 0 : OString aArguments = readStringFromPipe(aStreamPipe);
751 :
752 : // Is this a lookup message from another application? if so, ignore
753 0 : if (aArguments.isEmpty())
754 0 : continue;
755 :
756 0 : boost::scoped_ptr< CommandLineArgs > aCmdLineArgs;
757 : try
758 : {
759 0 : Parser p(aArguments);
760 0 : aCmdLineArgs.reset( new CommandLineArgs( p ) );
761 : }
762 0 : catch ( const CommandLineArgs::Supplier::Exception & )
763 : {
764 : #if (OSL_DEBUG_LEVEL > 1) || defined DBG_UTIL
765 : fprintf( stderr, "Error in received command line arguments\n" );
766 : #endif
767 0 : continue;
768 : }
769 :
770 0 : bool bDocRequestSent = false;
771 :
772 0 : OUString aUnknown( aCmdLineArgs->GetUnknown() );
773 0 : if ( !aUnknown.isEmpty() || aCmdLineArgs->IsHelp() )
774 : {
775 : ApplicationEvent* pAppEvent =
776 0 : new ApplicationEvent(ApplicationEvent::TYPE_HELP, aUnknown);
777 0 : ImplPostForeignAppEvent( pAppEvent );
778 : }
779 0 : else if ( aCmdLineArgs->IsVersion() )
780 : {
781 : ApplicationEvent* pAppEvent =
782 0 : new ApplicationEvent(ApplicationEvent::TYPE_VERSION);
783 0 : ImplPostForeignAppEvent( pAppEvent );
784 : }
785 : else
786 : {
787 0 : const CommandLineArgs &rCurrentCmdLineArgs = Desktop::GetCommandLineArgs();
788 :
789 0 : if ( aCmdLineArgs->IsQuickstart() )
790 : {
791 : // we have to use application event, because we have to start quickstart service in main thread!!
792 : ApplicationEvent* pAppEvent =
793 0 : new ApplicationEvent(ApplicationEvent::TYPE_QUICKSTART);
794 0 : ImplPostForeignAppEvent( pAppEvent );
795 : }
796 :
797 : // handle request for acceptor
798 : std::vector< OUString > const & accept = aCmdLineArgs->
799 0 : GetAccept();
800 0 : for (std::vector< OUString >::const_iterator i(accept.begin());
801 0 : i != accept.end(); ++i)
802 : {
803 : ApplicationEvent* pAppEvent = new ApplicationEvent(
804 0 : ApplicationEvent::TYPE_ACCEPT, *i);
805 0 : ImplPostForeignAppEvent( pAppEvent );
806 : }
807 : // handle acceptor removal
808 : std::vector< OUString > const & unaccept = aCmdLineArgs->
809 0 : GetUnaccept();
810 0 : for (std::vector< OUString >::const_iterator i(
811 0 : unaccept.begin());
812 0 : i != unaccept.end(); ++i)
813 : {
814 : ApplicationEvent* pAppEvent = new ApplicationEvent(
815 0 : ApplicationEvent::TYPE_UNACCEPT, *i);
816 0 : ImplPostForeignAppEvent( pAppEvent );
817 : }
818 :
819 : ProcessDocumentsRequest* pRequest = new ProcessDocumentsRequest(
820 0 : aCmdLineArgs->getCwdUrl());
821 0 : cProcessed.reset();
822 0 : pRequest->pcProcessed = &cProcessed;
823 :
824 : // Print requests are not dependent on the --invisible cmdline argument as they are
825 : // loaded with the "hidden" flag! So they are always checked.
826 0 : pRequest->aPrintList = aCmdLineArgs->GetPrintList();
827 0 : bDocRequestSent |= !pRequest->aPrintList.empty();
828 0 : pRequest->aPrintToList = aCmdLineArgs->GetPrintToList();
829 0 : pRequest->aPrinterName = aCmdLineArgs->GetPrinterName();
830 0 : bDocRequestSent |= !( pRequest->aPrintToList.empty() || pRequest->aPrinterName.isEmpty() );
831 :
832 0 : if ( !rCurrentCmdLineArgs.IsInvisible() )
833 : {
834 : // Read cmdline args that can open/create documents. As they would open a window
835 : // they are only allowed if the "--invisible" is currently not used!
836 0 : pRequest->aOpenList = aCmdLineArgs->GetOpenList();
837 0 : bDocRequestSent |= !pRequest->aOpenList.empty();
838 0 : pRequest->aViewList = aCmdLineArgs->GetViewList();
839 0 : bDocRequestSent |= !pRequest->aViewList.empty();
840 0 : pRequest->aStartList = aCmdLineArgs->GetStartList();
841 0 : bDocRequestSent |= !pRequest->aStartList.empty();
842 0 : pRequest->aForceOpenList = aCmdLineArgs->GetForceOpenList();
843 0 : bDocRequestSent |= !pRequest->aForceOpenList.empty();
844 0 : pRequest->aForceNewList = aCmdLineArgs->GetForceNewList();
845 0 : bDocRequestSent |= !pRequest->aForceNewList.empty();
846 :
847 : // Special command line args to create an empty document for a given module
848 :
849 : // #i18338# (lo)
850 : // we only do this if no document was specified on the command line,
851 : // since this would be inconsistent with the behaviour of
852 : // the first process, see OpenClients() (call to OpenDefault()) in app.cxx
853 0 : if ( aCmdLineArgs->HasModuleParam() && !bDocRequestSent )
854 : {
855 0 : SvtModuleOptions aOpt;
856 0 : SvtModuleOptions::EFactory eFactory = SvtModuleOptions::EFactory::WRITER;
857 0 : if ( aCmdLineArgs->IsWriter() )
858 0 : eFactory = SvtModuleOptions::EFactory::WRITER;
859 0 : else if ( aCmdLineArgs->IsCalc() )
860 0 : eFactory = SvtModuleOptions::EFactory::CALC;
861 0 : else if ( aCmdLineArgs->IsDraw() )
862 0 : eFactory = SvtModuleOptions::EFactory::DRAW;
863 0 : else if ( aCmdLineArgs->IsImpress() )
864 0 : eFactory = SvtModuleOptions::EFactory::IMPRESS;
865 0 : else if ( aCmdLineArgs->IsBase() )
866 0 : eFactory = SvtModuleOptions::EFactory::DATABASE;
867 0 : else if ( aCmdLineArgs->IsMath() )
868 0 : eFactory = SvtModuleOptions::EFactory::MATH;
869 0 : else if ( aCmdLineArgs->IsGlobal() )
870 0 : eFactory = SvtModuleOptions::EFactory::WRITERGLOBAL;
871 0 : else if ( aCmdLineArgs->IsWeb() )
872 0 : eFactory = SvtModuleOptions::EFactory::WRITERWEB;
873 :
874 0 : if ( !pRequest->aOpenList.empty() )
875 0 : pRequest->aModule = aOpt.GetFactoryName( eFactory );
876 : else
877 0 : pRequest->aOpenList.push_back( aOpt.GetFactoryEmptyDocumentURL( eFactory ) );
878 0 : bDocRequestSent = true;
879 : }
880 : }
881 :
882 0 : if ( !aCmdLineArgs->IsQuickstart() ) {
883 0 : bool bShowHelp = false;
884 0 : OUStringBuffer aHelpURLBuffer;
885 0 : if (aCmdLineArgs->IsHelpWriter()) {
886 0 : bShowHelp = true;
887 0 : aHelpURLBuffer.appendAscii("vnd.sun.star.help://swriter/start");
888 0 : } else if (aCmdLineArgs->IsHelpCalc()) {
889 0 : bShowHelp = true;
890 0 : aHelpURLBuffer.appendAscii("vnd.sun.star.help://scalc/start");
891 0 : } else if (aCmdLineArgs->IsHelpDraw()) {
892 0 : bShowHelp = true;
893 0 : aHelpURLBuffer.appendAscii("vnd.sun.star.help://sdraw/start");
894 0 : } else if (aCmdLineArgs->IsHelpImpress()) {
895 0 : bShowHelp = true;
896 0 : aHelpURLBuffer.appendAscii("vnd.sun.star.help://simpress/start");
897 0 : } else if (aCmdLineArgs->IsHelpBase()) {
898 0 : bShowHelp = true;
899 0 : aHelpURLBuffer.appendAscii("vnd.sun.star.help://sdatabase/start");
900 0 : } else if (aCmdLineArgs->IsHelpBasic()) {
901 0 : bShowHelp = true;
902 0 : aHelpURLBuffer.appendAscii("vnd.sun.star.help://sbasic/start");
903 0 : } else if (aCmdLineArgs->IsHelpMath()) {
904 0 : bShowHelp = true;
905 0 : aHelpURLBuffer.appendAscii("vnd.sun.star.help://smath/start");
906 : }
907 0 : if (bShowHelp) {
908 0 : aHelpURLBuffer.appendAscii("?Language=");
909 0 : aHelpURLBuffer.append(utl::ConfigManager::getLocale());
910 : #if defined UNX
911 0 : aHelpURLBuffer.appendAscii("&System=UNX");
912 : #elif defined WNT
913 : aHelpURLBuffer.appendAscii("&System=WIN");
914 : #endif
915 : ApplicationEvent* pAppEvent = new ApplicationEvent(
916 : ApplicationEvent::TYPE_OPENHELPURL,
917 0 : aHelpURLBuffer.makeStringAndClear());
918 0 : ImplPostForeignAppEvent( pAppEvent );
919 0 : }
920 : }
921 :
922 0 : if ( bDocRequestSent )
923 : {
924 : // Send requests to dispatch watcher if we have at least one. The receiver
925 : // is responsible to delete the request after processing it.
926 0 : if ( aCmdLineArgs->HasModuleParam() )
927 : {
928 0 : SvtModuleOptions aOpt;
929 :
930 : // Support command line parameters to start a module (as preselection)
931 0 : if ( aCmdLineArgs->IsWriter() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER ) )
932 0 : pRequest->aModule = aOpt.GetFactoryName( SvtModuleOptions::EFactory::WRITER );
933 0 : else if ( aCmdLineArgs->IsCalc() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::CALC ) )
934 0 : pRequest->aModule = aOpt.GetFactoryName( SvtModuleOptions::EFactory::CALC );
935 0 : else if ( aCmdLineArgs->IsImpress() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::IMPRESS ) )
936 0 : pRequest->aModule= aOpt.GetFactoryName( SvtModuleOptions::EFactory::IMPRESS );
937 0 : else if ( aCmdLineArgs->IsDraw() && aOpt.IsModuleInstalled( SvtModuleOptions::EModule::DRAW ) )
938 0 : pRequest->aModule= aOpt.GetFactoryName( SvtModuleOptions::EFactory::DRAW );
939 : }
940 :
941 0 : ImplPostProcessDocumentsEvent( pRequest );
942 : }
943 : else
944 : {
945 : // delete not used request again
946 0 : delete pRequest;
947 0 : pRequest = NULL;
948 : }
949 0 : if (aArguments.equalsL(RTL_CONSTASCII_STRINGPARAM("-tofront")) ||
950 0 : aCmdLineArgs->IsEmpty())
951 : {
952 : // no document was sent, just bring Office to front
953 : ApplicationEvent* pAppEvent =
954 0 : new ApplicationEvent(ApplicationEvent::TYPE_APPEAR);
955 0 : ImplPostForeignAppEvent( pAppEvent );
956 : }
957 : }
958 :
959 : // we don't need the mutex any longer...
960 0 : aGuard.clear();
961 : // wait for processing to finish
962 0 : if (bDocRequestSent)
963 0 : cProcessed.wait();
964 : // processing finished, inform the requesting end:
965 : n = aStreamPipe.write(
966 0 : PROCESSING_DONE, SAL_N_ELEMENTS(PROCESSING_DONE));
967 : // incl. terminating NUL
968 0 : if (n != SAL_N_ELEMENTS(PROCESSING_DONE)) {
969 : SAL_WARN("desktop", "short write: " << n);
970 0 : continue;
971 0 : }
972 : }
973 : else
974 : {
975 : {
976 0 : osl::MutexGuard aGuard( GetMutex() );
977 0 : if ( mbDowning )
978 : {
979 0 : break;
980 0 : }
981 : }
982 :
983 : #if (OSL_DEBUG_LEVEL > 1) || defined DBG_UTIL
984 : fprintf( stderr, "Error on accept: %d\n", (int)nError );
985 : #endif
986 : TimeValue tval;
987 0 : tval.Seconds = 1;
988 0 : tval.Nanosec = 0;
989 0 : salhelper::Thread::wait( tval );
990 0 : }
991 0 : } while( schedule() );
992 : #endif
993 : }
994 :
995 520 : static void AddToDispatchList(
996 : DispatchWatcher::DispatchList& rDispatchList,
997 : boost::optional< OUString > const & cwdUrl,
998 : std::vector< OUString > const & aRequestList,
999 : DispatchWatcher::RequestType nType,
1000 : const OUString& aParam,
1001 : const OUString& aFactory )
1002 : {
1003 1563 : for (std::vector< OUString >::const_iterator i(aRequestList.begin());
1004 1042 : i != aRequestList.end(); ++i)
1005 : {
1006 : rDispatchList.push_back(
1007 1 : DispatchWatcher::DispatchRequest( nType, *i, cwdUrl, aParam, aFactory ));
1008 : }
1009 520 : }
1010 :
1011 65 : static void AddConversionsToDispatchList(
1012 : DispatchWatcher::DispatchList& rDispatchList,
1013 : boost::optional< OUString > const & cwdUrl,
1014 : std::vector< OUString > const & rRequestList,
1015 : const OUString& rParam,
1016 : const OUString& rPrinterName,
1017 : const OUString& rFactory,
1018 : const OUString& rParamOut,
1019 : const bool isTextCat )
1020 : {
1021 : DispatchWatcher::RequestType nType;
1022 65 : OUString aParam( rParam );
1023 :
1024 65 : if( !rParam.isEmpty() )
1025 : {
1026 0 : nType = ( isTextCat ) ? DispatchWatcher::REQUEST_CAT : DispatchWatcher::REQUEST_CONVERSION;
1027 0 : aParam = rParam;
1028 : }
1029 : else
1030 : {
1031 65 : nType = DispatchWatcher::REQUEST_BATCHPRINT;
1032 65 : aParam = rPrinterName;
1033 : }
1034 :
1035 130 : OUString aOutDir( rParamOut.trim() );
1036 130 : OUString aPWD;
1037 65 : ::tools::getProcessWorkingDir( aPWD );
1038 :
1039 65 : if( !::osl::FileBase::getAbsoluteFileURL( aPWD, rParamOut, aOutDir ) )
1040 0 : ::osl::FileBase::getSystemPathFromFileURL( aOutDir, aOutDir );
1041 :
1042 65 : if( !rParamOut.trim().isEmpty() )
1043 : {
1044 0 : aParam += ";";
1045 0 : aParam += aOutDir;
1046 : }
1047 : else
1048 : {
1049 65 : ::osl::FileBase::getSystemPathFromFileURL( aPWD, aPWD );
1050 65 : aParam += ";" + aPWD;
1051 : }
1052 :
1053 195 : for (std::vector< OUString >::const_iterator i(rRequestList.begin());
1054 130 : i != rRequestList.end(); ++i)
1055 : {
1056 : rDispatchList.push_back(
1057 0 : DispatchWatcher::DispatchRequest( nType, *i, cwdUrl, aParam, rFactory ));
1058 65 : }
1059 65 : }
1060 :
1061 :
1062 65 : bool OfficeIPCThread::ExecuteCmdLineRequests( ProcessDocumentsRequest& aRequest )
1063 : {
1064 : // protect the dispatch list
1065 65 : osl::ClearableMutexGuard aGuard( GetMutex() );
1066 :
1067 65 : static DispatchWatcher::DispatchList aDispatchList;
1068 :
1069 130 : OUString aEmpty;
1070 : // Create dispatch list for dispatch watcher
1071 65 : AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aInFilter, DispatchWatcher::REQUEST_INFILTER, aEmpty, aRequest.aModule );
1072 65 : AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aOpenList, DispatchWatcher::REQUEST_OPEN, aEmpty, aRequest.aModule );
1073 65 : AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aViewList, DispatchWatcher::REQUEST_VIEW, aEmpty, aRequest.aModule );
1074 65 : AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aStartList, DispatchWatcher::REQUEST_START, aEmpty, aRequest.aModule );
1075 65 : AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aPrintList, DispatchWatcher::REQUEST_PRINT, aEmpty, aRequest.aModule );
1076 65 : AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aPrintToList, DispatchWatcher::REQUEST_PRINTTO, aRequest.aPrinterName, aRequest.aModule );
1077 65 : AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aForceOpenList, DispatchWatcher::REQUEST_FORCEOPEN, aEmpty, aRequest.aModule );
1078 65 : AddToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aForceNewList, DispatchWatcher::REQUEST_FORCENEW, aEmpty, aRequest.aModule );
1079 65 : AddConversionsToDispatchList( aDispatchList, aRequest.aCwdUrl, aRequest.aConversionList, aRequest.aConversionParams, aRequest.aPrinterName, aRequest.aModule, aRequest.aConversionOut, aRequest.bTextCat );
1080 65 : bool bShutdown( false );
1081 :
1082 65 : if ( pGlobalOfficeIPCThread.is() )
1083 : {
1084 65 : if( ! pGlobalOfficeIPCThread->AreRequestsEnabled() )
1085 0 : return bShutdown;
1086 :
1087 65 : pGlobalOfficeIPCThread->mnPendingRequests += aDispatchList.size();
1088 65 : if ( !pGlobalOfficeIPCThread->mpDispatchWatcher )
1089 : {
1090 64 : pGlobalOfficeIPCThread->mpDispatchWatcher = DispatchWatcher::GetDispatchWatcher();
1091 64 : pGlobalOfficeIPCThread->mpDispatchWatcher->acquire();
1092 : }
1093 :
1094 : // copy for execute
1095 65 : DispatchWatcher::DispatchList aTempList( aDispatchList );
1096 65 : aDispatchList.clear();
1097 :
1098 65 : aGuard.clear();
1099 :
1100 : // Execute dispatch requests
1101 65 : bShutdown = pGlobalOfficeIPCThread->mpDispatchWatcher->executeDispatchRequests( aTempList, s_bInEnableRequests );
1102 :
1103 : // set processed flag
1104 65 : if (aRequest.pcProcessed != NULL)
1105 0 : aRequest.pcProcessed->set();
1106 : }
1107 :
1108 130 : return bShutdown;
1109 : }
1110 :
1111 348 : }
1112 :
1113 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|