Coverage Report

Created: 2025-07-04 06:49

/src/cpython/Python/pathconfig.c
Line
Count
Source (jump to first uncovered line)
1
/* Path configuration like module_search_path (sys.path) */
2
3
#include "Python.h"
4
#include "pycore_initconfig.h"    // _PyStatus_OK()
5
#include "pycore_fileutils.h"     // _Py_wgetcwd()
6
#include "pycore_pathconfig.h"
7
#include "pycore_pymem.h"         // _PyMem_DefaultRawFree()
8
#include <wchar.h>
9
10
#include "marshal.h"              // PyMarshal_ReadObjectFromString
11
#include "osdefs.h"               // DELIM
12
13
#ifdef MS_WINDOWS
14
#  include <windows.h>            // GetFullPathNameW(), MAX_PATH
15
#  include <pathcch.h>
16
#  include <shlwapi.h>
17
#endif
18
19
20
/* External interface */
21
22
/* Stored values set by C API functions */
23
typedef struct _PyPathConfig {
24
    /* Full path to the Python program */
25
    wchar_t *program_full_path;
26
    wchar_t *prefix;
27
    wchar_t *exec_prefix;
28
    wchar_t *stdlib_dir;
29
    /* Set by Py_SetPath */
30
    wchar_t *module_search_path;
31
    /* Set by _PyPathConfig_UpdateGlobal */
32
    wchar_t *calculated_module_search_path;
33
    /* Python program name */
34
    wchar_t *program_name;
35
    /* Set by Py_SetPythonHome() or PYTHONHOME environment variable */
36
    wchar_t *home;
37
    int _is_python_build;
38
} _PyPathConfig;
39
40
#  define _PyPathConfig_INIT \
41
      {.module_search_path = NULL, ._is_python_build = 0}
