Allow redirecting logging to an FD

This change allows redirection of logging facilities, from syslog to a
file.

Bug: None
Test: make tests  // see logging in stderr
Change-Id: Ia45ccb87908f1d4a2f7964a01d11a74da6e9fdb7
diff --git a/Android.mk b/Android.mk
index daec814..d531f4b 100644
--- a/Android.mk
+++ b/Android.mk
@@ -26,9 +26,13 @@
 	system.c \
 	util.c
 
+targetUnittestSrcFiles := \
+	testrunner.cc
+
 hostUnittestSrcFiles := \
 	linux-x86/libconstants.gen.c \
-	linux-x86/libsyscalls.gen.c
+	linux-x86/libsyscalls.gen.c \
+	testrunner.cc
 
 minijailCommonCFlags := -DHAVE_SECUREBITS_H -Wall -Werror
 minijailCommonLibraries := libcap
@@ -121,6 +125,7 @@
 LOCAL_SRC_FILES := \
 	$(libminijailSrcFiles) \
 	libminijail_unittest.cc \
+	$(targetUnittestSrcFiles)
 
 LOCAL_STATIC_LIBRARIES := libminijail_generated
 LOCAL_SHARED_LIBRARIES := $(minijailCommonLibraries)
@@ -162,6 +167,7 @@
 	syscall_filter.c \
 	util.c \
 	syscall_filter_unittest.cc \
+	$(targetUnittestSrcFiles)
 
 LOCAL_STATIC_LIBRARIES := libminijail_generated
 LOCAL_SHARED_LIBRARIES := $(minijailCommonLibraries)
@@ -201,7 +207,9 @@
 LOCAL_CLANG := true
 LOCAL_SRC_FILES := \
 	system.c \
+	util.c \
 	system_unittest.cc \
+	$(targetUnittestSrcFiles)
 
 LOCAL_STATIC_LIBRARIES := libminijail_generated
 LOCAL_SHARED_LIBRARIES := $(minijailCommonLibraries)
@@ -220,6 +228,7 @@
 LOCAL_CLANG := true
 LOCAL_SRC_FILES := \
 	system.c \
+	util.c \
 	system_unittest.cc \
 	$(hostUnittestSrcFiles)
 
diff --git a/Makefile b/Makefile
index a102dbf..5d799b7 100644
--- a/Makefile
+++ b/Makefile
@@ -58,11 +58,12 @@
 
 CXX_BINARY(libminijail_unittest): CXXFLAGS += -Wno-write-strings \
 						$(GTEST_CXXFLAGS)
-CXX_BINARY(libminijail_unittest): LDLIBS += -lcap $(GTEST_MAIN)
+CXX_BINARY(libminijail_unittest): LDLIBS += -lcap $(GTEST_LIBS)
 ifeq ($(USE_SYSTEM_GTEST),no)
-CXX_BINARY(libminijail_unittest): $(GTEST_MAIN)
+CXX_BINARY(libminijail_unittest): $(GTEST_LIBS)
 endif
-CXX_BINARY(libminijail_unittest): libminijail_unittest.o $(CORE_OBJECT_FILES)
+CXX_BINARY(libminijail_unittest): libminijail_unittest.o $(CORE_OBJECT_FILES) \
+		testrunner.o
 clean: CLEAN(libminijail_unittest)
 
 
@@ -73,22 +74,22 @@
 
 CXX_BINARY(syscall_filter_unittest): CXXFLAGS += -Wno-write-strings \
 						$(GTEST_CXXFLAGS)
-CXX_BINARY(syscall_filter_unittest): LDLIBS += -lcap $(GTEST_MAIN)
+CXX_BINARY(syscall_filter_unittest): LDLIBS += -lcap $(GTEST_LIBS)
 ifeq ($(USE_SYSTEM_GTEST),no)
-CXX_BINARY(syscall_filter_unittest): $(GTEST_MAIN)
+CXX_BINARY(syscall_filter_unittest): $(GTEST_LIBS)
 endif
 CXX_BINARY(syscall_filter_unittest): syscall_filter_unittest.o \
