diff options
Diffstat (limited to 'PC/getpathp.c')
-rw-r--r-- | PC/getpathp.c | 276 |
1 files changed, 192 insertions, 84 deletions
diff --git a/PC/getpathp.c b/PC/getpathp.c index c7ddf1e..0b0ae49 100644 --- a/PC/getpathp.c +++ b/PC/getpathp.c @@ -6,7 +6,9 @@ PATH RULES FOR WINDOWS: This describes how sys.path is formed on Windows. It describes the functionality, not the implementation (ie, the order in which these - are actually fetched is different) + are actually fetched is different). The presence of a python._pth or + pythonXY._pth file alongside the program overrides these rules - see + below. * Python always adds an empty entry at the start, which corresponds to the current directory. @@ -23,9 +25,9 @@ * We attempt to locate the "Python Home" - if the PYTHONHOME env var is set, we believe it. Otherwise, we use the path of our host .EXE's - to try and locate our "landmark" (lib\\os.py) and deduce our home. + to try and locate one of our "landmarks" and deduce our home. - If we DO have a Python Home: The relevant sub-directories (Lib, - plat-win, etc) are based on the Python Home + DLLs, etc) are based on the Python Home - If we DO NOT have a Python Home, the core Python Path is loaded from the registry. This is the main PythonPath key, and both HKLM and HKCU are combined to form the path) @@ -33,7 +35,23 @@ * Iff - we can not locate the Python Home, have not had a PYTHONPATH specified, and can't locate any Registry entries (ie, we have _nothing_ we can assume is a good path), a default path with relative entries is - used (eg. .\Lib;.\plat-win, etc) + used (eg. .\Lib;.\DLLs, etc) + + + If a '._pth' file exists adjacent to the executable with the same base name + (e.g. python._pth adjacent to python.exe) or adjacent to the shared library + (e.g. python36._pth adjacent to python36.dll), it is used in preference to + the above process. The shared library file takes precedence over the + executable. The path file must contain a list of paths to add to sys.path, + one per line. Each path is relative to the directory containing the file. + Blank lines and comments beginning with '#' are permitted. + + In the presence of this ._pth file, no other paths are added to the search + path, the registry finder is not enabled, site.py is not imported and + isolated mode is enabled. The site package can be enabled by including a + line reading "import site"; no other imports are recognized. Any invalid + entry (other than directories that do not exist) will result in immediate + termination of the program. The end result of all this is: @@ -52,7 +70,11 @@ some default, but relative, paths. * An embedding application can use Py_SetPath() to override all of - these authomatic path computations. + these automatic path computations. + + * An install of Python can fully specify the contents of sys.path using + either a 'EXENAME._pth' or 'DLLNAME._pth' file, optionally including + "import site" to enable the site module. ---------------------------------------------------------------- */ @@ -61,10 +83,13 @@ #include "osdefs.h" #include <wchar.h> -#ifdef MS_WINDOWS -#include <windows.h> +#ifndef MS_WINDOWS +#error getpathp.c should only be built on Windows #endif +#include <windows.h> +#include <Shlwapi.h> + #ifdef HAVE_SYS_TYPES_H #include <sys/types.h> #endif /* HAVE_SYS_TYPES_H */ @@ -122,6 +147,33 @@ reduce(wchar_t *dir) dir[i] = '\0'; } +static int +change_ext(wchar_t *dest, const wchar_t *src, const wchar_t *ext) +{ + size_t src_len = wcsnlen_s(src, MAXPATHLEN+1); + size_t i = src_len; + if (i >= MAXPATHLEN+1) + Py_FatalError("buffer overflow in getpathp.c's reduce()"); + + while (i > 0 && src[i] != '.' && !is_sep(src[i])) + --i; + + if (i == 0) { + dest[0] = '\0'; + return -1; + } + + if (is_sep(src[i])) + i = src_len; + + if (wcsncpy_s(dest, MAXPATHLEN+1, src, i) || + wcscat_s(dest, MAXPATHLEN+1, ext)) { + dest[0] = '\0'; + return -1; + } + + return 0; +} static int exists(wchar_t *filename) @@ -135,7 +187,7 @@ exists(wchar_t *filename) static int ismodule(wchar_t *filename, int update_filename) /* Is module -- check for .pyc/.pyo too */ { - int n; + size_t n; if (exists(filename)) return 1; @@ -163,24 +215,30 @@ ismodule(wchar_t *filename, int update_filename) /* Is module -- check for .pyc/ than MAXPATHLEN characters at exit. If stuff is too long, only as much of stuff as fits will be appended. */ + +static int _PathCchCombineEx_Initialized = 0; +typedef HRESULT(__stdcall *PPathCchCombineEx)(PWSTR pszPathOut, size_t cchPathOut, PCWSTR pszPathIn, PCWSTR pszMore, unsigned long dwFlags); +static PPathCchCombineEx _PathCchCombineEx; + static void join(wchar_t *buffer, const wchar_t *stuff) { - size_t n; - if (is_sep(stuff[0]) || - (wcsnlen_s(stuff, 4) >= 3 && stuff[1] == ':' && is_sep(stuff[2]))) { - if (wcscpy_s(buffer, MAXPATHLEN+1, stuff) != 0) - Py_FatalError("buffer overflow in getpathp.c's join()"); - return; + if (_PathCchCombineEx_Initialized == 0) { + HMODULE pathapi = LoadLibraryW(L"api-ms-win-core-path-l1-1-0.dll"); + if (pathapi) + _PathCchCombineEx = (PPathCchCombineEx)GetProcAddress(pathapi, "PathCchCombineEx"); + else + _PathCchCombineEx = NULL; + _PathCchCombineEx_Initialized = 1; } - n = wcsnlen_s(buffer, MAXPATHLEN+1); - if (n > 0 && !is_sep(buffer[n - 1]) && n < MAXPATHLEN) { - buffer[n] = SEP; - buffer[n + 1] = '\0'; + if (_PathCchCombineEx) { + if (FAILED(_PathCchCombineEx(buffer, MAXPATHLEN+1, buffer, stuff, 0))) + Py_FatalError("buffer overflow in getpathp.c's join()"); + } else { + if (!PathCombineW(buffer, buffer, stuff)) + Py_FatalError("buffer overflow in getpathp.c's join()"); } - if (wcscat_s(buffer, MAXPATHLEN+1, stuff) != 0) - Py_FatalError("buffer overflow in getpathp.c's join()"); } /* gotlandmark only called by search_for_prefix, which ensures @@ -188,7 +246,7 @@ join(wchar_t *buffer, const wchar_t *stuff) 'landmark' can not overflow prefix if too long. */ static int -gotlandmark(wchar_t *landmark) +gotlandmark(const wchar_t *landmark) { int ok; Py_ssize_t n = wcsnlen_s(prefix, MAXPATHLEN); @@ -202,7 +260,7 @@ gotlandmark(wchar_t *landmark) /* assumes argv0_path is MAXPATHLEN+1 bytes long, already \0 term'd. assumption provided by only caller, calculate_path() */ static int -search_for_prefix(wchar_t *argv0_path, wchar_t *landmark) +search_for_prefix(wchar_t *argv0_path, const wchar_t *landmark) { /* Search from argv0_path, until landmark is found */ wcscpy_s(prefix, MAXPATHLEN + 1, argv0_path); @@ -214,7 +272,6 @@ search_for_prefix(wchar_t *argv0_path, wchar_t *landmark) return 0; } -#ifdef MS_WINDOWS #ifdef Py_ENABLE_SHARED /* a string loaded from the DLL at startup.*/ @@ -369,7 +426,6 @@ done: return retval; } #endif /* Py_ENABLE_SHARED */ -#endif /* MS_WINDOWS */ static void get_progpath(void) @@ -378,7 +434,6 @@ get_progpath(void) wchar_t *path = _wgetenv(L"PATH"); wchar_t *prog = Py_GetProgramName(); -#ifdef MS_WINDOWS #ifdef Py_ENABLE_SHARED extern HANDLE PyWin_DLLhModule; /* static init of progpath ensures final char remains \0 */ @@ -390,7 +445,6 @@ get_progpath(void) #endif if (GetModuleFileNameW(NULL, progpath, MAXPATHLEN)) return; -#endif if (prog == NULL || *prog == '\0') prog = L"python"; @@ -483,6 +537,92 @@ find_env_config_value(FILE * env_file, const wchar_t * key, wchar_t * value) return result; } +static int +read_pth_file(const wchar_t *path, wchar_t *prefix, int *isolated, int *nosite) +{ + FILE *sp_file = _Py_wfopen(path, L"r"); + if (sp_file == NULL) + return -1; + + wcscpy_s(prefix, MAXPATHLEN+1, path); + reduce(prefix); + *isolated = 1; + *nosite = 1; + + size_t bufsiz = MAXPATHLEN; + size_t prefixlen = wcslen(prefix); + + wchar_t *buf = (wchar_t*)PyMem_RawMalloc(bufsiz * sizeof(wchar_t)); + buf[0] = '\0'; + + while (!feof(sp_file)) { + char line[MAXPATHLEN + 1]; + char *p = fgets(line, MAXPATHLEN + 1, sp_file); + if (!p) + break; + if (*p == '\0' || *p == '#') + continue; + while (*++p) { + if (*p == '\r' || *p == '\n') { + *p = '\0'; + break; + } + } + + if (strcmp(line, "import site") == 0) { + *nosite = 0; + continue; + } else if (strncmp(line, "import ", 7) == 0) { + Py_FatalError("only 'import site' is supported in ._pth file"); + } + + DWORD wn = MultiByteToWideChar(CP_UTF8, 0, line, -1, NULL, 0); + wchar_t *wline = (wchar_t*)PyMem_RawMalloc((wn + 1) * sizeof(wchar_t)); + wn = MultiByteToWideChar(CP_UTF8, 0, line, -1, wline, wn + 1); + wline[wn] = '\0'; + + size_t usedsiz = wcslen(buf); + while (usedsiz + wn + prefixlen + 4 > bufsiz) { + bufsiz += MAXPATHLEN; + buf = (wchar_t*)PyMem_RawRealloc(buf, (bufsiz + 1) * sizeof(wchar_t)); + if (!buf) { + PyMem_RawFree(wline); + goto error; + } + } + + if (usedsiz) { + wcscat_s(buf, bufsiz, L";"); + usedsiz += 1; + } + + errno_t result; + _Py_BEGIN_SUPPRESS_IPH + result = wcscat_s(buf, bufsiz, prefix); + _Py_END_SUPPRESS_IPH + if (result == EINVAL) { + Py_FatalError("invalid argument during ._pth processing"); + } else if (result == ERANGE) { + Py_FatalError("buffer overflow during ._pth processing"); + } + wchar_t *b = &buf[usedsiz]; + join(b, wline); + + PyMem_RawFree(wline); + } + + module_search_path = buf; + + fclose(sp_file); + return 0; + +error: + PyMem_RawFree(buf); + fclose(sp_file); + return -1; +} + + static void calculate_path(void) { @@ -492,32 +632,33 @@ calculate_path(void) wchar_t *pythonhome = Py_GetPythonHome(); wchar_t *envpath = NULL; -#ifdef MS_WINDOWS int skiphome, skipdefault; wchar_t *machinepath = NULL; wchar_t *userpath = NULL; wchar_t zip_path[MAXPATHLEN+1]; - int applocal = 0; if (!Py_IgnoreEnvironmentFlag) { envpath = _wgetenv(L"PYTHONPATH"); } -#else - char *_envpath = Py_GETENV("PYTHONPATH"); - wchar_t wenvpath[MAXPATHLEN+1]; - if (_envpath) { - size_t r = mbstowcs(wenvpath, _envpath, MAXPATHLEN+1); - envpath = wenvpath; - if (r == (size_t)-1 || r >= MAXPATHLEN) - envpath = NULL; - } -#endif get_progpath(); /* progpath guaranteed \0 terminated in MAXPATH+1 bytes. */ wcscpy_s(argv0_path, MAXPATHLEN+1, progpath); reduce(argv0_path); + /* Search for a sys.path file */ + { + wchar_t spbuffer[MAXPATHLEN+1]; + + if ((dllpath[0] && !change_ext(spbuffer, dllpath, L"._pth") && exists(spbuffer)) || + (progpath[0] && !change_ext(spbuffer, progpath, L"._pth") && exists(spbuffer))) { + + if (!read_pth_file(spbuffer, prefix, &Py_IsolatedFlag, &Py_NoSiteFlag)) { + return; + } + } + } + /* Search for an environment configuration file, first in the executable's directory and then in the parent directory. If found, open it for use when searching for prefixes. @@ -543,17 +684,6 @@ calculate_path(void) } } if (env_file != NULL) { - /* Look for an 'applocal' variable and, if true, ignore all registry - * keys and environment variables, but retain the default paths - * (DLLs, Lib) and the zip file. Setting pythonhome here suppresses - * the search for LANDMARK below and overrides %PYTHONHOME%. - */ - if (find_env_config_value(env_file, L"applocal", tmpbuffer) && - (applocal = (wcsicmp(tmpbuffer, L"true") == 0))) { - envpath = NULL; - pythonhome = argv0_path; - } - /* Look for a 'home' variable and set argv0_path to it, if found */ if (find_env_config_value(env_file, L"home", tmpbuffer)) { wcscpy_s(argv0_path, MAXPATHLEN+1, tmpbuffer); @@ -563,8 +693,15 @@ calculate_path(void) } } + /* Calculate zip archive path from DLL or exe path */ + change_ext(zip_path, dllpath[0] ? dllpath : progpath, L".zip"); + if (pythonhome == NULL || *pythonhome == '\0') { - if (search_for_prefix(argv0_path, LANDMARK)) + if (zip_path[0] && exists(zip_path)) { + wcscpy_s(prefix, MAXPATHLEN+1, zip_path); + reduce(prefix); + pythonhome = prefix; + } else if (search_for_prefix(argv0_path, LANDMARK)) pythonhome = prefix; else pythonhome = NULL; @@ -576,30 +713,15 @@ calculate_path(void) envpath = NULL; -#ifdef MS_WINDOWS - /* Calculate zip archive path from DLL or exe path */ - if (wcscpy_s(zip_path, MAXPATHLEN+1, dllpath[0] ? dllpath : progpath)) - /* exceeded buffer length - ignore zip_path */ - zip_path[0] = '\0'; - else { - wchar_t *dot = wcsrchr(zip_path, '.'); - if (!dot || wcscpy_s(dot, MAXPATHLEN+1 - (dot - zip_path), L".zip")) - /* exceeded buffer length - ignore zip_path */ - zip_path[0] = L'\0'; - } - skiphome = pythonhome==NULL ? 0 : 1; #ifdef Py_ENABLE_SHARED - if (!applocal) { - machinepath = getpythonregpath(HKEY_LOCAL_MACHINE, skiphome); - userpath = getpythonregpath(HKEY_CURRENT_USER, skiphome); - } + machinepath = getpythonregpath(HKEY_LOCAL_MACHINE, skiphome); + userpath = getpythonregpath(HKEY_CURRENT_USER, skiphome); #endif /* We only use the default relative PYTHONPATH if we havent anything better to use! */ skipdefault = envpath!=NULL || pythonhome!=NULL || \ machinepath!=NULL || userpath!=NULL; -#endif /* We need to construct a path from the following parts. (1) the PYTHONPATH environment variable, if set; @@ -612,7 +734,6 @@ calculate_path(void) Extra rules: - If PYTHONHOME is set (in any way) item (3) is ignored. - If registry values are used, (4) and (5) are ignored. - - If applocal is set, (1), (3), and registry values are ignored */ /* Calculate size of return buffer */ @@ -629,13 +750,11 @@ calculate_path(void) bufsz = 0; bufsz += wcslen(PYTHONPATH) + 1; bufsz += wcslen(argv0_path) + 1; -#ifdef MS_WINDOWS - if (!applocal && userpath) + if (userpath) bufsz += wcslen(userpath) + 1; - if (!applocal && machinepath) + if (machinepath) bufsz += wcslen(machinepath) + 1; bufsz += wcslen(zip_path) + 1; -#endif if (envpath != NULL) bufsz += wcslen(envpath) + 1; @@ -651,10 +770,8 @@ calculate_path(void) fprintf(stderr, "Using default static path.\n"); module_search_path = PYTHONPATH; } -#ifdef MS_WINDOWS PyMem_RawFree(machinepath); PyMem_RawFree(userpath); -#endif /* MS_WINDOWS */ return; } @@ -664,7 +781,6 @@ calculate_path(void) buf = wcschr(buf, L'\0'); *buf++ = DELIM; } -#ifdef MS_WINDOWS if (zip_path[0]) { if (wcscpy_s(buf, bufsz - (buf - module_search_path), zip_path)) Py_FatalError("buffer overflow in getpathp.c's calculate_path()"); @@ -692,15 +808,7 @@ calculate_path(void) buf = wcschr(buf, L'\0'); *buf++ = DELIM; } - } -#else - if (pythonhome == NULL) { - wcscpy(buf, PYTHONPATH); - buf = wcschr(buf, L'\0'); - *buf++ = DELIM; - } -#endif /* MS_WINDOWS */ - else { + } else { wchar_t *p = PYTHONPATH; wchar_t *q; size_t n; |