bpo-34783: Fix Py_Main() (GH-9526)

Fix a crash with musl libc (on Alpine Linux) when the script filename
specified on the command line doesn't exist. pymain_open_filename()
now gets the current core configuration from the interpreter state.

Modify the code to make it closer to the master branch:

* Rename _Py_CommandLineDetails to _PyCmdline
* Remove _PyMain.config: replaced with a local variable
  'local_config' in pymain_init()
* Reorganize pymain_main(): move code using the "local config"
  into pymain_init()
* As soon as possible, switch from the local config to the core
  configuration attached to the interpreter.
diff --git a/Modules/main.c b/Modules/main.c
index 0f7498d..7771d27 100644
--- a/Modules/main.c
+++ b/Modules/main.c
@@ -459,7 +459,7 @@
     int legacy_windows_stdio;        /* Py_LegacyWindowsStdioFlag,
                                         PYTHONLEGACYWINDOWSSTDIO */
 #endif
-} _Py_CommandLineDetails;
+} _PyCmdline;
 
 /* Structure used by Py_Main() to pass data to subfunctions */
 typedef struct {
@@ -481,14 +481,10 @@
     wchar_t *command;            /* -c argument */
     wchar_t *module;             /* -m argument */
 
-    _PyCoreConfig config;
-
     PyObject *main_importer_path;
 } _PyMain;
 
-#define _PyMain_INIT \
-    {.config = _PyCoreConfig_INIT, \
-     .err = _Py_INIT_OK()}
+#define _PyMain_INIT {.err = _Py_INIT_OK()}
 /* Note: _PyMain_INIT sets other fields to 0/NULL */
 
 
@@ -522,7 +518,8 @@
 
 
 static int
