Coverage Report

Created: 2026-10-17 06:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/Python/preconfig.c
Line
Count
Source
1
#include "Python.h"
2
#include "pycore_fileutils.h"     // DECODE_LOCALE_ERR
3
#include "pycore_getopt.h"        // _PyOS_GetOpt()
4
#include "pycore_initconfig.h"    // _PyArgv
5
#include "pycore_pylifecycle.h"   // _Py_LegacyLocaleDetected()
6
#include "pycore_pymem.h"         // _PyMem_GetAllocatorName()
7
#include "pycore_runtime.h"       // _PyRuntime_Initialize()
8
9
#include <locale.h>               // setlocale()
10
#include <stdlib.h>               // getenv()
11
12
13
/* Forward declarations */
14
static void
15
preconfig_copy(PyPreConfig *config, const PyPreConfig *config2);
16
extern int Py_UTF8Mode;
17
18
19
/* --- File system encoding/errors -------------------------------- */
20
21
// Variables removed from Python limited C API 3.16, but kept in the stable ABI
22
PyAPI_DATA(const char *) Py_FileSystemDefaultEncoding;
23
PyAPI_DATA(const char *) Py_FileSystemDefaultEncodeErrors;
24
PyAPI_DATA(int) Py_HasFileSystemDefaultEncoding;
25
26
// The default encoding used by the platform file system APIs.
27
// If non-NULL, this is different than the default encoding for strings.
28
const char *Py_FileSystemDefaultEncoding = NULL;
29
int Py_HasFileSystemDefaultEncoding = 0;
30
const char *Py_FileSystemDefaultEncodeErrors = NULL;
31
int _Py_HasFileSystemDefaultEncodeErrors = 0;
32
33
void
34
_Py_ClearFileSystemEncoding(void)
35
37
{
36
37
    if (!Py_HasFileSystemDefaultEncoding && Py_FileSystemDefaultEncoding) {
37
0
        PyMem_RawFree((char*)Py_FileSystemDefaultEncoding);
38
0
        Py_FileSystemDefaultEncoding = NULL;
39
0
    }
40
37
    if (!_Py_HasFileSystemDefaultEncodeErrors && Py_FileSystemDefaultEncodeErrors) {
41
0
        PyMem_RawFree((char*)Py_FileSystemDefaultEncodeErrors);
42
0
        Py_FileSystemDefaultEncodeErrors = NULL;
43
0
    }
44
37
}
45
46
47
/* Set Py_FileSystemDefaultEncoding and Py_FileSystemDefaultEncodeErrors
48
   global configuration variables to PyConfig.filesystem_encoding and
49
   PyConfig.filesystem_errors (encoded to UTF-8).
50
51
   Function called by _PyUnicode_InitEncodings(). */
