aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/tools
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/tools')
-rw-r--r--src/main/tools/BUILD23
-rw-r--r--src/main/tools/build-runfiles.cc432
-rwxr-xr-xsrc/main/tools/build_interface_so8
-rw-r--r--src/main/tools/namespace-sandbox-dummy.c24
-rw-r--r--src/main/tools/namespace-sandbox.c323
-rw-r--r--src/main/tools/process-wrapper.c228
6 files changed, 1038 insertions, 0 deletions
diff --git a/src/main/tools/BUILD b/src/main/tools/BUILD
new file mode 100644
index 0000000000..0e73eda39d
--- /dev/null
+++ b/src/main/tools/BUILD
@@ -0,0 +1,23 @@
+package(default_visibility = ["//src:__subpackages__"])
+
+cc_binary(
+ name = "process-wrapper",
+ srcs = ["process-wrapper.c"],
+ copts = ["-std=c99"],
+)
+
+cc_binary(
+ name = "build-runfiles",
+ srcs = ["build-runfiles.cc"],
+)
+
+cc_binary(
+ name = "namespace-sandbox",
+ srcs = select({
+ "//src:darwin": ["namespace-sandbox-dummy.c"],
+ "//conditions:default": ["namespace-sandbox.c"],
+ }),
+ copts = ["-std=c99"],
+)
+
+exports_files(["build_interface_so"])
diff --git a/src/main/tools/build-runfiles.cc b/src/main/tools/build-runfiles.cc
new file mode 100644
index 0000000000..e0b8f5766f
--- /dev/null
+++ b/src/main/tools/build-runfiles.cc
@@ -0,0 +1,432 @@
+// Copyright 2014 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.
+//
+// This program creates a "runfiles tree" from a "runfiles manifest".
+//
+// The command line arguments are an input manifest INPUT and an output
+// directory RUNFILES. First, the files in the RUNFILES directory are scanned
+// and any extraneous ones are removed. Second, any missing files are created.
+// Finally, a copy of the input manifest is written to RUNFILES/MANIFEST.
+//
+// The input manifest consists of lines, each containing a relative path within
+// the runfiles, a space, and an optional absolute path. If this second path
+// is present, a symlink is created pointing to it; otherwise an empty file is
+// created.
+//
+// Given the line
+// <workspace root>/output/path /real/path
+// we will create directories
+// RUNFILES/<workspace root>
+// RUNFILES/<workspace root>/output
+// a symlink
+// RUNFILES/<workspace root>/output/path -> /real/path
+// and the output manifest will contain a line
+// <workspace root>/output/path /real/path
+//
+// If --use_metadata is supplied, every other line is treated as opaque
+// metadata, and is ignored here.
+//
+// All output paths must be relative and generally (but not always) begin with
+// <workspace root>. No output path may be equal to another. No output path may
+// be a path prefix of another.
+
+#define _FILE_OFFSET_BITS 64
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <map>
+#include <string>
+
+// program_invocation_short_name is not portable.
+static const char *argv0;
+
+const char *input_filename;
+const char *output_base_dir;
+
+#define LOG() { \
+ fprintf(stderr, "%s (args %s %s): ", \
+ argv0, input_filename, output_base_dir); \
+}
+
+#define DIE(args...) { \
+ LOG(); \
+ fprintf(stderr, args); \
+ fprintf(stderr, "\n"); \
+ exit(1); \
+}
+
+#define PDIE(args...) { \
+ int saved_errno = errno; \
+ LOG(); \
+ fprintf(stderr, args); \
+ fprintf(stderr, ": %s [%d]\n", strerror(saved_errno), saved_errno); \
+ exit(1); \
+}
+
+enum FileType {
+ FILE_TYPE_REGULAR,
+ FILE_TYPE_DIRECTORY,
+ FILE_TYPE_SYMLINK
+};
+
+struct FileInfo {
+ FileType type;
+ std::string symlink_target;
+
+ bool operator==(const FileInfo &other) const {
+ return type == other.type && symlink_target == other.symlink_target;
+ }
+
+ bool operator!=(const FileInfo &other) const {
+ return !(*this == other);
+ }
+};
+
+typedef std::map<std::string, FileInfo> FileInfoMap;
+
+class RunfilesCreator {
+ public:
+ explicit RunfilesCreator(const std::string &output_base)
+ : output_base_(output_base),
+ output_filename_("MANIFEST"),
+ temp_filename_(output_filename_ + ".tmp") {
+ SetupOutputBase();
+ if (chdir(output_base_.c_str()) != 0) {
+ PDIE("chdir '%s'", output_base_.c_str());
+ }
+ }
+
+ void ReadManifest(const std::string &manifest_file, bool allow_relative,
+ bool use_metadata) {
+ FILE *outfile = fopen(temp_filename_.c_str(), "w");
+ if (!outfile) {
+ PDIE("opening '%s/%s' for writing", output_base_.c_str(),
+ temp_filename_.c_str());
+ }
+ FILE *infile = fopen(manifest_file.c_str(), "r");
+ if (!infile) {
+ PDIE("opening '%s' for reading", manifest_file.c_str());
+ }
+
+ // read input manifest
+ int lineno = 0;
+ char buf[3 * PATH_MAX];
+ while (fgets(buf, sizeof buf, infile)) {
+ // copy line to output manifest
+ if (fputs(buf, outfile) == EOF) {
+ PDIE("writing to '%s/%s'", output_base_.c_str(),
+ temp_filename_.c_str());
+ }
+
+ // parse line
+ ++lineno;
+ // Skip metadata lines. They are used solely for
+ // dependency checking.
+ if (use_metadata && lineno % 2 == 0) continue;
+
+ int n = strlen(buf)-1;
+ if (!n || buf[n] != '\n') {
+ DIE("missing terminator at line %d: '%s'\n", lineno, buf);
+ }
+ buf[n] = '\0';
+ if (buf[0] == '/') {
+ DIE("paths must not be absolute: line %d: '%s'\n", lineno, buf);
+ }
+ const char *s = strchr(buf, ' ');
+ if (!s) {
+ DIE("missing field delimiter at line %d: '%s'\n", lineno, buf);
+ } else if (strchr(s+1, ' ')) {
+ DIE("link or target filename contains space on line %d: '%s'\n", lineno, buf);
+ }
+ std::string link(buf, s-buf);
+ const char *target = s+1;
+ if (!allow_relative && target[0] != '\0' && target[0] != '/'
+ && target[1] != ':') { // Match Windows paths, e.g. C:\foo or C:/foo.
+ DIE("expected absolute path at line %d: '%s'\n", lineno, buf);
+ }
+
+ FileInfo *info = &manifest_[link];
+ if (target[0] == '\0') {
+ // No target means an empty file.
+ info->type = FILE_TYPE_REGULAR;
+ } else {
+ info->type = FILE_TYPE_SYMLINK;
+ info->symlink_target = target;
+ }
+
+ FileInfo parent_info;
+ parent_info.type = FILE_TYPE_DIRECTORY;
+
+ while (true) {
+ int k = link.rfind('/');
+ if (k < 0) break;
+ link.erase(k, std::string::npos);
+ if (!manifest_.insert(std::make_pair(link, parent_info)).second) break;
+ }
+ }
+ if (fclose(outfile) != 0) {
+ PDIE("writing to '%s/%s'", output_base_.c_str(),
+ temp_filename_.c_str());
+ }
+ fclose(infile);
+
+ // Don't delete the temp manifest file.
+ manifest_[temp_filename_].type = FILE_TYPE_REGULAR;
+ }
+
+ void CreateRunfiles() {
+ if (unlink(output_filename_.c_str()) != 0 && errno != ENOENT) {
+ PDIE("removing previous file at '%s/%s'", output_base_.c_str(),
+ output_filename_.c_str());
+ }
+
+ ScanTreeAndPrune(".");
+ CreateFiles();
+
+ // rename output file into place
+ if (rename(temp_filename_.c_str(), output_filename_.c_str()) != 0) {
+ PDIE("renaming '%s/%s' to '%s/%s'",
+ output_base_.c_str(), temp_filename_.c_str(),
+ output_base_.c_str(), output_filename_.c_str());
+ }
+ }
+
+ private:
+ void SetupOutputBase() {
+ struct stat st;
+ if (stat(output_base_.c_str(), &st) != 0) {
+ // Technically, this will cause problems if the user's umask contains
+ // 0200, but we don't care. Anyone who does that deserves what's coming.
+ if (mkdir(output_base_.c_str(), 0777) != 0) {
+ PDIE("creating directory '%s'", output_base_.c_str());
+ }
+ } else {
+ EnsureDirReadAndWritePerms(output_base_);
+ }
+ }
+
+ void ScanTreeAndPrune(const std::string &path) {
+ // A note on non-empty files:
+ // We don't distinguish between empty and non-empty files. That is, if
+ // there's a file that has contents, we don't truncate it here, even though
+ // the manifest supports creation of empty files, only. Given that
+ // .runfiles are *supposed* to be immutable, this shouldn't be a problem.
+ EnsureDirReadAndWritePerms(path);
+
+ struct dirent *entry;
+ DIR *dh = opendir(path.c_str());
+ if (!dh) {
+ PDIE("opendir '%s'", path.c_str());
+ }
+
+ errno = 0;
+ const std::string prefix = (path == "." ? "" : path + "/");
+ while ((entry = readdir(dh)) != NULL) {
+ if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
+
+ std::string entry_path = prefix + entry->d_name;
+ FileInfo actual_info;
+ actual_info.type = DentryToFileType(entry_path, entry->d_type);
+
+ if (actual_info.type == FILE_TYPE_SYMLINK) {
+ ReadLinkOrDie(entry_path, &actual_info.symlink_target);
+ }
+
+ FileInfoMap::iterator expected_it = manifest_.find(entry_path);
+ if (expected_it == manifest_.end() ||
+ expected_it->second != actual_info) {
+ DelTree(entry_path, actual_info.type);
+ } else {
+ manifest_.erase(expected_it);
+ if (actual_info.type == FILE_TYPE_DIRECTORY) {
+ ScanTreeAndPrune(entry_path);
+ }
+ }
+
+ errno = 0;
+ }
+ if (errno != 0) {
+ PDIE("reading directory '%s'", path.c_str());
+ }
+ closedir(dh);
+ }
+
+ void CreateFiles() {
+ for (FileInfoMap::const_iterator it = manifest_.begin();
+ it != manifest_.end(); ++it) {
+ const std::string &path = it->first;
+ switch (it->second.type) {
+ case FILE_TYPE_DIRECTORY:
+ if (mkdir(path.c_str(), 0777) != 0) {
+ PDIE("mkdir '%s'", path.c_str());
+ }
+ break;
+ case FILE_TYPE_REGULAR:
+ {
+ int fd = open(path.c_str(), O_CREAT|O_EXCL|O_WRONLY, 0555);
+ if (fd < 0) {
+ PDIE("creating empty file '%s'", path.c_str());
+ }
+ close(fd);
+ }
+ break;
+ case FILE_TYPE_SYMLINK:
+ if (symlink(it->second.symlink_target.c_str(), path.c_str()) != 0) {
+ PDIE("symlinking '%s' -> '%s'", path.c_str(),
+ it->second.symlink_target.c_str());
+ }
+ break;
+ }
+ }
+ }
+
+ FileType DentryToFileType(const std::string &path, char d_type) {
+ if (d_type == DT_UNKNOWN) {
+ struct stat st;
+ LStatOrDie(path, &st);
+ if (S_ISDIR(st.st_mode)) {
+ return FILE_TYPE_DIRECTORY;
+ } else if (S_ISLNK(st.st_mode)) {
+ return FILE_TYPE_SYMLINK;
+ } else {
+ return FILE_TYPE_REGULAR;
+ }
+ } else if (d_type == DT_DIR) {
+ return FILE_TYPE_DIRECTORY;
+ } else if (d_type == DT_LNK) {
+ return FILE_TYPE_SYMLINK;
+ } else {
+ return FILE_TYPE_REGULAR;
+ }
+ }
+
+ void LStatOrDie(const std::string &path, struct stat *st) {
+ if (lstat(path.c_str(), st) != 0) {
+ PDIE("stating file '%s'", path.c_str());
+ }
+ }
+
+ void ReadLinkOrDie(const std::string &path, std::string *output) {
+ char readlink_buffer[PATH_MAX];
+ int sz = readlink(path.c_str(), readlink_buffer, sizeof(readlink_buffer));
+ if (sz < 0) {
+ PDIE("reading symlink '%s'", path.c_str());
+ }
+ // readlink returns a non-null terminated string.
+ std::string(readlink_buffer, sz).swap(*output);
+ }
+
+ void EnsureDirReadAndWritePerms(const std::string &path) {
+ const int kMode = 0700;
+ struct stat st;
+ LStatOrDie(path, &st);
+ if ((st.st_mode & kMode) != kMode) {
+ int new_mode = (st.st_mode | kMode) & ALLPERMS;
+ if (chmod(path.c_str(), new_mode) != 0) {
+ PDIE("chmod '%s'", path.c_str());
+ }
+ }
+ }
+
+ void DelTree(const std::string &path, FileType file_type) {
+ if (file_type != FILE_TYPE_DIRECTORY) {
+ if (unlink(path.c_str()) != 0) {
+ PDIE("unlinking '%s'", path.c_str());
+ }
+ return;
+ }
+
+ EnsureDirReadAndWritePerms(path);
+
+ struct dirent *entry;
+ DIR *dh = opendir(path.c_str());
+ if (!dh) {
+ PDIE("opendir '%s'", path.c_str());
+ }
+ errno = 0;
+ while ((entry = readdir(dh)) != NULL) {
+ if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
+ const std::string entry_path = path + '/' + entry->d_name;
+ FileType entry_file_type = DentryToFileType(entry_path, entry->d_type);
+ DelTree(entry_path, entry_file_type);
+ errno = 0;
+ }
+ if (errno != 0) {
+ PDIE("readdir '%s'", path.c_str());
+ }
+ closedir(dh);
+ if (rmdir(path.c_str()) != 0) {
+ PDIE("rmdir '%s'", path.c_str());
+ }
+ }
+
+ private:
+ std::string output_base_;
+ std::string output_filename_;
+ std::string temp_filename_;
+
+ FileInfoMap manifest_;
+};
+
+int main(int argc, char **argv) {
+ argv0 = argv[0];
+
+ argc--; argv++;
+ bool allow_relative = false;
+ bool use_metadata = false;
+
+ while (argc >= 1) {
+ if (strcmp(argv[0], "--allow_relative") == 0) {
+ allow_relative = true;
+ argc--; argv++;
+ } else if (strcmp(argv[0], "--use_metadata") == 0) {
+ use_metadata = true;
+ argc--; argv++;
+ } else {
+ break;
+ }
+ }
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s [--allow_relative] INPUT RUNFILES\n",
+ argv0);
+ return 1;
+ }
+
+ input_filename = argv[0];
+ output_base_dir = argv[1];
+
+ std::string manifest_file = input_filename;
+ if (input_filename[0] != '/') {
+ char cwd_buf[PATH_MAX];
+ if (getcwd(cwd_buf, sizeof(cwd_buf)) == NULL) {
+ PDIE("getcwd failed");
+ }
+ manifest_file = std::string(cwd_buf) + '/' + manifest_file;
+ }
+
+ RunfilesCreator runfiles_creator(output_base_dir);
+ runfiles_creator.ReadManifest(manifest_file, allow_relative, use_metadata);
+ runfiles_creator.CreateRunfiles();
+
+ return 0;
+}
diff --git a/src/main/tools/build_interface_so b/src/main/tools/build_interface_so
new file mode 100755
index 0000000000..626e707c6c
--- /dev/null
+++ b/src/main/tools/build_interface_so
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+if [[ $# != 2 ]]; then
+ echo "Usage: $0 <so> <interface so>" 1>&2
+ exit 1
+fi
+
+exec cp $1 $2
diff --git a/src/main/tools/namespace-sandbox-dummy.c b/src/main/tools/namespace-sandbox-dummy.c
new file mode 100644
index 0000000000..dd9f47fe54
--- /dev/null
+++ b/src/main/tools/namespace-sandbox-dummy.c
@@ -0,0 +1,24 @@
+// Copyright 2014 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.
+
+// This is a dummy file to compile on platforms where namespace sandboxing
+// doesn't work (ie. other than Linux). We need this for main/tools/BUILD file
+// - we can't restrict visibility of namespace-sandbox based on platform;
+// instead bazel build main/tools:namespace-sandbox is a no-op on non supported
+// platforms (if we didn't have this file, it would fail with a non-informative
+// message)
+
+int main() {
+ return 0;
+}
diff --git a/src/main/tools/namespace-sandbox.c b/src/main/tools/namespace-sandbox.c
new file mode 100644
index 0000000000..08e5fc2c4f
--- /dev/null
+++ b/src/main/tools/namespace-sandbox.c
@@ -0,0 +1,323 @@
+#define _GNU_SOURCE
+
+// Copyright 2014 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 <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <linux/capability.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+static int global_debug = 0;
+static int global_cpid; // Returned by fork()
+
+#define PRINT_DEBUG(...) do { if (global_debug) {fprintf(stderr, "sandbox.c: " __VA_ARGS__);}} while(0)
+
+#define CHECK_CALL(x) if ((x) == -1) { perror(#x); exit(1); }
+#define CHECK_NOT_NULL(x) if (x == NULL) { perror(#x); exit(1); }
+#define DIE() do { fprintf(stderr, "Error in %d\n", __LINE__); exit(-1); } while(0);
+
+int kChildrenCleanupDelay = 1;
+
+void Usage() {
+ fprintf(stderr,
+ "Usage: ./sandbox [-R sandbox-root] [-m mount] -C command arg1\n"
+ "Mandatory arguments:\n"
+ " -C command to run inside sandbox, followed by arguments\n"
+ " -S directory which will become the root of the sandbox\n"
+ "\n"
+ "Optional arguments:\n"
+ " -t absolute path to bazel tools directory\n"
+ " -T timeout after which sandbox will be terminated\n"
+ " -m system directory to mount inside the sandbox\n"
+ " Multiple directories can be specified and each of them will\n"
+ " be mount as readonly\n"
+ " -D if set, debug info will be printed\n");
+ exit(1);
+}
+
+void PropagateSignals();
+void EnableAlarm();
+void SetupSlashDev();
+
+static volatile sig_atomic_t global_signal_received = 0;
+
+int main(int argc, char *argv[]) {
+ char *include_prefix = NULL;
+ char *sandbox_root = NULL;
+ char *tools = NULL;
+ char **mounts = malloc(argc * sizeof(char*));
+ char **includes = malloc(argc * sizeof(char*));
+ int num_mounts = 0;
+ int num_includes = 0;
+ int iArg = 0;
+ int uid = getuid();
+ int gid = getgid();
+ int timeout = 0;
+
+ for (iArg = 1; iArg < argc - 1; iArg++) {
+ if (strlen(argv[iArg]) != 2) {
+ Usage();
+ }
+ if (argv[iArg][0] != '-') {
+ Usage();
+ }
+ switch (argv[iArg][1]) {
+ case 'S':
+ if (sandbox_root == NULL) {
+ sandbox_root = argv[++iArg];
+ } else {
+ fprintf(stderr,
+ "Multiple sandbox roots (-S) specified (expected one).\n");
+ Usage();
+ }
+ break;
+ case 'm':
+ mounts[num_mounts++] = argv[++iArg];
+ break;
+ case 'D':
+ global_debug = 1;
+ break;
+ case 'T':
+ sscanf(argv[iArg], "%d", &timeout);
+ break;
+ case 'N':
+ include_prefix = argv[++iArg];
+ break;
+ case 'n':
+ includes[num_includes++] = argv[++iArg];
+ break;
+ case 'C':
+ iArg++;
+ goto parsing_finished;
+ case 't':
+ tools = argv[++iArg];
+ break;
+ default:
+ fprintf(stderr, "Unrecognized argument: %s\n", argv[iArg]);
+ Usage();
+ }
+ }
+
+parsing_finished:
+ if (iArg == argc) {
+ fprintf(stderr, "No command specified.\n");
+ Usage();
+ }
+ if (timeout < 0) {
+ fprintf(stderr, "Invalid timeout (-T) value: %d", timeout);
+ Usage();
+ }
+
+ // parsed all arguments, now prepare sandbox
+
+ PRINT_DEBUG("%s\n", sandbox_root);
+ // create new namespaces in which this process and its children will live
+ CHECK_CALL(unshare(CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWUSER));
+ CHECK_CALL(mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL));
+ // mount sandbox and go there
+ CHECK_CALL(mount(sandbox_root, sandbox_root, NULL, MS_BIND | MS_NOSUID, NULL));
+ CHECK_CALL(chdir(sandbox_root));
+
+ SetupSlashDev();
+ // mount blaze specific directories - tools/ and build-runfiles/
+ if (tools != NULL) {
+ PRINT_DEBUG("tools: %s\n", tools);
+ CHECK_CALL(mkdir("tools", 0755));
+ CHECK_CALL(mount(tools, "tools", NULL, MS_BIND | MS_RDONLY, NULL));
+ }
+
+ // mounts passed in argv; those are mostly dirs for shared libs
+ for (int i = 0; i < num_mounts; i++) {
+ CHECK_CALL(mount(mounts[i], mounts[i] + 1, NULL, MS_BIND | MS_RDONLY, NULL));
+ }
+
+ // c++ compilation
+ // headers go in separate directory
+ if (include_prefix != NULL) {
+ CHECK_CALL(chdir(include_prefix));
+ for (int i = 0; i < num_includes; i++) {
+ // TODO(bazel-team) sometimes list of -iquote given by bazel contains
+ // invalid (non-existing) entries, ideally we would like not to have them
+ PRINT_DEBUG("include: %s\n", includes[i]);
+ if (mount(includes[i], includes[i] + 1 , NULL, MS_BIND, NULL) > -1) {
+ continue;
+ }
+ if (errno == ENOENT) {
+ continue;
+ }
+ CHECK_CALL(-1);
+ }
+ CHECK_CALL(chdir(".."));
+ }
+
+ // set group and user mapping from outer namespace to inner:
+ // no changes in the parent, be root in the child
+ int uid_fd, gid_fd;
+ char uid_mapping[64], gid_mapping[64];
+ sprintf(uid_mapping, "0 %d 1\n", uid);
+ sprintf(gid_mapping, "0 %d 1\n", gid);
+ uid_fd = open("/proc/self/uid_map", O_WRONLY);
+ CHECK_CALL(uid_fd);
+ CHECK_CALL(write(uid_fd, uid_mapping, strlen(uid_mapping)));
+ CHECK_CALL(close(uid_fd));
+
+ gid_fd = open("/proc/self/gid_map", O_WRONLY);
+ CHECK_CALL(gid_fd);
+ CHECK_CALL(write(gid_fd, gid_mapping, strlen(gid_mapping)));
+ CHECK_CALL(close(gid_fd));
+
+ CHECK_CALL(setresuid(0, 0, 0));
+ CHECK_CALL(setresgid(0, 0, 0));
+
+ CHECK_CALL(mkdir("proc", 0755));
+ CHECK_CALL(mount("/proc", "proc", NULL, MS_REC | MS_BIND, NULL));
+ // make sandbox actually hermetic:
+ // move the real root to old_root, then detach it
+ char old_root[16] = "old-root-XXXXXX";
+ CHECK_NOT_NULL(mkdtemp(old_root));
+ // pivot_root has no wrapper in libc, so we need syscall()
+ CHECK_CALL(syscall(SYS_pivot_root, ".", old_root));
+ CHECK_CALL(chroot("."));
+ CHECK_CALL(umount2(old_root, MNT_DETACH));
+ CHECK_CALL(rmdir(old_root));
+
+ free(mounts);
+ free(includes);
+
+ for (int i = iArg; i < argc; i += 1) {
+ PRINT_DEBUG("arg: %s\n", argv[i]);
+ }
+
+ // spawn child and wait until it finishes
+ global_cpid = fork();
+ if (global_cpid == 0) {
+ CHECK_CALL(setpgid(0, 0));
+ // if the execvp below fails with "No such file or directory" it means that:
+ // a) the binary is not in the sandbox (which means it wasn't included in
+ // the inputs)
+ // b) the binary uses shared library which is not inside sandbox - you can
+ // check for that by running "ldd ./a.out" (by default directories
+ // starting with /lib* and /usr/lib* should be there)
+ // c) the binary uses elf interpreter which is not inside sandbox - you can
+ // check for that by running "readelf -a a.out | grep interpreter" (the
+ // sandbox code assumes that it is either in /lib*/ or /usr/lib*/)
+ CHECK_CALL(execvp(argv[iArg], argv + iArg));
+ PRINT_DEBUG("Exec failed near %s:%d\n", __FILE__, __LINE__);
+ exit(1);
+ } else {
+ // PARENT
+ // make sure that all signals propagate to children (mostly useful to kill
+ // entire sandbox)
+ PropagateSignals();
+ // after given timeout, kill children
+ EnableAlarm(timeout);
+ int status = 0;
+ while (1) {
+ PRINT_DEBUG("Waiting for the child...\n");
+ pid_t pid = wait(&status);
+ if (global_signal_received) {
+ PRINT_DEBUG("Received signal: %s\n", strsignal(global_signal_received));
+ CHECK_CALL(killpg(global_cpid, global_signal_received));
+ // give children some time for cleanup before they terminate
+ sleep(kChildrenCleanupDelay);
+ CHECK_CALL(killpg(global_cpid, SIGKILL));
+ exit(128 | global_signal_received);
+ }
+ if (errno == EINTR) {
+ continue;
+ }
+ if (pid < 0) {
+ perror("Wait failed:");
+ exit(1);
+ }
+ if (WIFEXITED(status)) {
+ PRINT_DEBUG("Child exited with status: %d\n", WEXITSTATUS(status));
+ exit(WEXITSTATUS(status));
+ }
+ if (WIFSIGNALED(status)) {
+ PRINT_DEBUG("Child terminated by a signal: %d\n", WTERMSIG(status));
+ exit(WEXITSTATUS(status));
+ }
+ if (WIFSTOPPED(status)) {
+ PRINT_DEBUG("Child stopped by a signal: %d\n", WSTOPSIG(status));
+ }
+ }
+ }
+
+ return 0;
+}
+
+void SignalHandler(int signum, siginfo_t *info, void *uctxt) {
+ global_signal_received = signum;
+}
+
+void PropagateSignals() {
+ // propagate some signals received by the parent to processes in sandbox, so
+ // that it's easier to terminate entire sandbox
+ struct sigaction action = {};
+ action.sa_flags = SA_SIGINFO;
+ action.sa_sigaction = SignalHandler;
+
+ // handle all signals that could terminate the process
+ int signals[] = {SIGHUP, SIGINT, SIGKILL, SIGPIPE, SIGALRM, SIGTERM, SIGPOLL,
+ SIGPROF, SIGVTALRM,
+ // signals below produce core dump by default, however at the moment we'll
+ // just terminate
+ SIGQUIT, SIGILL, SIGABRT, SIGFPE, SIGSEGV, SIGBUS, SIGSYS, SIGTRAP, SIGXCPU,
+ SIGXFSZ, -1};
+ for (int *p = signals; *p != -1; p++) {
+ sigaction(*p, &action, NULL);
+ }
+}
+
+void SetupSlashDev() {
+ CHECK_CALL(mkdir("dev", 0755));
+ const char *devs[] = {
+ "/dev/null",
+ "/dev/random",
+ "/dev/urandom",
+ "/dev/zero",
+ NULL
+ };
+ for (int i = 0; devs[i] != NULL; i++) {
+ // open+close to create the file, which will become mount point for actual
+ // device
+ int handle = open(devs[i] + 1, O_CREAT | O_RDONLY, 0644);
+ CHECK_CALL(handle);
+ CHECK_CALL(close(handle));
+ CHECK_CALL(mount(devs[i], devs[i] + 1, NULL, MS_BIND, NULL));
+ }
+}
+
+void EnableAlarm(int timeout) {
+ if (timeout <= 0) return;
+
+ struct itimerval timer = {};
+ timer.it_value.tv_sec = (long) timeout;
+ CHECK_CALL(setitimer(ITIMER_REAL, &timer, NULL));
+}
diff --git a/src/main/tools/process-wrapper.c b/src/main/tools/process-wrapper.c
new file mode 100644
index 0000000000..5aff63cb46
--- /dev/null
+++ b/src/main/tools/process-wrapper.c
@@ -0,0 +1,228 @@
+// Copyright 2014 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.
+
+// process-wrapper runs a subprocess with a given timeout (optional),
+// redirecting stdout and stderr to given files. Upon exit, whether
+// from normal termination or timeout, the subprocess (and any of its children)
+// is killed.
+//
+// The exit status of this program is whatever the child process returned,
+// unless process-wrapper receives a signal. ie, on SIGTERM this program will
+// die with raise(SIGTERM) even if the child process handles SIGTERM with
+// exit(0).
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <fcntl.h>
+#include <math.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+// Not in headers on OSX.
+extern char **environ;
+
+static int global_pid; // Returned from fork().
+static int global_signal = -1;
+static double global_kill_delay = 0.0;
+
+#define DIE(args...) { \
+ fprintf(stderr, args); \
+ fprintf(stderr, " --- "); \
+ perror(NULL); \
+ fprintf(stderr, "\n"); \
+ exit(EXIT_FAILURE); \
+}
+
+#define CHECK_CALL(x) if (x != 0) { perror(#x); exit(1); }
+
+// Make sure the process and all subprocesses are killed.
+static void KillEverything(int pgrp) {
+ kill(-pgrp, SIGTERM);
+
+ // Round up fractional seconds in this polling implementation.
+ int kill_delay = (int)(global_kill_delay+0.999) ;
+ // If the process is still alive, give it some time to die gracefully.
+ while (kill(-pgrp, 0) == 0 && kill_delay-- > 0) {
+ sleep(1);
+ }
+
+ kill(-pgrp, SIGKILL);
+}
+
+// Called when timeout or Signal occurs.
+static void OnSignal(int sig) {
+ global_signal = sig;
+ if (sig == SIGALRM) {
+ // SIGALRM represents a timeout, so we should give the process a bit of
+ // time to die gracefully if it needs it.
+ KillEverything(global_pid);
+ } else {
+ // Signals should kill the process quickly, as it's typically blocking
+ // the return of the prompt after a user hits "Ctrl-C".
+ kill(-global_pid, SIGKILL);
+ }
+}
+
+// Set up a signal handler which kills all subprocesses when the
+// given signal is triggered.
+static void InstallSignalHandler(int sig) {
+ struct sigaction sa = {};
+
+ sa.sa_handler = OnSignal;
+ sigemptyset(&sa.sa_mask);
+ CHECK_CALL(sigaction(sig, &sa, NULL));
+}
+
+// Revert signal handler to default.
+static void UnHandle(int sig) {
+ struct sigaction sa = {};
+ sa.sa_handler = SIG_DFL;
+ sigemptyset(&sa.sa_mask);
+ CHECK_CALL(sigaction(sig, &sa, NULL));
+}
+
+// Enable the given timeout, or no-op if the timeout is non-positive.
+static void EnableAlarm(double timeout) {
+ if (timeout <= 0) return;
+
+ struct itimerval timer = {};
+ timer.it_interval.tv_sec = 0;
+ timer.it_interval.tv_usec = 0;
+
+ double int_val, fraction_val;
+ fraction_val = modf(timeout, &int_val);
+ timer.it_value.tv_sec = (long) int_val;
+ timer.it_value.tv_usec = (long) (fraction_val * 1e6);
+ CHECK_CALL(setitimer(ITIMER_REAL, &timer, NULL));
+}
+
+static void ClearSignalMask() {
+ // Use an empty signal mask and default signal handlers in the
+ // subprocess.
+ sigset_t sset;
+ sigemptyset(&sset);
+ sigprocmask(SIG_SETMASK, &sset, NULL);
+ for (int i = 1; i < NSIG; ++i) {
+ if (i == SIGKILL || i == SIGSTOP) continue;
+
+ struct sigaction sa = {};
+ sa.sa_handler = SIG_DFL;
+ sigemptyset(&sa.sa_mask);
+ sigaction(i, &sa, NULL);
+ }
+}
+
+static int WaitChild(pid_t pid, const char *name) {
+ int err = 0;
+ int status = 0;
+ do {
+ err = waitpid(pid, &status, 0);
+ } while (err == -1 && errno == EINTR);
+
+ if (err == -1) {
+ DIE("wait on %s (pid %d) failed", name, pid);
+ }
+ return status;
+}
+
+// Usage: process-wrapper
+// <timeout_sec> <kill_delay_sec> <stdout file> <stderr file>
+// [cmdline]
+int main(int argc, char *argv[]) {
+ if (argc <= 5) {
+ DIE("Not enough cmd line arguments to process-wrapper");
+ }
+
+ // Parse the cmdline args to get the timeout and redirect files.
+ argv++;
+ double timeout;
+ if (sscanf(*argv++, "%lf", &timeout) != 1) {
+ DIE("timeout_sec is not a real number.");
+ }
+ if (sscanf(*argv++, "%lf", &global_kill_delay) != 1) {
+ DIE("kill_delay_sec is not a real number.");
+ }
+ char *stdout_path = *argv++;
+ char *stderr_path = *argv++;
+
+ if (strcmp(stdout_path, "-")) {
+ // Redirect stdout and stderr.
+ int fd_out = open(stdout_path, O_WRONLY|O_CREAT|O_TRUNC, 0666);
+ if (fd_out == -1) {
+ DIE("Could not open %s for stdout", stdout_path);
+ }
+ if (dup2(fd_out, STDOUT_FILENO) == -1) {
+ DIE("dup2 failed for stdout");
+ }
+ CHECK_CALL(close(fd_out));
+ }
+
+ if (strcmp(stderr_path, "-")) {
+ int fd_err = open(stderr_path, O_WRONLY|O_CREAT|O_TRUNC, 0666);
+ if (fd_err == -1) {
+ DIE("Could not open %s for stderr", stderr_path);
+ }
+ if (dup2(fd_err, STDERR_FILENO) == -1) {
+ DIE("dup2 failed for stderr");
+ }
+ CHECK_CALL(close(fd_err));
+ }
+
+ global_pid = fork();
+ if (global_pid < 0) {
+ DIE("Fork failed");
+ } else if (global_pid == 0) {
+ // In child.
+ if (setsid() == -1) {
+ DIE("Could not setsid from child");
+ }
+ ClearSignalMask();
+ // Force umask to include read and execute for everyone, to make
+ // output permissions predictable.
+ umask(022);
+
+ execvp(argv[0], argv); // Does not return.
+ DIE("execvpe %s failed", argv[0]);
+ } else {
+ // In parent.
+ InstallSignalHandler(SIGALRM);
+ InstallSignalHandler(SIGTERM);
+ InstallSignalHandler(SIGINT);
+ EnableAlarm(timeout);
+
+ int status = WaitChild(global_pid, argv[0]);
+
+ // The child is done, but may have grandchildren.
+ kill(-global_pid, SIGKILL);
+ if (global_signal > 0) {
+ // Don't trust the exit code if we got a timeout or signal.
+ UnHandle(global_signal);
+ raise(global_signal);
+ } else if (WIFEXITED(status)) {
+ exit(WEXITSTATUS(status));
+ } else {
+ int sig = WTERMSIG(status);
+ UnHandle(sig);
+ raise(sig);
+ }
+ }
+}