1 /*
2  * ****************************************************************************
3  * Copyright (c) 2013-2019, PyInstaller Development Team.
4  * Distributed under the terms of the GNU General Public License with exception
5  * for distributing bootloader.
6  *
7  * The full license is in the file COPYING.txt, distributed with this software.
8  * ****************************************************************************
9  */
10 
11 /*
12  * Portable wrapper for some utility functions like getenv/setenv,
13  * file path manipulation and other shared data types or functions.
14  */
15 
16 /* TODO: use safe string functions */
17 #define _CRT_SECURE_NO_WARNINGS 1
18 
19 #ifdef _WIN32
20     #include <windows.h>
21     #include <direct.h>  /* _mkdir, _rmdir */
22     #include <io.h>      /* _finddata_t */
23     #include <process.h> /* getpid */
24     #include <signal.h>  /* signal */
25 #else
26     #include <dirent.h>
27 /*
28  * On AIX  RTLD_MEMBER  flag is only visible when _ALL_SOURCE flag is defined.
29  *
30  * There are quite a few issues with xlC compiler. GCC is much better,
31  * Without flag _ALL_SOURCE gcc get stuck on the RTLD_MEMBER flax when
32  * compiling the bootloader.
33  * This fix was tested wigh gcc on AIX6.1.
34  */
35     #if defined(AIX) && !defined(_ALL_SOURCE)
36         #define _ALL_SOURCE
37         #include <dlfcn.h>
38         #undef  _ALL_SOURCE
39     #else
40         #include <dlfcn.h>
41     #endif
42     #include <limits.h>  /* PATH_MAX */
43     #include <signal.h>  /* kill, */
44     #include <sys/wait.h>
45     #include <unistd.h>  /* rmdir, unlink, mkdtemp */
46 #endif /* ifdef _WIN32 */
47 #ifndef SIGCLD
48 #define SIGCLD SIGCHLD /* not defined on OS X */
49 #endif
50 #ifndef sighandler_t
51 typedef void (*sighandler_t)(int);
52 #endif
53 #include <errno.h>
54 #include <stddef.h> /* ptrdiff_t */
55 #include <stdio.h>  /* FILE */
56 #include <stdlib.h>
57 #include <string.h>
58 #include <sys/stat.h> /* struct stat */
59 #include <wchar.h>    /* wchar_t */
60 #if defined(__APPLE__) && defined(WINDOWED)
61     #include <Carbon/Carbon.h>  /* AppleEventsT */
62 #endif
63 
64 /*
65  * Function 'mkdtemp' (make temporary directory) is missing on some *nix platforms:
66  * - On Solaris function 'mkdtemp' is missing.
67  * - On AIX 5.2 function 'mkdtemp' is missing. It is there in version 6.1 but we don't know
68  *   the runtime platform at compile time, so we always include our own implementation on AIX.
69  */
70 #if defined(SUNOS) || defined(AIX) || defined(HPUX)
71     #if !defined(HAVE_MKDTEMP)
72     #include "mkdtemp.h"
73     #endif
74 #endif
75 
76 /* PyInstaller headers. */
77 #include "pyi_global.h"
78 #include "pyi_path.h"
79 #include "pyi_archive.h"
80 #include "pyi_utils.h"
81 #include "pyi_win32_utils.h"
82 
83 /*
84  *  global variables that are used to copy argc/argv, so that PyIstaller can manipulate them
85  *  if need be.  One case in which the incoming argc/argv is manipulated is in the case of
86  *  Apple/Windowed, where we watch for AppleEvents in order to add files to the command line.
87  *  (this is argv_emulation).  These variables must be of file global scope to be able to
88  *  be accessed inside of the AppleEvents handlers.
89  */
90 static char **argv_pyi = NULL;
91 static int argc_pyi = 0;
92 
93 /*
94  * Watch for OpenDocument AppleEvents and add the files passed in to the
95  * sys.argv command line on the Python side.
96  *
97  * This allows on Mac OS X to open files when a file is dragged and dropped
98  * on the App icon in the OS X dock.
99  */
100 #if defined(__APPLE__) && defined(WINDOWED)
101 static void process_apple_events();
102 #endif
103 
104 char *
pyi_strjoin(const char * first,const char * sep,const char * second)105 pyi_strjoin(const char *first, const char *sep, const char *second){
106     /* join first and second string, using sep as separator.
107      * any of them may be either a null-terminated string or NULL.
108      * sep will be only used if first and second string are not empty.
109      * returns a null-terminated string which the caller is responsible
110      * for freeing. Returns NULL if memory could not be allocated.
111      */
112     int first_len, sep_len, second_len;
113     char *result;
114     first_len = first ? strlen(first) : 0;
115     sep_len = sep ? strlen(sep) : 0;
116     second_len = second ? strlen(second) : 0;
117     result = malloc(first_len + sep_len + second_len + 1);
118     if (!result) {
119         return NULL;
120     }
121     *result = '\0';
122     if (first_len) {
123         strcat(result, first);
124     }
125     if (sep_len && first_len && second_len) {
126         strcat(result, sep);
127     }
128     if (second_len) {
129         strcat(result, second);
130     }
131     return result;
132 }
133 
134 /* Return string copy of environment variable. */
135 char *
pyi_getenv(const char * variable)136 pyi_getenv(const char *variable)
137 {
138     char *env = NULL;
139 
140 #ifdef _WIN32
141     wchar_t * wenv = NULL;
142     wchar_t * wvar = NULL;
143     wchar_t buf1[PATH_MAX], buf2[PATH_MAX];
144     DWORD rc;
145 
146     wvar = pyi_win32_utils_from_utf8(NULL, variable, 0);
147     rc = GetEnvironmentVariableW(wvar, buf1, sizeof(buf1));
148 
149     if (rc > 0) {
150         wenv = buf1;
151         /* Expand environment variables like %VAR% in value. */
152         rc = ExpandEnvironmentStringsW(wenv, buf2, sizeof(buf2));
153 
154         if (rc > 0) {
155             wenv = buf1;
156         }
157     }
158 
159     if (wenv) {
160         env = pyi_win32_utils_to_utf8(NULL, wenv, 0);
161     }
162 #else /*
163        * ifdef _WIN32
164        * Standard POSIX function.
165        */
166     env = getenv(variable);
167 #endif /* ifdef _WIN32 */
168 
169     /* If the Python program we are about to run invokes another PyInstaller
170      * one-file program as subprocess, this subprocess must not be fooled into
171      * thinking that it is already unpacked. Therefore, PyInstaller deletes
172      * the _MEIPASS2 variable from the environment in pyi_main().
173      *
174      * However, on some platforms (e.g. AIX) the Python function 'os.unsetenv()'
175      * does not always exist. In these cases we cannot delete the _MEIPASS2
176      * environment variable from Python but only set it to the empty string.
177      * The code below takes into account that a variable may exist while its
178      * value is only the empty string.
179      *
180      * Return copy of string to avoid modification of the process environment.
181      */
182     return (env && env[0]) ? strdup(env) : NULL;
183 }
184 
185 /* Set environment variable. */
186 int
pyi_setenv(const char * variable,const char * value)187 pyi_setenv(const char *variable, const char *value)
188 {
189     int rc;
190 
191 #ifdef _WIN32
192     wchar_t * wvar, *wval;
193 
194     wvar = pyi_win32_utils_from_utf8(NULL, variable, 0);
195     wval = pyi_win32_utils_from_utf8(NULL, value, 0);
196 
197     // Not sure why, but SetEnvironmentVariableW() didn't work with _wtempnam()
198     // Replaced it with _wputenv_s()
199     rc = _wputenv_s(wvar, wval);
200 
201     free(wvar);
202     free(wval);
203 #else
204     rc = setenv(variable, value, true);
205 #endif
206     return rc;
207 }
208 
209 /* Unset environment variable. */
210 int
pyi_unsetenv(const char * variable)211 pyi_unsetenv(const char *variable)
212 {
213     int rc;
214 
215 #ifdef _WIN32
216     wchar_t * wvar;
217     wvar = pyi_win32_utils_from_utf8(NULL, variable, 0);
218     rc = SetEnvironmentVariableW(wvar, NULL);
219     free(wvar);
220 #else  /* _WIN32 */
221     #if HAVE_UNSETENV
222     rc = unsetenv(variable);
223     #else /* HAVE_UNSETENV */
224     rc = setenv(variable, "", true);
225     #endif /* HAVE_UNSETENV */
226 #endif     /* _WIN32 */
227     return rc;
228 }
229 
230 #ifdef _WIN32
231 
232 /* TODO rename fuction and revisit */
233 int
pyi_get_temp_path(char * buffer,char * runtime_tmpdir)234 pyi_get_temp_path(char *buffer, char *runtime_tmpdir)
235 {
236     int i;
237     wchar_t *wchar_ret;
238     wchar_t prefix[16];
239     wchar_t wchar_buffer[PATH_MAX];
240     char *original_tmpdir;
241     char runtime_tmpdir_abspath[PATH_MAX + 1];
242 
243     if (runtime_tmpdir != NULL) {
244       /*
245        * Get original TMP environment variable so it can be restored
246        * after this is done.
247        */
248       original_tmpdir = pyi_getenv("TMP");
249       /*
250        * Set TMP to runtime_tmpdir for _wtempnam() later
251        */
252       pyi_path_fullpath(runtime_tmpdir_abspath, PATH_MAX, runtime_tmpdir);
253       pyi_setenv("TMP", runtime_tmpdir_abspath);
254     }
255 
256     GetTempPathW(PATH_MAX, wchar_buffer);
257 
258     swprintf(prefix, 16, L"_MEI%d", getpid());
259 
260     /*
261      * Windows does not have a race-free function to create a temporary
262      * directory. Thus, we rely on _tempnam, and simply try several times
263      * to avoid stupid race conditions.
264      */
265     for (i = 0; i < 5; i++) {
266         /* TODO use race-free fuction - if any exists? */
267         wchar_ret = _wtempnam(wchar_buffer, prefix);
268 
269         if (_wmkdir(wchar_ret) == 0) {
270             pyi_win32_utils_to_utf8(buffer, wchar_ret, PATH_MAX);
271             free(wchar_ret);
272             if (runtime_tmpdir != NULL) {
273               /*
274                * Restore TMP to what it was
275                */
276               if (original_tmpdir != NULL) {
277                 pyi_setenv("TMP", original_tmpdir);
278                 free(original_tmpdir);
279               } else {
280                 pyi_unsetenv("TMP");
281               }
282             }
283             return 1;
284         }
285         free(wchar_ret);
286     }
287     if (runtime_tmpdir != NULL) {
288       /*
289        * Restore TMP to what it was
290        */
291       if (original_tmpdir != NULL) {
292         pyi_setenv("TMP", original_tmpdir);
293         free(original_tmpdir);
294       } else {
295         pyi_unsetenv("TMP");
296       }
297     }
298     return 0;
299 }
300 
301 #else /* ifdef _WIN32 */
302 
303 /* TODO Is this really necessary to test for temp path? Why not just use mkdtemp()? */
304 int
pyi_test_temp_path(char * buff)305 pyi_test_temp_path(char *buff)
306 {
307     /*
308      * If path does not end with directory separator - append it there.
309      * On OSX the value from $TMPDIR ends with '/'.
310      */
311     if (buff[strlen(buff) - 1] != PYI_SEP) {
312         strcat(buff, PYI_SEPSTR);
313     }
314     strcat(buff, "_MEIXXXXXX");
315 
316     if (mkdtemp(buff)) {
317         return 1;
318     }
319     return 0;
320 }
321 
322 /* TODO merge this function with windows version. */
323 static int
pyi_get_temp_path(char * buff,char * runtime_tmpdir)324 pyi_get_temp_path(char *buff, char *runtime_tmpdir)
325 {
326     if (runtime_tmpdir != NULL) {
327       strcpy(buff, runtime_tmpdir);
328       if (pyi_test_temp_path(buff))
329         return 1;
330     } else {
331       /* On OSX the variable TMPDIR is usually defined. */
332       static const char *envname[] = {
333           "TMPDIR", "TEMP", "TMP", 0
334       };
335       static const char *dirname[] = {
336           "/tmp", "/var/tmp", "/usr/tmp", 0
337       };
338       int i;
339       char *p;
340 
341       for (i = 0; envname[i]; i++) {
342           p = pyi_getenv(envname[i]);
343 
344           if (p) {
345               strcpy(buff, p);
346 
347               if (pyi_test_temp_path(buff)) {
348                   return 1;
349               }
350           }
351       }
352 
353       for (i = 0; dirname[i]; i++) {
354           strcpy(buff, dirname[i]);
355 
356           if (pyi_test_temp_path(buff)) {
357               return 1;
358           }
359       }
360     }
361     return 0;
362 }
363 
364 #endif /* ifdef _WIN32 */
365 
366 /*
367  * Creates a temporany directory if it doesn't exists
368  * and properly sets the ARCHIVE_STATUS members.
369  */
370 int
pyi_create_temp_path(ARCHIVE_STATUS * status)371 pyi_create_temp_path(ARCHIVE_STATUS *status)
372 {
373     char *runtime_tmpdir = NULL;
374 
375     if (status->has_temp_directory != true) {
376         runtime_tmpdir = pyi_arch_get_option(status, "pyi-runtime-tmpdir");
377         if(runtime_tmpdir != NULL) {
378           VS("LOADER: Found runtime-tmpdir %s\n", runtime_tmpdir);
379         }
380 
381         if (!pyi_get_temp_path(status->temppath, runtime_tmpdir)) {
382             FATALERROR("INTERNAL ERROR: cannot create temporary directory!\n");
383             return -1;
384         }
385         /* Set flag that temp directory is created and available. */
386         status->has_temp_directory = true;
387     }
388     return 0;
389 }
390 
391 /* TODO merge unix/win versions of remove_one() and pyi_remove_temp_path() */
392 #ifdef _WIN32
393 static void
remove_one(wchar_t * wfnm,size_t pos,struct _wfinddata_t wfinfo)394 remove_one(wchar_t *wfnm, size_t pos, struct _wfinddata_t wfinfo)
395 {
396     char fnm[PATH_MAX + 1];
397 
398     if (wcscmp(wfinfo.name, L".") == 0  || wcscmp(wfinfo.name, L"..") == 0) {
399         return;
400     }
401     wfnm[pos] = PYI_NULLCHAR;
402     wcscat(wfnm, wfinfo.name);
403 
404     if (wfinfo.attrib & _A_SUBDIR) {
405         /* Use recursion to remove subdirectories. */
406         pyi_win32_utils_to_utf8(fnm, wfnm, PATH_MAX);
407         pyi_remove_temp_path(fnm);
408     }
409     else if (_wremove(wfnm)) {
410         /* HACK: Possible concurrency issue... spin a little while */
411         Sleep(100);
412         _wremove(wfnm);
413     }
414 }
415 
416 /* TODO Find easier and more portable implementation of removing directory recursively. */
417 /*     e.g. */
418 void
pyi_remove_temp_path(const char * dir)419 pyi_remove_temp_path(const char *dir)
420 {
421     wchar_t wfnm[PATH_MAX + 1];
422     wchar_t wdir[PATH_MAX + 1];
423     struct _wfinddata_t wfinfo;
424     intptr_t h;
425     size_t dirnmlen;
426 
427     pyi_win32_utils_from_utf8(wdir, dir, PATH_MAX);
428     wcscpy(wfnm, wdir);
429     dirnmlen = wcslen(wfnm);
430 
431     if (wfnm[dirnmlen - 1] != L'/' && wfnm[dirnmlen - 1] != L'\\') {
432         wcscat(wfnm, L"\\");
433         dirnmlen++;
434     }
435     wcscat(wfnm, L"*");
436     h = _wfindfirst(wfnm, &wfinfo);
437 
438     if (h != -1) {
439         remove_one(wfnm, dirnmlen, wfinfo);
440 
441         while (_wfindnext(h, &wfinfo) == 0) {
442             remove_one(wfnm, dirnmlen, wfinfo);
443         }
444         _findclose(h);
445     }
446     _wrmdir(wdir);
447 }
448 #else /* ifdef _WIN32 */
449 static void
remove_one(char * pnm,int pos,const char * fnm)450 remove_one(char *pnm, int pos, const char *fnm)
451 {
452     struct stat sbuf;
453 
454     if (strcmp(fnm, ".") == 0  || strcmp(fnm, "..") == 0) {
455         return;
456     }
457     pnm[pos] = PYI_NULLCHAR;
458     strcat(pnm, fnm);
459 
460     if (stat(pnm, &sbuf) == 0) {
461         if (S_ISDIR(sbuf.st_mode) ) {
462             /* Use recursion to remove subdirectories. */
463             pyi_remove_temp_path(pnm);
464         }
465         else {
466             unlink(pnm);
467         }
468     }
469 }
470 
471 void
pyi_remove_temp_path(const char * dir)472 pyi_remove_temp_path(const char *dir)
473 {
474     char fnm[PATH_MAX + 1];
475     DIR *ds;
476     struct dirent *finfo;
477     int dirnmlen;
478 
479     /* Leave 1 char for PY_SEP if needed */
480     strncpy(fnm, dir, PATH_MAX);
481     dirnmlen = strlen(fnm);
482 
483     if (fnm[dirnmlen - 1] != PYI_SEP) {
484         strcat(fnm, PYI_SEPSTR);
485         dirnmlen++;
486     }
487     ds = opendir(dir);
488     finfo = readdir(ds);
489 
490     while (finfo) {
491         remove_one(fnm, dirnmlen, finfo->d_name);
492         finfo = readdir(ds);
493     }
494     closedir(ds);
495     rmdir(dir);
496 }
497 #endif /* ifdef _WIN32 */
498 
499 /* TODO is this function still used? Could it be removed? */
500 /*
501  * If binaries were extracted, this should be called
502  * to remove them
503  */
504 void
cleanUp(ARCHIVE_STATUS * status)505 cleanUp(ARCHIVE_STATUS *status)
506 {
507     if (status->temppath[0]) {
508         pyi_remove_temp_path(status->temppath);
509     }
510 }
511 
512 /*
513  * helper for extract2fs
514  * which may try multiple places
515  */
516 /* TODO find better name for function. */
517 FILE *
pyi_open_target(const char * path,const char * name_)518 pyi_open_target(const char *path, const char* name_)
519 {
520 
521 #ifdef _WIN32
522     wchar_t wchar_buffer[PATH_MAX];
523     struct _stat sbuf;
524 #else
525     struct stat sbuf;
526 #endif
527     char fnm[PATH_MAX];
528     char name[PATH_MAX];
529     char *dir;
530     size_t len;
531 
532     strncpy(fnm, path, PATH_MAX);
533     strncpy(name, name_, PATH_MAX);
534 
535     /* Check if the path names could be copied */
536     if (fnm[PATH_MAX-1] != '\0' || name[PATH_MAX-1] != '\0') {
537         return NULL;
538     }
539 
540     len = strlen(fnm);
541     dir = strtok(name, PYI_SEPSTR);
542 
543     while (dir != NULL) {
544         len += strlen(dir) + strlen(PYI_SEPSTR);
545         /* Check if fnm does not exceed the buffer size */
546         if (len >= PATH_MAX-1) {
547             return NULL;
548         }
549         strcat(fnm, PYI_SEPSTR);
550         strcat(fnm, dir);
551         dir = strtok(NULL, PYI_SEPSTR);
552 
553         if (!dir) {
554             break;
555         }
556 
557 #ifdef _WIN32
558         pyi_win32_utils_from_utf8(wchar_buffer, fnm, PATH_MAX);
559 
560         if (_wstat(wchar_buffer, &sbuf) < 0) {
561             _wmkdir(wchar_buffer);
562         }
563 #else
564 
565         if (stat(fnm, &sbuf) < 0) {
566             mkdir(fnm, 0700);
567         }
568 #endif
569     }
570 
571 #ifdef _WIN32
572     pyi_win32_utils_from_utf8(wchar_buffer, fnm, PATH_MAX);
573 
574     if (_wstat(wchar_buffer, &sbuf) == 0) {
575         OTHERERROR("WARNING: file already exists but should not: %s\n", fnm);
576     }
577 #else
578 
579     if (stat(fnm, &sbuf) == 0) {
580         OTHERERROR("WARNING: file already exists but should not: %s\n", fnm);
581     }
582 #endif
583     /*
584      * pyi_path_fopen() wraps different fopen names. On Windows it uses
585      * wide-character version of fopen.
586      */
587     return pyi_path_fopen(fnm, "wb");
588 }
589 
590 /* Copy the file src to dst 4KB per time */
591 int
pyi_copy_file(const char * src,const char * dst,const char * filename)592 pyi_copy_file(const char *src, const char *dst, const char *filename)
593 {
594     FILE *in = pyi_path_fopen(src, "rb");
595     FILE *out = pyi_open_target(dst, filename);
596     char buf[4096];
597     int error = 0;
598 
599     if (in == NULL || out == NULL) {
600         if (in) {
601             fclose(in);
602         }
603         if (out) {
604             fclose(out);
605         }
606         return -1;
607     }
608 
609     while (!feof(in)) {
610         if (fread(buf, 4096, 1, in) == -1) {
611             if (ferror(in)) {
612                 clearerr(in);
613                 error = -1;
614                 break;
615             }
616         }
617         else {
618             int rc = fwrite(buf, 4096, 1, out);
619             if (rc <= 0 || ferror(out)) {
620                 clearerr(out);
621                 error = -1;
622                 break;
623             }
624         }
625     }
626 #ifndef WIN32
627     fchmod(fileno(out), S_IRUSR | S_IWUSR | S_IXUSR);
628 #endif
629     fclose(in);
630     fclose(out);
631 
632     return error;
633 }
634 
635 /* TODO use dlclose() when exiting. */
636 /* Load the shared dynamic library (DLL) */
637 dylib_t
pyi_utils_dlopen(const char * dllpath)638 pyi_utils_dlopen(const char *dllpath)
639 {
640 
641 #ifdef _WIN32
642     wchar_t * dllpath_w;
643     dylib_t ret;
644 #else
645     int dlopenMode = RTLD_NOW | RTLD_GLOBAL;
646 #endif
647 
648 #ifdef AIX
649     /* Append the RTLD_MEMBER to the open mode for 'dlopen()'
650      * in order to load shared object member from library.
651      */
652     dlopenMode |= RTLD_MEMBER;
653 #endif
654 
655 #ifdef _WIN32
656     dllpath_w = pyi_win32_utils_from_utf8(NULL, dllpath, 0);
657     ret = LoadLibraryExW(dllpath_w, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
658     free(dllpath_w);
659     return ret;
660 #else
661     return dlopen(dllpath, dlopenMode);
662 #endif
663 
664 }
665 
666 /* ////////////////////////////////////////////////////////////////// */
667 /* TODO better merging of the following platform specific functions. */
668 /* ////////////////////////////////////////////////////////////////// */
669 
670 #ifdef _WIN32
671 
672 int
pyi_utils_set_environment(const ARCHIVE_STATUS * status)673 pyi_utils_set_environment(const ARCHIVE_STATUS *status)
674 {
675     return 0;
676 }
677 
678 int
pyi_utils_create_child(const char * thisfile,const ARCHIVE_STATUS * status,const int argc,char * const argv[])679 pyi_utils_create_child(const char *thisfile, const ARCHIVE_STATUS* status,
680                        const int argc, char *const argv[])
681 {
682     SECURITY_ATTRIBUTES sa;
683     STARTUPINFOW si;
684     PROCESS_INFORMATION pi;
685     int rc = 0;
686     wchar_t buffer[PATH_MAX];
687 
688     /* TODO is there a replacement for this conversion or just use wchar_t everywhere? */
689     /* Convert file name to wchar_t from utf8. */
690     pyi_win32_utils_from_utf8(buffer, thisfile, PATH_MAX);
691 
692     /* the parent process should ignore all signals it can */
693     signal(SIGABRT, SIG_IGN);
694     signal(SIGINT, SIG_IGN);
695     signal(SIGTERM, SIG_IGN);
696     signal(SIGBREAK, SIG_IGN);
697 
698     VS("LOADER: Setting up to run child\n");
699     sa.nLength = sizeof(sa);
700     sa.lpSecurityDescriptor = NULL;
701     sa.bInheritHandle = TRUE;
702     GetStartupInfoW(&si);
703     si.lpReserved = NULL;
704     si.lpDesktop = NULL;
705     si.lpTitle = NULL;
706     si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
707     si.wShowWindow = SW_NORMAL;
708     si.hStdInput = (void*)_get_osfhandle(fileno(stdin));
709     si.hStdOutput = (void*)_get_osfhandle(fileno(stdout));
710     si.hStdError = (void*)_get_osfhandle(fileno(stderr));
711 
712     VS("LOADER: Creating child process\n");
713 
714     if (CreateProcessW(
715             buffer,            /* Pointer to name of executable module. */
716             GetCommandLineW(), /* pointer to command line string */
717             &sa,               /* pointer to process security attributes */
718             NULL,              /* pointer to thread security attributes */
719             TRUE,              /* handle inheritance flag */
720             0,                 /* creation flags */
721             NULL,              /* pointer to new environment block */
722             NULL,              /* pointer to current directory name */
723             &si,               /* pointer to STARTUPINFO */
724             &pi                /* pointer to PROCESS_INFORMATION */
725             )) {
726         VS("LOADER: Waiting for child process to finish...\n");
727         WaitForSingleObject(pi.hProcess, INFINITE);
728         GetExitCodeProcess(pi.hProcess, (unsigned long *)&rc);
729     }
730     else {
731         FATAL_WINERROR("CreateProcessW", "Error creating child process!\n");
732         rc = -1;
733     }
734     return rc;
735 }
736 
737 #else /* ifdef _WIN32 */
738 
739 static int
set_dynamic_library_path(const char * path)740 set_dynamic_library_path(const char* path)
741 {
742     int rc = 0;
743     char *env_var, *env_var_orig;
744     char *new_path, *orig_path;
745 
746     #ifdef AIX
747     /* LIBPATH is used to look up dynamic libraries on AIX. */
748     env_var = "LIBPATH";
749     env_var_orig = "LIBPATH_ORIG";
750     #else
751     /* LD_LIBRARY_PATH is used on other *nix platforms (except Darwin). */
752     env_var = "LD_LIBRARY_PATH";
753     env_var_orig = "LD_LIBRARY_PATH_ORIG";
754     #endif /* AIX */
755 
756     /* keep original value in a new env var so the application can restore it
757      * before forking subprocesses. This is important so that e.g. a forked
758      * (system installed) ssh can find the matching (system installed) ssh
759      * related libraries - not the potentially different versions of same libs
760      * that we have bundled.
761      */
762     orig_path = pyi_getenv(env_var);
763     if (orig_path) {
764         pyi_setenv(env_var_orig, orig_path);
765         VS("LOADER: %s=%s\n", env_var_orig, orig_path);
766     }
767     /* prepend our path to the original path, pyi_strjoin can deal with orig_path being NULL or empty string */
768     new_path = pyi_strjoin(path, ":", orig_path);
769     rc = pyi_setenv(env_var, new_path);
770     VS("LOADER: %s=%s\n", env_var, new_path);
771     free(new_path);
772     return rc;
773 }
774 
775 int
pyi_utils_set_environment(const ARCHIVE_STATUS * status)776 pyi_utils_set_environment(const ARCHIVE_STATUS *status)
777 {
778     int rc = 0;
779 
780     #ifdef __APPLE__
781     /* On Mac OS X we do not use environment variables DYLD_LIBRARY_PATH
782      * or others to tell OS where to look for dynamic libraries.
783      * There were some issues with this approach. In some cases some
784      * system libraries were trying to load incompatible libraries from
785      * the dist directory. For instance this was experienced with macprots
786      * and PyQt4 applications.
787      *
788      * To tell the OS where to look for dynamic libraries we modify
789      * .so/.dylib files to use relative paths to other dependend
790      * libraries starting with @executable_path.
791      *
792      * For more information see:
793      * http://blogs.oracle.com/dipol/entry/dynamic_libraries_rpath_and_mac
794      * http://developer.apple.com/library/mac/#documentation/DeveloperTools/  \
795      *     Conceptual/DynamicLibraries/100-Articles/DynamicLibraryUsageGuidelines.html
796      */
797     /* For environment variable details see 'man dyld'. */
798     pyi_unsetenv("DYLD_FRAMEWORK_PATH");
799     pyi_unsetenv("DYLD_FALLBACK_FRAMEWORK_PATH");
800     pyi_unsetenv("DYLD_VERSIONED_FRAMEWORK_PATH");
801     pyi_unsetenv("DYLD_LIBRARY_PATH");
802     pyi_unsetenv("DYLD_FALLBACK_LIBRARY_PATH");
803     pyi_unsetenv("DYLD_VERSIONED_LIBRARY_PATH");
804     pyi_unsetenv("DYLD_ROOT_PATH");
805 
806     #else
807 
808     /* Set library path to temppath. This is only for onefile mode.*/
809     if (status->temppath[0] != PYI_NULLCHAR) {
810         rc = set_dynamic_library_path(status->temppath);
811     }
812     /* Set library path to homepath. This is for default onedir mode.*/
813     else {
814         rc = set_dynamic_library_path(status->homepath);
815     }
816     #endif /* ifdef __APPLE__ */
817 
818     return rc;
819 }
820 
821 /*
822  * If the program is actived by a systemd socket, systemd will set
823  * LISTEN_PID, LISTEN_FDS environment variable for that process.
824  *
825  * LISTEN_PID is set to the pid of the parent process of bootloader,
826  * which is forked by systemd.
827  *
828  * Bootloader will duplicate LISTEN_FDS to child process, but the
829  * LISTEN_PID environment variable remains unchanged.
830  *
831  * Here we change the LISTEN_PID to the child pid in child process.
832  * So the application can detecte it and use the LISTEN_FDS created
833  * by systemd.
834  */
835 int
set_systemd_env()836 set_systemd_env()
837 {
838     const char * env_var = "LISTEN_PID";
839     if(pyi_getenv(env_var) != NULL) {
840         /* the ULONG_STRING_SIZE is roughly equal to log10(max number)
841          * but can be calculated in compile time.
842          * The idea is from an answer on stackoverflow,
843          * https://stackoverflow.com/questions/8257714/
844          */
845         #define ULONG_STRING_SIZE (sizeof (unsigned long) * CHAR_BIT / 3 + 2)
846         char pid_str[ULONG_STRING_SIZE];
847         snprintf(pid_str, ULONG_STRING_SIZE, "%ld", (unsigned long)getpid());
848         return pyi_setenv(env_var, pid_str);
849     }
850     return 0;
851 }
852 
853 /* Remember child process id. It allows sending a signal to child process.
854  * Frozen application always runs in a child process. Parent process is used
855  * to setup environment for child process and clean the environment when
856  * child exited.
857  */
858 pid_t child_pid = 0;
859 
860 static void
_ignoring_signal_handler(int signum)861 _ignoring_signal_handler(int signum)
862 {
863     VS("LOADER: Ignoring signal %d\n", signum);
864 }
865 
866 static void
_signal_handler(int signum)867 _signal_handler(int signum)
868 {
869     VS("LOADER: Forwarding signal %d to child pid %d\n", signum, child_pid);
870     kill(child_pid, signum);
871 }
872 
873 /* Start frozen application in a subprocess. The frozen application runs
874  * in a subprocess.
875  */
876 int
pyi_utils_create_child(const char * thisfile,const ARCHIVE_STATUS * status,const int argc,char * const argv[])877 pyi_utils_create_child(const char *thisfile, const ARCHIVE_STATUS* status,
878                        const int argc, char *const argv[])
879 {
880     pid_t pid = 0;
881     int rc = 0;
882     int i;
883 
884     /* cause nonzero return unless this is overwritten
885      * with a successful return code from wait() */
886     int wait_rc = -1;
887 
888     /* As indicated in signal(7), signal numbers range from 1-31 (standard)
889      * and 32-64 (Linux real-time). */
890     const size_t num_signals = 65;
891 
892     sighandler_t handler;
893     int ignore_signals;
894     int signum;
895 
896     argv_pyi = (char**)calloc(argc + 1, sizeof(char*));
897     argc_pyi = 0;
898 
899     for (i = 0; i < argc; i++) {
900     #if defined(__APPLE__) && defined(WINDOWED)
901 
902         /* if we are on a Mac, it passes a strange -psnxxx argument.  Filter it out. */
903         if (strstr(argv[i], "-psn") == argv[i]) {
904             /* skip */
905         }
906         else
907     #endif
908         {
909             argv_pyi[argc_pyi++] = strdup(argv[i]);
910         }
911     }
912 
913     #if defined(__APPLE__) && defined(WINDOWED)
914     process_apple_events();
915     #endif
916 
917     pid = fork();
918     if (pid < 0) {
919         VS("LOADER: failed to fork child process: %s\n", strerror(errno));
920         goto cleanup;
921     }
922 
923     /* Child code. */
924     if (pid == 0) {
925         /* Replace process by starting a new application. */
926         if (set_systemd_env() != 0) {
927             VS("WARNING: Application is started by systemd socket,"
928                "but we can't set proper LISTEN_PID on it.\n");
929         }
930         if (execvp(thisfile, argv_pyi) < 0) {
931             VS("Failed to exec: %s\n", strerror(errno));
932             goto cleanup;
933         }
934         /* NOTREACHED */
935     }
936 
937     /* From here to end-of-function is parent code (since the child exec'd).
938      * The exception is the `cleanup` block that frees argv_pyi; in the child,
939      * wait_rc is -1, so the child exit code checking is skipped. */
940 
941     child_pid = pid;
942     ignore_signals = (pyi_arch_get_option(status, "pyi-bootloader-ignore-signals") != NULL);
943     handler = ignore_signals ? &_ignoring_signal_handler : &_signal_handler;
944 
945     /* Redirect all signals received by parent to child process. */
946     if (ignore_signals) {
947         VS("LOADER: Ignoring all signals in parent\n");
948     } else {
949         VS("LOADER: Registering signal handlers\n");
950     }
951     for (signum = 0; signum < num_signals; ++signum) {
952         // don't mess with SIGCHLD/SIGCLD; it affects our ability
953         // to wait() for the child to exit
954         if (signum != SIGCHLD && signum != SIGCLD) {
955             signal(signum, handler);
956         }
957     }
958 
959     wait_rc = waitpid(child_pid, &rc, 0);
960     if (wait_rc < 0) {
961         VS("LOADER: failed to wait for child process: %s\n", strerror(errno));
962     }
963 
964     /* When child process exited, reset signal handlers to default values. */
965     VS("LOADER: Restoring signal handlers\n");
966     for (signum = 0; signum < num_signals; ++signum) {
967         signal(signum, SIG_DFL);
968     }
969 
970   cleanup:
971     VS("LOADER: freeing args\n");
972     for (i = 0; i < argc_pyi; i++) {
973         free(argv_pyi[i]);
974     }
975     free(argv_pyi);
976 
977     /* Either wait() failed, or we jumped to `cleanup` and
978      * didn't wait() at all. Either way, exit with error,
979      * because rc does not contain a valid process exit code. */
980     if (wait_rc < 0) {
981         VS("LOADER: exiting early\n");
982         return 1;
983     }
984 
985     if (WIFEXITED(rc)) {
986         VS("LOADER: returning child exit status %d\n", WEXITSTATUS(rc));
987         return WEXITSTATUS(rc);
988     }
989 
990     /* Process ended abnormally */
991     if (WIFSIGNALED(rc)) {
992         VS("LOADER: re-raising child signal %d\n", WTERMSIG(rc));
993         /* Mimick the signal the child received */
994         raise(WTERMSIG(rc));
995     }
996     return 1;
997 }
998 
999 /*
1000  * On Mac OS X this converts files from kAEOpenDocuments events into sys.argv.
1001  */
1002 #if defined(__APPLE__) && defined(WINDOWED)
1003 
1004 static int gQuit = false;
1005 
handle_open_doc_ae(const AppleEvent * theAppleEvent,AppleEvent * reply,SRefCon handlerRefcon)1006 static pascal OSErr handle_open_doc_ae(const AppleEvent *theAppleEvent, AppleEvent *reply, SRefCon handlerRefcon)
1007 {
1008    AEDescList docList;
1009    long index;
1010    long count = 0;
1011    int i;
1012    char *myFileName;
1013    Size actualSize;
1014    DescType returnedType;
1015    AEKeyword keywd;
1016    FSRef theRef;
1017 
1018    VS("LOADER [ARGV_EMU]: OpenDocument handler called.\n");
1019 
1020    OSErr err = AEGetParamDesc(theAppleEvent, keyDirectObject, typeAEList, &docList);
1021    if (err != noErr) return err;
1022 
1023    err = AECountItems(&docList, &count);
1024    if (err != noErr) return err;
1025 
1026    for (index = 1; index <= count; index++)
1027    {
1028      err = AEGetNthPtr(&docList, index, typeFSRef, &keywd, &returnedType, &theRef, sizeof(theRef), &actualSize);
1029 
1030      CFURLRef fullURLRef;
1031      fullURLRef = CFURLCreateFromFSRef(NULL, &theRef);
1032      CFStringRef cfString = CFURLCopyFileSystemPath(fullURLRef, kCFURLPOSIXPathStyle);
1033      CFRelease(fullURLRef);
1034      CFMutableStringRef cfMutableString = CFStringCreateMutableCopy(NULL, 0, cfString);
1035      CFRelease(cfString);
1036      CFStringNormalize(cfMutableString, kCFStringNormalizationFormC);
1037      int len = CFStringGetLength(cfMutableString);
1038      const int bufferSize = (len+1)*6;  // in theory up to six bytes per Unicode code point, for UTF-8.
1039      char* buffer = (char*)malloc(bufferSize);
1040      CFStringGetCString(cfMutableString, buffer, bufferSize, kCFStringEncodingUTF8);
1041 
1042      argv_pyi = (char**)realloc(argv_pyi,(argc_pyi+2)*sizeof(char*));
1043      argv_pyi[argc_pyi++] = strdup(buffer);
1044      argv_pyi[argc_pyi] = NULL;
1045 
1046      VS("LOADER [ARGV_EMU]: argv entry appended.");
1047 
1048      free(buffer);
1049    }
1050 
1051   err = AEDisposeDesc(&docList);
1052 
1053 
1054   return (err);
1055 }
1056 
1057 
process_apple_events()1058 static void process_apple_events()
1059 {
1060     OSStatus handler_install_status;
1061     OSStatus handler_remove_status;
1062     OSStatus rcv_status;
1063     OSStatus pcs_status;
1064     EventTypeSpec event_types[1];  /*  List of event types to handle. */
1065     AEEventHandlerUPP handler_open_doc;
1066     EventHandlerRef handler_ref; /* Reference for later removing the event handler. */
1067     EventRef event_ref;          /* Event that caused ReceiveNextEvent to return. */
1068     OSType ev_class;
1069     UInt32 ev_kind;
1070     EventTimeout timeout = 1.0;  /* number of seconds */
1071 
1072     VS("LOADER [ARGV_EMU]: AppleEvent - processing...\n");
1073 
1074     event_types[0].eventClass = kEventClassAppleEvent;
1075     event_types[0].eventKind = kEventAppleEvent;
1076 
1077     /* Carbon Event Manager requires us to convert the function pointer to type EventHandlerUPP. */
1078     /* https://developer.apple.com/legacy/library/documentation/Carbon/Conceptual/Carbon_Event_Manager/Tasks/CarbonEventsTasks.html */
1079     handler_open_doc = NewAEEventHandlerUPP(handle_open_doc_ae);
1080 
1081     handler_install_status = AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments, handler_open_doc, 0, false);
1082 
1083     if (handler_install_status == noErr) {
1084 
1085         VS("LOADER [ARGV_EMU]: AppleEvent - installed handler.\n");
1086 
1087         while(!gQuit) {
1088            VS("LOADER [ARGV_EMU]: AppleEvent - calling ReceiveNextEvent\n");
1089            rcv_status = ReceiveNextEvent(1, event_types, timeout, true, &event_ref);
1090 
1091            if (rcv_status == eventLoopTimedOutErr) {
1092               VS("LOADER [ARGV_EMU]: ReceiveNextEvent timed out\n");
1093               break;
1094            }
1095            else if (rcv_status != 0) {
1096               VS("LOADER [ARGV_EMU]: ReceiveNextEvent fetching events failed");
1097               break;
1098            }
1099            else
1100            {
1101               VS("LOADER [ARGV_EMU]: ReceiveNextEvent got an event");
1102 
1103               pcs_status = AEProcessEvent(event_ref);
1104               if (pcs_status != 0) {
1105                  VS("LOADER [ARGV_EMU]: processing events failed");
1106                  break;
1107               }
1108            }
1109         }
1110 
1111         VS("LOADER [ARGV_EMU]: Out of the event loop.");
1112 
1113         handler_remove_status = RemoveEventHandler(handler_ref);
1114 
1115     }
1116     else {
1117         VS("LOADER [ARGV_EMU]: AppleEvent - ERROR installing handler.\n");
1118     }
1119 
1120     /* Remove handler_ref reference when we are done with EventHandlerUPP. */
1121     /* Carbon Event Manager does not do this automatically. */
1122     DisposeEventHandlerUPP(handler_open_doc)
1123 }
1124 #endif /* if defined(__APPLE__) && defined(WINDOWED) */
1125 
1126 #endif  /* WIN32 */
1127