52
int
53
_Py_SetFileSystemEncoding(const char *encoding, const char *errors)
54
37
{
55
37
    char *encoding2 = _PyMem_RawStrdup(encoding);
56
37
    if (encoding2 == NULL) {
57
0
        return -1;
58
0
    }
59
60
37
    char *errors2 = _PyMem_RawStrdup(errors);
61
37
    if (errors2 == NULL) {
62
0
        PyMem_RawFree(encoding2);
63
0
        return -1;
64
0
    }
65
66
37
    _Py_ClearFileSystemEncoding();
67
68
37
    Py_FileSystemDefaultEncoding = encoding2;
69
37
    Py_HasFileSystemDefaultEncoding = 0;
70
71
37
    Py_FileSystemDefaultEncodeErrors = errors2;
72
37
    _Py_HasFileSystemDefaultEncodeErrors = 0;
73
37
    return 0;
74
37
}
75
76
77
/* --- _PyArgv ---------------------------------------------------- */
78
79
/* Decode bytes_argv using Py_DecodeLocale() */
80
PyStatus
81
_PyArgv_AsWstrList(const _PyArgv *args, PyWideStringList *list)
82
0
{
83
0
    PyWideStringList wargv = _PyWideStringList_INIT;
84
0
    if (args->use_bytes_argv) {
85
0
        size_t size = sizeof(wchar_t*) * args->argc;
86
0
        wargv.items = (wchar_t **)PyMem_RawMalloc(size);
87
0
        if (wargv.items == NULL) {
88
0
            return _PyStatus_NO_MEMORY();
89
0
        }
90
91
0
        for (Py_ssize_t i = 0; i < args->argc; i++) {
92
0
            size_t len;
93
0
            wchar_t *arg = Py_DecodeLocale(args->bytes_argv[i], &len);
94
0
            if (arg == NULL) {
95
0
                _PyWideStringList_Clear(&wargv);
96
0
                return DECODE_LOCALE_ERR("command line arguments", len);
97
0
            }
98
0
            wargv.items[i] = arg;
99
0
            wargv.length++;
100
0
        }
101
102
0
        _PyWideStringList_Clear(list);
103
0
        *list = wargv;
104
0
    }
105
0
    else {
106
0
        wargv.length = args->argc;
107
0
        wargv.items = (wchar_t **)args->wchar_argv;
108
0
        if (_PyWideStringList_Copy(list, &wargv) < 0) {
109
0
            return _PyStatus_NO_MEMORY();
110
0
        }
111
0
    }
112
0
    return _PyStatus_OK();
113
0
}
114
115
116
/* --- _PyPreCmdline ------------------------------------------------- */
117
118
void
119
_PyPreCmdline_Clear(_PyPreCmdline *cmdline)
120
74
{
121
74
    _PyWideStringList_Clear(&cmdline->argv);
122
74
    _PyWideStringList_Clear(&cmdline->xoptions);
123
74
}
124
125
126
PyStatus
127
_PyPreCmdline_SetArgv(_PyPreCmdline *cmdline, const _PyArgv *args)
128
0
{
129
0
    return _PyArgv_AsWstrList(args, &cmdline->argv);
130
0
}
131
132
133
static void
134
precmdline_get_preconfig(_PyPreCmdline *cmdline, const PyPreConfig *config)
135
74
{
136
74
#define COPY_ATTR(ATTR) \
137
222
    if (config->ATTR != -1) { \
138
185
        cmdline->ATTR = config->ATTR; \
139
185
    }
140
141
74
    COPY_ATTR(isolated);
142
74
    COPY_ATTR(use_environment);
143
74
    COPY_ATTR(dev_mode);
144
145
74
#undef COPY_ATTR
146
74
}
147
148
149
static void
150
precmdline_set_preconfig(const _PyPreCmdline *cmdline, PyPreConfig *config)
151
37
{
152
37
#define COPY_ATTR(ATTR) \
153
111
    config->ATTR = cmdline->ATTR
154
155
37
    COPY_ATTR(isolated);
156
37
    COPY_ATTR(use_environment);
157
37
    COPY_ATTR(dev_mode);
158
159
37
#undef COPY_ATTR
160
37
}
161
162
163
PyStatus
164
_PyPreCmdline_SetConfig(const _PyPreCmdline *cmdline, PyConfig *config)
165
37
{
166
37
#define COPY_ATTR(ATTR) \
167
148
    config->ATTR = cmdline->ATTR
168
169
37
    PyStatus status = _PyWideStringList_Extend(&config->xoptions, &cmdline->xoptions);
170
37
    if (_PyStatus_EXCEPTION(status)) {
171
0
        return status;
172
0
    }
173
174
37
    COPY_ATTR(isolated);
175
37
    COPY_ATTR(use_environment);
176
37
    COPY_ATTR(dev_mode);
177
37
    COPY_ATTR(warn_default_encoding);
178
37
    return _PyStatus_OK();
179
180
37
#undef COPY_ATTR
181
37
}
182
183
184
/* Parse the command line arguments */
185
static PyStatus
186
precmdline_parse_cmdline(_PyPreCmdline *cmdline)
187
0
{
188
0
    const PyWideStringList *argv = &cmdline->argv;
189
190
0
    struct _PyOS_GetOpt getopt;
191
0
    _PyOS_GetOpt_Init(&getopt, argv->length, argv->items);
192
    /* Don't log parsing errors into stderr here: PyConfig_Read()
193
       is responsible for that */
194
0
    getopt.error = 0;
195
0
    do {
196
0
        int c = _PyOS_GetOpt(&getopt);
197
0
        if (c == -1 || c == 'c' || c == 'm') {
198
0
            break;
199
0
        }
200
201
0
        switch (c) {
202
0
        case 'E':
203
0
            cmdline->use_environment = 0;
204
0
            break;
205
206
0
        case 'I':
207
0
            cmdline->isolated = 1;
208
0
            break;
209
210
0
        case 'X':
211
0
        {
212
0
            PyStatus status = PyWideStringList_Append(&cmdline->xoptions,
213
0
                                                      getopt.arg);
214
0
            if (_PyStatus_EXCEPTION(status)) {
215
0
                return status;
216
0
            }
217
0
            break;
218
0
        }
219
220
0
        default:
221
            /* ignore other argument:
222
               handled by PyConfig_Read() */
223
0
            break;
224
0
        }
225
0
    } while (1);
226
227
0
    return _PyStatus_OK();
228
0
}
229
230
231
PyStatus
232
_PyPreCmdline_Read(_PyPreCmdline *cmdline, const PyPreConfig *preconfig)
233
74
{
234
74
    precmdline_get_preconfig(cmdline, preconfig);
235
236
74
    if (preconfig->parse_argv) {
237
0
        PyStatus status = precmdline_parse_cmdline(cmdline);
238
0
        if (_PyStatus_EXCEPTION(status)) {
239
0
            return status;
240
0
        }
241
0
    }
242
243
    /* isolated, use_environment */
244
74
    if (cmdline->isolated < 0) {
245
0
        cmdline->isolated = 0;
246
0
    }
247
74
    if (cmdline->isolated > 0) {
248
0
        cmdline->use_environment = 0;
249
0
    }
250
74
    if (cmdline->use_environment < 0) {
251
0
        cmdline->use_environment = 0;
252
0
    }
253
254
    /* dev_mode */
255
74
    if ((cmdline->dev_mode < 0)
256
37
        && (_Py_get_xoption(&cmdline->xoptions, L"dev")
257
37
            || _Py_GetEnv(cmdline->use_environment, "PYTHONDEVMODE")))
258
0
    {
259
0
        cmdline->dev_mode = 1;
260
0
    }
261
74
    if (cmdline->dev_mode < 0) {
262
37
        cmdline->dev_mode = 0;
263
37
    }
264
265
    // warn_default_encoding
266
74
    if (_Py_get_xoption(&cmdline->xoptions, L"warn_default_encoding")
267
74
            || _Py_GetEnv(cmdline->use_environment, "PYTHONWARNDEFAULTENCODING"))
268
0
    {
269
0
        cmdline->warn_default_encoding = 1;
270
0
    }
271
272
74
    assert(cmdline->use_environment >= 0);
273
74
    assert(cmdline->isolated >= 0);
274
74
    assert(cmdline->dev_mode >= 0);
275
74
    assert(cmdline->warn_default_encoding >= 0);
276
277
74
    return _PyStatus_OK();
278
74
}
279
280
281
/* --- PyPreConfig ----------------------------------------------- */
282
283
284
void
285
_PyPreConfig_InitCompatConfig(PyPreConfig *config)
286
222
{
287
222
    memset(config, 0, sizeof(*config));
288
289
222
    config->_config_init = (int)_PyConfig_INIT_COMPAT;
290
222
    config->parse_argv = 0;
291
222
    config->isolated = -1;
292
222
    config->use_environment = -1;
293
222
    config->configure_locale = 1;
294
295
    /* gh-80624: C locale coercion (PEP 538) is disabled by default using
296
       the Compat configuration.
297
298
       Py_UTF8Mode=0 disables the UTF-8 mode. PYTHONUTF8 environment variable
299
       is ignored (even if use_environment=1). */
300
222
    config->utf8_mode = 1;
301
222
    config->coerce_c_locale = 0;
302
222
    config->coerce_c_locale_warn = 0;
303
304
222
    config->dev_mode = -1;
305
222
    config->allocator = PYMEM_ALLOCATOR_NOT_SET;
306
#ifdef MS_WINDOWS
307
    config->legacy_windows_fs_encoding = -1;
308
#endif
309
222
}
310
311
312
void
313
PyPreConfig_InitPythonConfig(PyPreConfig *config)
314
185
{
315
185
    _PyPreConfig_InitCompatConfig(config);
316
317
185
    config->_config_init = (int)_PyConfig_INIT_PYTHON;
318
185
    config->isolated = 0;
319
185
    config->parse_argv = 1;
320
185
    config->use_environment = 1;
321
    /* Set to -1 to enable C locale coercion (PEP 538) depending on
322
       the LC_CTYPE locale, PYTHONUTF8 and PYTHONCOERCECLOCALE
323
       environment variables. */
324
185
    config->coerce_c_locale = -1;
325
185
    config->coerce_c_locale_warn = -1;
326
185
    config->utf8_mode = -1;
327
#ifdef MS_WINDOWS
328
    config->legacy_windows_fs_encoding = 0;
329
#endif
330
185
}
331
332
333
void
334
PyPreConfig_InitIsolatedConfig(PyPreConfig *config)
335
0
{
336
0
    _PyPreConfig_InitCompatConfig(config);
337
338
0
    config->_config_init = (int)_PyConfig_INIT_ISOLATED;
339
0
    config->configure_locale = 0;
340
0
    config->isolated = 1;
341
0
    config->use_environment = 0;
342
0
    config->utf8_mode = 1;
343
0
    config->dev_mode = 0;
344
#ifdef MS_WINDOWS
345
    config->legacy_windows_fs_encoding = 0;
346
#endif
347
0
}
348
349
350
PyStatus
351
_PyPreConfig_InitFromPreConfig(PyPreConfig *config,
352
                               const PyPreConfig *config2)
