Back to index

lightning-sunbird  0.9+nobinonly
pyloader.cpp
Go to the documentation of this file.
00001 /* ***** BEGIN LICENSE BLOCK *****
00002  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00003  *
00004  * The contents of this file are subject to the Mozilla Public License Version
00005  * 1.1 (the "License"); you may not use this file except in compliance with
00006  * the License. You may obtain a copy of the License at
00007  * http://www.mozilla.org/MPL/
00008  *
00009  * Software distributed under the License is distributed on an "AS IS" basis,
00010  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00011  * for the specific language governing rights and limitations under the
00012  * License.
00013  *
00014  * The Original Code is the Python XPCOM language bindings.
00015  *
00016  * The Initial Developer of the Original Code is
00017  * ActiveState Tool Corp.
00018  * Portions created by the Initial Developer are Copyright (C) 2000
00019  * the Initial Developer. All Rights Reserved.
00020  *
00021  * Contributor(s):
00022  *   Mark Hammond <mhammond@skippinet.com.au> (original author)
00023  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either the GNU General Public License Version 2 or later (the "GPL"), or
00026  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 // pyloader
00039 //
00040 // Not part of the main Python _xpcom package, but a separate, thin DLL.
00041 //
00042 // The main loader and registrar for Python.  A thin DLL that is designed to live in
00043 // the xpcom "components" directory.  Simply locates and loads the standard
00044 // _xpcom support module and transfers control to that.
00045 
00046 #include <Python.h>
00047 
00048 #ifdef HAVE_LONG_LONG
00049 #undef HAVE_LONG_LONG
00050 #endif
00051 
00052 #include "nsIComponentLoader.h"
00053 #include "nsISupports.h"
00054 #include "nsIModule.h"
00055 #include "nsDirectoryServiceDefs.h"
00056 #include "nsILocalFile.h"
00057 #include "nsString.h"
00058 #include "nsXPIDLString.h"
00059 #include "nsString.h"
00060 #include "stdlib.h"
00061 #include "stdarg.h"
00062 
00063 #include "nsReadableUtils.h"
00064 #include "nsCRT.h"
00065 #include "nspr.h" // PR_fprintf
00066 
00067 #if (PY_VERSION_HEX >= 0x02030000)
00068 #define PYXPCOM_USE_PYGILSTATE
00069 #endif
00070 
00071 static char *PyTraceback_AsString(PyObject *exc_tb);
00072 
00073 #ifdef XP_WIN
00074 #include "windows.h"
00075 #endif
00076 
00077 #ifdef XP_UNIX
00078 #include <dlfcn.h>
00079 #include <sys/stat.h>
00080 
00081 #endif
00082 
00083 #include "nsITimelineService.h"
00084 
00085 typedef nsresult (*pfnPyXPCOM_NSGetModule)(nsIComponentManager *servMgr,
00086                                           nsIFile* location,
00087                                           nsIModule** result);
00088 
00089 
00090 pfnPyXPCOM_NSGetModule pfnEntryPoint = nsnull;
00091 
00092 
00093 static void LogError(const char *fmt, ...);
00094 static void LogDebug(const char *fmt, ...);
00095 
00096 // Ensure that any paths guaranteed by this package exist on sys.path
00097 // Only called once as we are first loaded into the process.
00098 void AddStandardPaths()
00099 {
00100        // Put {bin}\Python on the path if it exists.
00101        nsresult rv;
00102        nsCOMPtr<nsIFile> aFile;
00103        rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, getter_AddRefs(aFile));
00104        if (NS_FAILED(rv)) {
00105               LogError("The Python XPCOM loader could not locate the 'bin' directory\n");
00106               return;
00107        }
00108        aFile->Append(NS_LITERAL_STRING("python"));
00109        nsAutoString pathBuf;
00110        aFile->GetPath(pathBuf);
00111        PyObject *obPath = PySys_GetObject("path");
00112        if (!obPath) {
00113               LogError("The Python XPCOM loader could not get the Python sys.path variable\n");
00114               return;
00115        }
00116        NS_LossyConvertUCS2toASCII pathCBuf(pathBuf);
00117        LogDebug("The Python XPCOM loader is adding '%s' to sys.path\n", pathCBuf.get());
00118        PyObject *newStr = PyString_FromString(pathCBuf.get());
00119        PyList_Insert(obPath, 0, newStr);
00120        Py_XDECREF(newStr);
00121        // And now try and get Python to process this directory as a "site dir" 
00122        // - ie, look for .pth files, etc
00123        nsCAutoString cmdBuf(NS_LITERAL_CSTRING("import site;site.addsitedir(r'") + pathCBuf + NS_LITERAL_CSTRING("')\n"));
00124        if (0 != PyRun_SimpleString((char *)cmdBuf.get())) {
00125               LogError("The directory '%s' could not be added as a site directory", pathCBuf.get());
00126               PyErr_Clear();
00127        }
00128        // and somewhat like Python itself (site, citecustomize), we attempt 
00129        // to import "sitepyxpcom" ignoring ImportError
00130        if (NULL==PyImport_ImportModule("sitepyxpcom")) {
00131               if (!PyErr_ExceptionMatches(PyExc_ImportError))
00132                      LogError("Failed to import 'sitepyxpcom'");
00133               PyErr_Clear();
00134        }
00135 }
00136 
00137 extern "C" NS_EXPORT nsresult NSGetModule(nsIComponentManager *servMgr,
00138                                           nsIFile* location,
00139                                           nsIModule** result)
00140 {
00141 #ifdef XP_UNIX
00142        // *sob* - seems necessary to open the .so as RTLD_GLOBAL
00143        dlopen(PYTHON_SO,RTLD_NOW | RTLD_GLOBAL);
00144 #endif
00145        PRBool bDidInitPython = !Py_IsInitialized(); // well, I will next line, anyway :-)
00146        if (bDidInitPython) {
00147               NS_TIMELINE_START_TIMER("PyXPCOM: Python initializing");
00148               Py_Initialize();
00149               if (!Py_IsInitialized()) {
00150                      LogError("Python initialization failed!\n");
00151                      return NS_ERROR_FAILURE;
00152               }
00153 #ifndef NS_DEBUG
00154               Py_OptimizeFlag = 1;
00155 #endif // NS_DEBUG
00156               AddStandardPaths();
00157               PyEval_InitThreads();
00158               NS_TIMELINE_STOP_TIMER("PyXPCOM: Python initializing");
00159               NS_TIMELINE_MARK_TIMER("PyXPCOM: Python initializing");
00160        }
00161        // Get the Python interpreter state
00162        NS_TIMELINE_START_TIMER("PyXPCOM: Python threadstate setup");
00163 #ifndef PYXPCOM_USE_PYGILSTATE
00164        PyThreadState *threadStateCreated = NULL;
00165        PyThreadState *threadState = PyThreadState_Swap(NULL);
00166        if (threadState==NULL) {
00167               // no thread-state - set one up.
00168               // *sigh* - what I consider a bug is that Python
00169               // will deadlock unless we own the lock before creating
00170               // a new interpreter (it appear Py_NewInterpreter has
00171               // really only been tested/used with no thread lock
00172               PyEval_AcquireLock();
00173               threadState = threadStateCreated = Py_NewInterpreter();
00174               PyThreadState_Swap(NULL);
00175        }
00176        PyEval_ReleaseLock();
00177        PyEval_AcquireThread(threadState);
00178 #else
00179        PyGILState_STATE state = PyGILState_Ensure();
00180 #endif // PYXPCOM_USE_PYGILSTATE
00181        if (pfnEntryPoint == nsnull) {
00182               PyObject *mod = PyImport_ImportModule("xpcom._xpcom");
00183               if (mod==NULL) {
00184                      LogError("Could not import the Python XPCOM extension\n");
00185                      return NS_ERROR_FAILURE;
00186               }
00187               PyObject *obpfn = PyObject_GetAttrString(mod, "_NSGetModule_FuncPtr");
00188               void *pfn = NULL;
00189               if (obpfn) {
00190                      NS_ABORT_IF_FALSE(PyLong_Check(obpfn)||PyInt_Check(obpfn), "xpcom._NSGetModule_FuncPtr is not a long!");
00191                      pfn = PyLong_AsVoidPtr(obpfn);
00192               }
00193               pfnEntryPoint = (pfnPyXPCOM_NSGetModule)pfn;
00194        }
00195        if (pfnEntryPoint==NULL) {
00196               LogError("Could not load main Python entry point\n");
00197               return NS_ERROR_FAILURE;
00198        }
00199 
00200 #ifdef MOZ_TIMELINE
00201        // If the timeline service is installed, see if we can install our hooks.
00202        if (NULL==PyImport_ImportModule("timeline_hook")) {
00203               if (!PyErr_ExceptionMatches(PyExc_ImportError))
00204                      LogError("Failed to import 'timeline_hook'");
00205               PyErr_Clear(); // but don't care if we can't.
00206        }
00207 #endif
00208 #ifndef PYXPCOM_USE_PYGILSTATE
00209        // Abandon the thread-lock, as the first thing Python does
00210        // is re-establish the lock (the Python thread-state story SUCKS!!!)
00211        if (threadStateCreated) {
00212               Py_EndInterpreter(threadStateCreated);
00213               PyEval_ReleaseLock(); // see Py_NewInterpreter call above 
00214        } else {
00215               PyEval_ReleaseThread(threadState);
00216               PyThreadState *threadStateSave = PyThreadState_Swap(NULL);
00217               if (threadStateSave)
00218                      PyThreadState_Delete(threadStateSave);
00219        }
00220 #else
00221        // If we initialized Python, then we will also have acquired the thread
00222        // lock.  In that case, we want to leave it unlocked, so other threads
00223        // are free to run, even if they aren't running Python code.
00224        PyGILState_Release(bDidInitPython ? PyGILState_UNLOCKED : state);
00225 #endif 
00226 
00227        NS_TIMELINE_STOP_TIMER("PyXPCOM: Python threadstate setup");
00228        NS_TIMELINE_MARK_TIMER("PyXPCOM: Python threadstate setup");
00229        NS_TIMELINE_START_TIMER("PyXPCOM: PyXPCOM NSGetModule entry point");
00230        nsresult rc = (*pfnEntryPoint)(servMgr, location, result);
00231        NS_TIMELINE_STOP_TIMER("PyXPCOM: PyXPCOM NSGetModule entry point");
00232        NS_TIMELINE_MARK_TIMER("PyXPCOM: PyXPCOM NSGetModule entry point");
00233        return rc;
00234 }
00235 
00236 // The internal helper that actually moves the
00237 // formatted string to the target!
00238 
00239 void LogMessage(const char *prefix, const char *pszMessageText)
00240 {
00241        PR_fprintf(PR_STDERR, "%s", pszMessageText);
00242 }
00243 
00244 void LogMessage(const char *prefix, nsACString &text)
00245 {
00246        LogMessage(prefix, nsPromiseFlatCString(text).get());
00247 }
00248 
00249 // A helper for the various logging routines.
00250 static void VLogF(const char *prefix, const char *fmt, va_list argptr)
00251 {
00252        char buff[512];
00253 
00254        vsprintf(buff, fmt, argptr);
00255 
00256        LogMessage(prefix, buff);
00257 }
00258 
00259 static void LogError(const char *fmt, ...)
00260 {
00261        va_list marker;
00262        va_start(marker, fmt);
00263        VLogF("PyXPCOM Loader Error: ", fmt, marker);
00264        // If we have a Python exception, also log that:
00265        PyObject *exc_typ = NULL, *exc_val = NULL, *exc_tb = NULL;
00266        PyErr_Fetch( &exc_typ, &exc_val, &exc_tb);
00267        if (exc_typ) {
00268               nsCAutoString streamout;
00269 
00270               if (exc_tb) {
00271                      const char *szTraceback = PyTraceback_AsString(exc_tb);
00272                      if (szTraceback == NULL)
00273                             streamout += "Can't get the traceback info!";
00274                      else {
00275                             streamout += "Traceback (most recent call last):\n";
00276                             streamout += szTraceback;
00277                             PyMem_Free((void *)szTraceback);
00278                      }
00279               }
00280               PyObject *temp = PyObject_Str(exc_typ);
00281               if (temp) {
00282                      streamout += PyString_AsString(temp);
00283                      Py_DECREF(temp);
00284               } else
00285                      streamout += "Can convert exception to a string!";
00286               streamout += ": ";
00287               if (exc_val != NULL) {
00288                      temp = PyObject_Str(exc_val);
00289                      if (temp) {
00290                             streamout += PyString_AsString(temp);
00291                             Py_DECREF(temp);
00292                      } else
00293                             streamout += "Can convert exception value to a string!";
00294               }
00295               streamout += "\n";
00296               LogMessage("PyXPCOM Exception:", streamout);
00297        }
00298        PyErr_Restore(exc_typ, exc_val, exc_tb);
00299 }
00300 /*** - not currently used - silence compiler warning.
00301 static void LogWarning(const char *fmt, ...)
00302 {
00303        va_list marker;
00304        va_start(marker, fmt);
00305        VLogF("PyXPCOM Loader Warning: ", fmt, marker);
00306 }
00307 ***/
00308 #ifdef DEBUG
00309 static void LogDebug(const char *fmt, ...)
00310 {
00311        va_list marker;
00312        va_start(marker, fmt);
00313        VLogF("PyXPCOM Loader Debug: ", fmt, marker);
00314 }
00315 #else
00316 static void LogDebug(const char *fmt, ...)
00317 {
00318 }
00319 #endif
00320 
00321 /* Obtains a string from a Python traceback.
00322    This is the exact same string as "traceback.print_exc" would return.
00323 
00324    Pass in a Python traceback object (probably obtained from PyErr_Fetch())
00325    Result is a string which must be free'd using PyMem_Free()
00326 */
00327 #define TRACEBACK_FETCH_ERROR(what) {errMsg = what; goto done;}
00328 
00329 char *PyTraceback_AsString(PyObject *exc_tb)
00330 {
00331        char *errMsg = NULL; /* a static that hold a local error message */
00332        char *result = NULL; /* a valid, allocated result. */
00333        PyObject *modStringIO = NULL;
00334        PyObject *modTB = NULL;
00335        PyObject *obFuncStringIO = NULL;
00336        PyObject *obStringIO = NULL;
00337        PyObject *obFuncTB = NULL;
00338        PyObject *argsTB = NULL;
00339        PyObject *obResult = NULL;
00340 
00341        /* Import the modules we need - cStringIO and traceback */
00342        modStringIO = PyImport_ImportModule("cStringIO");
00343        if (modStringIO==NULL)
00344               TRACEBACK_FETCH_ERROR("cant import cStringIO\n");
00345 
00346        modTB = PyImport_ImportModule("traceback");
00347        if (modTB==NULL)
00348               TRACEBACK_FETCH_ERROR("cant import traceback\n");
00349        /* Construct a cStringIO object */
00350        obFuncStringIO = PyObject_GetAttrString(modStringIO, "StringIO");
00351        if (obFuncStringIO==NULL)
00352               TRACEBACK_FETCH_ERROR("cant find cStringIO.StringIO\n");
00353        obStringIO = PyObject_CallObject(obFuncStringIO, NULL);
00354        if (obStringIO==NULL)
00355               TRACEBACK_FETCH_ERROR("cStringIO.StringIO() failed\n");
00356        /* Get the traceback.print_exception function, and call it. */
00357        obFuncTB = PyObject_GetAttrString(modTB, "print_tb");
00358        if (obFuncTB==NULL)
00359               TRACEBACK_FETCH_ERROR("cant find traceback.print_tb\n");
00360 
00361        argsTB = Py_BuildValue("OOO", 
00362                      exc_tb  ? exc_tb  : Py_None,
00363                      Py_None, 
00364                      obStringIO);
00365        if (argsTB==NULL) 
00366               TRACEBACK_FETCH_ERROR("cant make print_tb arguments\n");
00367 
00368        obResult = PyObject_CallObject(obFuncTB, argsTB);
00369        if (obResult==NULL) 
00370               TRACEBACK_FETCH_ERROR("traceback.print_tb() failed\n");
00371        /* Now call the getvalue() method in the StringIO instance */
00372        Py_DECREF(obFuncStringIO);
00373        obFuncStringIO = PyObject_GetAttrString(obStringIO, "getvalue");
00374        if (obFuncStringIO==NULL)
00375               TRACEBACK_FETCH_ERROR("cant find getvalue function\n");
00376        Py_DECREF(obResult);
00377        obResult = PyObject_CallObject(obFuncStringIO, NULL);
00378        if (obResult==NULL) 
00379               TRACEBACK_FETCH_ERROR("getvalue() failed.\n");
00380 
00381        /* And it should be a string all ready to go - duplicate it. */
00382        if (!PyString_Check(obResult))
00383                      TRACEBACK_FETCH_ERROR("getvalue() did not return a string\n");
00384 
00385        { // a temp scope so I can use temp locals.
00386        char *tempResult = PyString_AsString(obResult);
00387        result = (char *)PyMem_Malloc(strlen(tempResult)+1);
00388        if (result==NULL)
00389               TRACEBACK_FETCH_ERROR("memory error duplicating the traceback string");
00390 
00391        strcpy(result, tempResult);
00392        } // end of temp scope.
00393 done:
00394        /* All finished - first see if we encountered an error */
00395        if (result==NULL && errMsg != NULL) {
00396               result = (char *)PyMem_Malloc(strlen(errMsg)+1);
00397               if (result != NULL)
00398                      /* if it does, not much we can do! */
00399                      strcpy(result, errMsg);
00400        }
00401        Py_XDECREF(modStringIO);
00402        Py_XDECREF(modTB);
00403        Py_XDECREF(obFuncStringIO);
00404        Py_XDECREF(obStringIO);
00405        Py_XDECREF(obFuncTB);
00406        Py_XDECREF(argsTB);
00407        Py_XDECREF(obResult);
00408        return result;
00409 }