Coverage Report

Created: 2026-10-17 06:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/Python/pathconfig.c
Line
Count
Source
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
37
{
50
37
    return _Py_path_config.module_search_path;
51
37
}
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
74
{
79
74
    PyStatus status = _PyStatus_OK();
80
81
74
#define COPY(ATTR) \
82
370
    do { \
83
370
        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
370
    } while (0)
88
89
74
#define COPY2(ATTR, SRCATTR) \
90
74
    do { \
91
74
        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
74
    } while (0)
96
97
74
#define COPY_INT(ATTR) \
98
74
    do { \
99
74
        assert(_Py_path_config.ATTR >= 0); \
100
74
        if ((_Py_path_config.ATTR >= 0) && (config->ATTR <= 0)) { \
101
74
            config->ATTR = _Py_path_config.ATTR; \
102
74
        } \
103
74
    } while (0)
104
105
74
    COPY(prefix);
106
74
    COPY(exec_prefix);
107
74
    COPY(stdlib_dir);
108
74
    COPY(program_name);
109
74
    COPY(home);
110
74
    COPY2(executable, program_full_path);
111
74
    COPY_INT(_is_python_build);
112
    // module_search_path must be initialised - not read
113
74
#undef COPY
114
74
#undef COPY2
115
74
#undef COPY_INT
116
117
74
done:
118
74
    return status;
119
74
}
120
121
PyStatus
122
_PyPathConfig_UpdateGlobal(const PyConfig *config)
123
37
{
124
37
#define COPY(ATTR) \
125
185
    do { \
126
185
        if (config->ATTR) { \
127
185
            _PyMem_DefaultRawFree(_Py_path_config.ATTR); \
128
185
            _Py_path_config.ATTR = _PyMem_DefaultRawWcsdup(config->ATTR); \
129
185
            if (!_Py_path_config.ATTR) goto error; \
130
185
        } \
131
185
    } while (0)
132
133
37
#define COPY2(ATTR, SRCATTR) \
134
37
    do { \
135
37
        if (config->SRCATTR) { \
136
37
            _PyMem_DefaultRawFree(_Py_path_config.ATTR); \
137
37
            _Py_path_config.ATTR = _PyMem_DefaultRawWcsdup(config->SRCATTR); \
138
37
            if (!_Py_path_config.ATTR) goto error; \
139
37
        } \
140
37
    } while (0)
141
142
37
#define COPY_INT(ATTR) \
143
37
    do { \
144
37
        if (config->ATTR > 0) { \
145
0
            _Py_path_config.ATTR = config->ATTR; \
146
0
        } \
147
37
    } while (0)
148
149
37
    COPY(prefix);
150
37
    COPY(exec_prefix);
151
37
    COPY(stdlib_dir);
152
37
    COPY(program_name);
153
37
    COPY(home);
154
37
    COPY2(program_full_path, executable);
155
37
    COPY_INT(_is_python_build);
156
37
#undef COPY
157
37
#undef COPY2
158
37
#undef COPY_INT
159
160
37
    _PyMem_DefaultRawFree(_Py_path_config.module_search_path);
161
37
    _Py_path_config.module_search_path = NULL;
162
37
    _PyMem_DefaultRawFree(_Py_path_config.calculated_module_search_path);
163
37
    _Py_path_config.calculated_module_search_path = NULL;
164
165
37
    do {
166
37
        size_t cch = 1;
167
148
        for (Py_ssize_t i = 0; i < config->module_search_paths.length; ++i) {
168
111
            cch += 1 + wcslen(config->module_search_paths.items[i]);
169
111
        }
170
171
37
        wchar_t *path = (wchar_t*)_PyMem_DefaultRawMalloc(sizeof(wchar_t) * cch);
172
37
        if (!path) {
173
0
            goto error;
174
0
        }
175
37
        wchar_t *p = path;
176
148
        for (Py_ssize_t i = 0; i < config->module_search_paths.length; ++i) {
177
111
            wcscpy(p, config->module_search_paths.items[i]);
178
111
            p = wcschr(p, L'\0');
179
111
            *p++ = DELIM;
180
111
            *p = L'\0';
181
111
        }
182
183
74
        do {
184
74
            *p = L'\0';
185
74
        } while (p != path && *--p == DELIM);
186
37
        _Py_path_config.calculated_module_search_path = path;
187
37
    } while (0);
188
189
37
    return _PyStatus_OK();
190
191
0
error:
192
0
    return _PyStatus_NO_MEMORY();
193
37
}
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
// Function removed from Python 3.16 limited C API, but kept in the stable ABI
240
PyAPI_FUNC(void)
241
Py_SetPythonHome(const wchar_t *home)
242
0
{
243
0
    int has_value = home && home[0];
244
245
0
    _PyMem_DefaultRawFree(_Py_path_config.home);
246
0
    _Py_path_config.home = NULL;
247
248
0
    if (has_value) {
249
0
        _Py_path_config.home = _PyMem_DefaultRawWcsdup(home);
250
0
    }
251
252
0
    if (has_value && _Py_path_config.home == NULL) {
253
0
        path_out_of_memory(__func__);
254
0
    }
255
0
}
256
257
258
// Function removed from Python 3.16 limited C API, but kept in the stable ABI
259
PyAPI_FUNC(void)
260
Py_SetProgramName(const wchar_t *program_name)
261
0
{
262
0
    int has_value = program_name && program_name[0];
263
264
0
    _PyMem_DefaultRawFree(_Py_path_config.program_name);
265
0
    _Py_path_config.program_name = NULL;
266
267
0
    if (has_value) {
268
0
        _Py_path_config.program_name = _PyMem_DefaultRawWcsdup(program_name);
269
0
    }
270
271
0
    if (has_value && _Py_path_config.program_name == NULL) {
272
0
        path_out_of_memory(__func__);
273
0
    }
274
0
}
275
276
277
/* removed in 3.15, but kept for stable ABI compatibility */
278
PyAPI_FUNC(wchar_t *)
279
Py_GetPath(void)
280
0
{
281
    /* If the user has provided a path, return that */
282
0
    if (_Py_path_config.module_search_path) {
283
0
        return _Py_path_config.module_search_path;
284
0
    }
285
    /* If we have already done calculations, return the calculated path */
286
0
    return _Py_path_config.calculated_module_search_path;
287
0
}
288
289
290
PyAPI_FUNC(wchar_t *)
291
_Py_GetStdlibDir(void)
292
37
{
293
37
    wchar_t *stdlib_dir = _Py_path_config.stdlib_dir;
294
37
    if (stdlib_dir != NULL && stdlib_dir[0] != L'\0') {
295
37
        return stdlib_dir;
296
37
    }
297
0
    return NULL;
298
37
}
299
300
301
/* removed in 3.15, but kept for stable ABI compatibility */
302
PyAPI_FUNC(wchar_t *)
303
Py_GetPrefix(void)
304
0
{
305
0
    return _Py_path_config.prefix;
306
0
}
307
308
309
/* removed in 3.15, but kept for stable ABI compatibility */
310
PyAPI_FUNC(wchar_t *)
311
Py_GetExecPrefix(void)
312
0
{
313
0
    return _Py_path_config.exec_prefix;
314
0
}
315
316
317
/* removed in 3.15, but kept for stable ABI compatibility */
318
PyAPI_FUNC(wchar_t *)
319
Py_GetProgramFullPath(void)
320
0
{
321
0
    return _Py_path_config.program_full_path;
322
0
}
323
324
325
/* removed in 3.15, but kept for stable ABI compatibility */
326
PyAPI_FUNC(wchar_t *)
327
Py_GetPythonHome(void)
328
0
{
329
0
    return _Py_path_config.home;
330
0
}
331
332
333
/* removed in 3.15, but kept for stable ABI compatibility */
334
PyAPI_FUNC(wchar_t *)
335
Py_GetProgramName(void)
336
0
{
337
0
    return _Py_path_config.program_name;
338
0
}
339
340
341
342
/* Compute module search path from argv[0] or the current working
343
   directory ("-m module" case) which will be prepended to sys.argv:
344
   sys.path[0].
345
346
   Return 1 if the path is correctly resolved and written into *path0_p.
347
348
   Return 0 if it fails to resolve the full path. For example, return 0 if the
349
   current working directory has been removed (bpo-36236) or if argv is empty.
350
351
   Raise an exception and return -1 on error.
352
   */
