1 /* This is built as a stand-alone executable by the Makefile, and helps turn
2    modules into frozen modules (like Lib/importlib/_bootstrap.py
3    into Python/importlib.h).
4 
5    This is used directly by Tools/scripts/freeze_modules.py, and indirectly by "make regen-frozen".
6 
7    See Python/frozen.c for more info.
8 
9    Keep this file in sync with Programs/_freeze_module.py.
10 */
11 
12 #include <Python.h>
13 #include <marshal.h>
14 #include "pycore_fileutils.h"     // _Py_stat_struct
15 #include <pycore_import.h>
16 
17 #include <stdio.h>
18 #include <stdlib.h>               // malloc()
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #ifndef MS_WINDOWS
22 #include <unistd.h>
23 #endif
24 
25 /* Empty initializer for deepfrozen modules */
_Py_Deepfreeze_Init(void)26 int _Py_Deepfreeze_Init(void)
27 {
28     return 0;
29 }
30 /* Empty finalizer for deepfrozen modules */
31 void
_Py_Deepfreeze_Fini(void)32 _Py_Deepfreeze_Fini(void)
33 {
34 }
35 
36 /* To avoid a circular dependency on frozen.o, we create our own structure
37    of frozen modules instead, left deliberately blank so as to avoid
38    unintentional import of a stale version of _frozen_importlib. */
39 
40 static const struct _frozen no_modules[] = {
41     {0, 0, 0} /* sentinel */
42 };
43 static const struct _module_alias aliases[] = {
44     {0, 0} /* sentinel */
45 };
46 
47 const struct _frozen *_PyImport_FrozenBootstrap;
48 const struct _frozen *_PyImport_FrozenStdlib;
49 const struct _frozen *_PyImport_FrozenTest;
50 const struct _frozen *PyImport_FrozenModules;
51 const struct _module_alias *_PyImport_FrozenAliases;
52 
53 static const char header[] =
54     "/* Auto-generated by Programs/_freeze_module.c */";
55 
56 static void
runtime_init(void)57 runtime_init(void)
58 {
59     PyConfig config;
60     PyConfig_InitIsolatedConfig(&config);
61 
62     config.site_import = 0;
63 
64     PyStatus status;
65     status = PyConfig_SetString(&config, &config.program_name,
66                                 L"./_freeze_module");
67     if (PyStatus_Exception(status)) {
68         PyConfig_Clear(&config);
69         Py_ExitStatusException(status);
70     }
71 
72     /* Don't install importlib, since it could execute outdated bytecode. */
73     config._install_importlib = 0;
74     config._init_main = 0;
75 
76     status = Py_InitializeFromConfig(&config);
77     PyConfig_Clear(&config);
78     if (PyStatus_Exception(status)) {
79         Py_ExitStatusException(status);
80     }
81 }
82 
83 static const char *
read_text(const char * inpath)84 read_text(const char *inpath)
85 {
86     FILE *infile = fopen(inpath, "rb");
87     if (infile == NULL) {
88         fprintf(stderr, "cannot open '%s' for reading\n", inpath);
89         return NULL;
90     }
91 
92     struct _Py_stat_struct stat;
93     if (_Py_fstat_noraise(fileno(infile), &stat)) {
94         fprintf(stderr, "cannot fstat '%s'\n", inpath);
95         fclose(infile);
96         return NULL;
97     }
98     size_t text_size = (size_t)stat.st_size;
99 
100     char *text = (char *) malloc(text_size + 1);
101     if (text == NULL) {
102         fprintf(stderr, "could not allocate %ld bytes\n", (long) text_size);
103         fclose(infile);
104         return NULL;
105     }
106     size_t n = fread(text, 1, text_size, infile);
107     fclose(infile);
108 
109     if (n < text_size) {
110         fprintf(stderr, "read too short: got %ld instead of %ld bytes\n",
111                 (long) n, (long) text_size);
112         free(text);
113         return NULL;
114     }
115 
116     text[text_size] = '\0';
117     return (const char *)text;
118 }
119 
120 static PyObject *
compile_and_marshal(const char * name,const char * text)121 compile_and_marshal(const char *name, const char *text)
122 {
123     char *filename = (char *) malloc(strlen(name) + 10);
124     sprintf(filename, "<frozen %s>", name);
125     PyObject *code = Py_CompileStringExFlags(text, filename,
126                                              Py_file_input, NULL, 0);
127     free(filename);
128     if (code == NULL) {
129         return NULL;
130     }
131 
132     PyObject *marshalled = PyMarshal_WriteObjectToString(code, Py_MARSHAL_VERSION);
133     Py_CLEAR(code);
134     if (marshalled == NULL) {
135         return NULL;
136     }
137     assert(PyBytes_CheckExact(marshalled));
138 
139     return marshalled;
140 }
141 
142 static char *
get_varname(const char * name,const char * prefix)143 get_varname(const char *name, const char *prefix)
144 {
145     size_t n = strlen(prefix);
146     char *varname = (char *) malloc(strlen(name) + n + 1);
147     (void)strcpy(varname, prefix);
148     for (size_t i = 0; name[i] != '\0'; i++) {
149         if (name[i] == '.') {
150             varname[n++] = '_';
151         }
152         else {
153             varname[n++] = name[i];
154         }
155     }
156     varname[n] = '\0';
157     return varname;
158 }
159 
160 static void
write_code(FILE * outfile,PyObject * marshalled,const char * varname)161 write_code(FILE *outfile, PyObject *marshalled, const char *varname)
162 {
163     unsigned char *data = (unsigned char *) PyBytes_AS_STRING(marshalled);
164     size_t data_size = PyBytes_GET_SIZE(marshalled);
165 
166     fprintf(outfile, "const unsigned char %s[] = {\n", varname);
167     for (size_t n = 0; n < data_size; n += 16) {
168         size_t i, end = Py_MIN(n + 16, data_size);
169         fprintf(outfile, "    ");
170         for (i = n; i < end; i++) {
171             fprintf(outfile, "%u,", (unsigned int) data[i]);
172         }
173         fprintf(outfile, "\n");
174     }
175     fprintf(outfile, "};\n");
176 }
177 
178 static int
write_frozen(const char * outpath,const char * inpath,const char * name,PyObject * marshalled)179 write_frozen(const char *outpath, const char *inpath, const char *name,
180              PyObject *marshalled)
181 {
182     /* Open the file in text mode. The hg checkout should be using the eol extension,
183        which in turn should cause the EOL style match the C library's text mode */
184     FILE *outfile = fopen(outpath, "w");
185     if (outfile == NULL) {
186         fprintf(stderr, "cannot open '%s' for writing\n", outpath);
187         return -1;
188     }
189 
190     fprintf(outfile, "%s\n", header);
191     char *arrayname = get_varname(name, "_Py_M__");
192     write_code(outfile, marshalled, arrayname);
193     free(arrayname);
194 
195     if (ferror(outfile)) {
196         fprintf(stderr, "error when writing to '%s'\n", outpath);
197         fclose(outfile);
198         return -1;
199     }
200     fclose(outfile);
201     return 0;
202 }
203 
204 int
main(int argc,char * argv[])205 main(int argc, char *argv[])
206 {
207     const char *name, *inpath, *outpath;
208 
209     _PyImport_FrozenBootstrap = no_modules;
210     _PyImport_FrozenStdlib = no_modules;
211     _PyImport_FrozenTest = no_modules;
212     PyImport_FrozenModules = NULL;
213     _PyImport_FrozenAliases = aliases;
214 
215     if (argc != 4) {
216         fprintf(stderr, "need to specify the name, input and output paths\n");
217         return 2;
218     }
219     name = argv[1];
220     inpath = argv[2];
221     outpath = argv[3];
222 
223     runtime_init();
224 
225     const char *text = read_text(inpath);
226     if (text == NULL) {
227         goto error;
228     }
229 
230     PyObject *marshalled = compile_and_marshal(name, text);
231     free((char *)text);
232     if (marshalled == NULL) {
233         goto error;
234     }
235 
236     int res = write_frozen(outpath, inpath, name, marshalled);
237     Py_DECREF(marshalled);
238     if (res != 0) {
239         goto error;
240     }
241 
242     Py_Finalize();
243     return 0;
244 
245 error:
246     PyErr_Print();
247     Py_Finalize();
248     return 1;
249 }
250 
251