1- /* Copyright (c) 2023, Oracle and/or its affiliates.
1+ /* Copyright (c) 2023, 2026, Oracle and/or its affiliates.
22 * Copyright (C) 1996-2023 Python Software Foundation
33 *
44 * Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
77
88#include <windows.h>
99#include <pathcch.h>
10+ #include <shellapi.h>
1011#include <stringapiset.h>
1112#include <stdio.h>
1213#include <stdbool.h>
1314#include <stdint.h>
1415#include <share.h>
16+ #include <string.h>
1517
1618#pragma comment(lib, "Pathcch.lib")
19+ #pragma comment(lib, "Shell32.lib")
1720
1821#define MAXLEN PATHCCH_MAX_CCH
1922#define MSGSIZE 1024
@@ -200,10 +203,58 @@ launchEnvironment(wchar_t *env, wchar_t *exe)
200203
201204#define GRAAL_PYTHON_ARGS L"GRAAL_PYTHON_ARGS="
202205#define GRAAL_PYTHON_EXE_ARG L"--python.Executable="
203- #define GRAAL_PYTHON_BASE_EXE_ARG L"--python.VenvlauncherCommand="
206+ #define GRAAL_PYTHON_BASE_EXECUTABLE_ARG L"--python.BaseExecutable="
207+ #define GRAAL_PYTHON_VENVLAUNCHER_COMMAND_ARG L"--python.VenvlauncherCommand="
204208#define GRAAL_PYTHON_COMMAND_CFG "venvlauncher_command = "
209+ #define GRAAL_PYTHON_BASE_EXECUTABLE_CFG "base-executable = "
205210#define PYVENV_CFG L"pyvenv.cfg"
206211
212+ static int
213+ copyUtf8ToWideChar (const char * in , int inLen , wchar_t * out , size_t outLen )
214+ {
215+ if (outLen == 0 ) {
216+ return 0 ;
217+ }
218+ int written = MultiByteToWideChar (CP_UTF8 , 0 , in , inLen , out , (int )outLen - 1 );
219+ if (written <= 0 ) {
220+ return 0 ;
221+ }
222+ out [written ] = L'\0' ;
223+ return written ;
224+ }
225+
226+ static int
227+ readPyVenvCfgValue (FILE * pyvenvCfgFile , const char * key , wchar_t * out , size_t outLen )
228+ {
229+ char line [MAXLEN ];
230+ size_t keyLen = strlen (key );
231+
232+ rewind (pyvenvCfgFile );
233+ while (fgets (line , sizeof (line ), pyvenvCfgFile ) != NULL ) {
234+ if (strncmp (line , key , keyLen ) == 0 ) {
235+ size_t valueLen = strcspn (line + keyLen , "\r\n" );
236+ return copyUtf8ToWideChar (line + keyLen , (int )valueLen , out , outLen );
237+ }
238+ }
239+ return 0 ;
240+ }
241+
242+ static int
243+ extractBaseExecutable (const wchar_t * command , wchar_t * out , size_t outLen )
244+ {
245+ int cmdArgc = 0 ;
246+ wchar_t * * cmdArgv = CommandLineToArgvW (command , & cmdArgc );
247+ if (cmdArgv == NULL || cmdArgc < 1 ) {
248+ if (cmdArgv != NULL ) {
249+ LocalFree (cmdArgv );
250+ }
251+ return -1 ;
252+ }
253+ int rc = wcscpy_s (out , outLen , cmdArgv [0 ]);
254+ LocalFree (cmdArgv );
255+ return rc ;
256+ }
257+
207258int
208259wmain (int argc , wchar_t * * argv )
209260{
@@ -221,16 +272,16 @@ wmain(int argc, wchar_t ** argv)
221272 wchar_t * newExeStart = NULL ;
222273 memset (newExecutable , 0 , sizeof (newExecutable ));
223274
275+ wchar_t baseExecutable [MAXLEN ];
276+ memset (baseExecutable , 0 , sizeof (baseExecutable ));
277+
224278 wchar_t currentExecutable [MAXLEN ];
225279 int currentExecutableSize = sizeof (currentExecutable ) / sizeof (currentExecutable [0 ]);
226280 memset (currentExecutable , 0 , sizeof (currentExecutable ));
227281
228282 wchar_t pyvenvCfg [MAXLEN ];
229283 memset (pyvenvCfg , 0 , sizeof (pyvenvCfg ));
230284
231- char pyvenvcfg_command [MAXLEN ];
232- memset (pyvenvcfg_command , 0 , sizeof (pyvenvcfg_command ));
233-
234285 if (isEnvVarSet (L"PYLAUNCHER_DEBUG" )) {
235286 setvbuf (stderr , (char * )NULL , _IONBF , 0 );
236287 log_fp = stderr ;
@@ -260,24 +311,12 @@ wmain(int argc, wchar_t ** argv)
260311 }
261312 if (pyvenvCfgFile ) {
262313 debug (L"pyvenv.cfg at %s\n" , pyvenvCfg );
263- int i = 0 ;
264- while (fread_s (pyvenvcfg_command + i , sizeof (pyvenvcfg_command ), 1 , 1 , pyvenvCfgFile )) {
265- if (pyvenvcfg_command [i ] == GRAAL_PYTHON_COMMAND_CFG [i ]) {
266- ++ i ;
267- } else {
268- i = 0 ;
269- }
270- if (strcmp (GRAAL_PYTHON_COMMAND_CFG , pyvenvcfg_command ) == 0 ) {
271- for (i = 0 ; i < sizeof (pyvenvcfg_command ); ++ i ) {
272- if (fread_s (pyvenvcfg_command + i , sizeof (pyvenvcfg_command ), 1 , 1 , pyvenvCfgFile ) < 1
273- || pyvenvcfg_command [i ] == '\r'
274- || pyvenvcfg_command [i ] == '\n' ) {
275- newExecutableSize = MultiByteToWideChar (CP_UTF8 , 0 , pyvenvcfg_command , i , newExecutable + 1 , sizeof (newExecutable ) - 2 ) * sizeof (newExecutable [0 ]);
276- break ;
277- }
278- }
279- break ;
280- }
314+ newExecutableSize = readPyVenvCfgValue (pyvenvCfgFile , GRAAL_PYTHON_COMMAND_CFG , newExecutable + 1 , MAXLEN - 1 ) * sizeof (newExecutable [0 ]);
315+ if (newExecutableSize ) {
316+ debug (L"new executable from pyvenv.cfg: %s\n" , newExecutable + 1 );
317+ }
318+ if (readPyVenvCfgValue (pyvenvCfgFile , GRAAL_PYTHON_BASE_EXECUTABLE_CFG , baseExecutable , MAXLEN )) {
319+ debug (L"base executable from pyvenv.cfg: %s\n" , baseExecutable );
281320 }
282321 } else {
283322 debug (L"no pyvenv.cfg at %s\n" , pyvenvCfg );
@@ -323,6 +362,19 @@ wmain(int argc, wchar_t ** argv)
323362 }
324363 debug (L"new exe: %s\n" , newExeStart );
325364
365+ if (!baseExecutable [0 ]) {
366+ exitCode = extractBaseExecutable (newExeStart , baseExecutable , MAXLEN );
367+ if (exitCode ) {
368+ debug (L"Failed to extract base executable from launcher command, using current executable instead\n" );
369+ exitCode = wcscpy_s (baseExecutable , MAXLEN , currentExecutable );
370+ if (exitCode ) {
371+ winerror (exitCode , L"Failed to copy current executable into base executable" );
372+ goto abort ;
373+ }
374+ }
375+ }
376+ debug (L"base executable: %s\n" , baseExecutable );
377+
326378 // calculate the size of the new environment, that is, the size of the previous environment
327379 // plus the size of the GRAAL_PYTHON_ARGS variable with the arguments to pass on
328380 env = GetEnvironmentStringsW ();
@@ -340,8 +392,10 @@ wmain(int argc, wchar_t ** argv)
340392 envSize += wcslen (GRAAL_PYTHON_ARGS );
341393 // need room to specify original launcher path
342394 envSize += 1 + wcslen (GRAAL_PYTHON_EXE_ARG ) + wcslen (currentExecutable );
343- // need room to specify base launcher path
344- envSize += 1 + wcslen (GRAAL_PYTHON_BASE_EXE_ARG ) + wcslen (newExeStart );
395+ // need room to specify base executable path
396+ envSize += 1 + wcslen (GRAAL_PYTHON_BASE_EXECUTABLE_ARG ) + wcslen (baseExecutable );
397+ // need room to specify original launcher command
398+ envSize += 1 + wcslen (GRAAL_PYTHON_VENVLAUNCHER_COMMAND_ARG ) + wcslen (newExeStart );
345399 for (int i = 1 ; i < argc ; ++ i ) {
346400 // env needs room for \v and arg, no \0
347401 envSize = envSize + 1 + wcslen (argv [i ]);
@@ -406,14 +460,34 @@ wmain(int argc, wchar_t ** argv)
406460 newEnvCur [0 ] = L'\v' ;
407461 -- envSize ;
408462 ++ newEnvCur ;
409- exitCode = wcscpy_s (newEnvCur , envSize , GRAAL_PYTHON_BASE_EXE_ARG );
463+ exitCode = wcscpy_s (newEnvCur , envSize , GRAAL_PYTHON_BASE_EXECUTABLE_ARG );
464+ if (exitCode ) {
465+ winerror (exitCode , L"Failed to copy %s" , GRAAL_PYTHON_BASE_EXECUTABLE_ARG );
466+ goto abort ;
467+ }
468+ debug (L"%s" , newEnvCur );
469+ envSize = envSize - wcslen (GRAAL_PYTHON_BASE_EXECUTABLE_ARG );
470+ newEnvCur = newEnvCur + wcslen (GRAAL_PYTHON_BASE_EXECUTABLE_ARG );
471+ exitCode = wcscpy_s (newEnvCur , envSize , baseExecutable );
472+ if (exitCode ) {
473+ winerror (exitCode , L"Failed to copy %s into env" , baseExecutable );
474+ goto abort ;
475+ }
476+ debug (L"%s" , newEnvCur );
477+ envSize = envSize - wcslen (baseExecutable );
478+ newEnvCur = newEnvCur + wcslen (baseExecutable );
479+ // specify original launcher command
480+ newEnvCur [0 ] = L'\v' ;
481+ -- envSize ;
482+ ++ newEnvCur ;
483+ exitCode = wcscpy_s (newEnvCur , envSize , GRAAL_PYTHON_VENVLAUNCHER_COMMAND_ARG );
410484 if (exitCode ) {
411- winerror (exitCode , L"Failed to copy %s" , GRAAL_PYTHON_BASE_EXE_ARG );
485+ winerror (exitCode , L"Failed to copy %s" , GRAAL_PYTHON_VENVLAUNCHER_COMMAND_ARG );
412486 goto abort ;
413487 }
414488 debug (L"%s" , newEnvCur );
415- envSize = envSize - wcslen (GRAAL_PYTHON_BASE_EXE_ARG );
416- newEnvCur = newEnvCur + wcslen (GRAAL_PYTHON_BASE_EXE_ARG );
489+ envSize = envSize - wcslen (GRAAL_PYTHON_VENVLAUNCHER_COMMAND_ARG );
490+ newEnvCur = newEnvCur + wcslen (GRAAL_PYTHON_VENVLAUNCHER_COMMAND_ARG );
417491 exitCode = wcscpy_s (newEnvCur , envSize , newExeStart );
418492 if (exitCode ) {
419493 winerror (exitCode , L"Failed to copy %s into env" , newExeStart );
0 commit comments