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