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