Panda3D
deploy-stub.c
1 /* Python interpreter main program for frozen scripts */
2 
3 #include "Python.h"
4 #ifdef _WIN32
5 # include "malloc.h"
6 # include <Shlobj.h>
7 #else
8 # include <sys/mman.h>
9 # include <pwd.h>
10 #endif
11 
12 #ifdef __FreeBSD__
13 # include <sys/sysctl.h>
14 #endif
15 
16 #ifdef __APPLE__
17 # include <mach-o/dyld.h>
18 # include <libgen.h>
19 #endif
20 
21 #include <stdio.h>
22 #include <stdint.h>
23 #include <fcntl.h>
24 
25 #if PY_MAJOR_VERSION >= 3
26 # include <locale.h>
27 
28 # if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION < 5
29 # define Py_DecodeLocale _Py_char2wchar
30 # endif
31 
32 # include "structmember.h"
33 #endif
34 
35 /* Leave room for future expansion. We only read pointer 0, but there are
36  other pointers that are being read by configPageManager.cxx. */
37 #define MAX_NUM_POINTERS 24
38 
39 /* Stored in the flags field of the blobinfo structure below. */
40 enum Flags {
41  F_log_append = 1,
42  F_log_filename_strftime = 2,
43 };
44 
45 /* Define an exposed symbol where we store the offset to the module data. */
46 #ifdef _MSC_VER
47 __declspec(dllexport)
48 #else
49 __attribute__((__visibility__("default"), used))
50 #endif
51 volatile struct {
52  uint64_t blob_offset;
53  uint64_t blob_size;
54  uint16_t version;
55  uint16_t num_pointers;
56  uint16_t codepage;
57  uint16_t flags;
58  uint64_t reserved;
59  void *pointers[MAX_NUM_POINTERS];
60 
61  // The reason we initialize it to -1 is because otherwise, smart linkers may
62  // end up putting it in the .bss section for zero-initialized data.
63 } blobinfo = {(uint64_t)-1};
64 
65 #ifdef MS_WINDOWS
66 # define WIN32_LEAN_AND_MEAN
67 # include <windows.h>
68 
69 extern void PyWinFreeze_ExeInit(void);
70 extern void PyWinFreeze_ExeTerm(void);
71 
72 static struct _inittab extensions[] = {
73  {0, 0},
74 };
75 
76 #if PY_MAJOR_VERSION >= 3
77 # define WIN_UNICODE
78 #endif
79 #endif
80 
81 #ifdef _WIN32
82 static wchar_t *log_pathw = NULL;
83 #endif
84 
85 #if defined(_WIN32) && PY_VERSION_HEX < 0x03060000
86 static int supports_code_page(UINT cp) {
87  if (cp == 0) {
88  cp = GetACP();
89  }
90 
91  /* Shortcut, because we know that these encodings are bundled by default--
92  * see FreezeTool.py and Python's encodings/aliases.py */
93  if (cp != 0 && cp != 1252 && cp != 367 && cp != 437 && cp != 850 && cp != 819) {
94  const struct _frozen *moddef;
95  char codec[100];
96 
97  /* Check if the codec was frozen into the program. We can't check this
98  * using _PyCodec_Lookup, since Python hasn't been initialized yet. */
99  PyOS_snprintf(codec, sizeof(codec), "encodings.cp%u", (unsigned int)cp);
100 
101  moddef = PyImport_FrozenModules;
102  while (moddef->name) {
103  if (strcmp(moddef->name, codec) == 0) {
104  return 1;
105  }
106  ++moddef;
107  }
108  return 0;
109  }
110 
111  return 1;
112 }
113 #endif
114 
115 /**
116  * Sets the main_dir field of the blobinfo structure, but only if it wasn't
117  * already set.
118  */
119 static void set_main_dir(char *main_dir) {
120  if (blobinfo.num_pointers >= 10) {
121  if (blobinfo.num_pointers == 10) {
122  ++blobinfo.num_pointers;
123  blobinfo.pointers[10] = NULL;
124  }
125  if (blobinfo.pointers[10] == NULL) {
126  blobinfo.pointers[10] = main_dir;
127  }
128  }
129 }
130 
131 /**
132  * Creates the parent directories of the given path. Returns 1 on success.
133  */
134 #ifdef _WIN32
135 static int mkdir_parent(const wchar_t *path) {
136  // Copy the path to a temporary buffer.
137  wchar_t buffer[4096];
138  size_t buflen = wcslen(path);
139  if (buflen + 1 >= _countof(buffer)) {
140  return 0;
141  }
142  wcscpy_s(buffer, _countof(buffer), path);
143 
144  // Seek back to find the last path separator.
145  while (buflen-- > 0) {
146  if (buffer[buflen] == '/' || buffer[buflen] == '\\') {
147  buffer[buflen] = 0;
148  break;
149  }
150  }
151  if (buflen == (size_t)-1 || buflen == 0) {
152  // There was no path separator, or this was the root directory.
153  return 0;
154  }
155 
156  if (CreateDirectoryW(buffer, NULL) != 0) {
157  // Success!
158  return 1;
159  }
160 
161  // Failed.
162  DWORD last_error = GetLastError();
163  if (last_error == ERROR_ALREADY_EXISTS) {
164  // Not really an error: the directory is already there.
165  return 1;
166  }
167 
168  if (last_error == ERROR_PATH_NOT_FOUND) {
169  // We need to make the parent directory first.
170  if (mkdir_parent(buffer)) {
171  // Parent successfully created. Try again to make the child.
172  if (CreateDirectoryW(buffer, NULL) != 0) {
173  // Got it!
174  return 1;
175  }
176  }
177  }
178  return 0;
179 }
180 #else
181 static int mkdir_parent(const char *path) {
182  // Copy the path to a temporary buffer.
183  char buffer[4096];
184  size_t buflen = strlen(path);
185  if (buflen + 1 >= sizeof(buffer)) {
186  return 0;
187  }
188  strcpy(buffer, path);
189 
190  // Seek back to find the last path separator.
191  while (buflen-- > 0) {
192  if (buffer[buflen] == '/') {
193  buffer[buflen] = 0;
194  break;
195  }
196  }
197  if (buflen == (size_t)-1 || buflen == 0) {
198  // There was no path separator, or this was the root directory.
199  return 0;
200  }
201  if (mkdir(buffer, 0755) == 0) {
202  // Success!
203  return 1;
204  }
205 
206  // Failed.
207  if (errno == EEXIST) {
208  // Not really an error: the directory is already there.
209  return 1;
210  }
211 
212  if (errno == ENOENT || errno == EACCES) {
213  // We need to make the parent directory first.
214  if (mkdir_parent(buffer)) {
215  // Parent successfully created. Try again to make the child.
216  if (mkdir(buffer, 0755) == 0) {
217  // Got it!
218  return 1;
219  }
220  }
221  }
222  return 0;
223 }
224 #endif
225 
226 /**
227  * Redirects the output streams to point to the log file with the given path.
228  *
229  * @param path specifies the location of log file, may start with ~
230  * @param append should be nonzero if it should not truncate the log file.
231  */
232 static int setup_logging(const char *path, int append) {
233 #ifdef _WIN32
234  // Does it start with a tilde? Perform tilde expansion if so.
235  wchar_t *pathw = (wchar_t *)malloc(sizeof(wchar_t) * MAX_PATH);
236  pathw[0] = 0;
237  size_t offset = 0;
238  if (path[0] == '~' && (path[1] == 0 || path[1] == '/' || path[1] == '\\')) {
239  // Strip off the tilde.
240  ++path;
241 
242  // Get the home directory path for the current user.
243  if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, 0, pathw))) {
244  free(pathw);
245  return 0;
246  }
247  offset = wcslen(pathw);
248  }
249 
250  // We need to convert the rest of the path from UTF-8 to UTF-16.
251  if (MultiByteToWideChar(CP_UTF8, 0, path, -1, pathw + offset,
252  (int)(MAX_PATH - offset)) == 0) {
253  free(pathw);
254  return 0;
255  }
256 
257  DWORD access = append ? FILE_APPEND_DATA : (GENERIC_READ | GENERIC_WRITE);
258  int creation = append ? OPEN_ALWAYS : CREATE_ALWAYS;
259  HANDLE handle = CreateFileW(pathw, access, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
260  NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL);
261 
262  if (handle == INVALID_HANDLE_VALUE) {
263  // Make the parent directories first.
264  mkdir_parent(pathw);
265  handle = CreateFileW(pathw, access, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
266  NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL);
267  }
268 
269  if (handle == INVALID_HANDLE_VALUE) {
270  free(pathw);
271  return 0;
272  }
273 
274  log_pathw = pathw;
275 
276  if (append) {
277  SetFilePointer(handle, 0, NULL, FILE_END);
278  }
279 
280  SetStdHandle(STD_OUTPUT_HANDLE, handle);
281  SetStdHandle(STD_ERROR_HANDLE, handle);
282 
283  // If we are running under the UCRT in a GUI application, we can't be sure
284  // that we have valid fds for stdout and stderr, so we have to set them up.
285  // One way to do this is to reopen them to something silly (like NUL).
286  if (_fileno(stdout) < 0) {
287  _close(1);
288  _wfreopen(L"\\\\.\\NUL", L"w", stdout);
289  }
290 
291  if (_fileno(stderr) < 0) {
292  _close(2);
293  _wfreopen(L"\\\\.\\NUL", L"w", stderr);
294  }
295 
296  // Now replace the stdout and stderr file descriptors with one pointing to
297  // our desired handle.
298  int fd = _open_osfhandle((intptr_t)handle, _O_WRONLY | _O_TEXT | _O_APPEND);
299  _dup2(fd, _fileno(stdout));
300  _dup2(fd, _fileno(stderr));
301  _close(fd);
302 
303  return 1;
304 #else
305  // Does it start with a tilde? Perform tilde expansion if so.
306  char buffer[PATH_MAX * 2];
307  size_t offset = 0;
308  if (path[0] == '~' && (path[1] == 0 || path[1] == '/')) {
309  // Strip off the tilde.
310  ++path;
311 
312  // Get the home directory path for the current user.
313  const char *home_dir = getenv("HOME");
314  if (home_dir == NULL) {
315  home_dir = getpwuid(getuid())->pw_dir;
316  }
317  offset = strlen(home_dir);
318  assert(offset < sizeof(buffer));
319  strncpy(buffer, home_dir, sizeof(buffer));
320  }
321 
322  // Copy over the rest of the path.
323  strcpy(buffer + offset, path);
324 
325  mode_t mode = O_CREAT | O_WRONLY | (append ? O_APPEND : O_TRUNC);
326  int fd = open(buffer, mode, 0644);
327  if (fd == -1) {
328  // Make the parent directories first.
329  mkdir_parent(buffer);
330  fd = open(buffer, mode, 0644);
331  }
332 
333  if (fd == -1) {
334  perror(buffer);
335  return 0;
336  }
337 
338  fflush(stdout);
339  fflush(stderr);
340 
341  dup2(fd, 1);
342  dup2(fd, 2);
343 
344  close(fd);
345  return 1;
346 #endif
347 }
348 
349 /**
350  * Sets the line_buffering property on a TextIOWrapper object.
351  */
352 #if PY_MAJOR_VERSION >= 3
353 static int enable_line_buffering(PyObject *file) {
354 #if PY_VERSION_HEX >= 0x03070000
355  /* Python 3.7 has a useful reconfigure() method. */
356  PyObject *kwargs = _PyDict_NewPresized(1);
357  PyDict_SetItemString(kwargs, "line_buffering", Py_True);
358  PyObject *args = PyTuple_New(0);
359 
360  PyObject *method = PyObject_GetAttrString(file, "reconfigure");
361  if (method != NULL) {
362  PyObject *result = PyObject_Call(method, args, kwargs);
363  Py_DECREF(method);
364  Py_DECREF(kwargs);
365  Py_DECREF(args);
366  if (result != NULL) {
367  Py_DECREF(result);
368  } else {
369  PyErr_Clear();
370  return 0;
371  }
372  } else {
373  Py_DECREF(kwargs);
374  Py_DECREF(args);
375  PyErr_Clear();
376  return 0;
377  }
378 #else
379  /* Older versions just don't expose a way to reconfigure(), but it's still
380  safe to override the property; we just have to use a hack to do it,
381  because it's officially marked "readonly". */
382 
383  PyTypeObject *type = Py_TYPE(file);
384  PyMemberDef *member = type->tp_members;
385 
386  while (member != NULL && member->name != NULL) {
387  if (strcmp(member->name, "line_buffering") == 0) {
388  *((char *)file + member->offset) = 1;
389  return 1;
390  }
391  ++member;
392  }
393  fflush(stdout);
394 #endif
395  return 1;
396 }
397 #endif
398 
399 /* Main program */
400 
401 #ifdef WIN_UNICODE
402 int Py_FrozenMain(int argc, wchar_t **argv)
403 #else
404 int Py_FrozenMain(int argc, char **argv)
405 #endif
406 {
407  char *p;
408  int n, sts = 1;
409  int unbuffered = 0;
410 #ifndef NDEBUG
411  int inspect = 0;
412 #endif
413 
414 #if PY_MAJOR_VERSION >= 3 && !defined(WIN_UNICODE)
415  int i;
416  char *oldloc;
417  wchar_t **argv_copy = NULL;
418  /* We need a second copies, as Python might modify the first one. */
419  wchar_t **argv_copy2 = NULL;
420 
421  if (argc > 0) {
422  argv_copy = (wchar_t **)alloca(sizeof(wchar_t *) * argc);
423  argv_copy2 = (wchar_t **)alloca(sizeof(wchar_t *) * argc);
424  }
425 #endif
426 
427 #if defined(MS_WINDOWS) && PY_VERSION_HEX >= 0x03040000 && PY_VERSION_HEX < 0x03060000
428  if (!supports_code_page(GetConsoleOutputCP()) ||
429  !supports_code_page(GetConsoleCP())) {
430  /* Revert to the active codepage, and tell Python to use the 'mbcs'
431  * encoding (which always uses the active codepage). In 99% of cases,
432  * this will be the same thing anyway. */
433  UINT acp = GetACP();
434  SetConsoleCP(acp);
435  SetConsoleOutputCP(acp);
436  Py_SetStandardStreamEncoding("mbcs", NULL);
437  }
438 #endif
439 
440  Py_FrozenFlag = 1; /* Suppress errors from getpath.c */
441  Py_NoSiteFlag = 0;
442  Py_NoUserSiteDirectory = 1;
443 
444 #ifndef NDEBUG
445  if ((p = Py_GETENV("PYTHONINSPECT")) && *p != '\0')
446  inspect = 1;
447 #endif
448  if ((p = Py_GETENV("PYTHONUNBUFFERED")) && *p != '\0')
449  unbuffered = 1;
450 
451  if (unbuffered) {
452  setbuf(stdin, (char *)NULL);
453  setbuf(stdout, (char *)NULL);
454  setbuf(stderr, (char *)NULL);
455  }
456 
457 #if PY_MAJOR_VERSION >= 3 && !defined(WIN_UNICODE)
458  oldloc = setlocale(LC_ALL, NULL);
459  setlocale(LC_ALL, "");
460  for (i = 0; i < argc; i++) {
461  argv_copy[i] = Py_DecodeLocale(argv[i], NULL);
462  argv_copy2[i] = argv_copy[i];
463  if (!argv_copy[i]) {
464  fprintf(stderr, "Unable to decode the command line argument #%i\n",
465  i + 1);
466  argc = i;
467  goto error;
468  }
469  }
470  setlocale(LC_ALL, oldloc);
471 #endif
472 
473 #ifdef MS_WINDOWS
474  PyImport_ExtendInittab(extensions);
475 #endif /* MS_WINDOWS */
476 
477  if (argc >= 1) {
478 #if PY_MAJOR_VERSION >= 3 && !defined(WIN_UNICODE)
479  Py_SetProgramName(argv_copy[0]);
480 #else
481  Py_SetProgramName(argv[0]);
482 #endif
483  }
484 
485  Py_Initialize();
486 #ifdef MS_WINDOWS
487  PyWinFreeze_ExeInit();
488 #endif
489 
490 #if defined(MS_WINDOWS) && PY_VERSION_HEX < 0x03040000
491  /* We can't rely on our overriding of the standard I/O to work on older
492  * versions of Python, since they are compiled with an incompatible CRT.
493  * The best solution I've found was to just replace sys.stdout/stderr with
494  * the log file reopened in append mode (which requires not locking it for
495  * write, and also passing in _O_APPEND above, and disabling buffering).
496  * It's not the most elegant solution, but it's better than crashing. */
497 #if PY_MAJOR_VERSION < 3
498  if (log_pathw != NULL) {
499  PyObject *uniobj = PyUnicode_FromWideChar(log_pathw, (Py_ssize_t)wcslen(log_pathw));
500  PyObject *file = PyObject_CallFunction((PyObject*)&PyFile_Type, "Nsi", uniobj, "a", 0);
501 
502  if (file != NULL) {
503  PyFile_SetEncodingAndErrors(file, "utf-8", NULL);
504 
505  PySys_SetObject("stdout", file);
506  PySys_SetObject("stderr", file);
507  PySys_SetObject("__stdout__", file);
508  PySys_SetObject("__stderr__", file);
509 
510  /* Be sure to disable buffering, otherwise we'll get overlap */
511  setbuf(stdout, (char *)NULL);
512  setbuf(stderr, (char *)NULL);
513  }
514  }
515  else
516 #endif
517  if (!supports_code_page(GetConsoleOutputCP()) ||
518  !supports_code_page(GetConsoleCP())) {
519  /* Same hack as before except for Python 2.7, which doesn't seem to have
520  * a way to set the encoding ahead of time, and setting PYTHONIOENCODING
521  * doesn't seem to work. Fortunately, Python 2.7 doesn't usually start
522  * causing codec errors until the first print statement. */
523  PyObject *sys_stream;
524  UINT acp = GetACP();
525  SetConsoleCP(acp);
526  SetConsoleOutputCP(acp);
527 
528  sys_stream = PySys_GetObject("stdin");
529  if (sys_stream && PyFile_Check(sys_stream)) {
530  PyFile_SetEncodingAndErrors(sys_stream, "mbcs", NULL);
531  }
532  sys_stream = PySys_GetObject("stdout");
533  if (sys_stream && PyFile_Check(sys_stream)) {
534  PyFile_SetEncodingAndErrors(sys_stream, "mbcs", NULL);
535  }
536  sys_stream = PySys_GetObject("stderr");
537  if (sys_stream && PyFile_Check(sys_stream)) {
538  PyFile_SetEncodingAndErrors(sys_stream, "mbcs", NULL);
539  }
540  }
541 #endif
542 
543 #if defined(MS_WINDOWS) && PY_VERSION_HEX >= 0x03040000
544  /* Ensure that line buffering is enabled on the output streams. */
545  if (!unbuffered) {
546  /* Python 3.7 has a useful reconfigure() method. */
547  PyObject *sys_stream;
548  sys_stream = PySys_GetObject("__stdout__");
549  if (sys_stream && !enable_line_buffering(sys_stream)) {
550  fprintf(stderr, "Failed to enable line buffering on sys.stdout\n");
551  fflush(stderr);
552  }
553  sys_stream = PySys_GetObject("__stderr__");
554  if (sys_stream && !enable_line_buffering(sys_stream)) {
555  fprintf(stderr, "Failed to enable line buffering on sys.stderr\n");
556  fflush(stderr);
557  }
558  }
559 #endif
560 
561  if (Py_VerboseFlag)
562  fprintf(stderr, "Python %s\n%s\n",
563  Py_GetVersion(), Py_GetCopyright());
564 
565 #if PY_MAJOR_VERSION >= 3 && !defined(WIN_UNICODE)
566  PySys_SetArgv(argc, argv_copy);
567 #else
568  PySys_SetArgv(argc, argv);
569 #endif
570 
571 #ifdef MACOS_APP_BUNDLE
572  // Add the Frameworks directory to sys.path.
573  char buffer[PATH_MAX];
574  uint32_t bufsize = sizeof(buffer);
575  if (_NSGetExecutablePath(buffer, &bufsize) != 0) {
576  assert(false);
577  return 1;
578  }
579  char resolved[PATH_MAX];
580  if (!realpath(buffer, resolved)) {
581  perror("realpath");
582  return 1;
583  }
584  const char *dir = dirname(resolved);
585  sprintf(buffer, "%s/../Frameworks", dir);
586 
587  PyObject *sys_path = PyList_New(1);
588  #if PY_MAJOR_VERSION >= 3
589  PyList_SET_ITEM(sys_path, 0, PyUnicode_FromString(buffer));
590  #else
591  PyList_SET_ITEM(sys_path, 0, PyString_FromString(buffer));
592  #endif
593  PySys_SetObject("path", sys_path);
594  Py_DECREF(sys_path);
595 
596  // Now, store a path to the Resources directory into the main_dir pointer,
597  // for ConfigPageManager to read out and assign to MAIN_DIR.
598  sprintf(buffer, "%s/../Resources", dir);
599  set_main_dir(buffer);
600 
601  // Finally, chdir to it, so that regular Python files are read from the
602  // right location.
603  chdir(buffer);
604 #endif
605 
606  n = PyImport_ImportFrozenModule("__main__");
607  if (n == 0)
608  Py_FatalError("__main__ not frozen");
609  if (n < 0) {
610  PyErr_Print();
611  sts = 1;
612  }
613  else
614  sts = 0;
615 
616 #ifndef NDEBUG
617  if (inspect && isatty((int)fileno(stdin)))
618  sts = PyRun_AnyFile(stdin, "<stdin>") != 0;
619 #endif
620 
621 #ifdef MS_WINDOWS
622  PyWinFreeze_ExeTerm();
623 #endif
624  Py_Finalize();
625 
626 #if PY_MAJOR_VERSION >= 3 && !defined(WIN_UNICODE)
627 error:
628  if (argv_copy2) {
629  for (i = 0; i < argc; i++) {
630 #if PY_MAJOR_VERSION > 3 || PY_MINOR_VERSION >= 4
631  PyMem_RawFree(argv_copy2[i]);
632 #else
633  PyMem_Free(argv_copy2[i]);
634 #endif
635  }
636  }
637 #endif
638  return sts;
639 }
640 
641 /**
642  * Maps the binary blob at the given memory address to memory, and returns the
643  * pointer to the beginning of it.
644  */
645 static void *map_blob(off_t offset, size_t size) {
646  void *blob;
647  FILE *runtime;
648 
649 #ifdef _WIN32
650  wchar_t buffer[2048];
651  GetModuleFileNameW(NULL, buffer, 2048);
652  runtime = _wfopen(buffer, L"rb");
653 #elif defined(__FreeBSD__)
654  size_t bufsize = 4096;
655  char buffer[4096];
656  int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
657  mib[3] = getpid();
658  if (sysctl(mib, 4, (void *)buffer, &bufsize, NULL, 0) == -1) {
659  perror("sysctl");
660  return NULL;
661  }
662  runtime = fopen(buffer, "rb");
663 #elif defined(__APPLE__)
664  char buffer[4096];
665  uint32_t bufsize = sizeof(buffer);
666  if (_NSGetExecutablePath(buffer, &bufsize) != 0) {
667  return NULL;
668  }
669  runtime = fopen(buffer, "rb");
670 #else
671  char buffer[4096];
672  ssize_t pathlen = readlink("/proc/self/exe", buffer, sizeof(buffer) - 1);
673  if (pathlen <= 0) {
674  perror("readlink(/proc/self/exe)");
675  return NULL;
676  }
677  buffer[pathlen] = '\0';
678  runtime = fopen(buffer, "rb");
679 #endif
680 
681  // Get offsets. In version 0, we read it from the end of the file.
682  if (blobinfo.version == 0) {
683  uint64_t end, begin;
684  fseek(runtime, -8, SEEK_END);
685  end = ftell(runtime);
686  fread(&begin, 8, 1, runtime);
687 
688  offset = (off_t)begin;
689  size = (size_t)(end - begin);
690  }
691 
692  // mmap the section indicated by the offset (or malloc/fread on windows)
693 #ifdef _WIN32
694  blob = (void *)malloc(size);
695  assert(blob != NULL);
696  fseek(runtime, (long)offset, SEEK_SET);
697  fread(blob, size, 1, runtime);
698 #else
699  blob = (void *)mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(runtime), offset);
700  assert(blob != MAP_FAILED);
701 #endif
702 
703  fclose(runtime);
704  return blob;
705 }
706 
707 /**
708  * The inverse of map_blob.
709  */
710 static void unmap_blob(void *blob) {
711  if (blob) {
712 #ifdef _WIN32
713  free(blob);
714 #else
715  munmap(blob, blobinfo.blob_size);
716 #endif
717  }
718 }
719 
720 /**
721  * Main entry point to deploy-stub.
722  */
723 #if defined(_WIN32) && PY_MAJOR_VERSION >= 3
724 int wmain(int argc, wchar_t *argv[]) {
725 #else
726 int main(int argc, char *argv[]) {
727 #endif
728  int retval;
729  struct _frozen *moddef;
730  const char *log_filename;
731  void *blob = NULL;
732  log_filename = NULL;
733 
734 #ifdef __APPLE__
735  // Strip a -psn_xxx argument passed in by macOS when run from an .app bundle.
736  if (argc > 1 && strncmp(argv[1], "-psn_", 5) == 0) {
737  argv[1] = argv[0];
738  ++argv;
739  --argc;
740  }
741 #endif
742 
743  /*
744  printf("blob_offset: %d\n", (int)blobinfo.blob_offset);
745  printf("blob_size: %d\n", (int)blobinfo.blob_size);
746  printf("version: %d\n", (int)blobinfo.version);
747  printf("num_pointers: %d\n", (int)blobinfo.num_pointers);
748  printf("codepage: %d\n", (int)blobinfo.codepage);
749  printf("flags: %d\n", (int)blobinfo.flags);
750  printf("reserved: %d\n", (int)blobinfo.reserved);
751  */
752 
753  // If we have a blob offset, we have to map the blob to memory.
754  if (blobinfo.version == 0 || blobinfo.blob_offset != 0) {
755  void *blob = map_blob((off_t)blobinfo.blob_offset, (size_t)blobinfo.blob_size);
756  assert(blob != NULL);
757 
758  // Offset the pointers in the header using the base mmap address.
759  if (blobinfo.version > 0 && blobinfo.num_pointers > 0) {
760  uint32_t i;
761  assert(blobinfo.num_pointers <= MAX_NUM_POINTERS);
762  for (i = 0; i < blobinfo.num_pointers; ++i) {
763  // Only offset if the pointer is non-NULL. Except for the first
764  // pointer, which may never be NULL and usually (but not always)
765  // points to the beginning of the blob.
766  if (i == 0 || blobinfo.pointers[i] != 0) {
767  blobinfo.pointers[i] = (void *)((uintptr_t)blobinfo.pointers[i] + (uintptr_t)blob);
768  }
769  }
770  if (blobinfo.num_pointers >= 12) {
771  log_filename = blobinfo.pointers[11];
772  }
773  } else {
774  blobinfo.pointers[0] = blob;
775  }
776 
777  // Offset the pointers in the module table using the base mmap address.
778  moddef = blobinfo.pointers[0];
779  while (moddef->name) {
780  moddef->name = (char *)((uintptr_t)moddef->name + (uintptr_t)blob);
781  if (moddef->code != 0) {
782  moddef->code = (unsigned char *)((uintptr_t)moddef->code + (uintptr_t)blob);
783  }
784  //printf("MOD: %s %p %d\n", moddef->name, (void*)moddef->code, moddef->size);
785  moddef++;
786  }
787  }
788 
789  if (log_filename != NULL) {
790  char log_filename_buf[4096];
791  if (blobinfo.flags & F_log_filename_strftime) {
792  log_filename_buf[0] = 0;
793  time_t now = time(NULL);
794  if (strftime(log_filename_buf, sizeof(log_filename_buf), log_filename, localtime(&now)) > 0) {
795  log_filename = log_filename_buf;
796  }
797  }
798  setup_logging(log_filename, (blobinfo.flags & F_log_append) != 0);
799  }
800 
801 #ifdef _WIN32
802  if (blobinfo.codepage != 0) {
803  SetConsoleCP(blobinfo.codepage);
804  SetConsoleOutputCP(blobinfo.codepage);
805  }
806 #endif
807 
808  // Run frozen application
809  PyImport_FrozenModules = blobinfo.pointers[0];
810  retval = Py_FrozenMain(argc, argv);
811 
812  fflush(stdout);
813  fflush(stderr);
814 
815  unmap_blob(blob);
816  return retval;
817 }
818 
819 #ifdef WIN_UNICODE
820 int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, wchar_t *lpCmdLine, int nCmdShow) {
821  return wmain(__argc, __wargv);
822 }
823 #elif defined(_WIN32)
824 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, char *lpCmdLine, int nCmdShow) {
825  return main(__argc, __argv);
826 }
827 #endif