<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/Launcher/launcher_main.cpp b/Launcher/launcher_main.cpp
new file mode 100644
index 0000000..a49998c
--- /dev/null
+++ b/Launcher/launcher_main.cpp
@@ -0,0 +1,124 @@
+// Copyright 2017 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "launcher_internal.h"
+
+#include <Python.h>
+#include <android-base/file.h>
+#include <osdefs.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
+
+int main(int argc, char *argv[]) {
+ int result = 0 /* Used to mark if current program runs with success/failure. */;
+
+ // Clear PYTHONPATH and PYTHONHOME so Python doesn't attempt to check the local
+ // disk for Python modules to load. The value of PYTHONHOME will replace "prefix"
+ // and "exe_prefix" based on the description in getpath.c.
+ // Please don't use PYTHONPATH and PYTHONHOME within user program.
+ // TODO(nanzhang): figure out if unsetenv("PYTHONPATH") is better.
+ unsetenv(const_cast<char *>("PYTHONPATH"));
+ // TODO(nanzhang): figure out if Py_SetPythonHome() is better.
+ unsetenv(const_cast<char *>("PYTHONHOME"));
+ // PYTHONEXECUTABLE is only used on MacOs X, when the Python interpreter
+ // embedded in an application bundle. It is not sure that we have this use case
+ // for Android hermetic Python. So override this environment variable to empty
+ // for now to make our self-contained environment more strict.
+ // For user (.py) program, it can access hermetic .par file path through
+ // sys.argv[0].
+ unsetenv(const_cast<char *>("PYTHONEXECUTABLE"));
+
+ // Resolving absolute path based on argv[0] is not reliable since it may
+ // include something unusable, too bad.
+ // android::base::GetExecutablePath() also handles for Darwin/Windows.
+ std::string executable_path = android::base::GetExecutablePath();
+
+ argv[0] = strdup(executable_path.c_str());
+ // argv[0] is used for setting internal path, and Python sys.argv[0]. It
+ // should not exceed MAXPATHLEN defined for CPython.
+ if (!argv[0] || strlen(argv[0]) > MAXPATHLEN) {
+ fprintf(stderr, "The executable path %s is NULL or of invalid length.\n", argv[0]);
+ return 1;
+ }
+
+ // For debugging/logging purpose, set stdin/stdout/stderr unbuffered through
+ // environment variable.
+ // TODO(nanzhang): Set Py_VerboseFlag if more debugging requests needed.
+ const char *unbuffered_env = getenv("PYTHONUNBUFFERED");
+ if (unbuffered_env && unbuffered_env[0]) {
+ #if defined(MS_WINDOWS) || defined(__CYGWIN__)
+ _setmode(fileno(stdin), O_BINARY);
+ _setmode(fileno(stdout), O_BINARY);
+ #endif
+ #ifdef HAVE_SETVBUF
+ setvbuf(stdin, (char *)NULL, _IONBF, BUFSIZ);
+ setvbuf(stdout, (char *)NULL, _IONBF, BUFSIZ);
+ setvbuf(stderr, (char *)NULL, _IONBF, BUFSIZ);
+ #else /* !HAVE_SETVBUF */
+ setbuf(stdin, (char *)NULL);
+ setbuf(stdout, (char *)NULL);
+ setbuf(stderr, (char *)NULL);
+ #endif /* !HAVE_SETVBUF */
+ }
+ //For debugging/logging purpose, Warning control.
+ //Python’s warning machinery by default prints warning messages to sys.stderr.
+ //The full form of argument is:action:message:category:module:line
+ char *warnings_env = getenv("PYTHONWARNINGS");
+ if (warnings_env && warnings_env[0]) {
+ char *warnings_buf, *warning;
+
+ // Note: "new" operation; we need free this chuck of data after use.
+ warnings_buf = new char[strlen(warnings_env) + 1];
+ if (warnings_buf == NULL)
+ Py_FatalError(
+ "not enough memory to copy PYTHONWARNINGS");
+ strcpy(warnings_buf, warnings_env);
+ for (warning = strtok(warnings_buf, ",");
+ warning != NULL;
+ warning = strtok(NULL, ","))
+ PySys_AddWarnOption(warning);
+ delete[] warnings_buf;
+ }
+
+ // Always enable Python "-s" option. We don't need user-site directories,
+ // everything's supposed to be hermetic.
+ Py_NoUserSiteDirectory = 1;
+
+ Py_SetProgramName(argv[0]);
+ Py_Initialize();
+ PySys_SetArgvEx(argc, argv, 0);
+
+ // Set sys.executable to None. The real executable is available as
+ // sys.argv[0], and too many things assume sys.executable is a regular Python
+ // binary, which isn't available. By setting it to None we get clear errors
+ // when people try to use it.
+ if (PySys_SetObject(const_cast<char *>("executable"), Py_None) < 0) {
+ PyErr_Print();
+ result = 1;
+ goto error;
+ }
+
+ result = android::cpython2::python_launcher::RunEntryPointOrMainModule(argv[0]);
+ if (result < 0) {
+ PyErr_Print();
+ goto error;
+ }
+
+error:
+ Py_Finalize();
+
+ free(argv[0]);
+ exit(abs(result));
+}