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 <stdio.h>
21 
22 #include <sal/main.h>
23 #include <sal/log.hxx>
24 #include <osl/diagnose.h>
25 #include <osl/mutex.hxx>
26 #include <osl/conditn.hxx>
27 
28 #include <rtl/process.h>
29 #include <rtl/string.h>
30 #include <rtl/strbuf.hxx>
31 #include <rtl/ustrbuf.hxx>
32 
33 #include <cppuhelper/bootstrap.hxx>
34 #include <cppuhelper/implbase.hxx>
35 
36 #include <com/sun/star/lang/XMain.hpp>
37 #include <com/sun/star/lang/XInitialization.hpp>
38 #include <com/sun/star/lang/XComponent.hpp>
39 #include <com/sun/star/lang/XSingleComponentFactory.hpp>
40 #include <com/sun/star/lang/XSingleServiceFactory.hpp>
41 #include <com/sun/star/lang/XEventListener.hpp>
42 #include <com/sun/star/loader/XImplementationLoader.hpp>
43 #include <com/sun/star/registry/XRegistryKey.hpp>
44 #include <com/sun/star/connection/Acceptor.hpp>
45 #include <com/sun/star/connection/XConnection.hpp>
46 #include <com/sun/star/bridge/XBridgeFactory.hpp>
47 #include <com/sun/star/bridge/XBridge.hpp>
48 
49 using namespace std;
50 using namespace osl;
51 using namespace cppu;
52 using namespace com::sun::star::uno;
53 using namespace com::sun::star::lang;
54 using namespace com::sun::star::loader;
55 using namespace com::sun::star::registry;
56 using namespace com::sun::star::connection;
57 using namespace com::sun::star::bridge;
58 using namespace com::sun::star::container;
59 
60 namespace unoexe
61 {
62 
63 static bool s_quiet = false;
64 
out(const sal_Char * pText)65 static void out( const sal_Char * pText )
66 {
67     if (! s_quiet)
68         fprintf( stderr, "%s", pText );
69 }
70 
out(const OUString & rText)71 static void out( const OUString & rText )
72 {
73     if (! s_quiet)
74     {
75         OString aText( OUStringToOString( rText, RTL_TEXTENCODING_ASCII_US ) );
76         fprintf( stderr, "%s", aText.getStr() );
77     }
78 }
79 
80 static const char arUsingText[] =
81 "\nusing:\n\n"
82 "uno [-c ComponentImplementationName -l LocationUrl | -s ServiceName]\n"
83 "    [-u uno:(socket[,host=HostName][,port=nnn]|pipe[,name=PipeName]);<protocol>;Name\n"
84 "        [--singleaccept] [--singleinstance]]\n"
85 "    [--quiet]\n"
86 "    [-- Argument1 Argument2 ...]\n";
87 
88 /// @throws RuntimeException
readOption(OUString * pValue,const sal_Char * pOpt,sal_uInt32 * pnIndex,const OUString & aArg)89 static bool readOption( OUString * pValue, const sal_Char * pOpt,
90                         sal_uInt32 * pnIndex, const OUString & aArg)
91 {
92     const OUString dash("-");
93     if(!aArg.startsWith(dash))
94         return false;
95 
96     OUString aOpt = OUString::createFromAscii( pOpt );
97 
98     if (aArg.getLength() < aOpt.getLength())
99         return false;
100 
101     if (aOpt.equalsIgnoreAsciiCase( aArg.copy(1) ))
102     {
103         // take next argument
104         ++(*pnIndex);
105 
106         rtl_getAppCommandArg(*pnIndex, &pValue->pData);
107         if (*pnIndex >= rtl_getAppCommandArgCount() || pValue->copy(1) == dash)
108         {
109             throw RuntimeException( "incomplete option \"-" + aOpt + "\" given!" );
110         }
111         SAL_INFO("cpputools.unoexe", "> identified option -" << pOpt << " = " << aArg);
112         ++(*pnIndex);
113         return true;
114     }
115     else if (aArg.indexOf(aOpt) == 1)
116     {
117         *pValue = aArg.copy(1 + aOpt.getLength());
118         SAL_INFO("cpputools.unoexe", "> identified option -" << pOpt << " = " << aArg);
119         ++(*pnIndex);
120 
121         return true;
122     }
123     return false;
124 }
125 
readOption(bool * pbOpt,const sal_Char * pOpt,sal_uInt32 * pnIndex,const OUString & aArg)126 static bool readOption( bool * pbOpt, const sal_Char * pOpt,
127                         sal_uInt32 * pnIndex, const OUString & aArg)
128 {
129     OUString aOpt = OUString::createFromAscii(pOpt);
130 
131     if(aArg.startsWith("--") && aOpt == aArg.copy(2))
132     {
133         ++(*pnIndex);
134         *pbOpt = true;
135         SAL_INFO("cpputools.unoexe", "> identified option --" << pOpt);
136         return true;
137     }
138     return false;
139 }
140 
141 /// @throws Exception
142 template< class T >
createInstance(Reference<T> & rxOut,const Reference<XComponentContext> & xContext,const OUString & rServiceName)143 static void createInstance(
144     Reference< T > & rxOut,
145     const Reference< XComponentContext > & xContext,
146     const OUString & rServiceName )
147 {
148     Reference< XMultiComponentFactory > xMgr( xContext->getServiceManager() );
149     Reference< XInterface > x( xMgr->createInstanceWithContext( rServiceName, xContext ) );
150 
151     if (! x.is())
152     {
153         throw RuntimeException( "cannot get service instance \"" + rServiceName + "\"!" );
154     }
155 
156     rxOut.set( x.get(), UNO_QUERY_THROW );
157 }
158 
159 /// @throws Exception
loadComponent(const Reference<XComponentContext> & xContext,const OUString & rImplName,const OUString & rLocation)160 static Reference< XInterface > loadComponent(
161     const Reference< XComponentContext > & xContext,
162     const OUString & rImplName, const OUString & rLocation )
163 {
164     // determine loader to be used
165     sal_Int32 nDot = rLocation.lastIndexOf( '.' );
166     if (nDot <= 0 || nDot >= rLocation.getLength())
167     {
168         throw RuntimeException(
169             "location \"" + rLocation + "\" has no extension!  Cannot determine loader to be used!" );
170     }
171 
172     Reference< XImplementationLoader > xLoader;
173 
174     OUString aExt( rLocation.copy( nDot +1 ) );
175 
176     if (aExt == "dll" || aExt == "exe" || aExt == "dylib" || aExt == "so")
177     {
178         createInstance(
179             xLoader, xContext, "com.sun.star.loader.SharedLibrary" );
180     }
181     else if (aExt == "jar" || aExt == "class")
182     {
183         createInstance(
184             xLoader, xContext, "com.sun.star.loader.Java" );
185     }
186     else
187     {
188         throw RuntimeException(
189             "unknown extension of \"" + rLocation + "\"!  No loader available!" );
190     }
191 
192     Reference< XInterface > xInstance;
193 
194     // activate
195     Reference< XInterface > xFactory( xLoader->activate(
196         rImplName, OUString(), rLocation, Reference< XRegistryKey >() ) );
197     if (xFactory.is())
198     {
199         Reference< XSingleComponentFactory > xCFac( xFactory, UNO_QUERY );
200         if (xCFac.is())
201         {
202             xInstance = xCFac->createInstanceWithContext( xContext );
203         }
204         else
205         {
206             Reference< XSingleServiceFactory > xSFac( xFactory, UNO_QUERY );
207             if (xSFac.is())
208             {
209                 out( "\n> warning: ignoring context for implementation \"" );
210                 out( rImplName );
211                 out( "\"!" );
212                 xInstance = xSFac->createInstance();
213             }
214         }
215     }
216 
217     if (! xInstance.is())
218     {
219         throw RuntimeException(
220             "activating component \"" + rImplName + "\" from location \"" + rLocation + "\" failed!" );
221     }
222 
223     return xInstance;
224 }
225 
226 class OInstanceProvider
227     : public WeakImplHelper< XInstanceProvider >
228 {
229     Reference< XComponentContext > _xContext;
230 
231     Mutex                             _aSingleInstanceMutex;
232     Reference< XInterface >           _xSingleInstance;
233     bool                              _bSingleInstance;
234 
235     OUString                          _aImplName;
236     OUString                          _aLocation;
237     OUString                          _aServiceName;
238     Sequence< Any >                   _aInitParams;
239 
240     OUString                          _aInstanceName;
241 
242     /// @throws Exception
243     inline Reference< XInterface > createInstance();
244 
245 public:
OInstanceProvider(const Reference<XComponentContext> & xContext,const OUString & rImplName,const OUString & rLocation,const OUString & rServiceName,const Sequence<Any> & rInitParams,bool bSingleInstance,const OUString & rInstanceName)246     OInstanceProvider( const Reference< XComponentContext > & xContext,
247                        const OUString & rImplName, const OUString & rLocation,
248                        const OUString & rServiceName, const Sequence< Any > & rInitParams,
249                        bool bSingleInstance, const OUString & rInstanceName )
250         : _xContext( xContext )
251         , _bSingleInstance( bSingleInstance )
252         , _aImplName( rImplName )
253         , _aLocation( rLocation )
254         , _aServiceName( rServiceName )
255         , _aInitParams( rInitParams )
256         , _aInstanceName( rInstanceName )
257         {}
258 
259     // XInstanceProvider
260     virtual Reference< XInterface > SAL_CALL getInstance( const OUString & rName ) override;
261 };
262 
createInstance()263 inline Reference< XInterface > OInstanceProvider::createInstance()
264 {
265     Reference< XInterface > xRet;
266     if (!_aImplName.isEmpty()) // manually via loader
267         xRet = loadComponent( _xContext, _aImplName, _aLocation );
268     else // via service manager
269         unoexe::createInstance( xRet, _xContext, _aServiceName );
270 
271     // opt XInit
272     Reference< XInitialization > xInit( xRet, UNO_QUERY );
273     if (xInit.is())
274         xInit->initialize( _aInitParams );
275 
276     return xRet;
277 }
278 
getInstance(const OUString & rName)279 Reference< XInterface > OInstanceProvider::getInstance( const OUString & rName )
280 {
281     try
282     {
283         if (_aInstanceName == rName)
284         {
285             Reference< XInterface > xRet;
286 
287             if (_aImplName.isEmpty() && _aServiceName.isEmpty())
288             {
289                 OSL_ASSERT( rName == "uno.ComponentContext" );
290                 xRet = _xContext;
291             }
292             else if (_bSingleInstance)
293             {
294                 if (! _xSingleInstance.is())
295                 {
296                     MutexGuard aGuard( _aSingleInstanceMutex );
297                     if (! _xSingleInstance.is())
298                     {
299                         _xSingleInstance = createInstance();
300                     }
301                 }
302                 xRet = _xSingleInstance;
303             }
304             else
305             {
306                 xRet = createInstance();
307             }
308 
309             return xRet;
310         }
311     }
312     catch (Exception & rExc)
313     {
314         out( "\n> error: " );
315         out( rExc.Message );
316     }
317     throw NoSuchElementException(
318         "no such element \"" + rName + "\"!" );
319 }
320 
321 struct ODisposingListener : public WeakImplHelper< XEventListener >
322 {
323     Condition cDisposed;
324 
325     // XEventListener
326     virtual void SAL_CALL disposing( const EventObject & rEvt ) override;
327 
328     static void waitFor( const Reference< XComponent > & xComp );
329 };
330 
disposing(const EventObject &)331 void ODisposingListener::disposing( const EventObject & )
332 {
333     cDisposed.set();
334 }
335 
waitFor(const Reference<XComponent> & xComp)336 void ODisposingListener::waitFor( const Reference< XComponent > & xComp )
337 {
338     ODisposingListener * pListener = new ODisposingListener;
339     Reference< XEventListener > xListener( pListener );
340 
341     xComp->addEventListener( xListener );
342     pListener->cDisposed.wait();
343 }
344 
345 } // namespace unoexe
346 
347 using namespace unoexe;
348 
SAL_IMPLEMENT_MAIN()349 SAL_IMPLEMENT_MAIN()
350 {
351     sal_uInt32 nCount = rtl_getAppCommandArgCount();
352     if (nCount == 0)
353     {
354         out( arUsingText );
355         return 0;
356     }
357 
358     sal_Int32 nRet = 0;
359     Reference< XComponentContext > xContext;
360 
361 
362     try
363     {
364         OUString aImplName, aLocation, aServiceName, aUnoUrl;
365         Sequence< OUString > aParams;
366         bool bSingleAccept = false;
367         bool bSingleInstance = false;
368 
369         // read command line arguments
370 
371         sal_uInt32 nPos = 0;
372         // read up to arguments
373         while (nPos < nCount)
374         {
375             OUString arg;
376 
377             rtl_getAppCommandArg(nPos, &arg.pData);
378 
379             if (arg == "--")
380             {
381                 ++nPos;
382                 break;
383             }
384 
385             if (!(readOption( &aImplName, "c", &nPos, arg)                ||
386                   readOption( &aLocation, "l", &nPos, arg)                ||
387                   readOption( &aServiceName, "s", &nPos, arg)             ||
388                   readOption( &aUnoUrl, "u", &nPos, arg)                  ||
389                   readOption( &s_quiet, "quiet", &nPos, arg)              ||
390                   readOption( &bSingleAccept, "singleaccept", &nPos, arg) ||
391                   readOption( &bSingleInstance, "singleinstance", &nPos, arg)))
392             {
393                 throw RuntimeException(
394                     "unexpected argument \"" + arg + "\"" );
395             }
396         }
397 
398         if (!(aImplName.isEmpty() || aServiceName.isEmpty()))
399             throw RuntimeException("give component exOR service name!" );
400         if (aImplName.isEmpty() && aServiceName.isEmpty())
401         {
402             if (! aUnoUrl.endsWithIgnoreAsciiCase( ";uno.ComponentContext" ))
403                 throw RuntimeException(
404                     "expected UNO-URL with instance name uno.ComponentContext!" );
405             if (bSingleInstance)
406                 throw RuntimeException(
407                     "unexpected option --singleinstance!" );
408         }
409         if (!aImplName.isEmpty() && aLocation.isEmpty())
410             throw RuntimeException("give component location!" );
411         if (!aServiceName.isEmpty() && !aLocation.isEmpty())
412             out( "\n> warning: service name given, will ignore location!" );
413 
414         // read component params
415         aParams.realloc( nCount - nPos );
416         OUString * pParams = aParams.getArray();
417 
418         sal_uInt32 nOffset = nPos;
419         for ( ; nPos < nCount; ++nPos )
420         {
421             rtl_getAppCommandArg( nPos, &pParams[nPos -nOffset].pData );
422         }
423 
424         xContext = defaultBootstrap_InitialComponentContext();
425 
426         // accept, instantiate, etc.
427 
428         if (!aUnoUrl.isEmpty()) // accepting connections
429         {
430             if (aUnoUrl.getLength() < 10 || !aUnoUrl.startsWithIgnoreAsciiCase( "uno:" ))
431             {
432                 throw RuntimeException("illegal uno url given!" );
433             }
434 
435             sal_Int32 nIndex = 4; // skip initial "uno:"
436             bool bTooFewTokens {false};
437             const OUString aConnectDescr{ aUnoUrl.getToken( 0, ';', nIndex ) }; // uno:CONNECTDESCR;iiop;InstanceName
438             if (nIndex<0) bTooFewTokens = true;
439             const OUString aUnoUrlToken{ aUnoUrl.getToken( 0, ';', nIndex ) };
440             if (nIndex<0) bTooFewTokens = true;
441             const OUString aInstanceName{ aUnoUrl.getToken( 0, ';', nIndex ) };
442 
443             // Exactly 3 tokens are required
444             if (bTooFewTokens || nIndex>0)
445             {
446                 throw RuntimeException("illegal uno url given!" );
447             }
448 
449             Reference< XAcceptor > xAcceptor = Acceptor::create(xContext);
450 
451             // init params
452             Sequence< Any > aInitParams( aParams.getLength() );
453             const OUString * p = aParams.getConstArray();
454             Any * pInitParams = aInitParams.getArray();
455             for ( sal_Int32 i = aParams.getLength(); i--; )
456             {
457                 pInitParams[i] <<= p[i];
458             }
459 
460             // instance provider
461             Reference< XInstanceProvider > xInstanceProvider( new OInstanceProvider(
462                 xContext, aImplName, aLocation, aServiceName, aInitParams,
463                 bSingleInstance, aInstanceName ) );
464 
465             // coverity[loop_top] - not really an infinite loop, we can be instructed to exit via the connection
466             for (;;)
467             {
468                 // accepting
469                 out( "\n> accepting " );
470                 out( aConnectDescr );
471                 out( "..." );
472                 Reference< XConnection > xConnection( xAcceptor->accept( aConnectDescr ) );
473                 out( "connection established." );
474 
475                 Reference< XBridgeFactory > xBridgeFactory;
476                 createInstance(
477                     xBridgeFactory, xContext,
478                     "com.sun.star.bridge.BridgeFactory" );
479 
480                 // bridge
481                 Reference< XBridge > xBridge( xBridgeFactory->createBridge(
482                     OUString(), aUnoUrlToken,
483                     xConnection, xInstanceProvider ) );
484 
485                 if (bSingleAccept)
486                 {
487                     Reference< XComponent > xComp( xBridge, UNO_QUERY_THROW );
488                     ODisposingListener::waitFor( xComp );
489                     xComp->dispose();
490                         // explicitly dispose the remote bridge so that it joins
491                         // on all spawned threads before process exit (see
492                         // binaryurp/source/bridge.cxx for details)
493                     break;
494                 }
495             }
496         }
497         else // no uno url
498         {
499             Reference< XInterface > xInstance;
500             if (!aImplName.isEmpty()) // manually via loader
501                 xInstance = loadComponent( xContext, aImplName, aLocation );
502             else // via service manager
503                 createInstance( xInstance, xContext, aServiceName );
504 
505             // execution
506             Reference< XMain > xMain( xInstance, UNO_QUERY );
507             if (xMain.is())
508             {
509                 nRet = xMain->run( aParams );
510             }
511             else
512             {
513                 Reference< XComponent > xComp( xInstance, UNO_QUERY );
514                 if (xComp.is())
515                     xComp->dispose();
516                 throw RuntimeException( "component does not export interface \"com.sun.star.lang.XMain\"!" );
517             }
518         }
519     }
520     catch (Exception & rExc)
521     {
522         out( "\n> error: " );
523         out( rExc.Message );
524         out( "\n> dying..." );
525         nRet = 1;
526     }
527 
528     // cleanup
529     Reference< XComponent > xComp( xContext, UNO_QUERY );
530     if (xComp.is())
531         xComp->dispose();
532 
533     return nRet;
534 }
535 
536 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
537