1 /*
2  exewrapper.c - wrapper for calling a python script on Windows
3 
4  Copyright 2012 Adrian Buehlmann <adrian@cadifra.com> and others
5 
6  This software may be used and distributed according to the terms of the
7  GNU General Public License version 2 or any later version.
8 */
9 
10 #include <Python.h>
11 #include <stdio.h>
12 #include <tchar.h>
13 #include <windows.h>
14 
15 #include "hgpythonlib.h"
16 
17 #ifdef __GNUC__
strcat_s(char * d,size_t n,const char * s)18 int strcat_s(char *d, size_t n, const char *s)
19 {
20 	return !strncat(d, s, n);
21 }
strcpy_s(char * d,size_t n,const char * s)22 int strcpy_s(char *d, size_t n, const char *s)
23 {
24 	return !strncpy(d, s, n);
25 }
26 
27 #define _tcscpy_s strcpy_s
28 #define _tcscat_s strcat_s
29 #define _countof(array) (sizeof(array) / sizeof(array[0]))
30 #endif
31 
32 static TCHAR pyscript[MAX_PATH + 10];
33 static TCHAR pyhome[MAX_PATH + 10];
34 static TCHAR pydllfile[MAX_PATH + 10];
35 
_tmain(int argc,TCHAR * argv[])36 int _tmain(int argc, TCHAR *argv[])
37 {
38 	TCHAR *p;
39 	int ret;
40 	int i;
41 	int n;
42 	TCHAR **pyargv;
43 	WIN32_FIND_DATA fdata;
44 	HANDLE hfind;
45 	const char *err;
46 	HMODULE pydll;
47 	void(__cdecl * Py_SetPythonHome)(TCHAR * home);
48 	int(__cdecl * Py_Main)(int argc, TCHAR *argv[]);
49 
50 #if PY_MAJOR_VERSION >= 3
51 	_wputenv(L"PYTHONLEGACYWINDOWSSTDIO=1");
52 #endif
53 
54 	if (GetModuleFileName(NULL, pyscript, _countof(pyscript)) == 0) {
55 		err = "GetModuleFileName failed";
56 		goto bail;
57 	}
58 
59 	p = _tcsrchr(pyscript, '.');
60 	if (p == NULL) {
61 		err = "malformed module filename";
62 		goto bail;
63 	}
64 	*p = 0; /* cut trailing ".exe" */
65 	_tcscpy_s(pyhome, _countof(pyhome), pyscript);
66 
67 	hfind = FindFirstFile(pyscript, &fdata);
68 	if (hfind != INVALID_HANDLE_VALUE) {
69 		/* pyscript exists, close handle */
70 		FindClose(hfind);
71 	} else {
72 		/* file pyscript isn't there, take <pyscript>exe.py */
73 		_tcscat_s(pyscript, _countof(pyscript), _T("exe.py"));
74 	}
75 
76 	pydll = NULL;
77 
78 	p = _tcsrchr(pyhome, _T('\\'));
79 	if (p == NULL) {
80 		err = "can't find backslash in module filename";
81 		goto bail;
82 	}
83 	*p = 0; /* cut at directory */
84 
85 	/* check for private Python of HackableMercurial */
86 	_tcscat_s(pyhome, _countof(pyhome), _T("\\hg-python"));
87 
88 	hfind = FindFirstFile(pyhome, &fdata);
89 	if (hfind != INVALID_HANDLE_VALUE) {
90 		/* Path .\hg-python exists. We are probably in HackableMercurial
91 		scenario, so let's load python dll from this dir. */
92 		FindClose(hfind);
93 		_tcscpy_s(pydllfile, _countof(pydllfile), pyhome);
94 		_tcscat_s(pydllfile, _countof(pydllfile),
95 		          _T("\\") _T(HGPYTHONLIB) _T(".dll"));
96 		pydll = LoadLibrary(pydllfile);
97 		if (pydll == NULL) {
98 			err = "failed to load private Python DLL " HGPYTHONLIB
99 			      ".dll";
100 			goto bail;
101 		}
102 		Py_SetPythonHome =
103 		    (void *)GetProcAddress(pydll, "Py_SetPythonHome");
104 		if (Py_SetPythonHome == NULL) {
105 			err = "failed to get Py_SetPythonHome";
106 			goto bail;
107 		}
108 		Py_SetPythonHome(pyhome);
109 	}
110 
111 	if (pydll == NULL) {
112 		pydll = LoadLibrary(_T(HGPYTHONLIB) _T(".dll"));
113 		if (pydll == NULL) {
114 			err = "failed to load Python DLL " HGPYTHONLIB ".dll";
115 			goto bail;
116 		}
117 	}
118 
119 	Py_Main = (void *)GetProcAddress(pydll, "Py_Main");
120 	if (Py_Main == NULL) {
121 		err = "failed to get Py_Main";
122 		goto bail;
123 	}
124 
125 	/*
126 	Only add the pyscript to the args, if it's not already there. It may
127 	already be there, if the script spawned a child process of itself, in
128 	the same way as it got called, that is, with the pyscript already in
129 	place. So we optionally accept the pyscript as the first argument
130 	(argv[1]), letting our exe taking the role of the python interpreter.
131 	*/
132 	if (argc >= 2 && _tcscmp(argv[1], pyscript) == 0) {
133 		/*
134 		pyscript is already in the args, so there is no need to copy
135 		the args and we can directly call the python interpreter with
136 		the original args.
137 		*/
138 		return Py_Main(argc, argv);
139 	}
140 
141 	/*
142 	Start assembling the args for the Python interpreter call. We put the
143 	name of our exe (argv[0]) in the position where the python.exe
144 	canonically is, and insert the pyscript next.
145 	*/
146 	pyargv = malloc((argc + 5) * sizeof(TCHAR *));
147 	if (pyargv == NULL) {
148 		err = "not enough memory";
149 		goto bail;
150 	}
151 	n = 0;
152 	pyargv[n++] = argv[0];
153 	pyargv[n++] = pyscript;
154 
155 	/* copy remaining args from the command line */
156 	for (i = 1; i < argc; i++)
157 		pyargv[n++] = argv[i];
158 	/* argv[argc] is guaranteed to be NULL, so we forward that guarantee */
159 	pyargv[n] = NULL;
160 
161 	ret = Py_Main(n, pyargv); /* The Python interpreter call */
162 
163 	free(pyargv);
164 	return ret;
165 
166 bail:
167 	fprintf(stderr, "abort: %s\n", err);
168 	return 255;
169 }
170