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 
41 int child_pid=0;
42 
fail(char * format,char * data)43 int fail(char *format, char *data) {
44     /* Print error message to stderr and return 2 */
45     fprintf(stderr, format, data);
46     return 2;
47 }
48 
quoted(char * data)49 char *quoted(char *data) {
50     int i, ln = strlen(data), nb;
51 
52     /* We allocate twice as much space as needed to deal with worse-case
53        of having to escape everything. */
54     char *result = calloc(ln*2+3, sizeof(char));
55     char *presult = result;
56 
57     *presult++ = '"';
58     for (nb=0, i=0; i < ln; i++)
59       {
60         if (data[i] == '\\')
61           nb += 1;
62         else if (data[i] == '"')
63           {
64             for (; nb > 0; nb--)
65               *presult++ = '\\';
66             *presult++ = '\\';
67           }
68         else
69           nb = 0;
70         *presult++ = data[i];
71       }
72 
73     for (; nb > 0; nb--)        /* Deal w trailing slashes */
74       *presult++ = '\\';
75 
76     *presult++ = '"';
77     *presult++ = 0;
78     return result;
79 }
80 
81 
82 
83 
84 
85 
86 
87 
88 
89 
loadable_exe(char * exename)90 char *loadable_exe(char *exename) {
91     /* HINSTANCE hPython;  DLL handle for python executable */
92     char *result;
93 
94     /* hPython = LoadLibraryEx(exename, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
95     if (!hPython) return NULL; */
96 
97     /* Return the absolute filename for spawnv */
98     result = calloc(MAX_PATH, sizeof(char));
99     strncpy(result, exename, MAX_PATH);
100     /*if (result) GetModuleFileNameA(hPython, result, MAX_PATH);
101 
102     FreeLibrary(hPython); */
103     return result;
104 }
105 
106 
find_exe(char * exename,char * script)107 char *find_exe(char *exename, char *script) {
108     char drive[_MAX_DRIVE], dir[_MAX_DIR], fname[_MAX_FNAME], ext[_MAX_EXT];
109     char path[_MAX_PATH], c, *result;
110 
111     /* convert slashes to backslashes for uniform search below */
112     result = exename;
113     while (c = *result++) if (c=='/') result[-1] = '\\';
114 
115     _splitpath(exename, drive, dir, fname, ext);
116     if (drive[0] || dir[0]=='\\') {
117         return loadable_exe(exename);   /* absolute path, use directly */
118     }
119     /* Use the script's parent directory, which should be the Python home
120        (This should only be used for bdist_wininst-installed scripts, because
121         easy_install-ed scripts use the absolute path to python[w].exe
122     */
123     _splitpath(script, drive, dir, fname, ext);
124     result = dir + strlen(dir) -1;
125     if (*result == '\\') result--;
126     while (*result != '\\' && result>=dir) *result-- = 0;
127     _makepath(path, drive, dir, exename, NULL);
128     return loadable_exe(path);
129 }
130 
131 
parse_argv(char * cmdline,int * argc)132 char **parse_argv(char *cmdline, int *argc)
133 {
134     /* Parse a command line in-place using MS C rules */
135 
136     char **result = calloc(strlen(cmdline), sizeof(char *));
137     char *output = cmdline;
138     char c;
139     int nb = 0;
140     int iq = 0;
141     *argc = 0;
142 
143     result[0] = output;
144     while (isspace(*cmdline)) cmdline++;   /* skip leading spaces */
145 
146     do {
147         c = *cmdline++;
148         if (!c || (isspace(c) && !iq)) {
149             while (nb) {*output++ = '\\'; nb--; }
150             *output++ = 0;
151             result[++*argc] = output;
152             if (!c) return result;
153             while (isspace(*cmdline)) cmdline++;  /* skip leading spaces */
154             if (!*cmdline) return result;  /* avoid empty arg if trailing ws */
155             continue;
156         }
157         if (c == '\\')
158             ++nb;   /* count \'s */
159         else {
160             if (c == '"') {
161                 if (!(nb & 1)) { iq = !iq; c = 0; }  /* skip " unless odd # of \ */
162                 nb = nb >> 1;   /* cut \'s in half */
163             }
164             while (nb) {*output++ = '\\'; nb--; }
165             if (c) *output++ = c;
166         }
167     } while (1);
168 }
169 
pass_control_to_child(DWORD control_type)170 void pass_control_to_child(DWORD control_type) {
171     /*
172      * distribute-issue207
173      * passes the control event to child process (Python)
174      */
175     if (!child_pid) {
176         return;
177     }
178     GenerateConsoleCtrlEvent(child_pid,0);
179 }
180 
control_handler(DWORD control_type)181 BOOL control_handler(DWORD control_type) {
182     /*
183      * distribute-issue207
184      * control event handler callback function
185      */
186     switch (control_type) {
187         case CTRL_C_EVENT:
188             pass_control_to_child(0);
189             break;
190     }
191     return TRUE;
192 }
193 
create_and_wait_for_subprocess(char * command)194 int create_and_wait_for_subprocess(char* command) {
195     /*
196      * distribute-issue207
197      * launches child process (Python)
198      */
199     DWORD return_value = 0;
200     LPSTR commandline = command;
201     STARTUPINFOA s_info;
202     PROCESS_INFORMATION p_info;
203     ZeroMemory(&p_info, sizeof(p_info));
204     ZeroMemory(&s_info, sizeof(s_info));
205     s_info.cb = sizeof(STARTUPINFO);
206     // set-up control handler callback funciotn
207     SetConsoleCtrlHandler((PHANDLER_ROUTINE) control_handler, TRUE);
208     if (!CreateProcessA(NULL, commandline, NULL, NULL, TRUE, 0, NULL, NULL, &s_info, &p_info)) {
209         fprintf(stderr, "failed to create process.\n");
210         return 0;
211     }
212     child_pid = p_info.dwProcessId;
213     // wait for Python to exit
214     WaitForSingleObject(p_info.hProcess, INFINITE);
215     if (!GetExitCodeProcess(p_info.hProcess, &return_value)) {
216         fprintf(stderr, "failed to get exit code from process.\n");
217         return 0;
218     }
219     return return_value;
220 }
221 
join_executable_and_args(char * executable,char ** args,int argc)222 char* join_executable_and_args(char *executable, char **args, int argc)
223 {
224     /*
225      * distribute-issue207
226      * CreateProcess needs a long string of the executable and command-line arguments,
227      * so we need to convert it from the args that was built
228      */
229     int len,counter;
230     char* cmdline;
231 
232     len=strlen(executable)+2;
233     for (counter=1; counter<argc; counter++) {
234         len+=strlen(args[counter])+1;
235     }
236 
237     cmdline = (char*)calloc(len, sizeof(char));
238     sprintf(cmdline, "%s", executable);
239     len=strlen(executable);
240     for (counter=1; counter<argc; counter++) {
241         sprintf(cmdline+len, " %s", args[counter]);
242         len+=strlen(args[counter])+1;
243     }
244     return cmdline;
245 }
246 
run(int argc,char ** argv,int is_gui)247 int run(int argc, char **argv, int is_gui) {
248 
249     char python[256];   /* python executable's filename*/
250     char *pyopt;        /* Python option */
251     char script[256];   /* the script's filename */
252 
253     int scriptf;        /* file descriptor for script file */
254 
255     char **newargs, **newargsp, **parsedargs; /* argument array for exec */
256     char *ptr, *end;    /* working pointers for string manipulation */
257     char *cmdline;
258     int i, parsedargc;              /* loop counter */
259 
260     /* compute script name from our .exe name*/
261     GetModuleFileNameA(NULL, script, sizeof(script));
262     end = script + strlen(script);
263     while( end>script && *end != '.')
264         *end-- = '\0';
265     *end-- = '\0';
266     strcat(script, (GUI ? "-script.pyw" : "-script.py"));
267 
268     /* figure out the target python executable */
269 
270     scriptf = open(script, O_RDONLY);
271     if (scriptf == -1) {
272         return fail("Cannot open %s\n", script);
273     }
274     end = python + read(scriptf, python, sizeof(python));
275     close(scriptf);
276 
277     ptr = python-1;
278     while(++ptr < end && *ptr && *ptr!='\n' && *ptr!='\r') {;}
279 
280     *ptr-- = '\0';
281 
282     if (strncmp(python, "#!", 2)) {
283         /* default to python.exe if no #! header */
284         strcpy(python, "#!python.exe");
285     }
286 
287     parsedargs = parse_argv(python+2, &parsedargc);
288 
289     /* Using spawnv() can fail strangely if you e.g. find the Cygwin
290        Python, so we'll make sure Windows can find and load it */
291 
292     ptr = find_exe(parsedargs[0], script);
293     if (!ptr) {
294         return fail("Cannot find Python executable %s\n", parsedargs[0]);
295     }
296 
297     /* printf("Python executable: %s\n", ptr); */
298 
299     /* Argument array needs to be
300        parsedargc + argc, plus 1 for null sentinel */
301 
302     newargs = (char **)calloc(parsedargc + argc + 1, sizeof(char *));
303     newargsp = newargs;
304 
305     *newargsp++ = quoted(ptr);
306     for (i = 1; i<parsedargc; i++) *newargsp++ = quoted(parsedargs[i]);
307 
308     *newargsp++ = quoted(script);
309     for (i = 1; i < argc; i++)     *newargsp++ = quoted(argv[i]);
310 
311     *newargsp++ = NULL;
312 
313     /* printf("args 0: %s\nargs 1: %s\n", newargs[0], newargs[1]); */
314 
315     if (is_gui) {
316         /* Use exec, we don't need to wait for the GUI to finish */
317         execv(ptr, (const char * const *)(newargs));
318         return fail("Could not exec %s", ptr);   /* shouldn't get here! */
319     }
320 
321     /*
322      * distribute-issue207: using CreateProcessA instead of spawnv
323      */
324     cmdline = join_executable_and_args(ptr, newargs, parsedargc + argc);
325     return create_and_wait_for_subprocess(cmdline);
326 }
327 
WinMain(HINSTANCE hI,HINSTANCE hP,LPSTR lpCmd,int nShow)328 int WINAPI WinMain(HINSTANCE hI, HINSTANCE hP, LPSTR lpCmd, int nShow) {
329     return run(__argc, __argv, GUI);
330 }
331 
main(int argc,char ** argv)332 int main(int argc, char** argv) {
333     return run(argc, argv, GUI);
334 }
335 
336