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_folders.h>
22 
23 #include <pyuno.hxx>
24 
25 #include <o3tl/any.hxx>
26 
27 #include <osl/process.h>
28 #include <osl/file.hxx>
29 #include <osl/thread.h>
30 
31 #include <rtl/ustrbuf.hxx>
32 #include <rtl/bootstrap.hxx>
33 
34 #include <cppuhelper/implementationentry.hxx>
35 #include <cppuhelper/factory.hxx>
36 
37 #include <com/sun/star/uno/XComponentContext.hpp>
38 
39 // apparently PATH_MAX is not standard and not defined by MSVC
40 #ifndef PATH_MAX
41 #ifdef _MAX_PATH
42 #define PATH_MAX _MAX_PATH
43 #else
44 #ifdef MAX_PATH
45 #define PATH_MAX MAX_PATH
46 #else
47 #error no PATH_MAX
48 #endif
49 #endif
50 #endif
51 
52 using pyuno::PyRef;
53 using pyuno::NOT_NULL;
54 using pyuno::Runtime;
55 using pyuno::PyThreadAttach;
56 
57 using com::sun::star::uno::Reference;
58 using com::sun::star::uno::XInterface;
59 using com::sun::star::uno::Sequence;
60 using com::sun::star::uno::XComponentContext;
61 using com::sun::star::uno::RuntimeException;
62 
63 namespace pyuno_loader
64 {
65 
66 /// @throws RuntimeException
raiseRuntimeExceptionWhenNeeded()67 static void raiseRuntimeExceptionWhenNeeded()
68 {
69     if( PyErr_Occurred() )
70     {
71         PyRef excType, excValue, excTraceback;
72         PyErr_Fetch(reinterpret_cast<PyObject **>(&excType), reinterpret_cast<PyObject**>(&excValue), reinterpret_cast<PyObject**>(&excTraceback));
73         Runtime runtime;
74         css::uno::Any a = runtime.extractUnoException( excType, excValue, excTraceback );
75         OUStringBuffer buf;
76         buf.append( "python-loader:" );
77         if( auto e = o3tl::tryAccess<css::uno::Exception>(a) )
78             buf.append( e->Message );
79         throw RuntimeException( buf.makeStringAndClear() );
80     }
81 }
82 
83 /// @throws RuntimeException
getLoaderModule()84 static PyRef getLoaderModule()
85 {
86     PyRef module(
87         PyImport_ImportModule( "pythonloader" ),
88         SAL_NO_ACQUIRE );
89     raiseRuntimeExceptionWhenNeeded();
90     if( !module.is() )
91     {
92         throw RuntimeException( "pythonloader: Couldn't load pythonloader module" );
93     }
94     return PyRef( PyModule_GetDict( module.get() ));
95 }
96 
97 /// @throws RuntimeException
getObjectFromLoaderModule(const char * func)98 static PyRef getObjectFromLoaderModule( const char * func )
99 {
100     PyRef object( PyDict_GetItemString(getLoaderModule().get(), func ) );
101     if( !object.is() )
102     {
103         throw RuntimeException( "pythonloader: couldn't find core element pythonloader." +
104                 OUString::createFromAscii( func ));
105     }
106     return object;
107 }
108 
getImplementationName()109 static OUString getImplementationName()
110 {
111     return "org.openoffice.comp.pyuno.Loader";
112 }
113 
getSupportedServiceNames()114 static Sequence< OUString > getSupportedServiceNames()
115 {
116     return { "com.sun.star.loader.Python" };
117 }
118 
setPythonHome(const OUString & pythonHome)119 static void setPythonHome ( const OUString & pythonHome )
120 {
121     OUString systemPythonHome;
122     osl_getSystemPathFromFileURL( pythonHome.pData, &(systemPythonHome.pData) );
123     OString o = OUStringToOString( systemPythonHome, osl_getThreadTextEncoding() );
124 #if PY_MAJOR_VERSION >= 3
125     // static because Py_SetPythonHome just copies the "wide" pointer
126     static wchar_t wide[PATH_MAX + 1];
127     size_t len = mbstowcs(wide, o.pData->buffer, PATH_MAX + 1);
128     if(len == size_t(-1))
129     {
130         PyErr_SetString(PyExc_SystemError, "invalid multibyte sequence in python home path");
131         return;
132     }
133     if(len == PATH_MAX + 1)
134     {
135         PyErr_SetString(PyExc_SystemError, "python home path is too long");
136         return;
137     }
138     Py_SetPythonHome(wide);
139 #else
140     rtl_string_acquire(o.pData); // increase reference count
141     Py_SetPythonHome(o.pData->buffer);
142 #endif
143 }
144 
prependPythonPath(const OUString & pythonPathBootstrap)145 static void prependPythonPath( const OUString & pythonPathBootstrap )
146 {
147     OUStringBuffer bufPYTHONPATH( 256 );
148     bool bAppendSep = false;
149     sal_Int32 nIndex = 0;
150     while( true )
151     {
152         sal_Int32 nNew = pythonPathBootstrap.indexOf( ' ', nIndex );
153         OUString fileUrl;
154         if( nNew == -1 )
155         {
156             fileUrl = pythonPathBootstrap.copy(nIndex);
157         }
158         else
159         {
160             fileUrl = pythonPathBootstrap.copy(nIndex, nNew - nIndex);
161         }
162         OUString systemPath;
163         osl_getSystemPathFromFileURL( fileUrl.pData, &(systemPath.pData) );
164         if (!systemPath.isEmpty())
165         {
166             if (bAppendSep)
167                 bufPYTHONPATH.append(static_cast<sal_Unicode>(SAL_PATHSEPARATOR));
168             bufPYTHONPATH.append(systemPath);
169             bAppendSep = true;
170         }
171         if( nNew == -1 )
172             break;
173         nIndex = nNew + 1;
174     }
175     const char * oldEnv = getenv( "PYTHONPATH");
176     if( oldEnv )
177     {
178         if (bAppendSep)
179             bufPYTHONPATH.append( static_cast<sal_Unicode>(SAL_PATHSEPARATOR) );
180         bufPYTHONPATH.append( OUString(oldEnv, strlen(oldEnv), osl_getThreadTextEncoding()) );
181     }
182 
183     OUString envVar("PYTHONPATH");
184     OUString envValue(bufPYTHONPATH.makeStringAndClear());
185     osl_setEnvironment(envVar.pData, envValue.pData);
186 }
187 
188 struct PythonInit
189 {
PythonInitpyuno_loader::PythonInit190 PythonInit() {
191     if (! Py_IsInitialized()) // may be inited by getComponentContext() already
192     {
193         OUString pythonPath;
194         OUString pythonHome;
195         OUString path( "$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("pythonloader.uno" ));
196         rtl::Bootstrap::expandMacros(path); //TODO: detect failure
197         rtl::Bootstrap bootstrap(path);
198 
199         // look for pythonhome
200         bootstrap.getFrom( "PYUNO_LOADER_PYTHONHOME", pythonHome );
201         bootstrap.getFrom( "PYUNO_LOADER_PYTHONPATH", pythonPath );
202 
203         // pythonhome+pythonpath must be set before Py_Initialize(), otherwise there appear warning on the console
204         // sadly, there is no api for setting the pythonpath, we have to use the environment variable
205         if( !pythonHome.isEmpty() )
206             setPythonHome( pythonHome );
207 
208         if( !pythonPath.isEmpty() )
209             prependPythonPath( pythonPath );
210 
211 #ifdef _WIN32
212         //extend PATH under windows to include the branddir/program so ssl libs will be found
213         //for use by terminal mailmerge dependency _ssl.pyd
214         OUString sEnvName("PATH");
215         OUString sPath;
216         osl_getEnvironment(sEnvName.pData, &sPath.pData);
217         OUString sBrandLocation("$BRAND_BASE_DIR/program");
218         rtl::Bootstrap::expandMacros(sBrandLocation);
219         osl::FileBase::getSystemPathFromFileURL(sBrandLocation, sBrandLocation);
220         sPath = OUStringBuffer(sPath).
221             append(static_cast<sal_Unicode>(SAL_PATHSEPARATOR)).
222             append(sBrandLocation).makeStringAndClear();
223         osl_setEnvironment(sEnvName.pData, sPath.pData);
224 #endif
225 
226 #if PY_MAJOR_VERSION >= 3
227         PyImport_AppendInittab( "pyuno", PyInit_pyuno );
228 #else
229         PyImport_AppendInittab( (char*)"pyuno", initpyuno );
230 #endif
231 
232 #if HAVE_FEATURE_READONLY_INSTALLSET
233         Py_DontWriteBytecodeFlag = 1;
234 #endif
235 
236         // initialize python
237         Py_Initialize();
238         PyEval_InitThreads();
239 
240         PyThreadState *tstate = PyThreadState_Get();
241         PyEval_ReleaseThread( tstate );
242         // This tstate is never used again, so delete it here.
243         // This prevents an assertion in PyThreadState_Swap on the
244         // PyThreadAttach below.
245         PyThreadState_Delete(tstate);
246     }
247 }
248 };
249 
CreateInstance(const Reference<XComponentContext> & ctx)250 static Reference<XInterface> CreateInstance(const Reference<XComponentContext> & ctx)
251 {
252     // tdf#114815 thread-safe static to init python only once
253     static PythonInit s_Init;
254 
255     Reference< XInterface > ret;
256 
257     PyThreadAttach attach( PyInterpreterState_Head() );
258     {
259         // note: this can't race against getComponentContext() because
260         // either (in soffice.bin) CreateInstance() must be called before
261         // getComponentContext() can be called, or (in python.bin) the other
262         // way around
263         if( ! Runtime::isInitialized() )
264         {
265             Runtime::initialize( ctx );
266         }
267         Runtime runtime;
268 
269         PyRef pyCtx = runtime.any2PyObject(
270             css::uno::makeAny( ctx ) );
271 
272         PyRef clazz = getObjectFromLoaderModule( "Loader" );
273         PyRef args ( PyTuple_New( 1 ), SAL_NO_ACQUIRE, NOT_NULL );
274         PyTuple_SetItem( args.get(), 0 , pyCtx.getAcquired() );
275         PyRef pyInstance( PyObject_CallObject( clazz.get() , args.get() ), SAL_NO_ACQUIRE );
276         runtime.pyObject2Any( pyInstance ) >>= ret;
277     }
278     return ret;
279 }
280 
281 }
282 
283 
284 static const struct cppu::ImplementationEntry g_entries[] =
285 {
286     {
287         pyuno_loader::CreateInstance, pyuno_loader::getImplementationName,
288         pyuno_loader::getSupportedServiceNames, cppu::createSingleComponentFactory,
289         nullptr , 0
290     },
291     { nullptr, nullptr, nullptr, nullptr, nullptr, 0 }
292 };
293 
294 extern "C"
295 {
296 
pythonloader_component_getFactory(const sal_Char * pImplName,void * pServiceManager,void * pRegistryKey)297 SAL_DLLPUBLIC_EXPORT void * pythonloader_component_getFactory(
298     const sal_Char * pImplName, void * pServiceManager, void * pRegistryKey )
299 {
300     return cppu::component_getFactoryHelper( pImplName, pServiceManager, pRegistryKey , g_entries );
301 }
302 
303 }
304 
305 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
306