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