bpo-32030: Add _PyMainInterpreterConfig.program_name (#4548)

* Py_Main() now calls Py_SetProgramName() earlier to be able to get
  the program name in _PyMainInterpreterConfig_ReadEnv().
* Rename prog to program_name
* Rename progpath to program_name
diff --git a/Include/pystate.h b/Include/pystate.h
index 533851f..60d001c 100644
--- a/Include/pystate.h
+++ b/Include/pystate.h
@@ -64,6 +64,8 @@
     wchar_t *module_search_path_env;
     /* PYTHONHOME environment variable, see also Py_SetPythonHome(). */
     wchar_t *home;
+    /* Program name, see also Py_GetProgramName() */
+    wchar_t *program_name;
 } _PyMainInterpreterConfig;
 
 #define _PyMainInterpreterConfig_INIT \
diff --git a/Modules/getpath.c b/Modules/getpath.c
index db28eaf..9078aa8 100644
--- a/Modules/getpath.c
+++ b/Modules/getpath.c
@@ -112,7 +112,7 @@
 typedef struct {
     wchar_t prefix[MAXPATHLEN+1];
     wchar_t exec_prefix[MAXPATHLEN+1];
-    wchar_t progpath[MAXPATHLEN+1];
+    wchar_t program_name[MAXPATHLEN+1];
     wchar_t *module_search_path;
 } PyPathConfig;
 
@@ -121,7 +121,7 @@
     wchar_t *home;                     /* PYTHONHOME environment variable */
     wchar_t *module_search_path_env;   /* PYTHONPATH environment variable */
 
-    wchar_t *prog;                     /* Program name */
+    wchar_t *program_name;             /* Program name */
     wchar_t *pythonpath;               /* PYTHONPATH define */
     wchar_t *prefix;                   /* PREFIX define */
     wchar_t *exec_prefix;              /* EXEC_PREFIX define */
@@ -602,8 +602,8 @@
      * other way to find a directory to start the search from.  If
      * $PATH isn't exported, you lose.
      */
-    if (wcschr(calculate->prog, SEP)) {
-        wcsncpy(config->progpath, calculate->prog, MAXPATHLEN);
+    if (wcschr(calculate->program_name, SEP)) {
+        wcsncpy(config->program_name, calculate->program_name, MAXPATHLEN);
     }
 #ifdef __APPLE__
      /* On Mac OS X, if a script uses an interpreter of the form
@@ -616,11 +616,13 @@
       * will fail if a relative path was used. but in that case,
       * absolutize() should help us out below
       */
-    else if(0 == _NSGetExecutablePath(execpath, &nsexeclength) && execpath[0] == SEP) {
-        size_t r = mbstowcs(config->progpath, execpath, MAXPATHLEN+1);
+    else if(0 == _NSGetExecutablePath(execpath, &nsexeclength) &&
+            execpath[0] == SEP)
+    {
+        size_t r = mbstowcs(config->program_name, execpath, MAXPATHLEN+1);
         if (r == (size_t)-1 || r > MAXPATHLEN) {
             /* Could not convert execpath, or it's too long. */
-            config->progpath[0] = '\0';
+            config->program_name[0] = '\0';
         }
     }
 #endif /* __APPLE__ */
@@ -634,30 +636,30 @@
                 if (len > MAXPATHLEN) {
                     len = MAXPATHLEN;
                 }
-                wcsncpy(config->progpath, path, len);
-                *(config->progpath + len) = '\0';
+                wcsncpy(config->program_name, path, len);
+                *(config->program_name + len) = '\0';
             }
             else {
-                wcsncpy(config->progpath, path, MAXPATHLEN);
+                wcsncpy(config->program_name, path, MAXPATHLEN);
             }
 
-            joinpath(config->progpath, calculate->prog);
-            if (isxfile(config->progpath)) {
+            joinpath(config->program_name, calculate->program_name);
+            if (isxfile(config->program_name)) {
                 break;
             }
 
             if (!delim) {
-                config->progpath[0] = L'\0';
+                config->program_name[0] = L'\0';
                 break;
             }
             path = delim + 1;
         }
     }
     else {
-        config->progpath[0] = '\0';
+        config->program_name[0] = '\0';
     }
-    if (config->progpath[0] != SEP && config->progpath[0] != '\0') {
-        absolutize(config->progpath);
+    if (config->program_name[0] != SEP && config->program_name[0] != '\0') {
+        absolutize(config->program_name);
     }
 }
 
