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