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