Panda3D

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