353
int
354
_PyPathConfig_ComputeSysPath0(const PyWideStringList *argv, PyObject **path0_p)
355
0
{
356
0
    assert(_PyWideStringList_CheckConsistency(argv));
357
358
0
    if (argv->length == 0) {
359
        /* Leave sys.path unchanged if sys.argv is empty */
360
0
        return 0;
361
0
    }
362
363
0
    wchar_t *argv0 = argv->items[0];
364
0
    int have_module_arg = (wcscmp(argv0, L"-m") == 0);
365
0
    int have_script_arg = (!have_module_arg && (wcscmp(argv0, L"-c") != 0));
366
367
0
    wchar_t *path0 = argv0;
368
0
    Py_ssize_t n = 0;
369
370
0
#ifdef HAVE_REALPATH
371
0
    wchar_t fullpath[MAXPATHLEN];
372
#elif defined(MS_WINDOWS)
373
    wchar_t fullpath[MAX_PATH];
374
#endif
375
376
0
    if (have_module_arg) {
377
0
#if defined(HAVE_REALPATH) || defined(MS_WINDOWS)
378
0
        if (!_Py_wgetcwd(fullpath, Py_ARRAY_LENGTH(fullpath))) {
379
0
            return 0;
380
0
        }
381
0
        path0 = fullpath;
382
#else
383
        path0 = L".";
384
#endif
385
0
        n = wcslen(path0);
386
0
    }
387
388
0
#ifdef HAVE_READLINK
389
0
    wchar_t link[MAXPATHLEN + 1];
390
0
    int nr = 0;
391
0
    wchar_t path0copy[2 * MAXPATHLEN + 1];
392
393
0
    if (have_script_arg) {
394
0
        nr = _Py_wreadlink(path0, link, Py_ARRAY_LENGTH(link));
395
0
    }
396
0
    if (nr > 0) {
397
        /* It's a symlink */
398
0
        link[nr] = '\0';
399
0
        if (link[0] == SEP) {
400
0
            path0 = link; /* Link to absolute path */
401
0
        }
402
0
        else if (wcschr(link, SEP) == NULL) {
403
            /* Link without path */
404
0
        }
405
0
        else {
406
            /* Must join(dirname(path0), link) */
407
0
            wchar_t *q = wcsrchr(path0, SEP);
408
0
            if (q == NULL) {
409
                /* path0 without path */
410
0
                path0 = link;
411
0
            }
412
0
            else {
413
                /* Must make a copy, path0copy has room for 2 * MAXPATHLEN */
414
0
                wcsncpy(path0copy, path0, MAXPATHLEN);
415
0
                q = wcsrchr(path0copy, SEP);
416
0
                wcsncpy(q+1, link, MAXPATHLEN);
417
0
                q[MAXPATHLEN + 1] = L'\0';
418
0
                path0 = path0copy;
419
0
            }
420
0
        }
421
0
    }
422
0
#endif /* HAVE_READLINK */
423
424
0
    wchar_t *p = NULL;
425
426
#if SEP == '\\'
427
    /* Special case for Microsoft filename syntax */
428
    if (have_script_arg) {
429
        wchar_t *q;
430
#if defined(MS_WINDOWS)
431
        /* Replace the first element in argv with the full path. */
432
        wchar_t *ptemp;
433
        if (GetFullPathNameW(path0,
434
                           Py_ARRAY_LENGTH(fullpath),
435
                           fullpath,
436
                           &ptemp)) {
437
            path0 = fullpath;
438
        }
439
#endif
440
        p = wcsrchr(path0, SEP);
441
        /* Test for alternate separator */
442
        q = wcsrchr(p ? p : path0, '/');
443
        if (q != NULL)
444
            p = q;
445
        if (p != NULL) {
446
            n = p + 1 - path0;
447
            if (n > 1 && p[-1] != ':')
448
                n--; /* Drop trailing separator */
449
        }
450
    }
451
#else
452
    /* All other filename syntaxes */
453
0
    if (have_script_arg) {
454
0
#if defined(HAVE_REALPATH)
455
0
        if (_Py_wrealpath(path0, fullpath, Py_ARRAY_LENGTH(fullpath))) {
456
0
            path0 = fullpath;
457
0
        }
458
0
#endif
459
0
        p = wcsrchr(path0, SEP);
460
0
    }
461
0
    if (p != NULL) {
462
0
        n = p + 1 - path0;
463
0
#if SEP == '/' /* Special case for Unix filename syntax */
464
0
        if (n > 1) {
465
            /* Drop trailing separator */
466
0
            n--;
467
0
        }
468
0
#endif /* Unix */
469
0
    }
470
0
#endif /* All others */
471
472
0
    PyObject *path0_obj = PyUnicode_FromWideChar(path0, n);
473
0
    if (path0_obj == NULL) {
474
0
        return -1;
475
0
    }
476
477
0
    *path0_p = path0_obj;
478
0
    return 1;
479
0
}