-		syscall_filter.o bpf.o util.o libconstants.gen.o \
-		libsyscalls.gen.o
+		$(CORE_OBJECT_FILES) testrunner.o
 clean: CLEAN(syscall_filter_unittest)
 
 
 CXX_BINARY(system_unittest): CXXFLAGS += $(GTEST_CXXFLAGS)
-CXX_BINARY(system_unittest): LDLIBS += $(GTEST_MAIN)
+CXX_BINARY(system_unittest): LDLIBS += -lcap $(GTEST_LIBS)
 ifeq ($(USE_SYSTEM_GTEST),no)
-CXX_BINARY(system_unittest): $(GTEST_MAIN)
+CXX_BINARY(system_unittest): $(GTEST_LIBS)
 endif
-CXX_BINARY(system_unittest): system_unittest.o system.o
+CXX_BINARY(system_unittest): system_unittest.o \
+		$(CORE_OBJECT_FILES) testrunner.o
 clean: CLEAN(system_unittest)
 
 
diff --git a/libminijail.c b/libminijail.c
index d06aeeb..0eca595 100644
--- a/libminijail.c
+++ b/libminijail.c
@@ -2691,3 +2691,8 @@
 		free(j->cgroups[i]);
 	free(j);
 }
+
+void API minijail_log_to_fd(int fd, int min_priority)
+{
+	init_logging(LOG_TO_FD, fd, min_priority);
+}
diff --git a/libminijail.h b/libminijail.h
index 15fa124..f1cb346 100644
--- a/libminijail.h
+++ b/libminijail.h
@@ -339,6 +339,16 @@
  */
 void minijail_destroy(struct minijail *j);
 
+/*
+ * minijail_log_to_fd: redirects the module-wide logging to an FD instead of
+ * syslog.
+ * @fd           FD to log to. Caller must ensure this is available after
+ *               jailing (e.g. with minijail_preserve_fd()).
+ * @min_priority the minimum logging priority. Same as the priority argument
+ *               to syslog(2).
+ */
+void minijail_log_to_fd(int fd, int min_priority);
+
 #ifdef __cplusplus
 }; /* extern "C" */
 #endif
diff --git a/minijail0.1 b/minijail0.1
index d80b182..7206104 100644
--- a/minijail0.1
+++ b/minijail0.1
@@ -167,6 +167,10 @@
 \fB--uts[=hostname]\fR
 Create a new UTS/hostname namespace, and optionally set the hostname in the new
 namespace to \fIhostname\fR.
+.TP
+\fB--logging=<system>\fR
+Use \fIsystem\fR as the logging system. \fIsystem\fR must be one of
+\fBsyslog\fR (the default) or \fBstderr\fR.
 .SH IMPLEMENTATION
 This program is broken up into two parts: \fBminijail0\fR (the frontend) and a helper
 library called \fBlibminijailpreload\fR. Some jailings can only be achieved from
diff --git a/minijail0.c b/minijail0.c
index db9fc40..d47aad5 100644
--- a/minijail0.c
+++ b/minijail0.c
@@ -212,7 +212,9 @@
 	       "  -Y:           Synchronize seccomp filters across thread group.\n"
 	       "  -z:           Don't forward signals to jailed process.\n"
 	       "  --ambient:    Raise ambient capabilities. Requires -c.\n"
-	       "  --uts[=name]: Enter a new UTS namespace (and set hostname).\n");
+	       "  --uts[=name]: Enter a new UTS namespace (and set hostname).\n"
+	       "  --logging=<s>:Use <s> as the logging system.\n"
+	       "                <s> must be 'syslog' (default) or 'stderr'.\n");
 	/* clang-format on */
 }
 
@@ -243,6 +245,7 @@
 	char *map;
 	size_t size;
 	const char *filter_path = NULL;
+	int log_to_stderr = 0;
 
 	const char *optstring =
 	    "+u:g:sS:c:C:P:b:B:V:f:m::M::k:a:e::R:T:vrGhHinNplLt::IUKwyYz";
