<Hermetic> Implement CPython2 Launcher in C++.
The launcher is actually a wrapper which will statically link Python
interpreter within the .par file. It is used to bootstrap embedded
interpreter.
The next step is to change/integrate with Soong to make this hermetic .par
generation process more automatic.
Bug: b/62380596
Test: The launcher has been tested using real files:
zip -r hermetic.zip entry_point.txt Stdlib/ runfiles/
cat launcher | cat - hermetic.zip > executable && chmod u+x executable
Change-Id: I293cae2fe74d46766044f3e3c4b654a54d319b67
diff --git a/Modules/config.c b/Modules/config.c
index 4f0e55e..807d1e9 100644
--- a/Modules/config.c
+++ b/Modules/config.c
@@ -75,6 +75,7 @@
extern void initresource(void);
extern void init_multibytecodec(void);
extern void init_multiprocessing(void);
+extern void initzlib(void);
/* -- ADDMODULE MARKER 1 -- */
@@ -138,6 +139,7 @@
{"resource", initresource},
{"_multibytecodec", init_multibytecodec},
{"_multiprocessing", init_multiprocessing},
+ {"zlib", initzlib},
/* -- ADDMODULE MARKER 2 -- */
diff --git a/Modules/getpath.c b/Modules/getpath.c
index c42ce31..e9d969b 100644
--- a/Modules/getpath.c
+++ b/Modules/getpath.c
@@ -104,6 +104,14 @@
#define LANDMARK "os.py"
#endif
+#ifndef INTERNALDIR
+#define INTERNALDIR "internal"
+#endif
+
+#ifndef STDLIBDIR
+#define STDLIBDIR "stdlib"
+#endif
+
static char prefix[MAXPATHLEN+1];
static char exec_prefix[MAXPATHLEN+1];
static char progpath[MAXPATHLEN+1];
@@ -255,8 +263,11 @@
return 1;
}
- /* Check to see if argv[0] is in the build directory */
+ // GOOGLE(nanzhang): Always set prefix with hermetic executable full path.
strcpy(prefix, argv0_path);
+ return 1;
+
+ /* Check to see if argv[0] is in the build directory */
joinpath(prefix, "Modules/Setup");
if (isfile(prefix)) {
/* Check VPATH to see if argv0_path is in the build directory. */
@@ -314,10 +325,13 @@
return 1;
}
+ // GOOGLE(nanzhang): Always set exec_prefix with hermetic executable full path.
+ strcpy(exec_prefix, argv0_path);
+ return 1;
+
/* Check to see if argv[0] is in the build directory. "pybuilddir.txt"
is written by setup.py and contains the relative path to the location
of shared library modules. */
- strcpy(exec_prefix, argv0_path);
joinpath(exec_prefix, "pybuilddir.txt");
if (isfile(exec_prefix)) {
FILE *f = fopen(exec_prefix, "r");
@@ -363,19 +377,62 @@
calculate_path(void)
{
extern char *Py_GetProgramName(void);
-
static char delimiter[2] = {DELIM, '\0'};
static char separator[2] = {SEP, '\0'};
char *pythonpath = PYTHONPATH;
- char *rtpypath = Py_GETENV("PYTHONPATH");
char *home = Py_GetPythonHome();
- char *path = getenv("PATH");
+ // We have overrided argv[0] using the full path to the hermetic Python
+ // launcher itself. And then Py_SetProgramName(argv[0]) was invoked at
+ // launcher_main.cpp. The launcher_main.cpp has guaranteed that
+ // strlen(Py_GetProgramName()) must not exceed MAXPATHLEN.
char *prog = Py_GetProgramName();
char argv0_path[MAXPATHLEN+1];
- char zip_path[MAXPATHLEN+1];
- int pfound, efound; /* 1 if found; -1 if found build directory */
char *buf;
size_t bufsz;
+
+ strncpy(progpath, prog, MAXPATHLEN);
+ progpath[MAXPATHLEN] = '\0' /* In case of no NUL-termination. */;
+ strncpy(argv0_path, prog, MAXPATHLEN);
+ argv0_path[MAXPATHLEN] = '\0' /* In case of no NUL-termination. */;
+
+ // We don't reduce the path of prefix, and exec_prefix.
+ search_for_prefix(argv0_path, home);
+ search_for_exec_prefix(argv0_path, home);
+
+ // Calculate size of return buffer.
+ bufsz = strlen(prog) + 1 + sizeof(INTERNALDIR) /* 1 is for SEP */;
+ bufsz += strlen(prog) + 1 + sizeof(INTERNALDIR) + 1 + sizeof(STDLIBDIR) /* 1 is for SEP */;
+
+ /* This is the only malloc call in this file */
+ buf = (char *)PyMem_Malloc(bufsz);
+
+ if (buf == NULL) {
+ /* We can't exit, so print a warning and limp along */
+ fprintf(stderr, "Not enough memory for dynamic PYTHONPATH.\n");
+ fprintf(stderr, "Using default static PYTHONPATH.\n");
+ exit(1);
+ } else {
+ buf[0] = '\0';
+ strcat(buf, prefix);
+ strcat(buf, separator);
+ strcat(buf, INTERNALDIR);
+ strcat(buf, delimiter);
+
+ strcat(buf, prefix);
+ strcat(buf, separator);
+ strcat(buf, INTERNALDIR);
+ strcat(buf, separator);
+ strcat(buf, STDLIBDIR);
+
+ module_search_path = buf;
+ }
+ // GOOGLE(nanzhang): Don't need all the code below for embedded Python launcher.
+ return;
+
+ char *path = getenv("PATH");
+ char *rtpypath = Py_GETENV("PYTHONPATH");
+ char zip_path[MAXPATHLEN+1];
+ int pfound, efound; /* 1 if found; -1 if found build directory */
size_t prefixsz;
char *defpath = pythonpath;
#ifdef WITH_NEXT_FRAMEWORK
diff --git a/Modules/zipimport.c b/Modules/zipimport.c
index 1691773..0a7cd0f 100644
--- a/Modules/zipimport.c
+++ b/Modules/zipimport.c
@@ -43,9 +43,11 @@
static PyObject *ZipImportError;
static PyObject *zip_directory_cache = NULL;
+// GOOGLE(nanzhang): Changed two functions below to be visible to launcher so
+// that launcher can access the zip metadata section.
/* forward decls */
-static PyObject *read_directory(const char *archive);
-static PyObject *get_data(const char *archive, PyObject *toc_entry);
+PyObject *read_directory(const char *archive);
+PyObject *get_data(const char *archive, PyObject *toc_entry);
static PyObject *get_module_code(ZipImporter *self, char *fullname,
int *p_ispackage, char **p_modpath);
@@ -702,7 +704,7 @@
Directories can be recognized by the trailing SEP in the name,
data_size and file_offset are 0.
*/
-static PyObject *
+PyObject *
read_directory(const char *archive)
{
PyObject *files = NULL;
@@ -912,7 +914,7 @@
/* Given a path to a Zip file and a toc_entry, return the (uncompressed)
data as a new reference. */
-static PyObject *
+PyObject *
get_data(const char *archive, PyObject *toc_entry)
{
PyObject *raw_data = NULL, *data, *decompress;