/* Convert a UTF-8 string to an ANSI string, also attempting to get the MS-DOS ShortFileName if the string is a filename. ShortFileName allows Python 2.7 to accept filenames which cannot encode in the current ANSI codepage. Preserves the filename's original basename, since the bootloader code depends on the unmodified basename. Assumes that the basename can be encoded using the current ANSI codepage. This is a workaround for <https://github.com/pyinstaller/pyinstaller/issues/298>. Copies the converted string to `dest`, which must be a buffer of at least PATH_MAX characters. Returns 'dest' if successful. Returns NULL and logs error reason if encoding fails. */ char * pyi_win32_utf8_to_mbs_sfn_keep_basename(char * dest, const char * src) { char * mbs_buffer; char * mbs_sfn_buffer; char basename[PATH_MAX]; char dirname[PATH_MAX]; /* Convert path to mbs*/ mbs_buffer = pyi_win32_utf8_to_mbs(NULL, src, 0); if(NULL == mbs_buffer) { return NULL; } /* Convert path again to mbs, this time with SFN */ mbs_sfn_buffer = pyi_win32_utf8_to_mbs_sfn(NULL, src, 0); if(NULL == mbs_sfn_buffer) { free(mbs_buffer); return NULL; } pyi_path_basename(basename, mbs_buffer); pyi_path_dirname(dirname, mbs_sfn_buffer); pyi_path_join(dest, dirname, basename); free(mbs_buffer); free(mbs_sfn_buffer); return dest; }
/* * Install a zlib from a toc entry. * * Must be called after Py_Initialize (i.e. after pyi_pylib_start_python) * * The installation is done by adding an entry like * absolute_path/dist/hello_world/hello_world?123456 * to sys.path. The end number is the offset where the * Python bootstrap code should read the zip data. * Return non zero on failure. * NB: This entry is removed from sys.path by the bootstrap scripts. */ int pyi_pylib_install_zlib(ARCHIVE_STATUS *status, TOC *ptoc) { int rc = 0; int zlibpos = status->pkgstart + ntohl(ptoc->pos); PyObject * sys_path, *zlib_entry, *archivename_obj; char *archivename; /* Note that sys.path contains PyString on py2, and PyUnicode on py3. Ensure * that filenames are encoded or decoded correctly. */ if(is_py2) { #ifdef _WIN32 /* Must be MBCS encoded. Use SFN if possible. * * We could instead pass the UTF-8 encoded form and modify FrozenImporter to * decode it on Windows, but this breaks the convention that `sys.path` * entries on Windows are MBCS encoded, and may interfere with any code * that inspects `sys.path` * * We could also pass the zlib path through a channel other than `sys.path` * to sidestep that requirement, but there's not much benefit as this only * improves non-codepage/non-SFN compatibility for the zlib and not any other * importable modules. */ archivename = pyi_win32_utf8_to_mbs_sfn(NULL, status->archivename, 0); if(NULL == archivename) { FATALERROR("Failed to convert %s to ShortFileName\n", status->archivename); return -1; } #else /* Use system-provided path. No encoding required. */ archivename = status->archivename; #endif zlib_entry = PI_PyString_FromFormat("%s?%d", archivename, zlibpos); if(archivename != status->archivename) free(archivename); } else { #ifdef _WIN32 /* Decode UTF-8 to PyUnicode */ archivename_obj = PI_PyUnicode_Decode(status->archivename, strlen(status->archivename), "utf-8", "strict"); #else /* Decode locale-encoded filename to PyUnicode object using Python's * preferred decoding method for filenames. */ archivename_obj = PI_PyUnicode_DecodeFSDefault(status->archivename); #endif zlib_entry = PI_PyUnicode_FromFormat("%U?%d", archivename_obj, zlibpos); PI_Py_DecRef(archivename_obj); } sys_path = PI_PySys_GetObject("path"); if(NULL == sys_path) { FATALERROR("Installing PYZ: Could not get sys.path\n"); PI_Py_DecRef(zlib_entry); return -1; } rc = PI_PyList_Append(sys_path, zlib_entry); if(rc) { FATALERROR("Failed to append to sys.path\n"); } return rc; }
/* * Import modules embedded in the archive - return 0 on success */ int pyi_pylib_import_modules(ARCHIVE_STATUS *status) { PyObject *marshal; PyObject *marshaldict; PyObject *loadfunc; TOC *ptoc; PyObject *co; PyObject *mod; PyObject *meipass_obj; char * meipass_ansi; VS("LOADER: setting sys._MEIPASS\n"); // TODO extract function pyi_char_to_pyobject if(is_py2) { #ifdef _WIN32 meipass_ansi = pyi_win32_utf8_to_mbs_sfn(NULL, status->mainpath, 0); if(!meipass_ansi) { FATALERROR("Failed to encode _MEIPASS as ANSI.\n"); return -1; } meipass_obj = PI_PyString_FromString(meipass_ansi); free(meipass_ansi); #else meipass_obj = PI_PyString_FromString(status->mainpath); #endif } else { #ifdef _WIN32 meipass_obj = PI_PyUnicode_Decode(status->mainpath, strlen(status->mainpath), "utf-8", "strict"); #else meipass_obj = PI_PyUnicode_DecodeFSDefault(status->mainpath); #endif } if(!meipass_obj) { FATALERROR("Failed to get _MEIPASS as PyObject.\n"); return -1; } PI_PySys_SetObject("_MEIPASS", meipass_obj); VS("LOADER: importing modules from CArchive\n"); /* Get the Python function marshall.load * Here we collect some reference to PyObject that we don't dereference * Doesn't matter because the objects won't be going away anyway. */ marshal = PI_PyImport_ImportModule("marshal"); marshaldict = PI_PyModule_GetDict(marshal); loadfunc = PI_PyDict_GetItemString(marshaldict, "loads"); /* Iterate through toc looking for module entries (type 'm') * this is normally just bootstrap stuff (archive and iu) */ ptoc = status->tocbuff; while (ptoc < status->tocend) { if (ptoc->typcd == ARCHIVE_ITEM_PYMODULE || ptoc->typcd == ARCHIVE_ITEM_PYPACKAGE) { unsigned char *modbuf = pyi_arch_extract(status, ptoc); VS("LOADER: extracted %s\n", ptoc->name); /* .pyc/.pyo files have 8 bytes header. Skip it and load marshalled * data form the right point. */ if (is_py2) { co = PI_PyObject_CallFunction(loadfunc, "s#", modbuf+8, ntohl(ptoc->ulen)-8); } else { // It looks like from python 3.3 the header // size was changed to 12 bytes. co = PI_PyObject_CallFunction(loadfunc, "y#", modbuf+12, ntohl(ptoc->ulen)-12); }; if (co != NULL) { VS("LOADER: callfunction returned...\n"); mod = PI_PyImport_ExecCodeModule(ptoc->name, co); } else { // TODO callfunctions might return NULL - find yout why and foor what modules. VS("LOADER: callfunction returned NULL"); mod = NULL; } /* Check for errors in loading */ if (mod == NULL) { FATALERROR("mod is NULL - %s", ptoc->name); } if (PI_PyErr_Occurred()) { PI_PyErr_Print(); PI_PyErr_Clear(); } free(modbuf); } ptoc = pyi_arch_increment_toc_ptr(status, ptoc); } return 0; }
/* * Start python - return 0 on success */ int pyi_pylib_start_python(ARCHIVE_STATUS *status) { /* Set sys.path, sys.prefix, and sys.executable so dynamic libs will load. * * The Python APIs we use here (Py_SetProgramName, Py_SetPythonHome) * specify their argument should be a "string in static storage". * That is, the APIs use the string pointer as given and will neither copy * its contents nor free its memory. * * NOTE: Statics are zero-initialized. */ static char pypath[2*PATH_MAX + 14]; static char pypath_sfn[2*PATH_MAX +14]; static char pyhome[PATH_MAX+1]; static char progname[PATH_MAX+1]; /* Wide string forms of the above, for Python 3. */ static wchar_t pypath_w[PATH_MAX+1]; static wchar_t pyhome_w[PATH_MAX+1]; static wchar_t progname_w[PATH_MAX+1]; if (is_py2) { #ifdef _WIN32 /* Use ShortFileName - affects sys.executable */ if(!pyi_win32_utf8_to_mbs_sfn(progname, status->archivename, PATH_MAX)) { FATALERROR("Failed to convert progname to wchar_t\n"); return -1; } #else /* Use system-provided filename. No encoding. */ strncpy(progname, status->archivename, PATH_MAX); #endif PI_Py2_SetProgramName(progname); } else { /* Decode using current locale */ if(!pyi_locale_char2wchar(progname_w, status->archivename, PATH_MAX)) { FATALERROR("Failed to convert progname to wchar_t\n"); return -1; } // In Python 3 Py_SetProgramName() should be called before Py_SetPath(). PI_Py_SetProgramName(progname_w); }; /* Set sys.path */ VS("LOADER: Manipulating environment (sys.path, sys.prefix)\n"); if(is_py2) { /* sys.path = [mainpath] */ strncpy(pypath, status->mainpath, strlen(status->mainpath)); } else { /* sys.path = [base_library, mainpath] */ strncpy(pypath, status->mainpath, strlen(status->mainpath)); strncat(pypath, PYI_SEPSTR, strlen(PYI_SEPSTR)); strncat(pypath, "base_library.zip", strlen("base_library.zip")); strncat(pypath, PYI_PATHSEPSTR, strlen(PYI_PATHSEPSTR)); strncat(pypath, status->mainpath, strlen(status->mainpath)); }; /* * On Python 3, we must set sys.path to have base_library.zip before * calling Py_Initialize as it needs `encodings` and other modules. */ if (!is_py2) { /* Decode using current locale */ if(!pyi_locale_char2wchar(pypath_w, pypath, PATH_MAX)) { FATALERROR("Failed to convert pypath to wchar_t\n"); return -1; } VS("LOADER: Pre-init sys.path is %s\n", pypath); PI_Py_SetPath(pypath_w); }; /* Set sys.prefix and sys.exec_prefix using Py_SetPythonHome */ if (is_py2) { #ifdef _WIN32 if(!pyi_win32_utf8_to_mbs_sfn(pyhome, status->mainpath, PATH_MAX)) { FATALERROR("Failed to convert pyhome to ANSI (invalid multibyte string)\n"); return -1; } #else strcpy(pyhome, status->mainpath); #endif VS("LOADER: sys.prefix is %s\n", pyhome); PI_Py2_SetPythonHome(pyhome); } else { /* Decode using current locale */ if(!pyi_locale_char2wchar(pyhome_w, status->mainpath, PATH_MAX)) { FATALERROR("Failed to convert pyhome to wchar_t\n"); return -1; } VS("LOADER: sys.prefix is %s\n", status->mainpath); PI_Py_SetPythonHome(pyhome_w); }; /* Start python. */ VS("LOADER: Setting runtime options\n"); pyi_pylib_set_runtime_opts(status); /* * Py_Initialize() may rudely call abort(), and on Windows this triggers the error * reporting service, which results in a dialog box that says "Close program", "Check * for a solution", and also "Debug" if Visual Studio is installed. The dialog box * makes it frustrating to run the test suite. * * For debug builds of the bootloader, disable the error reporting before calling * Py_Initialize and enable it afterward. */ #if defined(_WIN32) && defined(LAUNCH_DEBUG) SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); #endif VS("LOADER: Initializing python\n"); PI_Py_Initialize(); #if defined(_WIN32) && defined(LAUNCH_DEBUG) SetErrorMode(0); #endif /* * Set sys.path list. * Python's default sys.path is no good - it includes the working directory * and the folder containing the executable. Replace sys.path with only * the paths we want. */ VS("LOADER: Overriding Python's sys.path\n"); VS("LOADER: Post-init sys.path is %s\n", pypath); if (is_py2) { #ifdef _WIN32 if(!pyi_win32_utf8_to_mbs_sfn(pypath_sfn, pypath, PATH_MAX)) { FATALERROR("Failed to convert pypath to ANSI (invalid multibyte string)\n"); } PI_Py2Sys_SetPath(pypath_sfn); #else PI_Py2Sys_SetPath(pypath); #endif } else { PI_PySys_SetPath(pypath_w); }; /* Setting sys.argv should be after Py_Initialize() call. */ if(pyi_pylib_set_sys_argv(status)) { return -1; } /* Check for a python error */ if (PI_PyErr_Occurred()) { FATALERROR("Error detected starting Python VM."); return -1; } return 0; }