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