@@ -665,7 +667,7 @@
 static void
 calculate_argv0_path(PyCalculatePath *calculate, PyPathConfig *config)
 {
-    wcsncpy(calculate->argv0_path, config->progpath, MAXPATHLEN);
+    wcsncpy(calculate->argv0_path, config->program_name, MAXPATHLEN);
     calculate->argv0_path[MAXPATHLEN] = '\0';
 
 #ifdef WITH_NEXT_FRAMEWORK
@@ -700,10 +702,10 @@
         if (!ismodule(calculate->argv0_path)) {
             /* We are in the build directory so use the name of the
                executable - we know that the absolute path is passed */
-            wcsncpy(calculate->argv0_path, config->progpath, MAXPATHLEN);
+            wcsncpy(calculate->argv0_path, config->program_name, MAXPATHLEN);
         }
         else {
-            /* Use the location of the library as the progpath */
+            /* Use the location of the library as the program_name */
             wcsncpy(calculate->argv0_path, wbuf, MAXPATHLEN);
         }
         PyMem_RawFree(wbuf);
@@ -712,7 +714,7 @@
 
 #if HAVE_READLINK
     wchar_t tmpbuffer[MAXPATHLEN+1];
-    int linklen = _Py_wreadlink(config->progpath, tmpbuffer, MAXPATHLEN);
+    int linklen = _Py_wreadlink(config->program_name, tmpbuffer, MAXPATHLEN);
     while (linklen != -1) {
         if (tmpbuffer[0] == SEP) {
             /* tmpbuffer should never be longer than MAXPATHLEN,
@@ -720,7 +722,7 @@
             wcsncpy(calculate->argv0_path, tmpbuffer, MAXPATHLEN);
         }
         else {
-            /* Interpret relative to progpath */
+            /* Interpret relative to program_name */
             reduce(calculate->argv0_path);
             joinpath(calculate->argv0_path, tmpbuffer);
         }
@@ -897,6 +899,7 @@
 {
     calculate->home = main_config->home;
     calculate->module_search_path_env = main_config->module_search_path_env;
+    calculate->program_name = main_config->program_name;
 
     size_t len;
     char *path = getenv("PATH");
@@ -907,8 +910,6 @@
         }
     }
 
-    calculate->prog = Py_GetProgramName();
-
     calculate->pythonpath = Py_DecodeLocale(PYTHONPATH, &len);
     if (!calculate->pythonpath) {
         return DECODE_LOCALE_ERR("PYTHONPATH define", len);
@@ -950,7 +951,9 @@
     calculate_zip_path(calculate, config);
     calculate_exec_prefix(calculate, config);
 
-    if ((!calculate->prefix_found || !calculate->exec_prefix_found) && !Py_FrozenFlag) {
+    if ((!calculate->prefix_found || !calculate->exec_prefix_found) &&
+        !Py_FrozenFlag)
+    {
         fprintf(stderr,
                 "Consider setting $PYTHONHOME to <prefix>[:<exec_prefix>]\n");
     }
@@ -1018,10 +1021,11 @@
         return;
     }
 
-    wchar_t *prog = Py_GetProgramName();
-    wcsncpy(path_config.progpath, prog, MAXPATHLEN);
+    wchar_t *program_name = Py_GetProgramName();
+    wcsncpy(path_config.program_name, program_name, MAXPATHLEN);
     path_config.exec_prefix[0] = path_config.prefix[0] = L'\0';
-    path_config.module_search_path = PyMem_RawMalloc((wcslen(path) + 1) * sizeof(wchar_t));
+    size_t size = (wcslen(path) + 1) * sizeof(wchar_t);
+    path_config.module_search_path = PyMem_RawMalloc(size);
     if (path_config.module_search_path != NULL) {
         wcscpy(path_config.module_search_path, path);
     }
@@ -1074,7 +1078,7 @@
     if (!path_config.module_search_path) {
         calculate_path(NULL);
     }
-    return path_config.progpath;
+    return path_config.program_name;
 }
 
 #ifdef __cplusplus
diff --git a/Modules/main.c b/Modules/main.c
index 5b0c049..dca165d 100644
--- a/Modules/main.c
+++ b/Modules/main.c
@@ -1452,6 +1452,13 @@
         return err;
     }
 
+    /* FIXME: _PyMainInterpreterConfig_Read() has the same code. Remove it
+       here? See also pymain_get_program_name() and pymain_parse_envvars(). */
+    config->program_name = _PyMem_RawWcsdup(Py_GetProgramName());
+    if (config->program_name == NULL) {
+        return _Py_INIT_NO_MEMORY();
+    }
+
     return _Py_INIT_OK();
 }
 