42
43
44
_PyPathConfig _Py_path_config = _PyPathConfig_INIT;
45
46
47
const wchar_t *
48
_PyPathConfig_GetGlobalModuleSearchPath(void)
49
16
{
50
16
    return _Py_path_config.module_search_path;
51
16
}
52
53
54
void
55
_PyPathConfig_ClearGlobal(void)
56
0
{
57
0
#define CLEAR(ATTR) \
58
0
    do { \
59
0
        _PyMem_DefaultRawFree(_Py_path_config.ATTR); \
60
0
        _Py_path_config.ATTR = NULL; \
61
0
    } while (0)
62
63
0
    CLEAR(program_full_path);
64
0
    CLEAR(prefix);
65
0
    CLEAR(exec_prefix);
66
0
    CLEAR(stdlib_dir);
67
0
    CLEAR(module_search_path);
68
0
    CLEAR(calculated_module_search_path);
69
0
    CLEAR(program_name);
70
0
    CLEAR(home);
71
0
    _Py_path_config._is_python_build = 0;
72
73
0
#undef CLEAR
74
0
}
75
76
PyStatus
77
_PyPathConfig_ReadGlobal(PyConfig *config)
78
32
{
79
32
    PyStatus status = _PyStatus_OK();
80
81
32
#define COPY(ATTR) \
82
160
    do { \
83
160
        if (_Py_path_config.ATTR && !config->ATTR) { \
84
0
            status = PyConfig_SetString(config, &config->ATTR, _Py_path_config.ATTR); \
85
0
            if (_PyStatus_EXCEPTION(status)) goto done; \
86
0
        } \
87
160
    } while (0)
88
89
32
#define COPY2(ATTR, SRCATTR) \
90
32
    do { \
91
32
        if (_Py_path_config.SRCATTR && !config->ATTR) { \
92
0
            status = PyConfig_SetString(config, &config->ATTR, _Py_path_config.SRCATTR); \
93
0
            if (_PyStatus_EXCEPTION(status)) goto done; \
94
0
        } \
95
32
    } while (0)
96
97
32
#define COPY_INT(ATTR) \
98
32
    do { \
99
32
        assert(_Py_path_config.ATTR >= 0); \
100
32
        if ((_Py_path_config.ATTR >= 0) && (config->ATTR <= 0)) { \
101
32
            config->ATTR = _Py_path_config.ATTR; \
102
32
        } \
103
32
    } while (0)
104
105
32
    COPY(prefix);
106
32
    COPY(exec_prefix);
107
32
    COPY(stdlib_dir);
108
32
    COPY(program_name);
109
32
    COPY(home);
110
32
    COPY2(executable, program_full_path);
111
32
    COPY_INT(_is_python_build);
112
    // module_search_path must be initialised - not read
113
32
#undef COPY
114
32
#undef COPY2
115
32
#undef COPY_INT
116
117
32
done:
118
32
    return status;
119
32
}
120
121
PyStatus
122
_PyPathConfig_UpdateGlobal(const PyConfig *config)
123
16
{
124
16
#define COPY(ATTR) \
125
80
    do { \
126
80
        if (config->ATTR) { \
127
80
            _PyMem_DefaultRawFree(_Py_path_config.ATTR); \
128
80
            _Py_path_config.ATTR = _PyMem_DefaultRawWcsdup(config->ATTR); \
129
80
            if (!_Py_path_config.ATTR) goto error; \
130
80
        } \
131
80
    } while (0)
132
133
16
#define COPY2(ATTR, SRCATTR) \
134
16
    do { \
135
16
        if (config->SRCATTR) { \
136
16
            _PyMem_DefaultRawFree(_Py_path_config.ATTR); \
137
16
            _Py_path_config.ATTR = _PyMem_DefaultRawWcsdup(config->SRCATTR); \
138
16
            if (!_Py_path_config.ATTR) goto error; \
139
16
        } \
140
16
    } while (0)
141
142
16
#define COPY_INT(ATTR) \
143
16
    do { \
144
16
        if (config->ATTR > 0) { \
145
0
            _Py_path_config.ATTR = config->ATTR; \
146
0
        } \
147
16
    } while (0)
148
149
16
    COPY(prefix);
150
16
    COPY(exec_prefix);
151
16
    COPY(stdlib_dir);
152
16
    COPY(program_name);
153
16
    COPY(home);
154
16
    COPY2(program_full_path, executable);
155
16
    COPY_INT(_is_python_build);
156
16
#undef COPY
157
16
#undef COPY2
158
16
#undef COPY_INT
159
160
16
    _PyMem_DefaultRawFree(_Py_path_config.module_search_path);
161
16
    _Py_path_config.module_search_path = NULL;
162
16
    _PyMem_DefaultRawFree(_Py_path_config.calculated_module_search_path);
163
16
    _Py_path_config.calculated_module_search_path = NULL;
164
165
16
    do {
166
16
        size_t cch = 1;
167
64
        for (Py_ssize_t i = 0; i < config->module_search_paths.length; ++i) {
168
48
            cch += 1 + wcslen(config->module_search_paths.items[i]);
169
48
        }
170
171
16
        wchar_t *path = (wchar_t*)_PyMem_DefaultRawMalloc(sizeof(wchar_t) * cch);
172
16
        if (!path) {
173
0
            goto error;
174
0
        }
175
16
        wchar_t *p = path;
176
64
        for (Py_ssize_t i = 0; i < config->module_search_paths.length; ++i) {
177
48
            wcscpy(p, config->module_search_paths.items[i]);
178
48
            p = wcschr(p, L'\0');
179
48
            *p++ = DELIM;
180
48
            *p = L'\0';
181
48
        }
182
183
32
        do {
184
32
            *p = L'\0';
185
32
        } while (p != path && *--p == DELIM);
186
16
        _Py_path_config.calculated_module_search_path = path;
187
16
    } while (0);
188
189
16
    return _PyStatus_OK();
190
191
0
error:
192
0
    return _PyStatus_NO_MEMORY();
193
16
}
194
195
196
static void _Py_NO_RETURN
197
path_out_of_memory(const char *func)
198
0
{
199
0
    _Py_FatalErrorFunc(func, "out of memory");
200
0
}
201
202
// Removed in Python 3.13 API, but kept for the stable ABI
203
PyAPI_FUNC(void)
204
Py_SetPath(const wchar_t *path)
205
0
{
206
0
    if (path == NULL) {
207
0
        _PyPathConfig_ClearGlobal();
208
0
        return;
209
0
    }
210
211
0
    _PyMem_DefaultRawFree(_Py_path_config.prefix);
212
0
    _PyMem_DefaultRawFree(_Py_path_config.exec_prefix);
213
0
    _PyMem_DefaultRawFree(_Py_path_config.stdlib_dir);
214
0
    _PyMem_DefaultRawFree(_Py_path_config.module_search_path);
215
0
    _PyMem_DefaultRawFree(_Py_path_config.calculated_module_search_path);
216
217
0
    _Py_path_config.prefix = _PyMem_DefaultRawWcsdup(L"");
218
0
    _Py_path_config.exec_prefix = _PyMem_DefaultRawWcsdup(L"");
219
    // XXX Copy this from the new module_search_path?
220
0
    if (_Py_path_config.home != NULL) {
221
0
        _Py_path_config.stdlib_dir = _PyMem_DefaultRawWcsdup(_Py_path_config.home);
222
0
    }
223
0
    else {
224
0
        _Py_path_config.stdlib_dir = _PyMem_DefaultRawWcsdup(L"");
225
0
    }
226
0
    _Py_path_config.module_search_path = _PyMem_DefaultRawWcsdup(path);
227
0
    _Py_path_config.calculated_module_search_path = NULL;
228
229
0
    if (_Py_path_config.prefix == NULL
230
0
        || _Py_path_config.exec_prefix == NULL
231
0
        || _Py_path_config.stdlib_dir == NULL
232
0
        || _Py_path_config.module_search_path == NULL)
233
0
    {
234
0
        path_out_of_memory(__func__);
235
0
    }
236
0
}
237
238
239
void
240
Py_SetPythonHome(const wchar_t *home)
241
0
{
242
0
    int has_value = home && home[0];
243
244
0
    _PyMem_DefaultRawFree(_Py_path_config.home);
245
0
    _Py_path_config.home = NULL;
246
247
0
    if (has_value) {
248
0
        _Py_path_config.home = _PyMem_DefaultRawWcsdup(home);
249
0
    }
250
251
0
    if (has_value && _Py_path_config.home == NULL) {
252
0
        path_out_of_memory(__func__);
253
0
    }
254
0
}
255
256
257
void
258
Py_SetProgramName(const wchar_t *program_name)
259
0
{
260
0
    int has_value = program_name && program_name[0];
261
262
0
    _PyMem_DefaultRawFree(_Py_path_config.program_name);
263
0
    _Py_path_config.program_name = NULL;
264
265
0
    if (has_value) {
266
0
        _Py_path_config.program_name = _PyMem_DefaultRawWcsdup(program_name);
267
0
    }
268
269
0
    if (has_value && _Py_path_config.program_name == NULL) {
270
0
        path_out_of_memory(__func__);
271
0
    }
272
0
}
273
274
275
/* removed in 3.15, but kept for stable ABI compatibility */
276
PyAPI_FUNC(wchar_t *)
277
Py_GetPath(void)
278
0
{
279
    /* If the user has provided a path, return that */
280
0
    if (_Py_path_config.module_search_path) {
281
0
        return _Py_path_config.module_search_path;
282
0
    }
283
    /* If we have already done calculations, return the calculated path */
284
0
    return _Py_path_config.calculated_module_search_path;
285
0
}
286
287
288
PyAPI_FUNC(wchar_t *)
289
_Py_GetStdlibDir(void)
290
16
{
291
16
    wchar_t *stdlib_dir = _Py_path_config.stdlib_dir;
292
16
    if (stdlib_dir != NULL && stdlib_dir[0] != L'\0') {
293
16
        return stdlib_dir;
294
16
    }
295
0
    return NULL;
296
16
}
297
298
299
/* removed in 3.15, but kept for stable ABI compatibility */
300
PyAPI_FUNC(wchar_t *)
301
Py_GetPrefix(void)
302
0
{
303
0
    return _Py_path_config.prefix;
304
0
}
305
306
307
/* removed in 3.15, but kept for stable ABI compatibility */
308
PyAPI_FUNC(wchar_t *)
309
Py_GetExecPrefix(void)
310
0
{
311
0
    return _Py_path_config.exec_prefix;
312
0
}
313
314
315
/* removed in 3.15, but kept for stable ABI compatibility */
316
PyAPI_FUNC(wchar_t *)
317
Py_GetProgramFullPath(void)
318
0
{
319
0
    return _Py_path_config.program_full_path;
320
0
}
321
322
323
/* removed in 3.15, but kept for stable ABI compatibility */
324
PyAPI_FUNC(wchar_t *)
325
Py_GetPythonHome(void)
326
0
{
327
0
    return _Py_path_config.home;
328
0
}
329
330
331
/* removed in 3.15, but kept for stable ABI compatibility */
332
PyAPI_FUNC(wchar_t *)
333
Py_GetProgramName(void)
334
0
{
335
0
    return _Py_path_config.program_name;
336
0
}
337
338
339
340
/* Compute module search path from argv[0] or the current working
341
   directory ("-m module" case) which will be prepended to sys.argv:
342
   sys.path[0].
343
344
   Return 1 if the path is correctly resolved and written into *path0_p.
345
346
   Return 0 if it fails to resolve the full path. For example, return 0 if the
347
   current working directory has been removed (bpo-36236) or if argv is empty.
348
349
   Raise an exception and return -1 on error.
350
   */
