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