@@ -1480,6 +1487,15 @@
     }
     core_config->allocator = Py_GETENV("PYTHONMALLOC");
 
+    /* FIXME: move pymain_get_program_name() code into
+       _PyMainInterpreterConfig_ReadEnv().
+       Problem: _PyMainInterpreterConfig_ReadEnv() doesn't have access
+       to argv[0]. */
+    Py_SetProgramName(pymain->program_name);
+    /* Don't free program_name here: the argument to Py_SetProgramName
+       must remain valid until Py_FinalizeEx is called. The string is freed
+       by pymain_free(). */
+
     _PyInitError err = _PyMainInterpreterConfig_ReadEnv(&pymain->config);
     if (_Py_INIT_FAILED(pymain->err)) {
         pymain->err = err;
@@ -1569,11 +1585,6 @@
         return -1;
     }
 
-    Py_SetProgramName(pymain->program_name);
-    /* Don't free program_name here: the argument to Py_SetProgramName
-       must remain valid until Py_FinalizeEx is called. The string is freed
-       by pymain_free(). */
-
     if (pymain_add_xoptions(pymain)) {
         return -1;
     }
diff --git a/PC/getpathp.c b/PC/getpathp.c
index 5adf16d..38e433b 100644
--- a/PC/getpathp.c
+++ b/PC/getpathp.c
@@ -118,7 +118,7 @@
 
 typedef struct {
     wchar_t prefix[MAXPATHLEN+1];
-    wchar_t progpath[MAXPATHLEN+1];
+    wchar_t program_name[MAXPATHLEN+1];
     wchar_t dllpath[MAXPATHLEN+1];
     wchar_t *module_search_path;
 } PyPathConfig;
@@ -132,7 +132,7 @@
     wchar_t *machine_path;   /* from HKEY_LOCAL_MACHINE */
     wchar_t *user_path;      /* from HKEY_CURRENT_USER */
 
-    wchar_t *prog;                     /* Program name */
+    wchar_t *program_name;             /* Program name */
     wchar_t argv0_path[MAXPATHLEN+1];
     wchar_t zip_path[MAXPATHLEN+1];
 } PyCalculatePath;
@@ -484,22 +484,22 @@
 
 
 static void
-get_progpath(PyCalculatePath *calculate, wchar_t *progpath, wchar_t *dllpath)
+get_progpath(PyCalculatePath *calculate, PyPathConfig *config)
 {
     wchar_t *path = calculate->path_env;
 
 #ifdef Py_ENABLE_SHARED
     extern HANDLE PyWin_DLLhModule;
-    /* static init of progpath ensures final char remains \0 */
+    /* static init of program_name ensures final char remains \0 */
     if (PyWin_DLLhModule) {
-        if (!GetModuleFileNameW(PyWin_DLLhModule, dllpath, MAXPATHLEN)) {
-            dllpath[0] = 0;
+        if (!GetModuleFileNameW(PyWin_DLLhModule, config->dllpath, MAXPATHLEN)) {
+            config->dllpath[0] = 0;
         }
     }
 #else
-    dllpath[0] = 0;
+    config->dllpath[0] = 0;
 #endif
-    if (GetModuleFileNameW(NULL, progpath, MAXPATHLEN)) {
+    if (GetModuleFileNameW(NULL, config->program_name, MAXPATHLEN)) {
         return;
     }
 
@@ -509,12 +509,12 @@
      * $PATH isn't exported, you lose.
      */
 #ifdef ALTSEP
-    if (wcschr(calculate->prog, SEP) || wcschr(calculate->prog, ALTSEP))
+    if (wcschr(calculate->program_name, SEP) || wcschr(calculate->program_name, ALTSEP))
 #else
-    if (wcschr(calculate->prog, SEP))
+    if (wcschr(calculate->program_name, SEP))
 #endif
     {
-        wcsncpy(progpath, calculate->prog, MAXPATHLEN);
+        wcsncpy(config->program_name, calculate->program_name, MAXPATHLEN);
     }
     else if (path) {
         while (1) {
@@ -524,28 +524,28 @@
                 size_t len = delim - path;
                 /* ensure we can't overwrite buffer */
                 len = min(MAXPATHLEN,len);
-                wcsncpy(progpath, path, len);
-                *(progpath + len) = '\0';
+                wcsncpy(config->program_name, path, len);
+                *(config->program_name + len) = '\0';
             }
             else {
-                wcsncpy(progpath, path, MAXPATHLEN);
+                wcsncpy(config->program_name, path, MAXPATHLEN);
             }
 
             /* join() is safe for MAXPATHLEN+1 size buffer */
-            join(progpath, calculate->prog);
-            if (exists(progpath)) {
+            join(config->program_name, calculate->program_name);
+            if (exists(config->program_name)) {
                 break;
             }
 
             if (!delim) {
-                progpath[0] = '\0';
+                config->program_name[0] = '\0';
                 break;
             }
             path = delim + 1;
         }
     }
     else {
-        progpath[0] = '\0';
+        config->program_name[0] = '\0';
     }
 }
 
@@ -695,14 +695,9 @@
 {
     calculate->home = main_config->home;
     calculate->module_search_path_env = main_config->module_search_path_env;
+    calculate->program_name = main_config->program_name;
 
     calculate->path_env = _wgetenv(L"PATH");
-
-    wchar_t *prog = Py_GetProgramName();
-    if (prog == NULL || *prog == '\0') {
-        prog = L"python";
-    }
-    calculate->prog = prog;
 }
 
 
@@ -714,8 +709,8 @@
             return 1;
         }
     }
