• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Return the initial module search path. */
2 
3 #include "Python.h"
4 #include "pycore_fileutils.h"
5 #include "pycore_initconfig.h"
6 #include "pycore_pathconfig.h"
7 #include "osdefs.h"               // DELIM
8 
9 #include <sys/types.h>
10 #include <string.h>
11 
12 #ifdef __APPLE__
13 #  include <AvailabilityMacros.h>
14 #  include <mach-o/dyld.h>
15 #endif
16 
17 /* Search in some common locations for the associated Python libraries.
18  *
19  * Two directories must be found, the platform independent directory
20  * (prefix), containing the common .py and .pyc files, and the platform
21  * dependent directory (exec_prefix), containing the shared library
22  * modules.  Note that prefix and exec_prefix can be the same directory,
23  * but for some installations, they are different.
24  *
25  * Py_GetPath() carries out separate searches for prefix and exec_prefix.
26  * Each search tries a number of different locations until a ``landmark''
27  * file or directory is found.  If no prefix or exec_prefix is found, a
28  * warning message is issued and the preprocessor defined PREFIX and
29  * EXEC_PREFIX are used (even though they will not work); python carries on
30  * as best as is possible, but most imports will fail.
31  *
32  * Before any searches are done, the location of the executable is
33  * determined.  If argv[0] has one or more slashes in it, it is used
34  * unchanged.  Otherwise, it must have been invoked from the shell's path,
35  * so we search $PATH for the named executable and use that.  If the
36  * executable was not found on $PATH (or there was no $PATH environment
37  * variable), the original argv[0] string is used.
38  *
39  * Next, the executable location is examined to see if it is a symbolic
40  * link.  If so, the link is chased (correctly interpreting a relative
41  * pathname if one is found) and the directory of the link target is used.
42  *
43  * Finally, argv0_path is set to the directory containing the executable
44  * (i.e. the last component is stripped).
45  *
46  * With argv0_path in hand, we perform a number of steps.  The same steps
47  * are performed for prefix and for exec_prefix, but with a different
48  * landmark.
49  *
50  * Step 1. Are we running python out of the build directory?  This is
51  * checked by looking for a different kind of landmark relative to
52  * argv0_path.  For prefix, the landmark's path is derived from the VPATH
53  * preprocessor variable (taking into account that its value is almost, but
54  * not quite, what we need).  For exec_prefix, the landmark is
55  * pybuilddir.txt.  If the landmark is found, we're done.
56  *
57  * For the remaining steps, the prefix landmark will always be
58  * lib/python$VERSION/os.py and the exec_prefix will always be
59  * lib/python$VERSION/lib-dynload, where $VERSION is Python's version
60  * number as supplied by the Makefile.  Note that this means that no more
61  * build directory checking is performed; if the first step did not find
62  * the landmarks, the assumption is that python is running from an
63  * installed setup.
64  *
65  * Step 2. See if the $PYTHONHOME environment variable points to the
66  * installed location of the Python libraries.  If $PYTHONHOME is set, then
67  * it points to prefix and exec_prefix.  $PYTHONHOME can be a single
68  * directory, which is used for both, or the prefix and exec_prefix
69  * directories separated by a colon.
70  *
71  * Step 3. Try to find prefix and exec_prefix relative to argv0_path,
72  * backtracking up the path until it is exhausted.  This is the most common
73  * step to succeed.  Note that if prefix and exec_prefix are different,
74  * exec_prefix is more likely to be found; however if exec_prefix is a
75  * subdirectory of prefix, both will be found.
76  *
77  * Step 4. Search the directories pointed to by the preprocessor variables
78  * PREFIX and EXEC_PREFIX.  These are supplied by the Makefile but can be
79  * passed in as options to the configure script.
80  *
81  * That's it!
82  *
83  * Well, almost.  Once we have determined prefix and exec_prefix, the
84  * preprocessor variable PYTHONPATH is used to construct a path.  Each
85  * relative path on PYTHONPATH is prefixed with prefix.  Then the directory
86  * containing the shared library modules is appended.  The environment
87  * variable $PYTHONPATH is inserted in front of it all.  Finally, the
88  * prefix and exec_prefix globals are tweaked so they reflect the values
89  * expected by other code, by stripping the "lib/python$VERSION/..." stuff
90  * off.  If either points to the build directory, the globals are reset to
91  * the corresponding preprocessor variables (so sys.prefix will reflect the
92  * installation location, even though sys.path points into the build
93  * directory).  This seems to make more sense given that currently the only
94  * known use of sys.prefix and sys.exec_prefix is for the ILU installation
95  * process to find the installed Python tree.
96  *
97  * An embedding application can use Py_SetPath() to override all of
98  * these automatic path computations.
99  *
100  * NOTE: Windows MSVC builds use PC/getpathp.c instead!
101  */
102 
103 #ifdef __cplusplus
104 extern "C" {
105 #endif
106 
107 
108 #if (!defined(PREFIX) || !defined(EXEC_PREFIX) \
109         || !defined(VERSION) || !defined(VPATH))
110 #error "PREFIX, EXEC_PREFIX, VERSION and VPATH macros must be defined"
111 #endif
112 
113 #ifndef LANDMARK
114 #define LANDMARK L"os.py"
115 #endif
116 
117 #define BUILD_LANDMARK L"Modules/Setup.local"
118 
119 #define DECODE_LOCALE_ERR(NAME, LEN) \
120     ((LEN) == (size_t)-2) \
121      ? _PyStatus_ERR("cannot decode " NAME) \
122      : _PyStatus_NO_MEMORY()
123 
124 #define PATHLEN_ERR() _PyStatus_ERR("path configuration: path too long")
125 
126 typedef struct {
127     wchar_t *path_env;                 /* PATH environment variable */
128 
129     wchar_t *pythonpath_macro;         /* PYTHONPATH macro */
130     wchar_t *prefix_macro;             /* PREFIX macro */
131     wchar_t *exec_prefix_macro;        /* EXEC_PREFIX macro */
132     wchar_t *vpath_macro;              /* VPATH macro */
133 
134     wchar_t *lib_python;               /* <platlibdir> / "pythonX.Y" */
135 
136     int prefix_found;         /* found platform independent libraries? */
137     int exec_prefix_found;    /* found the platform dependent libraries? */
138 
139     int warnings;
140     const wchar_t *pythonpath_env;
141     const wchar_t *platlibdir;
142 
143     wchar_t *argv0_path;
144     wchar_t *zip_path;
145     wchar_t *prefix;
146     wchar_t *exec_prefix;
147 } PyCalculatePath;
148 
149 static const wchar_t delimiter[2] = {DELIM, '\0'};
150 static const wchar_t separator[2] = {SEP, '\0'};
151 
152 
153 /* Get file status. Encode the path to the locale encoding. */
154 static int
_Py_wstat(const wchar_t * path,struct stat * buf)155 _Py_wstat(const wchar_t* path, struct stat *buf)
156 {
157     int err;
158     char *fname;
159     fname = _Py_EncodeLocaleRaw(path, NULL);
160     if (fname == NULL) {
161         errno = EINVAL;
162         return -1;
163     }
164     err = stat(fname, buf);
165     PyMem_RawFree(fname);
166     return err;
167 }
168 
169 
170 static void
reduce(wchar_t * dir)171 reduce(wchar_t *dir)
172 {
173     size_t i = wcslen(dir);
174     while (i > 0 && dir[i] != SEP) {
175         --i;
176     }
177     dir[i] = '\0';
178 }
179 
180 
181 /* Is file, not directory */
182 static int
isfile(const wchar_t * filename)183 isfile(const wchar_t *filename)
184 {
185     struct stat buf;
186     if (_Py_wstat(filename, &buf) != 0) {
187         return 0;
188     }
189     if (!S_ISREG(buf.st_mode)) {
190         return 0;
191     }
192     return 1;
193 }
194 
195 
196 /* Is executable file */
197 static int
isxfile(const wchar_t * filename)198 isxfile(const wchar_t *filename)
199 {
200     struct stat buf;
201     if (_Py_wstat(filename, &buf) != 0) {
202         return 0;
203     }
204     if (!S_ISREG(buf.st_mode)) {
205         return 0;
206     }
207     if ((buf.st_mode & 0111) == 0) {
208         return 0;
209     }
210     return 1;
211 }
212 
213 
214 /* Is directory */
215 static int
isdir(const wchar_t * filename)216 isdir(const wchar_t *filename)
217 {
218     struct stat buf;
219     if (_Py_wstat(filename, &buf) != 0) {
220         return 0;
221     }
222     if (!S_ISDIR(buf.st_mode)) {
223         return 0;
224     }
225     return 1;
226 }
227 
228 
229 /* Add a path component, by appending stuff to buffer.
230    buflen: 'buffer' length in characters including trailing NUL.
231 
232    If path2 is empty:
233 
234    - if path doesn't end with SEP and is not empty, add SEP to path
235    - otherwise, do nothing. */
236 static PyStatus
joinpath(wchar_t * path,const wchar_t * path2,size_t path_len)237 joinpath(wchar_t *path, const wchar_t *path2, size_t path_len)
238 {
239     size_t n;
240     if (!_Py_isabs(path2)) {
241         n = wcslen(path);
242         if (n >= path_len) {
243             return PATHLEN_ERR();
244         }
245 
246         if (n > 0 && path[n-1] != SEP) {
247             path[n++] = SEP;
248         }
249     }
250     else {
251         n = 0;
252     }
253 
254     size_t k = wcslen(path2);
255     if (n + k >= path_len) {
256         return PATHLEN_ERR();
257     }
258     wcsncpy(path + n, path2, k);
259     path[n + k] = '\0';
260 
261     return _PyStatus_OK();
262 }
263 
264 
265 static wchar_t*
substring(const wchar_t * str,size_t len)266 substring(const wchar_t *str, size_t len)
267 {
268     wchar_t *substr = PyMem_RawMalloc((len + 1) * sizeof(wchar_t));
269     if (substr == NULL) {
270         return NULL;
271     }
272 
273     if (len) {
274         memcpy(substr, str, len * sizeof(wchar_t));
275     }
276     substr[len] = L'\0';
277     return substr;
278 }
279 
280 
281 static wchar_t*
joinpath2(const wchar_t * path,const wchar_t * path2)282 joinpath2(const wchar_t *path, const wchar_t *path2)
283 {
284     if (_Py_isabs(path2)) {
285         return _PyMem_RawWcsdup(path2);
286     }
287 
288     size_t len = wcslen(path);
289     int add_sep = (len > 0 && path[len - 1] != SEP);
290     len += add_sep;
291     len += wcslen(path2);
292 
293     wchar_t *new_path = PyMem_RawMalloc((len + 1) * sizeof(wchar_t));
294     if (new_path == NULL) {
295         return NULL;
296     }
297 
298     wcscpy(new_path, path);
299     if (add_sep) {
300         wcscat(new_path, separator);
301     }
302     wcscat(new_path, path2);
303     return new_path;
304 }
305 
306 
307 static inline int
safe_wcscpy(wchar_t * dst,const wchar_t * src,size_t n)308 safe_wcscpy(wchar_t *dst, const wchar_t *src, size_t n)
309 {
310     size_t srclen = wcslen(src);
311     if (n <= srclen) {
312         dst[0] = L'\0';
313         return -1;
314     }
315     memcpy(dst, src, (srclen + 1) * sizeof(wchar_t));
316     return 0;
317 }
318 
319 
320 /* copy_absolute requires that path be allocated at least
321    'abs_path_len' characters (including trailing NUL). */
322 static PyStatus
copy_absolute(wchar_t * abs_path,const wchar_t * path,size_t abs_path_len)323 copy_absolute(wchar_t *abs_path, const wchar_t *path, size_t abs_path_len)
324 {
325     if (_Py_isabs(path)) {
326         if (safe_wcscpy(abs_path, path, abs_path_len) < 0) {
327             return PATHLEN_ERR();
328         }
329     }
330     else {
331         if (!_Py_wgetcwd(abs_path, abs_path_len)) {
332             /* unable to get the current directory */
333             if (safe_wcscpy(abs_path, path, abs_path_len) < 0) {
334                 return PATHLEN_ERR();
335             }
336             return _PyStatus_OK();
337         }
338         if (path[0] == '.' && path[1] == SEP) {
339             path += 2;
340         }
341         PyStatus status = joinpath(abs_path, path, abs_path_len);
342         if (_PyStatus_EXCEPTION(status)) {
343             return status;
344         }
345     }
346     return _PyStatus_OK();
347 }
348 
349 
350 /* path_len: path length in characters including trailing NUL */
351 static PyStatus
absolutize(wchar_t ** path_p)352 absolutize(wchar_t **path_p)
353 {
354     assert(!_Py_isabs(*path_p));
355 
356     wchar_t abs_path[MAXPATHLEN+1];
357     wchar_t *path = *path_p;
358 
359     PyStatus status = copy_absolute(abs_path, path, Py_ARRAY_LENGTH(abs_path));
360     if (_PyStatus_EXCEPTION(status)) {
361         return status;
362     }
363 
364     PyMem_RawFree(*path_p);
365     *path_p = _PyMem_RawWcsdup(abs_path);
366     if (*path_p == NULL) {
367         return _PyStatus_NO_MEMORY();
368     }
369     return _PyStatus_OK();
370 }
371 
372 
373 /* Is module -- check for .pyc too */
374 static PyStatus
ismodule(const wchar_t * path,int * result)375 ismodule(const wchar_t *path, int *result)
376 {
377     wchar_t *filename = joinpath2(path, LANDMARK);
378     if (filename == NULL) {
379         return _PyStatus_NO_MEMORY();
380     }
381 
382     if (isfile(filename)) {
383         PyMem_RawFree(filename);
384         *result = 1;
385         return _PyStatus_OK();
386     }
387 
388     /* Check for the compiled version of prefix. */
389     size_t len = wcslen(filename);
390     wchar_t *pyc = PyMem_RawMalloc((len + 2) * sizeof(wchar_t));
391     if (pyc == NULL) {
392         PyMem_RawFree(filename);
393         return _PyStatus_NO_MEMORY();
394     }
395 
396     memcpy(pyc, filename, len * sizeof(wchar_t));
397     pyc[len] = L'c';
398     pyc[len + 1] = L'\0';
399     *result = isfile(pyc);
400 
401     PyMem_RawFree(filename);
402     PyMem_RawFree(pyc);
403 
404     return _PyStatus_OK();
405 }
406 
407 
408 #if defined(__CYGWIN__) || defined(__MINGW32__)
409 #ifndef EXE_SUFFIX
410 #define EXE_SUFFIX L".exe"
411 #endif
412 
413 /* pathlen: 'path' length in characters including trailing NUL */
414 static PyStatus
add_exe_suffix(wchar_t ** progpath_p)415 add_exe_suffix(wchar_t **progpath_p)
416 {
417     wchar_t *progpath = *progpath_p;
418 
419     /* Check for already have an executable suffix */
420     size_t n = wcslen(progpath);
421     size_t s = wcslen(EXE_SUFFIX);
422     if (wcsncasecmp(EXE_SUFFIX, progpath + n - s, s) == 0) {
423         return _PyStatus_OK();
424     }
425 
426     wchar_t *progpath2 = PyMem_RawMalloc((n + s + 1) * sizeof(wchar_t));
427     if (progpath2 == NULL) {
428         return _PyStatus_NO_MEMORY();
429     }
430 
431     memcpy(progpath2, progpath, n * sizeof(wchar_t));
432     memcpy(progpath2 + n, EXE_SUFFIX, s * sizeof(wchar_t));
433     progpath2[n+s] = L'\0';
434 
435     if (isxfile(progpath2)) {
436         PyMem_RawFree(*progpath_p);
437         *progpath_p = progpath2;
438     }
439     else {
440         PyMem_RawFree(progpath2);
441     }
442     return _PyStatus_OK();
443 }
444 #endif
445 
446 
447 /* search_for_prefix requires that argv0_path be no more than MAXPATHLEN
448    bytes long.
449 */
450 static PyStatus
search_for_prefix(PyCalculatePath * calculate,_PyPathConfig * pathconfig,wchar_t * prefix,size_t prefix_len,int * found)451 search_for_prefix(PyCalculatePath *calculate, _PyPathConfig *pathconfig,
452                   wchar_t *prefix, size_t prefix_len, int *found)
453 {
454     PyStatus status;
455 
456     /* If PYTHONHOME is set, we believe it unconditionally */
457     if (pathconfig->home) {
458         /* Path: <home> / <lib_python> */
459         if (safe_wcscpy(prefix, pathconfig->home, prefix_len) < 0) {
460             return PATHLEN_ERR();
461         }
462         wchar_t *delim = wcschr(prefix, DELIM);
463         if (delim) {
464             *delim = L'\0';
465         }
466         status = joinpath(prefix, calculate->lib_python, prefix_len);
467         if (_PyStatus_EXCEPTION(status)) {
468             return status;
469         }
470         *found = 1;
471         return _PyStatus_OK();
472     }
473 
474     /* Check to see if argv0_path is in the build directory
475 
476        Path: <argv0_path> / <BUILD_LANDMARK define> */
477     wchar_t *path = joinpath2(calculate->argv0_path, BUILD_LANDMARK);
478     if (path == NULL) {
479         return _PyStatus_NO_MEMORY();
480     }
481 
482     int is_build_dir = isfile(path);
483     PyMem_RawFree(path);
484 
485     if (is_build_dir) {
486         /* argv0_path is the build directory (BUILD_LANDMARK exists),
487            now also check LANDMARK using ismodule(). */
488 
489         /* Path: <argv0_path> / <VPATH macro> / Lib */
490         /* or if VPATH is empty: <argv0_path> / Lib */
491         if (safe_wcscpy(prefix, calculate->argv0_path, prefix_len) < 0) {
492             return PATHLEN_ERR();
493         }
494 
495         status = joinpath(prefix, calculate->vpath_macro, prefix_len);
496         if (_PyStatus_EXCEPTION(status)) {
497             return status;
498         }
499 
500         status = joinpath(prefix, L"Lib", prefix_len);
501         if (_PyStatus_EXCEPTION(status)) {
502             return status;
503         }
504 
505         int module;
506         status = ismodule(prefix, &module);
507         if (_PyStatus_EXCEPTION(status)) {
508             return status;
509         }
510         if (module) {
511             /* BUILD_LANDMARK and LANDMARK found */
512             *found = -1;
513             return _PyStatus_OK();
514         }
515     }
516 
517     /* Search from argv0_path, until root is found */
518     status = copy_absolute(prefix, calculate->argv0_path, prefix_len);
519     if (_PyStatus_EXCEPTION(status)) {
520         return status;
521     }
522 
523     do {
524         /* Path: <argv0_path or substring> / <lib_python> / LANDMARK */
525         size_t n = wcslen(prefix);
526         status = joinpath(prefix, calculate->lib_python, prefix_len);
527         if (_PyStatus_EXCEPTION(status)) {
528             return status;
529         }
530 
531         int module;
532         status = ismodule(prefix, &module);
533         if (_PyStatus_EXCEPTION(status)) {
534             return status;
535         }
536         if (module) {
537             *found = 1;
538             return _PyStatus_OK();
539         }
540         prefix[n] = L'\0';
541         reduce(prefix);
542     } while (prefix[0]);
543 
544     /* Look at configure's PREFIX.
545        Path: <PREFIX macro> / <lib_python> / LANDMARK */
546     if (safe_wcscpy(prefix, calculate->prefix_macro, prefix_len) < 0) {
547         return PATHLEN_ERR();
548     }
549     status = joinpath(prefix, calculate->lib_python, prefix_len);
550     if (_PyStatus_EXCEPTION(status)) {
551         return status;
552     }
553 
554     int module;
555     status = ismodule(prefix, &module);
556     if (_PyStatus_EXCEPTION(status)) {
557         return status;
558     }
559     if (module) {
560         *found = 1;
561         return _PyStatus_OK();
562     }
563 
564     /* Fail */
565     *found = 0;
566     return _PyStatus_OK();
567 }
568 
569 
570 static PyStatus
calculate_prefix(PyCalculatePath * calculate,_PyPathConfig * pathconfig)571 calculate_prefix(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
572 {
573     wchar_t prefix[MAXPATHLEN+1];
574     memset(prefix, 0, sizeof(prefix));
575     size_t prefix_len = Py_ARRAY_LENGTH(prefix);
576 
577     PyStatus status;
578     status = search_for_prefix(calculate, pathconfig,
579                                prefix, prefix_len,
580                                &calculate->prefix_found);
581     if (_PyStatus_EXCEPTION(status)) {
582         return status;
583     }
584 
585     if (!calculate->prefix_found) {
586         if (calculate->warnings) {
587             fprintf(stderr,
588                 "Could not find platform independent libraries <prefix>\n");
589         }
590 
591         calculate->prefix = joinpath2(calculate->prefix_macro,
592                                       calculate->lib_python);
593     }
594     else {
595         calculate->prefix = _PyMem_RawWcsdup(prefix);
596     }
597 
598     if (calculate->prefix == NULL) {
599         return _PyStatus_NO_MEMORY();
600     }
601     return _PyStatus_OK();
602 }
603 
604 
605 static PyStatus
calculate_set_prefix(PyCalculatePath * calculate,_PyPathConfig * pathconfig)606 calculate_set_prefix(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
607 {
608     /* Reduce prefix and exec_prefix to their essence,
609      * e.g. /usr/local/lib/python1.5 is reduced to /usr/local.
610      * If we're loading relative to the build directory,
611      * return the compiled-in defaults instead.
612      */
613     if (calculate->prefix_found > 0) {
614         wchar_t *prefix = _PyMem_RawWcsdup(calculate->prefix);
615         if (prefix == NULL) {
616             return _PyStatus_NO_MEMORY();
617         }
618 
619         reduce(prefix);
620         reduce(prefix);
621         if (prefix[0]) {
622             pathconfig->prefix = prefix;
623         }
624         else {
625             PyMem_RawFree(prefix);
626 
627             /* The prefix is the root directory, but reduce() chopped
628                off the "/". */
629             pathconfig->prefix = _PyMem_RawWcsdup(separator);
630             if (pathconfig->prefix == NULL) {
631                 return _PyStatus_NO_MEMORY();
632             }
633         }
634     }
635     else {
636         pathconfig->prefix = _PyMem_RawWcsdup(calculate->prefix_macro);
637         if (pathconfig->prefix == NULL) {
638             return _PyStatus_NO_MEMORY();
639         }
640     }
641     return _PyStatus_OK();
642 }
643 
644 
645 static PyStatus
calculate_pybuilddir(const wchar_t * argv0_path,wchar_t * exec_prefix,size_t exec_prefix_len,int * found)646 calculate_pybuilddir(const wchar_t *argv0_path,
647                      wchar_t *exec_prefix, size_t exec_prefix_len,
648                      int *found)
649 {
650     PyStatus status;
651 
652     /* Check to see if argv[0] is in the build directory. "pybuilddir.txt"
653        is written by setup.py and contains the relative path to the location
654        of shared library modules.
655 
656        Filename: <argv0_path> / "pybuilddir.txt" */
657     wchar_t *filename = joinpath2(argv0_path, L"pybuilddir.txt");
658     if (filename == NULL) {
659         return _PyStatus_NO_MEMORY();
660     }
661 
662     FILE *fp = _Py_wfopen(filename, L"rb");
663     PyMem_RawFree(filename);
664     if (fp == NULL) {
665         errno = 0;
666         return _PyStatus_OK();
667     }
668 
669     char buf[MAXPATHLEN + 1];
670     size_t n = fread(buf, 1, Py_ARRAY_LENGTH(buf) - 1, fp);
671     buf[n] = '\0';
672     fclose(fp);
673 
674     size_t dec_len;
675     wchar_t *pybuilddir = _Py_DecodeUTF8_surrogateescape(buf, n, &dec_len);
676     if (!pybuilddir) {
677         return DECODE_LOCALE_ERR("pybuilddir.txt", dec_len);
678     }
679 
680     /* Path: <argv0_path> / <pybuilddir content> */
681     if (safe_wcscpy(exec_prefix, argv0_path, exec_prefix_len) < 0) {
682         PyMem_RawFree(pybuilddir);
683         return PATHLEN_ERR();
684     }
685     status = joinpath(exec_prefix, pybuilddir, exec_prefix_len);
686     PyMem_RawFree(pybuilddir);
687     if (_PyStatus_EXCEPTION(status)) {
688         return status;
689     }
690 
691     *found = -1;
692     return _PyStatus_OK();
693 }
694 
695 
696 /* search_for_exec_prefix requires that argv0_path be no more than
697    MAXPATHLEN bytes long.
698 */
699 static PyStatus
search_for_exec_prefix(PyCalculatePath * calculate,_PyPathConfig * pathconfig,wchar_t * exec_prefix,size_t exec_prefix_len,int * found)700 search_for_exec_prefix(PyCalculatePath *calculate, _PyPathConfig *pathconfig,
701                        wchar_t *exec_prefix, size_t exec_prefix_len,
702                        int *found)
703 {
704     PyStatus status;
705 
706     /* If PYTHONHOME is set, we believe it unconditionally */
707     if (pathconfig->home) {
708         /* Path: <home> / <lib_python> / "lib-dynload" */
709         wchar_t *delim = wcschr(pathconfig->home, DELIM);
710         if (delim) {
711             if (safe_wcscpy(exec_prefix, delim+1, exec_prefix_len) < 0) {
712                 return PATHLEN_ERR();
713             }
714         }
715         else {
716             if (safe_wcscpy(exec_prefix, pathconfig->home, exec_prefix_len) < 0) {
717                 return PATHLEN_ERR();
718             }
719         }
720         status = joinpath(exec_prefix, calculate->lib_python, exec_prefix_len);
721         if (_PyStatus_EXCEPTION(status)) {
722             return status;
723         }
724         status = joinpath(exec_prefix, L"lib-dynload", exec_prefix_len);
725         if (_PyStatus_EXCEPTION(status)) {
726             return status;
727         }
728         *found = 1;
729         return _PyStatus_OK();
730     }
731 
732     /* Check for pybuilddir.txt */
733     assert(*found == 0);
734     status = calculate_pybuilddir(calculate->argv0_path,
735                                   exec_prefix, exec_prefix_len, found);
736     if (_PyStatus_EXCEPTION(status)) {
737         return status;
738     }
739     if (*found) {
740         return _PyStatus_OK();
741     }
742 
743     /* Search from argv0_path, until root is found */
744     status = copy_absolute(exec_prefix, calculate->argv0_path, exec_prefix_len);
745     if (_PyStatus_EXCEPTION(status)) {
746         return status;
747     }
748 
749     do {
750         /* Path: <argv0_path or substring> / <lib_python> / "lib-dynload" */
751         size_t n = wcslen(exec_prefix);
752         status = joinpath(exec_prefix, calculate->lib_python, exec_prefix_len);
753         if (_PyStatus_EXCEPTION(status)) {
754             return status;
755         }
756         status = joinpath(exec_prefix, L"lib-dynload", exec_prefix_len);
757         if (_PyStatus_EXCEPTION(status)) {
758             return status;
759         }
760         if (isdir(exec_prefix)) {
761             *found = 1;
762             return _PyStatus_OK();
763         }
764         exec_prefix[n] = L'\0';
765         reduce(exec_prefix);
766     } while (exec_prefix[0]);
767 
768     /* Look at configure's EXEC_PREFIX.
769 
770        Path: <EXEC_PREFIX macro> / <lib_python> / "lib-dynload" */
771     if (safe_wcscpy(exec_prefix, calculate->exec_prefix_macro, exec_prefix_len) < 0) {
772         return PATHLEN_ERR();
773     }
774     status = joinpath(exec_prefix, calculate->lib_python, exec_prefix_len);
775     if (_PyStatus_EXCEPTION(status)) {
776         return status;
777     }
778     status = joinpath(exec_prefix, L"lib-dynload", exec_prefix_len);
779     if (_PyStatus_EXCEPTION(status)) {
780         return status;
781     }
782     if (isdir(exec_prefix)) {
783         *found = 1;
784         return _PyStatus_OK();
785     }
786 
787     /* Fail */
788     *found = 0;
789     return _PyStatus_OK();
790 }
791 
792 
793 static PyStatus
calculate_exec_prefix(PyCalculatePath * calculate,_PyPathConfig * pathconfig)794 calculate_exec_prefix(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
795 {
796     PyStatus status;
797     wchar_t exec_prefix[MAXPATHLEN+1];
798     memset(exec_prefix, 0, sizeof(exec_prefix));
799     size_t exec_prefix_len = Py_ARRAY_LENGTH(exec_prefix);
800 
801     status = search_for_exec_prefix(calculate, pathconfig,
802                                     exec_prefix, exec_prefix_len,
803                                     &calculate->exec_prefix_found);
804     if (_PyStatus_EXCEPTION(status)) {
805         return status;
806     }
807 
808     if (!calculate->exec_prefix_found) {
809         if (calculate->warnings) {
810             fprintf(stderr,
811                 "Could not find platform dependent libraries <exec_prefix>\n");
812         }
813 
814         /* <platlibdir> / "lib-dynload" */
815         wchar_t *lib_dynload = joinpath2(calculate->platlibdir,
816                                          L"lib-dynload");
817         if (lib_dynload == NULL) {
818             return _PyStatus_NO_MEMORY();
819         }
820 
821         calculate->exec_prefix = joinpath2(calculate->exec_prefix_macro,
822                                            lib_dynload);
823         PyMem_RawFree(lib_dynload);
824 
825         if (calculate->exec_prefix == NULL) {
826             return _PyStatus_NO_MEMORY();
827         }
828     }
829     else {
830         /* If we found EXEC_PREFIX do *not* reduce it!  (Yet.) */
831         calculate->exec_prefix = _PyMem_RawWcsdup(exec_prefix);
832         if (calculate->exec_prefix == NULL) {
833             return _PyStatus_NO_MEMORY();
834         }
835     }
836     return _PyStatus_OK();
837 }
838 
839 
840 static PyStatus
calculate_set_exec_prefix(PyCalculatePath * calculate,_PyPathConfig * pathconfig)841 calculate_set_exec_prefix(PyCalculatePath *calculate,
842                           _PyPathConfig *pathconfig)
843 {
844     if (calculate->exec_prefix_found > 0) {
845         wchar_t *exec_prefix = _PyMem_RawWcsdup(calculate->exec_prefix);
846         if (exec_prefix == NULL) {
847             return _PyStatus_NO_MEMORY();
848         }
849 
850         reduce(exec_prefix);
851         reduce(exec_prefix);
852         reduce(exec_prefix);
853 
854         if (exec_prefix[0]) {
855             pathconfig->exec_prefix = exec_prefix;
856         }
857         else {
858             /* empty string: use SEP instead */
859             PyMem_RawFree(exec_prefix);
860 
861             /* The exec_prefix is the root directory, but reduce() chopped
862                off the "/". */
863             pathconfig->exec_prefix = _PyMem_RawWcsdup(separator);
864             if (pathconfig->exec_prefix == NULL) {
865                 return _PyStatus_NO_MEMORY();
866             }
867         }
868     }
869     else {
870         pathconfig->exec_prefix = _PyMem_RawWcsdup(calculate->exec_prefix_macro);
871         if (pathconfig->exec_prefix == NULL) {
872             return _PyStatus_NO_MEMORY();
873         }
874     }
875     return _PyStatus_OK();
876 }
877 
878 
879 /* Similar to shutil.which().
880    If found, write the path into *abs_path_p. */
881 static PyStatus
calculate_which(const wchar_t * path_env,wchar_t * program_name,wchar_t ** abs_path_p)882 calculate_which(const wchar_t *path_env, wchar_t *program_name,
883                 wchar_t **abs_path_p)
884 {
885     while (1) {
886         wchar_t *delim = wcschr(path_env, DELIM);
887         wchar_t *abs_path;
888 
889         if (delim) {
890             wchar_t *path = substring(path_env, delim - path_env);
891             if (path == NULL) {
892                 return _PyStatus_NO_MEMORY();
893             }
894             abs_path = joinpath2(path, program_name);
895             PyMem_RawFree(path);
896         }
897         else {
898             abs_path = joinpath2(path_env, program_name);
899         }
900 
901         if (abs_path == NULL) {
902             return _PyStatus_NO_MEMORY();
903         }
904 
905         if (isxfile(abs_path)) {
906             *abs_path_p = abs_path;
907             return _PyStatus_OK();
908         }
909         PyMem_RawFree(abs_path);
910 
911         if (!delim) {
912             break;
913         }
914         path_env = delim + 1;
915     }
916 
917     /* not found */
918     return _PyStatus_OK();
919 }
920 
921 
922 #ifdef __APPLE__
923 static PyStatus
calculate_program_macos(wchar_t ** abs_path_p)924 calculate_program_macos(wchar_t **abs_path_p)
925 {
926     char execpath[MAXPATHLEN + 1];
927     uint32_t nsexeclength = Py_ARRAY_LENGTH(execpath) - 1;
928 
929     /* On Mac OS X, if a script uses an interpreter of the form
930        "#!/opt/python2.3/bin/python", the kernel only passes "python"
931        as argv[0], which falls through to the $PATH search below.
932        If /opt/python2.3/bin isn't in your path, or is near the end,
933        this algorithm may incorrectly find /usr/bin/python. To work
934        around this, we can use _NSGetExecutablePath to get a better
935        hint of what the intended interpreter was, although this
936        will fail if a relative path was used. but in that case,
937        absolutize() should help us out below
938      */
939     if (_NSGetExecutablePath(execpath, &nsexeclength) != 0
940         || (wchar_t)execpath[0] != SEP)
941     {
942         /* _NSGetExecutablePath() failed or the path is relative */
943         return _PyStatus_OK();
944     }
945 
946     size_t len;
947     *abs_path_p = Py_DecodeLocale(execpath, &len);
948     if (*abs_path_p == NULL) {
949         return DECODE_LOCALE_ERR("executable path", len);
950     }
951     return _PyStatus_OK();
952 }
953 #endif  /* __APPLE__ */
954 
955 
956 static PyStatus
calculate_program_impl(PyCalculatePath * calculate,_PyPathConfig * pathconfig)957 calculate_program_impl(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
958 {
959     assert(pathconfig->program_full_path == NULL);
960 
961     PyStatus status;
962 
963     /* If there is no slash in the argv0 path, then we have to
964      * assume python is on the user's $PATH, since there's no
965      * other way to find a directory to start the search from.  If
966      * $PATH isn't exported, you lose.
967      */
968     if (wcschr(pathconfig->program_name, SEP)) {
969         pathconfig->program_full_path = _PyMem_RawWcsdup(pathconfig->program_name);
970         if (pathconfig->program_full_path == NULL) {
971             return _PyStatus_NO_MEMORY();
972         }
973         return _PyStatus_OK();
974     }
975 
976 #ifdef __APPLE__
977     wchar_t *abs_path = NULL;
978     status = calculate_program_macos(&abs_path);
979     if (_PyStatus_EXCEPTION(status)) {
980         return status;
981     }
982     if (abs_path) {
983         pathconfig->program_full_path = abs_path;
984         return _PyStatus_OK();
985     }
986 #endif /* __APPLE__ */
987 
988     if (calculate->path_env) {
989         wchar_t *abs_path = NULL;
990         status = calculate_which(calculate->path_env, pathconfig->program_name,
991                                  &abs_path);
992         if (_PyStatus_EXCEPTION(status)) {
993             return status;
994         }
995         if (abs_path) {
996             pathconfig->program_full_path = abs_path;
997             return _PyStatus_OK();
998         }
999     }
1000 
1001     /* In the last resort, use an empty string */
1002     pathconfig->program_full_path = _PyMem_RawWcsdup(L"");
1003     if (pathconfig->program_full_path == NULL) {
1004         return _PyStatus_NO_MEMORY();
1005     }
1006     return _PyStatus_OK();
1007 }
1008 
1009 
1010 /* Calculate pathconfig->program_full_path */
1011 static PyStatus
calculate_program(PyCalculatePath * calculate,_PyPathConfig * pathconfig)1012 calculate_program(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
1013 {
1014     PyStatus status;
1015 
1016     status = calculate_program_impl(calculate, pathconfig);
1017     if (_PyStatus_EXCEPTION(status)) {
1018         return status;
1019     }
1020 
1021     if (pathconfig->program_full_path[0] != '\0') {
1022         /* program_full_path is not empty */
1023 
1024         /* Make sure that program_full_path is an absolute path */
1025         if (!_Py_isabs(pathconfig->program_full_path)) {
1026             status = absolutize(&pathconfig->program_full_path);
1027             if (_PyStatus_EXCEPTION(status)) {
1028                 return status;
1029             }
1030         }
1031 
1032 #if defined(__CYGWIN__) || defined(__MINGW32__)
1033         /* For these platforms it is necessary to ensure that the .exe suffix
1034          * is appended to the filename, otherwise there is potential for
1035          * sys.executable to return the name of a directory under the same
1036          * path (bpo-28441).
1037          */
1038         status = add_exe_suffix(&pathconfig->program_full_path);
1039         if (_PyStatus_EXCEPTION(status)) {
1040             return status;
1041         }
1042 #endif
1043     }
1044     return _PyStatus_OK();
1045 }
1046 
1047 
1048 #if HAVE_READLINK
1049 static PyStatus
resolve_symlinks(wchar_t ** path_p)1050 resolve_symlinks(wchar_t **path_p)
1051 {
1052     wchar_t new_path[MAXPATHLEN + 1];
1053     const size_t new_path_len = Py_ARRAY_LENGTH(new_path);
1054     unsigned int nlink = 0;
1055 
1056     while (1) {
1057         int linklen = _Py_wreadlink(*path_p, new_path, new_path_len);
1058         if (linklen == -1) {
1059             /* not a symbolic link: we are done */
1060             break;
1061         }
1062 
1063         if (_Py_isabs(new_path)) {
1064             PyMem_RawFree(*path_p);
1065             *path_p = _PyMem_RawWcsdup(new_path);
1066             if (*path_p == NULL) {
1067                 return _PyStatus_NO_MEMORY();
1068             }
1069         }
1070         else {
1071             /* new_path is relative to path */
1072             reduce(*path_p);
1073 
1074             wchar_t *abs_path = joinpath2(*path_p, new_path);
1075             if (abs_path == NULL) {
1076                 return _PyStatus_NO_MEMORY();
1077             }
1078 
1079             PyMem_RawFree(*path_p);
1080             *path_p = abs_path;
1081         }
1082 
1083         nlink++;
1084         /* 40 is the Linux kernel 4.2 limit */
1085         if (nlink >= 40) {
1086             return _PyStatus_ERR("maximum number of symbolic links reached");
1087         }
1088     }
1089     return _PyStatus_OK();
1090 }
1091 #endif /* HAVE_READLINK */
1092 
1093 
1094 #ifdef WITH_NEXT_FRAMEWORK
1095 static PyStatus
calculate_argv0_path_framework(PyCalculatePath * calculate,_PyPathConfig * pathconfig)1096 calculate_argv0_path_framework(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
1097 {
1098     NSModule pythonModule;
1099 
1100     /* On Mac OS X we have a special case if we're running from a framework.
1101        This is because the python home should be set relative to the library,
1102        which is in the framework, not relative to the executable, which may
1103        be outside of the framework. Except when we're in the build
1104        directory... */
1105     pythonModule = NSModuleForSymbol(NSLookupAndBindSymbol("_Py_Initialize"));
1106 
1107     /* Use dylib functions to find out where the framework was loaded from */
1108     const char* modPath = NSLibraryNameForModule(pythonModule);
1109     if (modPath == NULL) {
1110         return _PyStatus_OK();
1111     }
1112 
1113     /* We're in a framework.
1114        See if we might be in the build directory. The framework in the
1115        build directory is incomplete, it only has the .dylib and a few
1116        needed symlinks, it doesn't have the Lib directories and such.
1117        If we're running with the framework from the build directory we must
1118        be running the interpreter in the build directory, so we use the
1119        build-directory-specific logic to find Lib and such. */
1120     size_t len;
1121     wchar_t* wbuf = Py_DecodeLocale(modPath, &len);
1122     if (wbuf == NULL) {
1123         return DECODE_LOCALE_ERR("framework location", len);
1124     }
1125 
1126     /* Path: reduce(modPath) / lib_python / LANDMARK */
1127     PyStatus status;
1128 
1129     wchar_t *parent = _PyMem_RawWcsdup(wbuf);
1130     if (parent == NULL) {
1131         status = _PyStatus_NO_MEMORY();
1132         goto done;
1133     }
1134 
1135     reduce(parent);
1136     wchar_t *lib_python = joinpath2(parent, calculate->lib_python);
1137     PyMem_RawFree(parent);
1138 
1139     if (lib_python == NULL) {
1140         status = _PyStatus_NO_MEMORY();
1141         goto done;
1142     }
1143 
1144     int module;
1145     status = ismodule(lib_python, &module);
1146     PyMem_RawFree(lib_python);
1147 
1148     if (_PyStatus_EXCEPTION(status)) {
1149         goto done;
1150     }
1151     if (!module) {
1152         /* We are in the build directory so use the name of the
1153            executable - we know that the absolute path is passed */
1154         PyMem_RawFree(calculate->argv0_path);
1155         calculate->argv0_path = _PyMem_RawWcsdup(pathconfig->program_full_path);
1156         if (calculate->argv0_path == NULL) {
1157             status = _PyStatus_NO_MEMORY();
1158             goto done;
1159         }
1160 
1161         status = _PyStatus_OK();
1162         goto done;
1163     }
1164 
1165     /* Use the location of the library as argv0_path */
1166     PyMem_RawFree(calculate->argv0_path);
1167     calculate->argv0_path = wbuf;
1168     return _PyStatus_OK();
1169 
1170 done:
1171     PyMem_RawFree(wbuf);
1172     return status;
1173 }
1174 #endif
1175 
1176 
1177 static PyStatus
calculate_argv0_path(PyCalculatePath * calculate,_PyPathConfig * pathconfig)1178 calculate_argv0_path(PyCalculatePath *calculate,
1179                      _PyPathConfig *pathconfig)
1180 {
1181     PyStatus status;
1182 
1183     calculate->argv0_path = _PyMem_RawWcsdup(pathconfig->program_full_path);
1184     if (calculate->argv0_path == NULL) {
1185         return _PyStatus_NO_MEMORY();
1186     }
1187 
1188 #ifdef WITH_NEXT_FRAMEWORK
1189     status = calculate_argv0_path_framework(calculate, pathconfig);
1190     if (_PyStatus_EXCEPTION(status)) {
1191         return status;
1192     }
1193 #endif
1194 
1195     status = resolve_symlinks(&calculate->argv0_path);
1196     if (_PyStatus_EXCEPTION(status)) {
1197         return status;
1198     }
1199 
1200     reduce(calculate->argv0_path);
1201 
1202     return _PyStatus_OK();
1203 }
1204 
1205 
1206 static PyStatus
calculate_open_pyenv(PyCalculatePath * calculate,FILE ** env_file_p)1207 calculate_open_pyenv(PyCalculatePath *calculate, FILE **env_file_p)
1208 {
1209     *env_file_p = NULL;
1210 
1211     const wchar_t *env_cfg = L"pyvenv.cfg";
1212 
1213     /* Filename: <argv0_path> / "pyvenv.cfg" */
1214     wchar_t *filename = joinpath2(calculate->argv0_path, env_cfg);
1215     if (filename == NULL) {
1216         return _PyStatus_NO_MEMORY();
1217     }
1218 
1219     *env_file_p = _Py_wfopen(filename, L"r");
1220     PyMem_RawFree(filename);
1221 
1222     if (*env_file_p != NULL) {
1223         return _PyStatus_OK();
1224 
1225     }
1226 
1227     /* fopen() failed: reset errno */
1228     errno = 0;
1229 
1230     /* Path: <basename(argv0_path)> / "pyvenv.cfg" */
1231     wchar_t *parent = _PyMem_RawWcsdup(calculate->argv0_path);
1232     if (parent == NULL) {
1233         return _PyStatus_NO_MEMORY();
1234     }
1235     reduce(parent);
1236 
1237     filename = joinpath2(parent, env_cfg);
1238     PyMem_RawFree(parent);
1239     if (filename == NULL) {
1240         return _PyStatus_NO_MEMORY();
1241     }
1242 
1243     *env_file_p = _Py_wfopen(filename, L"r");
1244     PyMem_RawFree(filename);
1245 
1246     if (*env_file_p == NULL) {
1247         /* fopen() failed: reset errno */
1248         errno = 0;
1249     }
1250     return _PyStatus_OK();
1251 }
1252 
1253 
1254 /* Search for an "pyvenv.cfg" environment configuration file, first in the
1255    executable's directory and then in the parent directory.
1256    If found, open it for use when searching for prefixes.
1257 
1258    Write the 'home' variable of pyvenv.cfg into calculate->argv0_path. */
1259 static PyStatus
calculate_read_pyenv(PyCalculatePath * calculate)1260 calculate_read_pyenv(PyCalculatePath *calculate)
1261 {
1262     PyStatus status;
1263     FILE *env_file = NULL;
1264 
1265     status = calculate_open_pyenv(calculate, &env_file);
1266     if (_PyStatus_EXCEPTION(status)) {
1267         return status;
1268     }
1269     if (env_file == NULL) {
1270         /* pyvenv.cfg not found */
1271         return _PyStatus_OK();
1272     }
1273 
1274     /* Look for a 'home' variable and set argv0_path to it, if found */
1275     wchar_t *home = NULL;
1276     status = _Py_FindEnvConfigValue(env_file, L"home", &home);
1277     if (_PyStatus_EXCEPTION(status)) {
1278         fclose(env_file);
1279         return status;
1280     }
1281 
1282     if (home) {
1283         PyMem_RawFree(calculate->argv0_path);
1284         calculate->argv0_path = home;
1285     }
1286     fclose(env_file);
1287     return _PyStatus_OK();
1288 }
1289 
1290 
1291 static PyStatus
calculate_zip_path(PyCalculatePath * calculate)1292 calculate_zip_path(PyCalculatePath *calculate)
1293 {
1294     PyStatus res;
1295 
1296     /* Path: <platlibdir> / "pythonXY.zip" */
1297     wchar_t *path = joinpath2(calculate->platlibdir,
1298                               L"python" Py_STRINGIFY(PY_MAJOR_VERSION) Py_STRINGIFY(PY_MINOR_VERSION)
1299                               L".zip");
1300     if (path == NULL) {
1301         return _PyStatus_NO_MEMORY();
1302     }
1303 
1304     if (calculate->prefix_found > 0) {
1305         /* Use the reduced prefix returned by Py_GetPrefix()
1306 
1307            Path: <basename(basename(prefix))> / <platlibdir> / "pythonXY.zip" */
1308         wchar_t *parent = _PyMem_RawWcsdup(calculate->prefix);
1309         if (parent == NULL) {
1310             res = _PyStatus_NO_MEMORY();
1311             goto done;
1312         }
1313         reduce(parent);
1314         reduce(parent);
1315         calculate->zip_path = joinpath2(parent, path);
1316         PyMem_RawFree(parent);
1317     }
1318     else {
1319         calculate->zip_path = joinpath2(calculate->prefix_macro, path);
1320     }
1321 
1322     if (calculate->zip_path == NULL) {
1323         res = _PyStatus_NO_MEMORY();
1324         goto done;
1325     }
1326 
1327     /* Replace "00" with version */
1328     size_t len = wcslen(calculate->zip_path);
1329     calculate->zip_path[len - 6] = VERSION[0];
1330     calculate->zip_path[len - 5] = VERSION[2];
1331 
1332     res = _PyStatus_OK();
1333 
1334 done:
1335     PyMem_RawFree(path);
1336     return res;
1337 }
1338 
1339 
1340 static PyStatus
calculate_module_search_path(PyCalculatePath * calculate,_PyPathConfig * pathconfig)1341 calculate_module_search_path(PyCalculatePath *calculate,
1342                              _PyPathConfig *pathconfig)
1343 {
1344     /* Calculate size of return buffer */
1345     size_t bufsz = 0;
1346     if (calculate->pythonpath_env != NULL) {
1347         bufsz += wcslen(calculate->pythonpath_env) + 1;
1348     }
1349 
1350     wchar_t *defpath = calculate->pythonpath_macro;
1351     size_t prefixsz = wcslen(calculate->prefix) + 1;
1352     while (1) {
1353         wchar_t *delim = wcschr(defpath, DELIM);
1354 
1355         if (!_Py_isabs(defpath)) {
1356             /* Paths are relative to prefix */
1357             bufsz += prefixsz;
1358         }
1359 
1360         if (delim) {
1361             bufsz += delim - defpath + 1;
1362         }
1363         else {
1364             bufsz += wcslen(defpath) + 1;
1365             break;
1366         }
1367         defpath = delim + 1;
1368     }
1369 
1370     bufsz += wcslen(calculate->zip_path) + 1;
1371     bufsz += wcslen(calculate->exec_prefix) + 1;
1372 
1373     /* Allocate the buffer */
1374     wchar_t *buf = PyMem_RawMalloc(bufsz * sizeof(wchar_t));
1375     if (buf == NULL) {
1376         return _PyStatus_NO_MEMORY();
1377     }
1378     buf[0] = '\0';
1379 
1380     /* Run-time value of $PYTHONPATH goes first */
1381     if (calculate->pythonpath_env) {
1382         wcscpy(buf, calculate->pythonpath_env);
1383         wcscat(buf, delimiter);
1384     }
1385 
1386     /* Next is the default zip path */
1387     wcscat(buf, calculate->zip_path);
1388     wcscat(buf, delimiter);
1389 
1390     /* Next goes merge of compile-time $PYTHONPATH with
1391      * dynamically located prefix.
1392      */
1393     defpath = calculate->pythonpath_macro;
1394     while (1) {
1395         wchar_t *delim = wcschr(defpath, DELIM);
1396 
1397         if (!_Py_isabs(defpath)) {
1398             wcscat(buf, calculate->prefix);
1399             if (prefixsz >= 2 && calculate->prefix[prefixsz - 2] != SEP &&
1400                 defpath[0] != (delim ? DELIM : L'\0'))
1401             {
1402                 /* not empty */
1403                 wcscat(buf, separator);
1404             }
1405         }
1406 
1407         if (delim) {
1408             size_t len = delim - defpath + 1;
1409             size_t end = wcslen(buf) + len;
1410             wcsncat(buf, defpath, len);
1411             buf[end] = '\0';
1412         }
1413         else {
1414             wcscat(buf, defpath);
1415             break;
1416         }
1417         defpath = delim + 1;
1418     }
1419     wcscat(buf, delimiter);
1420 
1421     /* Finally, on goes the directory for dynamic-load modules */
1422     wcscat(buf, calculate->exec_prefix);
1423 
1424     pathconfig->module_search_path = buf;
1425     return _PyStatus_OK();
1426 }
1427 
1428 
1429 static PyStatus
calculate_init(PyCalculatePath * calculate,const PyConfig * config)1430 calculate_init(PyCalculatePath *calculate, const PyConfig *config)
1431 {
1432     size_t len;
1433 
1434     calculate->warnings = config->pathconfig_warnings;
1435     calculate->pythonpath_env = config->pythonpath_env;
1436     calculate->platlibdir = config->platlibdir;
1437 
1438     const char *path = getenv("PATH");
1439     if (path) {
1440         calculate->path_env = Py_DecodeLocale(path, &len);
1441         if (!calculate->path_env) {
1442             return DECODE_LOCALE_ERR("PATH environment variable", len);
1443         }
1444     }
1445 
1446     /* Decode macros */
1447     calculate->pythonpath_macro = Py_DecodeLocale(PYTHONPATH, &len);
1448     if (!calculate->pythonpath_macro) {
1449         return DECODE_LOCALE_ERR("PYTHONPATH macro", len);
1450     }
1451     calculate->prefix_macro = Py_DecodeLocale(PREFIX, &len);
1452     if (!calculate->prefix_macro) {
1453         return DECODE_LOCALE_ERR("PREFIX macro", len);
1454     }
1455     calculate->exec_prefix_macro = Py_DecodeLocale(EXEC_PREFIX, &len);
1456     if (!calculate->exec_prefix_macro) {
1457         return DECODE_LOCALE_ERR("EXEC_PREFIX macro", len);
1458     }
1459     calculate->vpath_macro = Py_DecodeLocale(VPATH, &len);
1460     if (!calculate->vpath_macro) {
1461         return DECODE_LOCALE_ERR("VPATH macro", len);
1462     }
1463 
1464     // <platlibdir> / "pythonX.Y"
1465     wchar_t *pyversion = Py_DecodeLocale("python" VERSION, &len);
1466     if (!pyversion) {
1467         return DECODE_LOCALE_ERR("VERSION macro", len);
1468     }
1469     calculate->lib_python = joinpath2(config->platlibdir, pyversion);
1470     PyMem_RawFree(pyversion);
1471     if (calculate->lib_python == NULL) {
1472         return _PyStatus_NO_MEMORY();
1473     }
1474 
1475     return _PyStatus_OK();
1476 }
1477 
1478 
1479 static void
calculate_free(PyCalculatePath * calculate)1480 calculate_free(PyCalculatePath *calculate)
1481 {
1482     PyMem_RawFree(calculate->pythonpath_macro);
1483     PyMem_RawFree(calculate->prefix_macro);
1484     PyMem_RawFree(calculate->exec_prefix_macro);
1485     PyMem_RawFree(calculate->vpath_macro);
1486     PyMem_RawFree(calculate->lib_python);
1487     PyMem_RawFree(calculate->path_env);
1488     PyMem_RawFree(calculate->zip_path);
1489     PyMem_RawFree(calculate->argv0_path);
1490     PyMem_RawFree(calculate->prefix);
1491     PyMem_RawFree(calculate->exec_prefix);
1492 }
1493 
1494 
1495 static PyStatus
calculate_path(PyCalculatePath * calculate,_PyPathConfig * pathconfig)1496 calculate_path(PyCalculatePath *calculate, _PyPathConfig *pathconfig)
1497 {
1498     PyStatus status;
1499 
1500     if (pathconfig->program_full_path == NULL) {
1501         status = calculate_program(calculate, pathconfig);
1502         if (_PyStatus_EXCEPTION(status)) {
1503             return status;
1504         }
1505     }
1506 
1507     status = calculate_argv0_path(calculate, pathconfig);
1508     if (_PyStatus_EXCEPTION(status)) {
1509         return status;
1510     }
1511 
1512     /* If a pyvenv.cfg configure file is found,
1513        argv0_path is overriden with its 'home' variable. */
1514     status = calculate_read_pyenv(calculate);
1515     if (_PyStatus_EXCEPTION(status)) {
1516         return status;
1517     }
1518 
1519     status = calculate_prefix(calculate, pathconfig);
1520     if (_PyStatus_EXCEPTION(status)) {
1521         return status;
1522     }
1523 
1524     status = calculate_zip_path(calculate);
1525     if (_PyStatus_EXCEPTION(status)) {
1526         return status;
1527     }
1528 
1529     status = calculate_exec_prefix(calculate, pathconfig);
1530     if (_PyStatus_EXCEPTION(status)) {
1531         return status;
1532     }
1533 
1534     if ((!calculate->prefix_found || !calculate->exec_prefix_found)
1535         && calculate->warnings)
1536     {
1537         fprintf(stderr,
1538                 "Consider setting $PYTHONHOME to <prefix>[:<exec_prefix>]\n");
1539     }
1540 
1541     if (pathconfig->module_search_path == NULL) {
1542         status = calculate_module_search_path(calculate, pathconfig);
1543         if (_PyStatus_EXCEPTION(status)) {
1544             return status;
1545         }
1546     }
1547 
1548     if (pathconfig->prefix == NULL) {
1549         status = calculate_set_prefix(calculate, pathconfig);
1550         if (_PyStatus_EXCEPTION(status)) {
1551             return status;
1552         }
1553     }
1554 
1555     if (pathconfig->exec_prefix == NULL) {
1556         status = calculate_set_exec_prefix(calculate, pathconfig);
1557         if (_PyStatus_EXCEPTION(status)) {
1558             return status;
1559         }
1560     }
1561     return _PyStatus_OK();
1562 }
1563 
1564 
1565 /* Calculate the Python path configuration.
1566 
1567    Inputs:
1568 
1569    - PATH environment variable
1570    - Macros: PYTHONPATH, PREFIX, EXEC_PREFIX, VERSION (ex: "3.9").
1571      PREFIX and EXEC_PREFIX are generated by the configure script.
1572      PYTHONPATH macro is the default search path.
1573    - pybuilddir.txt file
1574    - pyvenv.cfg configuration file
1575    - PyConfig fields ('config' function argument):
1576 
1577      - pathconfig_warnings
1578      - pythonpath_env (PYTHONPATH environment variable)
1579 
1580    - _PyPathConfig fields ('pathconfig' function argument):
1581 
1582      - program_name: see config_init_program_name()
1583      - home: Py_SetPythonHome() or PYTHONHOME environment variable
1584 
1585    - current working directory: see copy_absolute()
1586 
1587    Outputs, 'pathconfig' fields:
1588 
1589    - program_full_path
1590    - module_search_path
1591    - prefix
1592    - exec_prefix
1593 
1594    If a field is already set (non NULL), it is left unchanged. */
1595 PyStatus
_PyPathConfig_Calculate(_PyPathConfig * pathconfig,const PyConfig * config)1596 _PyPathConfig_Calculate(_PyPathConfig *pathconfig, const PyConfig *config)
1597 {
1598     PyStatus status;
1599     PyCalculatePath calculate;
1600     memset(&calculate, 0, sizeof(calculate));
1601 
1602     status = calculate_init(&calculate, config);
1603     if (_PyStatus_EXCEPTION(status)) {
1604         goto done;
1605     }
1606 
1607     status = calculate_path(&calculate, pathconfig);
1608     if (_PyStatus_EXCEPTION(status)) {
1609         goto done;
1610     }
1611 
1612     /* program_full_path must an either an empty string or an absolute path */
1613     assert(wcslen(pathconfig->program_full_path) == 0
1614            || _Py_isabs(pathconfig->program_full_path));
1615 
1616     status = _PyStatus_OK();
1617 
1618 done:
1619     calculate_free(&calculate);
1620     return status;
1621 }
1622 
1623 #ifdef __cplusplus
1624 }
1625 #endif
1626