353
148
{
354
148
    PyPreConfig_InitPythonConfig(config);
355
148
    preconfig_copy(config, config2);
356
148
    return _PyStatus_OK();
357
148
}
358
359
360
void
361
_PyPreConfig_InitFromConfig(PyPreConfig *preconfig, const PyConfig *config)
362
37
{
363
37
    _PyConfigInitEnum config_init = (_PyConfigInitEnum)config->_config_init;
364
37
    switch (config_init) {
365
0
    case _PyConfig_INIT_PYTHON:
366
0
        PyPreConfig_InitPythonConfig(preconfig);
367
0
        break;
368
0
    case _PyConfig_INIT_ISOLATED:
369
0
        PyPreConfig_InitIsolatedConfig(preconfig);
370
0
        break;
371
37
    case _PyConfig_INIT_COMPAT:
372
37
    default:
373
37
        _PyPreConfig_InitCompatConfig(preconfig);
374
37
    }
375
376
37
    _PyPreConfig_GetConfig(preconfig, config);
377
37
}
378
379
380
static void
381
preconfig_copy(PyPreConfig *config, const PyPreConfig *config2)
382
296
{
383
2.96k
#define COPY_ATTR(ATTR) config->ATTR = config2->ATTR
384
385
296
    COPY_ATTR(_config_init);
386
296
    COPY_ATTR(parse_argv);
387
296
    COPY_ATTR(isolated);
388
296
    COPY_ATTR(use_environment);
389
296
    COPY_ATTR(configure_locale);
390
296
    COPY_ATTR(dev_mode);
391
296
    COPY_ATTR(coerce_c_locale);
392
296
    COPY_ATTR(coerce_c_locale_warn);
393
296
    COPY_ATTR(utf8_mode);
394
296
    COPY_ATTR(allocator);
395
#ifdef MS_WINDOWS
396
    COPY_ATTR(legacy_windows_fs_encoding);
397
#endif
398
399
296
#undef COPY_ATTR
400
296
}
401
402
403
PyObject*
404
_PyPreConfig_AsDict(const PyPreConfig *config)
405
0
{
406
0
    PyObject *dict;
407
408
0
    dict = PyDict_New();
409
0
    if (dict == NULL) {
410
0
        return NULL;
411
0
    }
412
413
0
#define SET_ITEM_INT(ATTR) \
414
0
        do { \
415
0
            PyObject *obj = PyLong_FromLong(config->ATTR); \
416
0
            if (obj == NULL) { \
417
0
                goto fail; \
418
0
            } \
419
0
            int res = PyDict_SetItemString(dict, #ATTR, obj); \
420
0
            Py_DECREF(obj); \
421
0
            if (res < 0) { \
422
0
                goto fail; \
423
0
            } \
424
0
        } while (0)
425
426
0
    SET_ITEM_INT(_config_init);
427
0
    SET_ITEM_INT(parse_argv);
428
0
    SET_ITEM_INT(isolated);
429
0
    SET_ITEM_INT(use_environment);
430
0
    SET_ITEM_INT(configure_locale);
431
0
    SET_ITEM_INT(coerce_c_locale);
432
0
    SET_ITEM_INT(coerce_c_locale_warn);
433
0
    SET_ITEM_INT(utf8_mode);
434
#ifdef MS_WINDOWS
435
    SET_ITEM_INT(legacy_windows_fs_encoding);
436
#endif
437
0
    SET_ITEM_INT(dev_mode);
438
0
    SET_ITEM_INT(allocator);
439
0
    return dict;
440
441
0
fail:
442
0
    Py_DECREF(dict);
443
0
    return NULL;
444
445
0
#undef SET_ITEM_INT
446
0
}
447
448
449
void
450
_PyPreConfig_GetConfig(PyPreConfig *preconfig, const PyConfig *config)
451
74
{
452
74
#define COPY_ATTR(ATTR) \
453
296
    if (config->ATTR != -1) { \
454
185
        preconfig->ATTR = config->ATTR; \
455
185
    }
456
457
74
    COPY_ATTR(parse_argv);
458
74
    COPY_ATTR(isolated);
459
74
    COPY_ATTR(use_environment);
460
74
    COPY_ATTR(dev_mode);
461
462
74
#undef COPY_ATTR
463
74
}
464
465
466
static void
467
preconfig_get_global_var(PyPreConfig *config)
468
37
{
469
37
    if (config->_config_init != _PyConfig_INIT_COMPAT) {
470
        /* Python and Isolated configuration ignore global variables */
471
0
        return;
472
0
    }
473
474
37
    if (Py_UTF8Mode > 0) {
475
0
        config->utf8_mode = Py_UTF8Mode;
476
0
    }
477
37
}
478
479
480
static void
481
preconfig_set_global_var(const PyPreConfig *config)
482
37
{
483
37
    if (config->utf8_mode >= 0) {
484
37
        Py_UTF8Mode = config->utf8_mode;
485
37
    }
486
37
}
487
488
489
const char*
490
_Py_GetEnv(int use_environment, const char *name)
491
1.22k
{
492
1.22k
    assert(use_environment >= 0);
493
494
1.22k
    if (!use_environment) {
495
0
        return NULL;
496
0
    }
497
498
1.22k
    const char *var = getenv(name);
499
1.22k
    if (var && var[0] != '\0') {
500
0
        return var;
501
0
    }
502
1.22k
    else {
503
1.22k
        return NULL;
504
1.22k
    }
505
1.22k
}
506
507
508
int
509
_Py_str_to_int(const char *str, int *result)
510
0
{
511
0
    const char *endptr = str;
512
0
    errno = 0;
513
0
    long value = strtol(str, (char **)&endptr, 10);
514
0
    if (*endptr != '\0' || errno == ERANGE) {
515
0
        return -1;
516
0
    }
517
0
    if (value < INT_MIN || value > INT_MAX) {
518
0
        return -1;
519
0
    }
520
521
0
    *result = (int)value;
522
0
    return 0;
523
0
}
524
525
526
void
527
_Py_get_env_flag(int use_environment, int *flag, const char *name)
528
222
{
529
222
    const char *var = _Py_GetEnv(use_environment, name);
530
222
    if (!var) {
531
222
        return;
532
222
    }
533
0
    int value;
534
0
    if (_Py_str_to_int(var, &value) < 0 || value < 0) {
535
        /* PYTHONDEBUG=text and PYTHONDEBUG=-2 behave as PYTHONDEBUG=1 */
536
0
        value = 1;
537
0
    }
538
0
    if (*flag < value) {
539
0
        *flag = value;
540
0
    }
541
0
}
542
543
544
const wchar_t*
545
_Py_get_xoption(const PyWideStringList *xoptions, const wchar_t *name)
546
777
{
547
777
    for (Py_ssize_t i=0; i < xoptions->length; i++) {
548
0
        const wchar_t *option = xoptions->items[i];
549
0
        size_t len;
550
0
        const wchar_t *sep = wcschr(option, L'=');
551
0
        if (sep != NULL) {
552
0
            len = (sep - option);
553
0
        }
554
0
        else {
555
0
            len = wcslen(option);
556
0
        }
557
0
        if (wcsncmp(option, name, len) == 0 && name[len] == L'\0') {
558
0
            return option;
559
0
        }
560
0
    }
561
777
    return NULL;
562
777
}
563
564
565
static PyStatus
566
preconfig_init_utf8_mode(PyPreConfig *config, const _PyPreCmdline *cmdline)
567
37
{
568
#ifdef MS_WINDOWS
569
    if (config->legacy_windows_fs_encoding) {
570
        config->utf8_mode = 0;
571
    }
572
#endif
573
574
37
    if (config->utf8_mode >= 0) {
575
37
        return _PyStatus_OK();
576
37
    }
577
578
0
    const wchar_t *xopt;
579
0
    xopt = _Py_get_xoption(&cmdline->xoptions, L"utf8");
580
0
    if (xopt) {
581
0
        const wchar_t *sep = wcschr(xopt, L'=');
582
0
        if (sep) {
583
0
            xopt = sep + 1;
584
0
            if (wcscmp(xopt, L"1") == 0) {
585
0
                config->utf8_mode = 1;
586
0
            }
587
0
            else if (wcscmp(xopt, L"0") == 0) {
588
0
                config->utf8_mode = 0;
589
0
            }
590
0
            else {
591
0
                return _PyStatus_ERR("invalid -X utf8 option value");
592
0
            }
593
0
        }
594
0
        else {
595
0
            config->utf8_mode = 1;
596
0
        }
597
0
        return _PyStatus_OK();
598
0
    }
599
600
0
    const char *opt = _Py_GetEnv(config->use_environment, "PYTHONUTF8");
601
0
    if (opt) {
602
0
        if (strcmp(opt, "1") == 0) {
603
0
            config->utf8_mode = 1;
604
0
        }
605
0
        else if (strcmp(opt, "0") == 0) {
606
0
            config->utf8_mode = 0;
607
0
        }
608
0
        else {
609
0
            return _PyStatus_ERR("invalid PYTHONUTF8 environment "
610
0
                                "variable value");
611
0
        }
612
0
        return _PyStatus_OK();
613
0
    }
614
615
0
    config->utf8_mode = 1;
616
0
    return _PyStatus_OK();
617
0
}
618
619
620
static void
621
preconfig_init_coerce_c_locale(PyPreConfig *config)
622
37
{
623
37
    if (!config->configure_locale) {
624
0
        config->coerce_c_locale = 0;
625
0
        config->coerce_c_locale_warn = 0;
626
0
        return;
627
0
    }
628
629
37
    const char *env = _Py_GetEnv(config->use_environment, "PYTHONCOERCECLOCALE");
630
37
    if (env) {
631
0
        if (strcmp(env, "0") == 0) {
632
0
            if (config->coerce_c_locale < 0) {
633
0
                config->coerce_c_locale = 0;
634
0
            }
635
0
        }
636
0
        else if (strcmp(env, "warn") == 0) {
637
0
            if (config->coerce_c_locale_warn < 0) {
638
0
                config->coerce_c_locale_warn = 1;
639
0
            }
640
0
        }
641
0
        else {
642
0
            if (config->coerce_c_locale < 0) {
643
0
                config->coerce_c_locale = 1;
644
0
            }
645
0
        }
646
0
    }
647
648
    /* Test if coerce_c_locale equals to -1 or equals to 1:
649
       PYTHONCOERCECLOCALE=1 doesn't imply that the C locale is always coerced.
650
       It is only coerced if the LC_CTYPE locale is "C". */
651
37
    if (config->coerce_c_locale < 0 || config->coerce_c_locale == 1) {
652
        /* The C locale enables the C locale coercion (PEP 538) */
653
0
        if (_Py_LegacyLocaleDetected(0)) {
654
0
            config->coerce_c_locale = 2;
655
0
        }
656
0
        else {
657
0
            config->coerce_c_locale = 0;
658
0
        }
659
0
    }
660
661
37
    if (config->coerce_c_locale_warn < 0) {
662
0
        config->coerce_c_locale_warn = 0;
663
0
    }
664
37
}
665
666
667
static PyStatus
668
preconfig_init_allocator(PyPreConfig *config)
669
37
{
670
37
    if (config->allocator == PYMEM_ALLOCATOR_NOT_SET) {
671
        /* bpo-34247. The PYTHONMALLOC environment variable has the priority
672
           over PYTHONDEV env var and "-X dev" command line option.
673
           For example, PYTHONMALLOC=malloc PYTHONDEVMODE=1 sets the memory
674
           allocators to "malloc" (and not to "debug"). */
675
37
        const char *envvar = _Py_GetEnv(config->use_environment, "PYTHONMALLOC");
676
37
        if (envvar) {
677
0
            PyMemAllocatorName name;
678
0
            if (_PyMem_GetAllocatorName(envvar, &name) < 0) {
679
0
                return _PyStatus_ERR("PYTHONMALLOC: unknown allocator");
680
0
            }
681
0
            config->allocator = (int)name;
682
0
        }
683
37
    }
684
685
37
    if (config->dev_mode && config->allocator == PYMEM_ALLOCATOR_NOT_SET) {
686
0
        config->allocator = PYMEM_ALLOCATOR_DEBUG;
687
0
    }
688
37
    return _PyStatus_OK();
689
37
}
690
691
692
static PyStatus
693
preconfig_read(PyPreConfig *config, _PyPreCmdline *cmdline)
694
37
{
695
37
    PyStatus status;
696
697
37
    status = _PyPreCmdline_Read(cmdline, config);
698
37
    if (_PyStatus_EXCEPTION(status)) {
699
0
        return status;
700
0
    }
701
702
37
    precmdline_set_preconfig(cmdline, config);
703
704
    /* legacy_windows_fs_encoding, coerce_c_locale, utf8_mode */
705
#ifdef MS_WINDOWS
706
    _Py_get_env_flag(config->use_environment,
707
                     &config->legacy_windows_fs_encoding,
708
                     "PYTHONLEGACYWINDOWSFSENCODING");
709
#endif
710
711
37
    preconfig_init_coerce_c_locale(config);
712
713
37
    status = preconfig_init_utf8_mode(config, cmdline);
714
37
    if (_PyStatus_EXCEPTION(status)) {
715
0
        return status;
716
0
    }
717
718
    /* allocator */
719
37
    status = preconfig_init_allocator(config);
720
37
    if (_PyStatus_EXCEPTION(status)) {
721
0
        return status;
722
0
    }
723
724
37
    assert(config->coerce_c_locale >= 0);
725
37
    assert(config->coerce_c_locale_warn >= 0);
726
#ifdef MS_WINDOWS
727
    assert(config->legacy_windows_fs_encoding >= 0);
728
#endif
729
37
    assert(config->utf8_mode >= 0);
730
37
    assert(config->isolated >= 0);
731
37
    assert(config->use_environment >= 0);
732
37
    assert(config->dev_mode >= 0);
733
734
37
    return _PyStatus_OK();
735
37
}
736
737
738
/* Read the configuration from:
739
740
   - command line arguments
741
   - environment variables
742
   - Py_UTF8Mode global configuration variable
743
   - the LC_CTYPE locale */
744
PyStatus
745
_PyPreConfig_Read(PyPreConfig *config, const _PyArgv *args)
746
37
{
747
37
    PyStatus status;
748
749
37
    status = _PyRuntime_Initialize();
750
37
    if (_PyStatus_EXCEPTION(status)) {
751
0
        return status;
752
0
    }
753
754
37
    preconfig_get_global_var(config);
755
37
    if (config->use_environment < 0) {
756
37
        config->use_environment = 1;
757
37
    }
758
37
    if (config->isolated < 0) {
759
37
        config->isolated = 0;
760
37
    }
761
#ifdef MS_WINDOWS
762
    if (config->legacy_windows_fs_encoding < 0) {
763
        config->legacy_windows_fs_encoding = 0;
764
    }
765
#endif
766
767
    /* Copy LC_CTYPE locale, since it's modified later */
768
37
    const char *loc = setlocale(LC_CTYPE, NULL);
769
37
    if (loc == NULL) {
770
0
        return _PyStatus_ERR("failed to LC_CTYPE locale");
771
0
    }
772
37
    char *init_ctype_locale = _PyMem_RawStrdup(loc);
773
37
    if (init_ctype_locale == NULL) {
774
0
        return _PyStatus_NO_MEMORY();
775
0
    }
776
777
    /* Save the config to be able to restore it if encodings change */
778
37
    PyPreConfig save_config;
779
780
37
    status = _PyPreConfig_InitFromPreConfig(&save_config, config);
781
37
    if (_PyStatus_EXCEPTION(status)) {
782
0
        return status;
783
0
    }
784
785
    /* Set LC_CTYPE to the user preferred locale */
786
37
    if (config->configure_locale) {
787
37
        _Py_SetLocaleFromEnv(LC_CTYPE);
788
37
    }
789
790
37
    PyPreConfig save_runtime_config;
791
37
    preconfig_copy(&save_runtime_config, &_PyRuntime.preconfig);
792
793
37
    _PyPreCmdline cmdline = _PyPreCmdline_INIT;
794
37
    int locale_coerced = 0;
795
37
    int loops = 0;
796
797
37
    while (1) {
798
37
        int utf8_mode = config->utf8_mode;
799
800
        /* Watchdog to prevent an infinite loop */
801
37
        loops++;
802
37
        if (loops == 3) {
803
0
            status = _PyStatus_ERR("Encoding changed twice while "
804
0
                                   "reading the configuration");
805
0
            goto done;
806
0
        }
807
808
        /* bpo-34207: Py_DecodeLocale() and Py_EncodeLocale() depend
809
           on the utf8_mode and legacy_windows_fs_encoding members
810
           of _PyRuntime.preconfig. */
811
37
        preconfig_copy(&_PyRuntime.preconfig, config);
812
813
37
        if (args) {
814
            // Set command line arguments at each iteration. If they are bytes
815
            // strings, they are decoded from the new encoding.
816
0
            status = _PyPreCmdline_SetArgv(&cmdline, args);
817
0
            if (_PyStatus_EXCEPTION(status)) {
818
0
                goto done;
819
0
            }
820
0
        }
821
822
37
        status = preconfig_read(config, &cmdline);
823
37
        if (_PyStatus_EXCEPTION(status)) {
824
0
            goto done;
825
0
        }
826
827
        /* The legacy C locale assumes ASCII as the default text encoding, which
828
         * causes problems not only for the CPython runtime, but also other
829
         * components like GNU readline.
830
         *
831
         * Accordingly, when the CLI detects it, it attempts to coerce it to a
832
         * more capable UTF-8 based alternative.
833
         *
834
         * See the documentation of the PYTHONCOERCECLOCALE setting for more
835
         * details.
836
         */
837
37
        int encoding_changed = 0;
838
37
        if (config->coerce_c_locale && !locale_coerced) {
839
0
            locale_coerced = 1;
840
0
            _Py_CoerceLegacyLocale(0);
841
0
            encoding_changed = 1;
842
0
        }
843
844
37
        if (utf8_mode == -1) {
845
0
            if (config->utf8_mode == 1) {
846
                /* UTF-8 Mode enabled */
847
0
                encoding_changed = 1;
848
0
            }
849
0
        }
850
37
        else {
851
37
            if (config->utf8_mode != utf8_mode) {
852
0
                encoding_changed = 1;
853
0
            }
854
37
        }
855
856
37
        if (!encoding_changed) {
857
37
            break;
858
37
        }
859
860
        /* Reset the configuration before reading again the configuration,
861
           just keep UTF-8 Mode and coerce C locale value. */
862
0
        int new_utf8_mode = config->utf8_mode;
863
0
        int new_coerce_c_locale = config->coerce_c_locale;
864
0
        preconfig_copy(config, &save_config);
865
0
        config->utf8_mode = new_utf8_mode;
866
0
        config->coerce_c_locale = new_coerce_c_locale;
867
868
        /* The encoding changed: read again the configuration
869
           with the new encoding */
870
0
    }
871
37
    status = _PyStatus_OK();
872
873
37
done:
874
    // Revert side effects
875
37
    setlocale(LC_CTYPE, init_ctype_locale);
876
37
    PyMem_RawFree(init_ctype_locale);
877
37
    preconfig_copy(&_PyRuntime.preconfig, &save_runtime_config);
878
37
    _PyPreCmdline_Clear(&cmdline);
879
37
    return status;
880
37
}
881
882
883
/* Write the pre-configuration:
884
885
   - set the memory allocators
886
   - set Py_xxx global configuration variables
887
   - set the LC_CTYPE locale (coerce C locale, PEP 538) and set the UTF-8 mode
888
     (PEP 540)
889
890
   The applied configuration is written into _PyRuntime.preconfig.
891
   If the C locale cannot be coerced, set coerce_c_locale to 0.
892
893
   Do nothing if called after Py_Initialize(): ignore the new
894
   pre-configuration. */
895
PyStatus
896
_PyPreConfig_Write(const PyPreConfig *src_config)
897
37
{
898
37
    PyPreConfig config;
899
900
37
    PyStatus status = _PyPreConfig_InitFromPreConfig(&config, src_config);
901
37
    if (_PyStatus_EXCEPTION(status)) {
902
0
        return status;
903
0
    }
904
905
37
    if (_Py_IsCoreInitialized()) {
906
        /* bpo-34008: Calling this functions after Py_Initialize() ignores
907
           the new configuration. */
908
0
        return _PyStatus_OK();
909
0
    }
910
911
37
    PyMemAllocatorName name = (PyMemAllocatorName)config.allocator;
912
37
    if (name != PYMEM_ALLOCATOR_NOT_SET) {
913
0
        if (_PyMem_SetupAllocators(name) < 0) {
914
0
            return _PyStatus_ERR("Unknown PYTHONMALLOC allocator");
915
0
        }
916
0
    }
917
918
37
    preconfig_set_global_var(&config);
919
920
37
    if (config.configure_locale) {
921
37
        if (config.coerce_c_locale) {
922
0
            if (!_Py_CoerceLegacyLocale(config.coerce_c_locale_warn)) {
923
                /* C locale not coerced */
924
0
                config.coerce_c_locale = 0;
925
0
            }
926
0
        }
927
928
        /* Set LC_CTYPE to the user preferred locale */
929
37
        _Py_SetLocaleFromEnv(LC_CTYPE);
930
37
    }
931
932
    /* Write the new pre-configuration into _PyRuntime */
933
37
    preconfig_copy(&_PyRuntime.preconfig, &config);
934
935
37
    return _PyStatus_OK();
936
37
}