1 /*  Setuptools Script Launcher for Windows
2 
3     This is a stub executable for Windows that functions somewhat like
4     Effbot's "exemaker", in that it runs a script with the same name but
5     a .py extension, using information from a #! line.  It differs in that
6     it spawns the actual Python executable, rather than attempting to
7     hook into the Python DLL.  This means that the script will run with
8     sys.executable set to the Python executable, where exemaker ends up with
9     sys.executable pointing to itself.  (Which means it won't work if you try
10     to run another Python process using sys.executable.)
11 
12     To build/rebuild with mingw32, do this in the setuptools project directory:
13 
14        gcc -DGUI=0           -mno-cygwin -O -s -o setuptools/cli.exe launcher.c
15        gcc -DGUI=1 -mwindows -mno-cygwin -O -s -o setuptools/gui.exe launcher.c
16 
17     To build for Windows RT, install both Visual Studio Express for Windows 8
18     and for Windows Desktop (both freeware), create "win32" application using
19     "Windows Desktop" version, create new "ARM" target via
20     "Configuration Manager" menu and modify ".vcxproj" file by adding
21     "<WindowsSDKDesktopARMSupport>true</WindowsSDKDesktopARMSupport>" tag
22     as child of "PropertyGroup" tags that has "Debug|ARM" and "Release|ARM"
23     properties.
24 
25     It links to msvcrt.dll, but this shouldn't be a problem since it doesn't
26     actually run Python in the same process.  Note that using 'exec' instead
27     of 'spawn' doesn't work, because on Windows this leads to the Python
28     executable running in the *background*, attached to the same console
29     window, meaning you get a command prompt back *before* Python even finishes
30     starting.  So, we have to use spawnv() and wait for Python to exit before
31     continuing.  :(
32 */
33 
34 #include <stdlib.h>
35 #include <stdio.h>
36 #include <string.h>
37 #include <windows.h>
38 #include <tchar.h>
39 #include <fcntl.h>
40 #include <process.h>
41 
42 int child_pid=0;
43 
fail(char * format,char * data)44 int fail(char *format, char *data) {
45     /* Print error message to stderr and return 2 */
46     fprintf(stderr, format, data);
47     return 2;
48 }
49 
quoted(char * data)50 char *quoted(char *data) {
51     int i, ln = strlen(data), nb;
52 
53     /* We allocate twice as much space as needed to deal with worse-case
54        of having to escape everything. */
55     char *result = calloc(ln*2+3, sizeof(char));
56     char *presult = result;
57 
58     *presult++ = '"';
59     for (nb=0, i=0; i < ln; i++)
60       {
61         if (data[i] == '\\')
62           nb += 1;
63         else if (data[i] == '"')
64           {
65             for (; nb > 0; nb--)
66               *presult++ = '\\';
67             *presult++ = '\\';
68           }
69         else
70           nb = 0;
71         *presult++ = data[i];
72       }
73 
74     for (; nb > 0; nb--)        /* Deal w trailing slashes */
75       *presult++ = '\\';
76 
77     *presult++ = '"';
78     *presult++ = 0;
79     return result;
80 }
81 
82 
83 
84 
85 
86 
87 
88 
89 
90 
loadable_exe(char * exename)91 char *loadable_exe(char *exename) {
92     /* HINSTANCE hPython;  DLL handle for python executable */
93     char *result;
94 
95     /* hPython = LoadLibraryEx(exename, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
96     if (!hPython) return NULL; */
97 
98     /* Return the absolute filename for spawnv */
99     result = calloc(MAX_PATH, sizeof(char));
100     strncpy(result, exename, MAX_PATH);
101     /*if (result) GetModuleFileNameA(hPython, result, MAX_PATH);
102 
103     FreeLibrary(hPython); */
104     return result;
105 }
106 
107 
find_exe(char * exename,char * script)108 char *find_exe(char *exename, char *script) {
109     char drive[_MAX_DRIVE], dir[_MAX_DIR], fname[_MAX_FNAME], ext[_MAX_EXT];
110     char path[_MAX_PATH], c, *result;
111 
112     /* convert slashes to backslashes for uniform search below */
113     result = exename;
114     while (c = *result++) if (c=='/') result[-1] = '\\';
115 
116     _splitpath(exename, drive, dir, fname, ext);
117     if (drive[0] || dir[0]=='\\') {
118         return loadable_exe(exename);   /* absolute path, use directly */
119     }
120     /* Use the script's parent directory, which should be the Python home
121        (This should only be used for bdist_wininst-installed scripts, because
122         easy_install-ed scripts use the absolute path to python[w].exe
123     */
124     _splitpath(script, drive, dir, fname, ext);
125     result = dir + strlen(dir) -1;
126     if (*result == '\\') result--;
127     while (*result != '\\' && result>=dir) *result-- = 0;
128     _makepath(path, drive, dir, exename, NULL);
129     return loadable_exe(path);
130 }
131 
132 
parse_argv(char * cmdline,int * argc)133 char **parse_argv(char *cmdline, int *argc)
134 {
135     /* Parse a command line in-place using MS C rules */
136 
137     char **result = calloc(strlen(cmdline), sizeof(char *));
138     char *output = cmdline;
139     char c;
140     int nb = 0;
141     int iq = 0;
142     *argc = 0;
143 
144     result[0] = output;
145     while (isspace(*cmdline)) cmdline++;   /* skip leading spaces */
146 
147     do {
148         c = *cmdline++;
149         if (!c || (isspace(c) && !iq)) {
150             while (nb) {*output++ = '\\'; nb--; }
151             *output++ = 0;
152             result[++*argc] = output;
153             if (!c) return result;
154             while (isspace(*cmdline)) cmdline++;  /* skip leading spaces */
155             if (!*cmdline) return result;  /* avoid empty arg if trailing ws */
156             continue;
157         }
158         if (c == '\\')
159             ++nb;   /* count \'s */
160         else {
161             if (c == '"') {
162                 if (!(nb & 1)) { iq = !iq; c = 0; }  /* skip " unless odd # of \ */
163                 nb = nb >> 1;   /* cut \'s in half */
164             }
165             while (nb) {*output++ = '\\'; nb--; }
166             if (c) *output++ = c;
167         }
168     } while (1);
169 }
170 
pass_control_to_child(DWORD control_type)171 void pass_control_to_child(DWORD control_type) {
172     /*
173      * distribute-issue207
174      * passes the control event to child process (Python)
175      */
176     if (!child_pid) {
177         return;
178     }
179     GenerateConsoleCtrlEvent(child_pid,0);
180 }
181 
control_handler(DWORD control_type)182 BOOL control_handler(DWORD control_type) {
183     /*
184      * distribute-issue207
185      * control event handler callback function
186      */
187     switch (control_type) {
188         case CTRL_C_EVENT:
189             pass_control_to_child(0);
190             break;
191     }
192     return TRUE;
193 }
194 
create_and_wait_for_subprocess(char * command)195 int create_and_wait_for_subprocess(char* command) {
196     /*
197      * distribute-issue207
198      * launches child process (Python)
199      */
200     DWORD return_value = 0;
201     LPSTR commandline = command;
202     STARTUPINFOA s_info;
203     PROCESS_INFORMATION p_info;
204     ZeroMemory(&p_info, sizeof(p_info));
205     ZeroMemory(&s_info, sizeof(s_info));
206     s_info.cb = sizeof(STARTUPINFO);
207     // set-up control handler callback funciotn
208     SetConsoleCtrlHandler((PHANDLER_ROUTINE) control_handler, TRUE);
209     if (!CreateProcessA(NULL, commandline, NULL, NULL, TRUE, 0, NULL, NULL, &s_info, &p_info)) {
210         fprintf(stderr, "failed to create process.\n");
211         return 0;
212     }
213     child_pid = p_info.dwProcessId;
214     // wait for Python to exit
215     WaitForSingleObject(p_info.hProcess, INFINITE);
216     if (!GetExitCodeProcess(p_info.hProcess, &return_value)) {
217         fprintf(stderr, "failed to get exit code from process.\n");
218         return 0;
219     }
220     return return_value;
221 }
222 
join_executable_and_args(char * executable,char ** args,int argc)223 char* join_executable_and_args(char *executable, char **args, int argc)
224 {
225     /*
226      * distribute-issue207
227      * CreateProcess needs a long string of the executable and command-line arguments,
228      * so we need to convert it from the args that was built
229      */
230     int len,counter;
231     char* cmdline;
232 
233     len=strlen(executable)+2;
234     for (counter=1; counter<argc; counter++) {
235         len+=strlen(args[counter])+1;
236     }
237 
238     cmdline = (char*)calloc(len, sizeof(char));
239     sprintf(cmdline, "%s", executable);
240     len=strlen(executable);
241     for (counter=1; counter<argc; counter++) {
242         sprintf(cmdline+len, " %s", args[counter]);
243         len+=strlen(args[counter])+1;
244     }
245     return cmdline;
246 }
247 
run(int argc,char ** argv,int is_gui)248 int run(int argc, char **argv, int is_gui) {
249 
250     char python[256];   /* python executable's filename*/
251     char *pyopt;        /* Python option */
252     char script[256];   /* the script's filename */
253 
254     int scriptf;        /* file descriptor for script file */
255 
256     char **newargs, **newargsp, **parsedargs; /* argument array for exec */
257     char *ptr, *end;    /* working pointers for string manipulation */
258     char *cmdline;
259     int i, parsedargc;              /* loop counter */
260 
261     /* compute script name from our .exe name*/
262     GetModuleFileNameA(NULL, script, sizeof(script));
263     end = script + strlen(script);
264     while( end>script && *end != '.')
265         *end-- = '\0';
266     *end-- = '\0';
267     strcat(script, (GUI ? "-script.pyw" : "-script.py"));
268 
269     /* figure out the target python executable */
270 
271     scriptf = open(script, O_RDONLY);
272     if (scriptf == -1) {
273         return fail("Cannot open %s\n", script);
274     }
275     end = python + read(scriptf, python, sizeof(python));
276     close(scriptf);
277 
278     ptr = python-1;
279     while(++ptr < end && *ptr && *ptr!='\n' && *ptr!='\r') {;}
280 
281     *ptr-- = '\0';
282 
283     if (strncmp(python, "#!", 2)) {
284         /* default to python.exe if no #! header */
285         strcpy(python, "#!python.exe");
286     }
287 
288     parsedargs = parse_argv(python+2, &parsedargc);
289 
290     /* Using spawnv() can fail strangely if you e.g. find the Cygwin
291        Python, so we'll make sure Windows can find and load it */
292 
293     ptr = find_exe(parsedargs[0], script);
294     if (!ptr) {
295         return fail("Cannot find Python executable %s\n", parsedargs[0]);
296     }
297 
298     /* printf("Python executable: %s\n", ptr); */
299 
300     /* Argument array needs to be
301        parsedargc + argc, plus 1 for null sentinel */
302 
303     newargs = (char **)calloc(parsedargc + argc + 1, sizeof(char *));
304     newargsp = newargs;
305 
306     *newargsp++ = quoted(ptr);
307     for (i = 1; i<parsedargc; i++) *newargsp++ = quoted(parsedargs[i]);
308 
309     *newargsp++ = quoted(script);
310     for (i = 1; i < argc; i++)     *newargsp++ = quoted(argv[i]);
311 
312     *newargsp++ = NULL;
313 
314     /* printf("args 0: %s\nargs 1: %s\n", newargs[0], newargs[1]); */
315 
316     if (is_gui) {
317         /* Use exec, we don't need to wait for the GUI to finish */
318         execv(ptr, (const char * const *)(newargs));
319         return fail("Could not exec %s", ptr);   /* shouldn't get here! */
320     }
321 
322     /*
323      * distribute-issue207: using CreateProcessA instead of spawnv
324      */
325     cmdline = join_executable_and_args(ptr, newargs, parsedargc + argc);
326     return create_and_wait_for_subprocess(cmdline);
327 }
328 
WinMain(HINSTANCE hI,HINSTANCE hP,LPSTR lpCmd,int nShow)329 int WINAPI WinMain(HINSTANCE hI, HINSTANCE hP, LPSTR lpCmd, int nShow) {
330     return run(__argc, __argv, GUI);
331 }
332 
main(int argc,char ** argv)333 int main(int argc, char** argv) {
334     return run(argc, argv, GUI);
335 }
336 
337