351
int
352
_PyPathConfig_ComputeSysPath0(const PyWideStringList *argv, PyObject **path0_p)
353
0
{
354
0
    assert(_PyWideStringList_CheckConsistency(argv));
355
356
0
    if (argv->length == 0) {
357
        /* Leave sys.path unchanged if sys.argv is empty */
358
0
        return 0;
359
0
    }
360
361
0
    wchar_t *argv0 = argv->items[0];
362
0
    int have_module_arg = (wcscmp(argv0, L"-m") == 0);
363
0
    int have_script_arg = (!have_module_arg && (wcscmp(argv0, L"-c") != 0));
364
365
0
    wchar_t *path0 = argv0;
366
0
    Py_ssize_t n = 0;
367
368
0
#ifdef HAVE_REALPATH
369
0
    wchar_t fullpath[MAXPATHLEN];
370
#elif defined(MS_WINDOWS)
371
    wchar_t fullpath[MAX_PATH];
372
#endif
373
374
0
    if (have_module_arg) {
375
0
#if defined(HAVE_REALPATH) || defined(MS_WINDOWS)
376
0
        if (!_Py_wgetcwd(fullpath, Py_ARRAY_LENGTH(fullpath))) {
377
0
            return 0;
378
0
        }
379
0
        path0 = fullpath;
380
#else
381
        path0 = L".";
382
#endif
383
0
        n = wcslen(path0);
384
0
    }
385
386
0
#ifdef HAVE_READLINK
387
0
    wchar_t link[MAXPATHLEN + 1];
388
0
    int nr = 0;
389
0
    wchar_t path0copy[2 * MAXPATHLEN + 1];
390
391
0
    if (have_script_arg) {
392
0
        nr = _Py_wreadlink(path0, link, Py_ARRAY_LENGTH(link));
393
0
    }
394
0
    if (nr > 0) {
395
        /* It's a symlink */
396
0
        link[nr] = '\0';
397
0
        if (link[0] == SEP) {
398
0
            path0 = link; /* Link to absolute path */
399
0
        }
400
0
        else if (wcschr(link, SEP) == NULL) {
401
            /* Link without path */
402
0
        }
403
0
        else {
404
            /* Must join(dirname(path0), link) */
405
0
            wchar_t *q = wcsrchr(path0, SEP);
406
0
            if (q == NULL) {
407
                /* path0 without path */
408
0
                path0 = link;
409
0
            }
410
0
            else {
411
                /* Must make a copy, path0copy has room for 2 * MAXPATHLEN */
412
0
                wcsncpy(path0copy, path0, MAXPATHLEN);
413
0
                q = wcsrchr(path0copy, SEP);
414
0
                wcsncpy(q+1, link, MAXPATHLEN);
415
0
                q[MAXPATHLEN + 1] = L'\0';
416
0
                path0 = path0copy;
417
0
            }
418
0
        }
419
0
    }
420
0
#endif /* HAVE_READLINK */
421
422
0
    wchar_t *p = NULL;
423
424
#if SEP == '\\'
425
    /* Special case for Microsoft filename syntax */
426
    if (have_script_arg) {
427
        wchar_t *q;
428
#if defined(MS_WINDOWS)
429
        /* Replace the first element in argv with the full path. */
430
        wchar_t *ptemp;
431
        if (GetFullPathNameW(path0,
432
                           Py_ARRAY_LENGTH(fullpath),
433
                           fullpath,
434
                           &ptemp)) {
435
            path0 = fullpath;
436
        }
437
#endif
438
        p = wcsrchr(path0, SEP);
439
        /* Test for alternate separator */
440
        q = wcsrchr(p ? p : path0, '/');
441
        if (q != NULL)
442
            p = q;
443
        if (p != NULL) {
444
            n = p + 1 - path0;
445
            if (n > 1 && p[-1] != ':')
446
                n--; /* Drop trailing separator */
447
        }
448
    }
449
#else
450
    /* All other filename syntaxes */
451
0
    if (have_script_arg) {
452
0
#if defined(HAVE_REALPATH)
453
0
        if (_Py_wrealpath(path0, fullpath, Py_ARRAY_LENGTH(fullpath))) {
454
0
            path0 = fullpath;
455
0
        }
456
0
#endif
457
0
        p = wcsrchr(path0, SEP);
458
0
    }
459
0
    if (p != NULL) {
460
0
        n = p + 1 - path0;
461
0
#if SEP == '/' /* Special case for Unix filename syntax */
462
0
        if (n > 1) {
463
            /* Drop trailing separator */
464
0
            n--;
465
0
        }
466
0
#endif /* Unix */
467
0
    }
468
0
#endif /* All others */
469
470
0
    PyObject *path0_obj = PyUnicode_FromWideChar(path0, n);
471
0
    if (path0_obj == NULL) {
472
0
        return -1;
473
0
    }
474
475
0
    *path0_p = path0_obj;
476
0
    return 1;
477
0
}