cpython/Python/pathconfig.c
Sam Gross ca22147547
gh-111924: Fix data races when swapping allocators (gh-130287)
CPython current temporarily changes `PYMEM_DOMAIN_RAW` to the default
allocator during initialization and shutdown. The motivation is to
ensure that core runtime structures are allocated and freed using the
same allocator. However, modifying the current allocator changes global
state and is not thread-safe even with the GIL. Other threads may be
allocating or freeing objects use PYMEM_DOMAIN_RAW; they are not
required to hold the GIL to call PyMem_RawMalloc/PyMem_RawFree.

This adds new internal-only functions like `_PyMem_DefaultRawMalloc`
that aren't affected by calls to `PyMem_SetAllocator()`, so they're
appropriate for Python runtime initialization and finalization. Use
these calls in places where we previously swapped to the default raw
allocator.
2025-02-20 11:31:15 -05:00

472 lines
12 KiB
C

/* Path configuration like module_search_path (sys.path) */
#include "Python.h"
#include "pycore_initconfig.h" // _PyStatus_OK()
#include "pycore_fileutils.h" // _Py_wgetcwd()
#include "pycore_pathconfig.h"
#include "pycore_pymem.h" // _PyMem_DefaultRawFree()
#include <wchar.h>
#include "marshal.h" // PyMarshal_ReadObjectFromString
#include "osdefs.h" // DELIM
#ifdef MS_WINDOWS
# include <windows.h> // GetFullPathNameW(), MAX_PATH
# include <pathcch.h>
# include <shlwapi.h>
#endif
/* External interface */
/* Stored values set by C API functions */
typedef struct _PyPathConfig {
/* Full path to the Python program */
wchar_t *program_full_path;
wchar_t *prefix;
wchar_t *exec_prefix;
wchar_t *stdlib_dir;
/* Set by Py_SetPath */
wchar_t *module_search_path;
/* Set by _PyPathConfig_UpdateGlobal */
wchar_t *calculated_module_search_path;
/* Python program name */
wchar_t *program_name;
/* Set by Py_SetPythonHome() or PYTHONHOME environment variable */
wchar_t *home;
int _is_python_build;
} _PyPathConfig;
# define _PyPathConfig_INIT \
{.module_search_path = NULL, ._is_python_build = 0}
_PyPathConfig _Py_path_config = _PyPathConfig_INIT;
const wchar_t *
_PyPathConfig_GetGlobalModuleSearchPath(void)
{
return _Py_path_config.module_search_path;
}
void
_PyPathConfig_ClearGlobal(void)
{
#define CLEAR(ATTR) \
do { \
_PyMem_DefaultRawFree(_Py_path_config.ATTR); \
_Py_path_config.ATTR = NULL; \
} while (0)
CLEAR(program_full_path);
CLEAR(prefix);
CLEAR(exec_prefix);
CLEAR(stdlib_dir);
CLEAR(module_search_path);
CLEAR(calculated_module_search_path);
CLEAR(program_name);
CLEAR(home);
_Py_path_config._is_python_build = 0;
#undef CLEAR
}
PyStatus
_PyPathConfig_ReadGlobal(PyConfig *config)
{
PyStatus status = _PyStatus_OK();
#define COPY(ATTR) \
do { \
if (_Py_path_config.ATTR && !config->ATTR) { \
status = PyConfig_SetString(config, &config->ATTR, _Py_path_config.ATTR); \
if (_PyStatus_EXCEPTION(status)) goto done; \
} \
} while (0)
#define COPY2(ATTR, SRCATTR) \
do { \
if (_Py_path_config.SRCATTR && !config->ATTR) { \
status = PyConfig_SetString(config, &config->ATTR, _Py_path_config.SRCATTR); \
if (_PyStatus_EXCEPTION(status)) goto done; \
} \
} while (0)
#define COPY_INT(ATTR) \
do { \
assert(_Py_path_config.ATTR >= 0); \
if ((_Py_path_config.ATTR >= 0) && (config->ATTR <= 0)) { \
config->ATTR = _Py_path_config.ATTR; \
} \
} while (0)
COPY(prefix);
COPY(exec_prefix);
COPY(stdlib_dir);
COPY(program_name);
COPY(home);
COPY2(executable, program_full_path);
COPY_INT(_is_python_build);
// module_search_path must be initialised - not read
#undef COPY
#undef COPY2
#undef COPY_INT
done:
return status;
}
PyStatus
_PyPathConfig_UpdateGlobal(const PyConfig *config)
{
#define COPY(ATTR) \
do { \
if (config->ATTR) { \
_PyMem_DefaultRawFree(_Py_path_config.ATTR); \
_Py_path_config.ATTR = _PyMem_DefaultRawWcsdup(config->ATTR); \
if (!_Py_path_config.ATTR) goto error; \
} \
} while (0)
#define COPY2(ATTR, SRCATTR) \
do { \
if (config->SRCATTR) { \
_PyMem_DefaultRawFree(_Py_path_config.ATTR); \
_Py_path_config.ATTR = _PyMem_DefaultRawWcsdup(config->SRCATTR); \
if (!_Py_path_config.ATTR) goto error; \
} \
} while (0)
#define COPY_INT(ATTR) \
do { \
if (config->ATTR > 0) { \
_Py_path_config.ATTR = config->ATTR; \
} \
} while (0)
COPY(prefix);
COPY(exec_prefix);
COPY(stdlib_dir);
COPY(program_name);
COPY(home);
COPY2(program_full_path, executable);
COPY_INT(_is_python_build);
#undef COPY
#undef COPY2
#undef COPY_INT
_PyMem_DefaultRawFree(_Py_path_config.module_search_path);
_Py_path_config.module_search_path = NULL;
_PyMem_DefaultRawFree(_Py_path_config.calculated_module_search_path);
_Py_path_config.calculated_module_search_path = NULL;
do {
size_t cch = 1;
for (Py_ssize_t i = 0; i < config->module_search_paths.length; ++i) {
cch += 1 + wcslen(config->module_search_paths.items[i]);
}
wchar_t *path = (wchar_t*)_PyMem_DefaultRawMalloc(sizeof(wchar_t) * cch);
if (!path) {
goto error;
}
wchar_t *p = path;
for (Py_ssize_t i = 0; i < config->module_search_paths.length; ++i) {
wcscpy(p, config->module_search_paths.items[i]);
p = wcschr(p, L'\0');
*p++ = DELIM;
*p = L'\0';
}
do {
*p = L'\0';
} while (p != path && *--p == DELIM);
_Py_path_config.calculated_module_search_path = path;
} while (0);
return _PyStatus_OK();
error:
return _PyStatus_NO_MEMORY();
}
static void _Py_NO_RETURN
path_out_of_memory(const char *func)
{
_Py_FatalErrorFunc(func, "out of memory");
}
// Removed in Python 3.13 API, but kept for the stable ABI
PyAPI_FUNC(void)
Py_SetPath(const wchar_t *path)
{
if (path == NULL) {
_PyPathConfig_ClearGlobal();
return;
}
_PyMem_DefaultRawFree(_Py_path_config.prefix);
_PyMem_DefaultRawFree(_Py_path_config.exec_prefix);
_PyMem_DefaultRawFree(_Py_path_config.stdlib_dir);
_PyMem_DefaultRawFree(_Py_path_config.module_search_path);
_PyMem_DefaultRawFree(_Py_path_config.calculated_module_search_path);
_Py_path_config.prefix = _PyMem_DefaultRawWcsdup(L"");
_Py_path_config.exec_prefix = _PyMem_DefaultRawWcsdup(L"");
// XXX Copy this from the new module_search_path?
if (_Py_path_config.home != NULL) {
_Py_path_config.stdlib_dir = _PyMem_DefaultRawWcsdup(_Py_path_config.home);
}
else {
_Py_path_config.stdlib_dir = _PyMem_DefaultRawWcsdup(L"");
}
_Py_path_config.module_search_path = _PyMem_DefaultRawWcsdup(path);
_Py_path_config.calculated_module_search_path = NULL;
if (_Py_path_config.prefix == NULL
|| _Py_path_config.exec_prefix == NULL
|| _Py_path_config.stdlib_dir == NULL
|| _Py_path_config.module_search_path == NULL)
{
path_out_of_memory(__func__);
}
}
void
Py_SetPythonHome(const wchar_t *home)
{
int has_value = home && home[0];
_PyMem_DefaultRawFree(_Py_path_config.home);
_Py_path_config.home = NULL;
if (has_value) {
_Py_path_config.home = _PyMem_DefaultRawWcsdup(home);
}
if (has_value && _Py_path_config.home == NULL) {
path_out_of_memory(__func__);
}
}
void
Py_SetProgramName(const wchar_t *program_name)
{
int has_value = program_name && program_name[0];
_PyMem_DefaultRawFree(_Py_path_config.program_name);
_Py_path_config.program_name = NULL;
if (has_value) {
_Py_path_config.program_name = _PyMem_DefaultRawWcsdup(program_name);
}
if (has_value && _Py_path_config.program_name == NULL) {
path_out_of_memory(__func__);
}
}
wchar_t *
Py_GetPath(void)
{
/* If the user has provided a path, return that */
if (_Py_path_config.module_search_path) {
return _Py_path_config.module_search_path;
}
/* If we have already done calculations, return the calculated path */
return _Py_path_config.calculated_module_search_path;
}
wchar_t *
_Py_GetStdlibDir(void)
{
wchar_t *stdlib_dir = _Py_path_config.stdlib_dir;
if (stdlib_dir != NULL && stdlib_dir[0] != L'\0') {
return stdlib_dir;
}
return NULL;
}
wchar_t *
Py_GetPrefix(void)
{
return _Py_path_config.prefix;
}
wchar_t *
Py_GetExecPrefix(void)
{
return _Py_path_config.exec_prefix;
}
wchar_t *
Py_GetProgramFullPath(void)
{
return _Py_path_config.program_full_path;
}
wchar_t*
Py_GetPythonHome(void)
{
return _Py_path_config.home;
}
wchar_t *
Py_GetProgramName(void)
{
return _Py_path_config.program_name;
}
/* Compute module search path from argv[0] or the current working
directory ("-m module" case) which will be prepended to sys.argv:
sys.path[0].
Return 1 if the path is correctly resolved and written into *path0_p.
Return 0 if it fails to resolve the full path. For example, return 0 if the
current working directory has been removed (bpo-36236) or if argv is empty.
Raise an exception and return -1 on error.
*/
int
_PyPathConfig_ComputeSysPath0(const PyWideStringList *argv, PyObject **path0_p)
{
assert(_PyWideStringList_CheckConsistency(argv));
if (argv->length == 0) {
/* Leave sys.path unchanged if sys.argv is empty */
return 0;
}
wchar_t *argv0 = argv->items[0];
int have_module_arg = (wcscmp(argv0, L"-m") == 0);
int have_script_arg = (!have_module_arg && (wcscmp(argv0, L"-c") != 0));
wchar_t *path0 = argv0;
Py_ssize_t n = 0;
#ifdef HAVE_REALPATH
wchar_t fullpath[MAXPATHLEN];
#elif defined(MS_WINDOWS)
wchar_t fullpath[MAX_PATH];
#endif
if (have_module_arg) {
#if defined(HAVE_REALPATH) || defined(MS_WINDOWS)
if (!_Py_wgetcwd(fullpath, Py_ARRAY_LENGTH(fullpath))) {
return 0;
}
path0 = fullpath;
#else
path0 = L".";
#endif
n = wcslen(path0);
}
#ifdef HAVE_READLINK
wchar_t link[MAXPATHLEN + 1];
int nr = 0;
wchar_t path0copy[2 * MAXPATHLEN + 1];
if (have_script_arg) {
nr = _Py_wreadlink(path0, link, Py_ARRAY_LENGTH(link));
}
if (nr > 0) {
/* It's a symlink */
link[nr] = '\0';
if (link[0] == SEP) {
path0 = link; /* Link to absolute path */
}
else if (wcschr(link, SEP) == NULL) {
/* Link without path */
}
else {
/* Must join(dirname(path0), link) */
wchar_t *q = wcsrchr(path0, SEP);
if (q == NULL) {
/* path0 without path */
path0 = link;
}
else {
/* Must make a copy, path0copy has room for 2 * MAXPATHLEN */
wcsncpy(path0copy, path0, MAXPATHLEN);
q = wcsrchr(path0copy, SEP);
wcsncpy(q+1, link, MAXPATHLEN);
q[MAXPATHLEN + 1] = L'\0';
path0 = path0copy;
}
}
}
#endif /* HAVE_READLINK */
wchar_t *p = NULL;
#if SEP == '\\'
/* Special case for Microsoft filename syntax */
if (have_script_arg) {
wchar_t *q;
#if defined(MS_WINDOWS)
/* Replace the first element in argv with the full path. */
wchar_t *ptemp;
if (GetFullPathNameW(path0,
Py_ARRAY_LENGTH(fullpath),
fullpath,
&ptemp)) {
path0 = fullpath;
}
#endif
p = wcsrchr(path0, SEP);
/* Test for alternate separator */
q = wcsrchr(p ? p : path0, '/');
if (q != NULL)
p = q;
if (p != NULL) {
n = p + 1 - path0;
if (n > 1 && p[-1] != ':')
n--; /* Drop trailing separator */
}
}
#else
/* All other filename syntaxes */
if (have_script_arg) {
#if defined(HAVE_REALPATH)
if (_Py_wrealpath(path0, fullpath, Py_ARRAY_LENGTH(fullpath))) {
path0 = fullpath;
}
#endif
p = wcsrchr(path0, SEP);
}
if (p != NULL) {
n = p + 1 - path0;
#if SEP == '/' /* Special case for Unix filename syntax */
if (n > 1) {
/* Drop trailing separator */
n--;
}
#endif /* Unix */
}
#endif /* All others */
PyObject *path0_obj = PyUnicode_FromWideChar(path0, n);
if (path0_obj == NULL) {
return -1;
}
*path0_p = path0_obj;
return 1;
}