@@ -251,6 +254,7 @@
 	const struct option long_options[] = {
 		{"ambient", no_argument, 0, 128},
 		{"uts", optional_argument, 0, 129},
+		{"logging", required_argument, 0, 130},
 		{0, 0, 0, 0},
 	};
 	/* clang-format on */
@@ -499,12 +503,35 @@
 			if (optarg)
 				minijail_namespace_set_hostname(j, optarg);
 			break;
+		case 130: /* Logging. */
+			if (!strcmp(optarg, "syslog"))
+				log_to_stderr = 0;
+			else if (!strcmp(optarg, "stderr")) {
+				log_to_stderr = 1;
+			} else {
+				fprintf(stderr, "--logger must be 'syslog' or "
+						"'stderr'.\n");
+				exit(1);
+			}
+			break;
 		default:
 			usage(argv[0]);
 			exit(1);
 		}
 	}
 
+	if (log_to_stderr) {
+		init_logging(LOG_TO_FD, STDERR_FILENO, LOG_INFO);
+		/*
+		 * When logging to stderr, ensure the FD survives the jailing.
+		 */
+		if (0 !=
+		    minijail_preserve_fd(j, STDERR_FILENO, STDERR_FILENO)) {
+			fprintf(stderr, "Could not preserve stderr.\n");
+			exit(1);
+		}
+	}
+
 	/* Can only set ambient caps when using regular caps. */
 	if (ambient_caps && !caps) {
 		fprintf(stderr, "Can't set ambient capabilities (--ambient) "
@@ -625,7 +652,7 @@
 	}
 
 	if (exit_immediately) {
-		info("not running init loop, exiting immediately");
+		info("not running init loop, exiting immediately\n");
 		return 0;
 	}
 	int ret = minijail_wait(j);
diff --git a/parse_seccomp_policy.cc b/parse_seccomp_policy.cc
index 32b2853..88debbe 100644
--- a/parse_seccomp_policy.cc
+++ b/parse_seccomp_policy.cc
@@ -21,6 +21,8 @@
 
 /* TODO(jorgelo): Use libseccomp disassembler here. */
 int main(int argc, char **argv) {
+	init_logging(LOG_TO_FD, STDERR_FILENO, LOG_INFO);
+
 	if (argc < 2) {
 		fprintf(stderr, "Usage: %s <policy file>\n", argv[0]);
 		return 1;
diff --git a/testrunner.cc b/testrunner.cc
new file mode 100644
index 0000000..b848c9c
--- /dev/null
+++ b/testrunner.cc
@@ -0,0 +1,42 @@
+// testrunner.cpp
+// Copyright (C) 2017 The Android Open Source Project
+//
+// 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.
+//
+// Main entrypoint for gtest.
+// Redirects logging to stderr to avoid syslog logspam.
+
+#include <stdio.h>
+
+#include <gtest/gtest.h>
+
+#include "util.h"
+
+namespace {
+
+class Environment : public ::testing::Environment {
+ public:
+  ~Environment() override = default;
+
+  void SetUp() {
+    init_logging(LOG_TO_FD, STDERR_FILENO, LOG_INFO);
+  }
+};
+
+}  // namespace
+
+int main(int argc, char **argv) {
+  testing::InitGoogleTest(&argc, argv);
+  ::testing::AddGlobalTestEnvironment(new Environment());
+  return RUN_ALL_TESTS();
+}
diff --git a/util.c b/util.c
index 2605a3f..f228ff7 100644
--- a/util.c
+++ b/util.c
@@ -8,6 +8,7 @@
 #include <ctype.h>
 #include <errno.h>
 #include <limits.h>
+#include <stdarg.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <string.h>
@@ -64,6 +65,41 @@
 
 const size_t log_syscalls_len = ARRAY_SIZE(log_syscalls);
 
+/* clang-format off */
+static struct logging_config_t {
+	/* The logging system to use. The default is syslog. */
+	enum logging_system_t logger;
+
+	/* File descriptor to log to. Only used when logger is LOG_TO_FD. */
+	int fd;
+
+	/* Minimum priority to log. Only used when logger is LOG_TO_FD. */
+	int min_priority;
+} logging_config = {
+	.logger = LOG_TO_SYSLOG,
+};
+/* clang-format on */
+
+void do_log(int priority, const char *format, ...)
+{
+	if (logging_config.logger == LOG_TO_SYSLOG) {
+		va_list args;
+		va_start(args, format);
+		vsyslog(priority, format, args);
+		va_end(args);
+		return;
+	}
+
+	if (logging_config.min_priority < priority)
+		return;
+
+	va_list args;
+	va_start(args, format);
+	vdprintf(logging_config.fd, format, args);
+	va_end(args);
+	dprintf(logging_config.fd, "\n");
+}
+
 int lookup_syscall(const char *name)
 {
 	const struct syscall_entry *entry = syscall_table;
@@ -298,3 +334,10 @@
 		return NULL;
 	return consumebytes(len + 1, buf, buflength);
 }
+
+void init_logging(enum logging_system_t logger, int fd, int min_priority)
+{
+	logging_config.logger = logger;
+	logging_config.fd = fd;
+	logging_config.min_priority = min_priority;
+}
diff --git a/util.h b/util.h
index 14e79f5..56d1246 100644
--- a/util.h
+++ b/util.h
@@ -9,6 +9,7 @@
 #ifndef _UTIL_H_
 #define _UTIL_H_
 
+#include <stdio.h>
 #include <stdlib.h>
 #include <sys/types.h>
 #include <syslog.h>
@@ -20,7 +21,7 @@
 
 /* clang-format off */
 #define die(_msg, ...) do { \
-	syslog(LOG_ERR, "libminijail[%d]: " _msg, getpid(), ## __VA_ARGS__); \
+	do_log(LOG_ERR, "libminijail[%d]: " _msg, getpid(), ## __VA_ARGS__); \
 	abort(); \
 } while (0)
 
@@ -28,13 +29,13 @@
 	die(_msg ": %m", ## __VA_ARGS__)
 
 #define warn(_msg, ...) \
-	syslog(LOG_WARNING, "libminijail[%d]: " _msg, getpid(), ## __VA_ARGS__)
+	do_log(LOG_WARNING, "libminijail[%d]: " _msg, getpid(), ## __VA_ARGS__)
 
 #define pwarn(_msg, ...) \
 	warn(_msg ": %m", ## __VA_ARGS__)
 
 #define info(_msg, ...) \
-	syslog(LOG_INFO, "libminijail[%d]: " _msg, getpid(), ## __VA_ARGS__)
+	do_log(LOG_INFO, "libminijail[%d]: " _msg, getpid(), ## __VA_ARGS__)
 
 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
 /* clang-format on */
@@ -42,6 +43,17 @@
 extern const char *log_syscalls[];
 extern const size_t log_syscalls_len;
 
+enum logging_system_t {
+	/* Log to syslog. This is the default. */
+	LOG_TO_SYSLOG = 0,
+
+	/* Log to a file descriptor. */
+	LOG_TO_FD,
+};
+
+extern void do_log(int priority, const char *format, ...)
+    __attribute__((__format__(__printf__, 2, 3)));
+
 static inline int is_android(void)
 {
 #if defined(__ANDROID__)
@@ -88,6 +100,16 @@
  */
 char *consumestr(char **buf, size_t *buflength);
 
+/*
+ * init_logging: initializes the module-wide logging.
+ * @logger       The logging system to use.
+ * @fd           The file descriptor to log into. Ignored unless
+ *               @logger = LOG_TO_FD.
+ * @min_priority The minimum priority to display. Corresponds to syslog's
+                 priority parameter. Ignored unless @logger = LOG_TO_FD.
+ */
+void init_logging(enum logging_system_t logger, int fd, int min_priority);
+
 #ifdef __cplusplus
 }; /* extern "C" */
 #endif