Coverage Report

Created: 2025-07-04 06:49

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