-pymain_init_cmdline_argv(_PyMain *pymain, _Py_CommandLineDetails *cmdline)
+pymain_init_cmdline_argv(_PyMain *pymain, _PyCoreConfig *config,
+                         _PyCmdline *cmdline)
 {
     assert(cmdline->argv == NULL);
 
@@ -561,8 +558,8 @@
     else {
         program = L"";
     }
-    pymain->config.program = pymain_wstrdup(pymain, program);
-    if (pymain->config.program == NULL) {
+    config->program = pymain_wstrdup(pymain, program);
+    if (config->program == NULL) {
         return -1;
     }
 
@@ -571,7 +568,7 @@
 
 
 static void
-pymain_clear_cmdline(_PyMain *pymain, _Py_CommandLineDetails *cmdline)
+pymain_clear_cmdline(_PyMain *pymain, _PyCmdline *cmdline)
 {
     PyMemAllocatorEx old_alloc;
     _PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
@@ -609,14 +606,14 @@
 }
 
 static void
-pymain_clear_config(_PyMain *pymain)
+pymain_clear_config(_PyCoreConfig *config)
 {
     /* Clear core config with the memory allocator
        used by pymain_read_conf() */
     PyMemAllocatorEx old_alloc;
     _PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
 
-    _PyCoreConfig_Clear(&pymain->config);
+    _PyCoreConfig_Clear(config);
 
     PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
 }
@@ -653,8 +650,6 @@
        Py_Initialize()-Py_Finalize() can be called multiple times. */
     _PyPathConfig_Clear(&_Py_path_config);
 
-    pymain_clear_config(pymain);
-
     /* Force the allocator used by pymain_read_conf() */
     PyMemAllocatorEx old_alloc;
     _PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
@@ -740,10 +735,9 @@
    Return 1 if parsing failed.
    Set pymain->err and return -1 on other errors. */
 static int
-pymain_parse_cmdline_impl(_PyMain *pymain, _Py_CommandLineDetails *cmdline)
+pymain_parse_cmdline_impl(_PyMain *pymain, _PyCoreConfig *config,
+                          _PyCmdline *cmdline)
 {
-    _PyCoreConfig *config = &pymain->config;
-
     _PyOS_ResetGetOpt();
     do {
         int longindex = -1;
@@ -985,7 +979,7 @@
 
 
 static _PyInitError
-config_init_warnoptions(_PyCoreConfig *config, _Py_CommandLineDetails *cmdline)
+config_init_warnoptions(_PyCoreConfig *config, _PyCmdline *cmdline)
 {
     _PyInitError err;
 
@@ -1056,7 +1050,7 @@
    Return 0 on success.
    Set pymain->err and return -1 on error. */
 static _PyInitError
-cmdline_init_env_warnoptions(_Py_CommandLineDetails *cmdline)
+cmdline_init_env_warnoptions(_PyCmdline *cmdline)
 {
     if (Py_IgnoreEnvironmentFlag) {
         return _Py_INIT_OK();
@@ -1252,7 +1246,8 @@
 
 
 static int
-pymain_init_core_argv(_PyMain *pymain, _Py_CommandLineDetails *cmdline)
+pymain_init_core_argv(_PyMain *pymain, _PyCoreConfig *config,
+                      _PyCmdline *cmdline)
 {
     /* Copy argv to be able to modify it (to force -c/-m) */
     int argc = pymain->argc - _PyOS_optind;
@@ -1295,8 +1290,8 @@
         argv[0] = arg0;
     }
 
-    pymain->config.argc = argc;
-    pymain->config.argv = argv;
+    config->argc = argc;
+    config->argv = argv;
     return 0;
 }
 
@@ -1324,7 +1319,7 @@
 
 
 static int
-pymain_compute_path0(_PyMain *pymain, PyObject **path0)
+pymain_compute_path0(_PyMain *pymain, _PyCoreConfig *config, PyObject **path0)
 {
     if (pymain->main_importer_path != NULL) {
         /* Let pymain_run_main_from_importer() adjust sys.path[0] later */
@@ -1337,8 +1332,7 @@
         return 0;
     }
 
-    *path0 = _PyPathConfig_ComputeArgv0(pymain->config.argc,
-                                        pymain->config.argv);
+    *path0 = _PyPathConfig_ComputeArgv0(config->argc, config->argv);
     if (*path0 == NULL) {
         pymain->err = _Py_INIT_NO_MEMORY();
         return -1;
@@ -1384,7 +1378,7 @@
 
 /* Get Py_xxx global configuration variables */
 static void
-cmdline_get_global_config(_Py_CommandLineDetails *cmdline)
+cmdline_get_global_config(_PyCmdline *cmdline)
 {
     cmdline->bytes_warning = Py_BytesWarningFlag;
     cmdline->debug = Py_DebugFlag;
@@ -1420,7 +1414,7 @@
 
 /* Set Py_xxx global configuration variables */
 static void
-cmdline_set_global_config(_Py_CommandLineDetails *cmdline)
+cmdline_set_global_config(_PyCmdline *cmdline)
 {
     Py_BytesWarningFlag = cmdline->bytes_warning;
     Py_DebugFlag = cmdline->debug;
@@ -1468,6 +1462,7 @@
 static FILE*
 pymain_open_filename(_PyMain *pymain)
 {
+    const _PyCoreConfig *config = &_PyGILState_GetInterpreterStateUnsafe()->core_config;
     FILE* fp;
 
     fp = _Py_wfopen(pymain->filename, L"r");
@@ -1481,7 +1476,7 @@
         else
             cfilename = "<unprintable file name>";
         fprintf(stderr, "%ls: can't open file '%s': [Errno %d] %s\n",
-                pymain->config.program, cfilename, err, strerror(err));
+                config->program, cfilename, err, strerror(err));
         PyMem_RawFree(cfilename_buffer);
         pymain->status = 2;
         return NULL;
@@ -1504,7 +1499,7 @@
             S_ISDIR(sb.st_mode)) {
         fprintf(stderr,
                 "%ls: '%ls' is a directory, cannot continue\n",
-                pymain->config.program, pymain->filename);
+                config->program, pymain->filename);
         fclose(fp);
         pymain->status = 1;
         return NULL;
@@ -1571,14 +1566,15 @@
    Return 0 on success.
    Set pymain->err and return -1 on failure. */
 static int
-pymain_parse_cmdline(_PyMain *pymain, _Py_CommandLineDetails *cmdline)
+pymain_parse_cmdline(_PyMain *pymain, _PyCoreConfig *config,
+                     _PyCmdline *cmdline)
 {
-    int res = pymain_parse_cmdline_impl(pymain, cmdline);
+    int res = pymain_parse_cmdline_impl(pymain, config, cmdline);
     if (res < 0) {
         return -1;
     }
     if (res) {
-        pymain_usage(1, pymain->config.program);
+        pymain_usage(1, config->program);
         pymain->status = 2;
         return 1;
     }
@@ -1716,7 +1712,7 @@
 
 
 static void
-cmdline_get_env_flags(_Py_CommandLineDetails *cmdline)
+cmdline_get_env_flags(_PyCmdline *cmdline)
 {
     get_env_flag(&cmdline->debug, "PYTHONDEBUG");
     get_env_flag(&cmdline->verbosity, "PYTHONVERBOSE");
@@ -1738,7 +1734,7 @@
 void
 _Py_Initialize_ReadEnvVarsNoAlloc(void)
 {
-    _Py_CommandLineDetails cmdline;
+    _PyCmdline cmdline;
     memset(&cmdline, 0, sizeof(cmdline));
 
     cmdline_get_global_config(&cmdline);
@@ -1926,17 +1922,17 @@
    Return 1 if Python is done and must exit.
    Set pymain->err and return -1 on error. */
 static int
-pymain_read_conf_impl(_PyMain *pymain, _Py_CommandLineDetails *cmdline)
+pymain_read_conf_impl(_PyMain *pymain, _PyCoreConfig *config,
+                      _PyCmdline *cmdline)
 {
     _PyInitError err;
 
-    int res = pymain_parse_cmdline(pymain, cmdline);
+    int res = pymain_parse_cmdline(pymain, config, cmdline);
     if (res != 0) {
         return res;
     }
 
     /* Set Py_IgnoreEnvironmentFlag for Py_GETENV() */
-    _PyCoreConfig *config = &pymain->config;
     Py_IgnoreEnvironmentFlag = config->ignore_environment || cmdline->isolated;
 
     /* Get environment variables */
@@ -1956,7 +1952,7 @@
     }
 #endif
 
-    if (pymain_init_core_argv(pymain, cmdline) < 0) {
+    if (pymain_init_core_argv(pymain, config, cmdline) < 0) {
         return -1;
     }
 
@@ -1985,10 +1981,9 @@
 /* Read the configuration, but initialize also the LC_CTYPE locale:
    enable UTF-8 mode (PEP 540) and/or coerce the C locale (PEP 538) */
 static int
-pymain_read_conf(_PyMain *pymain, _Py_CommandLineDetails *cmdline)
+pymain_read_conf(_PyMain *pymain, _PyCoreConfig *config, _PyCmdline *cmdline)
 {
     int init_utf8_mode = Py_UTF8Mode;
-    _PyCoreConfig *config = &pymain->config;
     _PyCoreConfig save_config = _PyCoreConfig_INIT;
     int res = -1;
 
@@ -2026,11 +2021,11 @@
            functions depend on Py_UTF8Mode. */
         Py_UTF8Mode = config->utf8_mode;
 
-        if (pymain_init_cmdline_argv(pymain, cmdline) < 0) {
+        if (pymain_init_cmdline_argv(pymain, config, cmdline) < 0) {
             goto done;
         }
 
-        int conf_res = pymain_read_conf_impl(pymain, cmdline);
+        int conf_res = pymain_read_conf_impl(pymain, config, cmdline);
         if (conf_res != 0) {
             res = conf_res;
             goto done;
@@ -2587,7 +2582,7 @@
 
 
 static int
-pymain_init_sys_path(_PyMain *pymain)
+pymain_init_sys_path(_PyMain *pymain, _PyCoreConfig *config)
 {
     if (pymain->filename != NULL) {
         /* If filename is a package (ex: directory or ZIP file) which contains
@@ -2598,12 +2593,10 @@
     }
 
     PyObject *path0;
-    if (pymain_compute_path0(pymain, &path0) < 0) {
+    if (pymain_compute_path0(pymain, config, &path0) < 0) {
         return -1;
     }
 
-    pymain_clear_config(pymain);
-
     if (path0 != NULL) {
         if (pymain_update_sys_path(pymain, path0) < 0) {
             Py_DECREF(path0);
@@ -2637,32 +2630,16 @@
 }
 
 
-static void
-pymain_init(_PyMain *pymain)
-{
-    /* 754 requires that FP exceptions run in "no stop" mode by default,
-     * and until C vendors implement C99's ways to control FP exceptions,
-     * Python requires non-stop mode.  Alas, some platforms enable FP
-     * exceptions by default.  Here we disable them.
-     */
-#ifdef __FreeBSD__
-    fedisableexcept(FE_OVERFLOW);
-#endif
-
-    pymain->config._disable_importlib = 0;
-    pymain->config.install_signal_handlers = 1;
-}
-
-
 static int
-pymain_cmdline_impl(_PyMain *pymain, _Py_CommandLineDetails *cmdline)
+pymain_cmdline_impl(_PyMain *pymain, _PyCoreConfig *config,
+                    _PyCmdline *cmdline)
 {
     pymain->err = _PyRuntime_Initialize();
     if (_Py_INIT_FAILED(pymain->err)) {
         return -1;
     }
 
-    int res = pymain_read_conf(pymain, cmdline);
+    int res = pymain_read_conf(pymain, config, cmdline);
     if (res < 0) {
         return -1;
     }
@@ -2672,7 +2649,7 @@
     }
 
     if (cmdline->print_help) {
-        pymain_usage(0, pymain->config.program);
+        pymain_usage(0, config->program);
         return 1;
     }
 
@@ -2690,7 +2667,7 @@
     }
     orig_argc = pymain->argc;
 
-    _PyInitError err = config_init_warnoptions(&pymain->config, cmdline);
+    _PyInitError err = config_init_warnoptions(config, cmdline);
     if (_Py_INIT_FAILED(err)) {
         pymain->err = err;
         return -1;
@@ -2708,10 +2685,10 @@
    * Environment variables
    * Py_xxx global configuration variables
 
-   _Py_CommandLineDetails is a temporary structure used to prioritize these
+   _PyCmdline is a temporary structure used to prioritize these
    variables. */
 static int
-pymain_cmdline(_PyMain *pymain)
+pymain_cmdline(_PyMain *pymain, _PyCoreConfig *config)
 {
     /* Force default allocator, since pymain_free() and pymain_clear_config()
        must use the same allocator than this function. */
@@ -2722,16 +2699,15 @@
     PyMem_GetAllocator(PYMEM_DOMAIN_RAW, &default_alloc);
 #endif
 
-    _Py_CommandLineDetails cmdline;
+    _PyCmdline cmdline;
     memset(&cmdline, 0, sizeof(cmdline));
 
     cmdline_get_global_config(&cmdline);
-    _PyCoreConfig_GetGlobalConfig(&pymain->config);
 
-    int res = pymain_cmdline_impl(pymain, &cmdline);
+    int res = pymain_cmdline_impl(pymain, config, &cmdline);
 
     cmdline_set_global_config(&cmdline);
-    _PyCoreConfig_SetGlobalConfig(&pymain->config);
+    _PyCoreConfig_SetGlobalConfig(config);
     if (Py_IsolatedFlag) {
         Py_IgnoreEnvironmentFlag = 1;
         Py_NoUserSiteDirectory = 1;
@@ -2751,33 +2727,62 @@
 
 
 static int
-pymain_main(_PyMain *pymain)
+pymain_init(_PyMain *pymain)
 {
-    pymain_init(pymain);
+    _PyCoreConfig local_config = _PyCoreConfig_INIT;
+    _PyCoreConfig *config = &local_config;
 
-    int res = pymain_cmdline(pymain);
+    /* 754 requires that FP exceptions run in "no stop" mode by default,
+     * and until C vendors implement C99's ways to control FP exceptions,
+     * Python requires non-stop mode.  Alas, some platforms enable FP
+     * exceptions by default.  Here we disable them.
+     */
+#ifdef __FreeBSD__
+    fedisableexcept(FE_OVERFLOW);
+#endif
+
+    config->_disable_importlib = 0;
+    config->install_signal_handlers = 1;
+    _PyCoreConfig_GetGlobalConfig(config);
+
+    int res = pymain_cmdline(pymain, config);
     if (res < 0) {
         _Py_FatalInitError(pymain->err);
     }
     if (res == 1) {
-        goto done;
+        pymain_clear_config(&local_config);
+        return res;
     }
 
     pymain_init_stdio(pymain);
 
     PyInterpreterState *interp;
-    pymain->err = _Py_InitializeCore(&interp, &pymain->config);
+    pymain->err = _Py_InitializeCore(&interp, config);
     if (_Py_INIT_FAILED(pymain->err)) {
         _Py_FatalInitError(pymain->err);
     }
 
+    pymain_clear_config(&local_config);
+    config = &interp->core_config;
+
     if (pymain_init_python_main(pymain, interp) < 0) {
         _Py_FatalInitError(pymain->err);
     }
 
-    if (pymain_init_sys_path(pymain) < 0) {
+    if (pymain_init_sys_path(pymain, config) < 0) {
         _Py_FatalInitError(pymain->err);
     }
+    return 0;
+}
+
+
+static int
+pymain_main(_PyMain *pymain)
+{
+    int res = pymain_init(pymain);
+    if (res == 1) {
+        goto done;
+    }
 
     pymain_run_python(pymain);