-    if (config->progpath[0]) {
-        if (!change_ext(spbuffer, config->progpath, L"._pth") && exists(spbuffer)) {
+    if (config->program_name[0]) {
+        if (!change_ext(spbuffer, config->program_name, L"._pth") && exists(spbuffer)) {
             return 1;
         }
     }
@@ -784,9 +779,9 @@
 calculate_path_impl(PyCalculatePath *calculate, PyPathConfig *config,
                     const _PyMainInterpreterConfig *main_config)
 {
-    get_progpath(calculate, config->progpath, config->dllpath);
-    /* progpath guaranteed \0 terminated in MAXPATH+1 bytes. */
-    wcscpy_s(calculate->argv0_path, MAXPATHLEN+1, config->progpath);
+    get_progpath(calculate, config);
+    /* program_name guaranteed \0 terminated in MAXPATH+1 bytes. */
+    wcscpy_s(calculate->argv0_path, MAXPATHLEN+1, config->program_name);
     reduce(calculate->argv0_path);
 
     /* Search for a sys.path file */
@@ -798,7 +793,7 @@
 
     /* Calculate zip archive path from DLL or exe path */
     change_ext(calculate->zip_path,
-               config->dllpath[0] ? config->dllpath : config->progpath,
+               config->dllpath[0] ? config->dllpath : config->program_name,
                L".zip");
 
     if (calculate->home == NULL || *calculate->home == '\0') {
@@ -1057,8 +1052,8 @@
         return;
     }
 
-    wchar_t *prog = Py_GetProgramName();
-    wcsncpy(path_config.progpath, prog, MAXPATHLEN);
+    wchar_t *program_name = Py_GetProgramName();
+    wcsncpy(path_config.program_name, program_name, MAXPATHLEN);
     path_config.prefix[0] = L'\0';
     path_config.module_search_path = PyMem_RawMalloc((wcslen(path) + 1) * sizeof(wchar_t));
     if (path_config.module_search_path != NULL) {
@@ -1110,7 +1105,7 @@
     if (!path_config.module_search_path) {
         calculate_path(NULL);
     }
-    return path_config.progpath;
+    return path_config.program_name;
 }
 
 
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index e36b6c1..714be37 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -802,6 +802,14 @@
     if (config->install_signal_handlers < 0) {
         config->install_signal_handlers = 1;
     }
+
+    if (config->program_name == NULL) {
+        config->program_name = _PyMem_RawWcsdup(Py_GetProgramName());
+        if (config->program_name == NULL) {
+            return _Py_INIT_NO_MEMORY();
+        }
+    }
+
     return _Py_INIT_OK();
 }
 
@@ -809,10 +817,16 @@
 void
 _PyMainInterpreterConfig_Clear(_PyMainInterpreterConfig *config)
 {
-    PyMem_RawFree(config->module_search_path_env);
-    config->module_search_path_env = NULL;
-    PyMem_RawFree(config->home);
-    config->home = NULL;
+#define CLEAR(ATTR) \
+    do { \
+        PyMem_RawFree(ATTR); \
+        ATTR = NULL; \
+    } while (0)
+
+    CLEAR(config->module_search_path_env);
+    CLEAR(config->home);
+    CLEAR(config->program_name);
+#undef CLEAR
 }