[automerger] DO NOT MERGE Remove window obscurement information. am: edac95e737 am: dda3bdf8a8 am: 3cd049ca28 skipped: 2564b9fdf5 am: 67c9dace8b am: 09c59e7f69 am: a31fb53edd am: b63caaf102 skipped: c2116af15c
Change-Id: Ieec59681e3dc14340da368355ead622a7f0dcc1c
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..03af56d
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,13 @@
+BasedOnStyle: Google
+
+AccessModifierOffset: -4
+AlignOperands: false
+AllowShortFunctionsOnASingleLine: Inline
+AlwaysBreakBeforeMultilineStrings: false
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+ConstructorInitializerIndentWidth: 6
+ContinuationIndentWidth: 8
+IndentWidth: 4
+PenaltyBreakBeforeFirstCallParameter: 100000
+SpacesBeforeTrailingComments: 1
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..cd05b21
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,15 @@
+ndk_headers {
+ name: "libandroid_headers",
+ from: "include/android",
+ to: "android",
+ srcs: ["include/android/**/*.h"],
+ license: "NOTICE",
+}
+
+subdirs = [
+ "cmds/*",
+ "libs/*",
+ "opengl",
+ "services/*",
+ "vulkan",
+]
diff --git a/aidl/gui/android/view/Surface.aidl b/aidl/gui/android/view/Surface.aidl
index 674c163..7e89220 100644
--- a/aidl/gui/android/view/Surface.aidl
+++ b/aidl/gui/android/view/Surface.aidl
@@ -17,4 +17,4 @@
package android.view;
-parcelable Surface cpp_header "gui/Surface.h";
+parcelable Surface cpp_header "gui/view/Surface.h";
diff --git a/cmds/atrace/Android.bp b/cmds/atrace/Android.bp
new file mode 100644
index 0000000..6c5869a
--- /dev/null
+++ b/cmds/atrace/Android.bp
@@ -0,0 +1,27 @@
+// Copyright 2012 The Android Open Source Project
+
+cc_binary {
+ name: "atrace",
+ srcs: ["atrace.cpp"],
+
+ shared_libs: [
+ "libbinder",
+ "libhwbinder",
+ "android.hidl.manager@1.0",
+ "libhidlbase",
+ "libhidltransport",
+ "liblog",
+ "libcutils",
+ "libutils",
+ "libz",
+ "libbase",
+ ],
+
+ init_rc: ["atrace.rc"],
+
+ product_variables: {
+ debuggable: {
+ init_rc: ["atrace_userdebug.rc"],
+ },
+ },
+}
diff --git a/cmds/atrace/Android.mk b/cmds/atrace/Android.mk
deleted file mode 100644
index a787e95..0000000
--- a/cmds/atrace/Android.mk
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright 2012 The Android Open Source Project
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= atrace.cpp
-
-LOCAL_C_INCLUDES += external/zlib
-
-LOCAL_MODULE:= atrace
-
-LOCAL_MODULE_TAGS:= optional
-
-LOCAL_SHARED_LIBRARIES := \
- libbinder \
- libcutils \
- libutils \
- libz \
-
-LOCAL_INIT_RC := atrace.rc
-
-include $(BUILD_EXECUTABLE)
diff --git a/cmds/atrace/atrace.cpp b/cmds/atrace/atrace.cpp
index 5885738..47e04e7 100644
--- a/cmds/atrace/atrace.cpp
+++ b/cmds/atrace/atrace.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
- #define LOG_TAG "atrace"
+#define LOG_TAG "atrace"
#include <errno.h>
#include <fcntl.h>
@@ -31,19 +31,26 @@
#include <unistd.h>
#include <zlib.h>
+#include <fstream>
+#include <memory>
+
#include <binder/IBinder.h>
#include <binder/IServiceManager.h>
#include <binder/Parcel.h>
+#include <android/hidl/manager/1.0/IServiceManager.h>
+#include <hidl/ServiceManagement.h>
#include <cutils/properties.h>
#include <utils/String8.h>
#include <utils/Timers.h>
#include <utils/Tokenizer.h>
#include <utils/Trace.h>
+#include <android-base/file.h>
using namespace android;
+using std::string;
#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
#define MAX_SYS_FILES 10
@@ -103,72 +110,85 @@
{ "ss", "System Server", ATRACE_TAG_SYSTEM_SERVER, { } },
{ "database", "Database", ATRACE_TAG_DATABASE, { } },
{ "network", "Network", ATRACE_TAG_NETWORK, { } },
+ { "adb", "ADB", ATRACE_TAG_ADB, { } },
{ k_coreServiceCategory, "Core services", 0, { } },
{ "sched", "CPU Scheduling", 0, {
- { REQ, "/sys/kernel/debug/tracing/events/sched/sched_switch/enable" },
- { REQ, "/sys/kernel/debug/tracing/events/sched/sched_wakeup/enable" },
- { OPT, "/sys/kernel/debug/tracing/events/sched/sched_blocked_reason/enable" },
- { OPT, "/sys/kernel/debug/tracing/events/sched/sched_cpu_hotplug/enable" },
+ { REQ, "events/sched/sched_switch/enable" },
+ { REQ, "events/sched/sched_wakeup/enable" },
+ { OPT, "events/sched/sched_waking/enable" },
+ { OPT, "events/sched/sched_blocked_reason/enable" },
+ { OPT, "events/sched/sched_cpu_hotplug/enable" },
} },
{ "irq", "IRQ Events", 0, {
- { REQ, "/sys/kernel/debug/tracing/events/irq/enable" },
- { OPT, "/sys/kernel/debug/tracing/events/ipi/enable" },
+ { REQ, "events/irq/enable" },
+ { OPT, "events/ipi/enable" },
+ } },
+ { "i2c", "I2C Events", 0, {
+ { REQ, "events/i2c/enable" },
+ { REQ, "events/i2c/i2c_read/enable" },
+ { REQ, "events/i2c/i2c_write/enable" },
+ { REQ, "events/i2c/i2c_result/enable" },
+ { REQ, "events/i2c/i2c_reply/enable" },
+ { OPT, "events/i2c/smbus_read/enable" },
+ { OPT, "events/i2c/smbus_write/enable" },
+ { OPT, "events/i2c/smbus_result/enable" },
+ { OPT, "events/i2c/smbus_reply/enable" },
} },
{ "freq", "CPU Frequency", 0, {
- { REQ, "/sys/kernel/debug/tracing/events/power/cpu_frequency/enable" },
- { OPT, "/sys/kernel/debug/tracing/events/power/clock_set_rate/enable" },
- { OPT, "/sys/kernel/debug/tracing/events/power/cpu_frequency_limits/enable" },
+ { REQ, "events/power/cpu_frequency/enable" },
+ { OPT, "events/power/clock_set_rate/enable" },
+ { OPT, "events/power/cpu_frequency_limits/enable" },
} },
{ "membus", "Memory Bus Utilization", 0, {
- { REQ, "/sys/kernel/debug/tracing/events/memory_bus/enable" },
+ { REQ, "events/memory_bus/enable" },
} },
{ "idle", "CPU Idle", 0, {
- { REQ, "/sys/kernel/debug/tracing/events/power/cpu_idle/enable" },
+ { REQ, "events/power/cpu_idle/enable" },
} },
{ "disk", "Disk I/O", 0, {
- { OPT, "/sys/kernel/debug/tracing/events/f2fs/f2fs_sync_file_enter/enable" },
- { OPT, "/sys/kernel/debug/tracing/events/f2fs/f2fs_sync_file_exit/enable" },
- { OPT, "/sys/kernel/debug/tracing/events/f2fs/f2fs_write_begin/enable" },
- { OPT, "/sys/kernel/debug/tracing/events/f2fs/f2fs_write_end/enable" },
- { OPT, "/sys/kernel/debug/tracing/events/ext4/ext4_da_write_begin/enable" },
- { OPT, "/sys/kernel/debug/tracing/events/ext4/ext4_da_write_end/enable" },
- { OPT, "/sys/kernel/debug/tracing/events/ext4/ext4_sync_file_enter/enable" },
- { OPT, "/sys/kernel/debug/tracing/events/ext4/ext4_sync_file_exit/enable" },
- { REQ, "/sys/kernel/debug/tracing/events/block/block_rq_issue/enable" },
- { REQ, "/sys/kernel/debug/tracing/events/block/block_rq_complete/enable" },
+ { OPT, "events/f2fs/f2fs_sync_file_enter/enable" },
+ { OPT, "events/f2fs/f2fs_sync_file_exit/enable" },
+ { OPT, "events/f2fs/f2fs_write_begin/enable" },
+ { OPT, "events/f2fs/f2fs_write_end/enable" },
+ { OPT, "events/ext4/ext4_da_write_begin/enable" },
+ { OPT, "events/ext4/ext4_da_write_end/enable" },
+ { OPT, "events/ext4/ext4_sync_file_enter/enable" },
+ { OPT, "events/ext4/ext4_sync_file_exit/enable" },
+ { REQ, "events/block/block_rq_issue/enable" },
+ { REQ, "events/block/block_rq_complete/enable" },
} },
{ "mmc", "eMMC commands", 0, {
- { REQ, "/sys/kernel/debug/tracing/events/mmc/enable" },
+ { REQ, "events/mmc/enable" },
} },
{ "load", "CPU Load", 0, {
- { REQ, "/sys/kernel/debug/tracing/events/cpufreq_interactive/enable" },
+ { REQ, "events/cpufreq_interactive/enable" },
} },
{ "sync", "Synchronization", 0, {
- { REQ, "/sys/kernel/debug/tracing/events/sync/enable" },
+ { REQ, "events/sync/enable" },
} },
{ "workq", "Kernel Workqueues", 0, {
- { REQ, "/sys/kernel/debug/tracing/events/workqueue/enable" },
+ { REQ, "events/workqueue/enable" },
} },
{ "memreclaim", "Kernel Memory Reclaim", 0, {
- { REQ, "/sys/kernel/debug/tracing/events/vmscan/mm_vmscan_direct_reclaim_begin/enable" },
- { REQ, "/sys/kernel/debug/tracing/events/vmscan/mm_vmscan_direct_reclaim_end/enable" },
- { REQ, "/sys/kernel/debug/tracing/events/vmscan/mm_vmscan_kswapd_wake/enable" },
- { REQ, "/sys/kernel/debug/tracing/events/vmscan/mm_vmscan_kswapd_sleep/enable" },
+ { REQ, "events/vmscan/mm_vmscan_direct_reclaim_begin/enable" },
+ { REQ, "events/vmscan/mm_vmscan_direct_reclaim_end/enable" },
+ { REQ, "events/vmscan/mm_vmscan_kswapd_wake/enable" },
+ { REQ, "events/vmscan/mm_vmscan_kswapd_sleep/enable" },
} },
{ "regulators", "Voltage and Current Regulators", 0, {
- { REQ, "/sys/kernel/debug/tracing/events/regulator/enable" },
+ { REQ, "events/regulator/enable" },
} },
{ "binder_driver", "Binder Kernel driver", 0, {
- { REQ, "/sys/kernel/debug/tracing/events/binder/binder_transaction/enable" },
- { REQ, "/sys/kernel/debug/tracing/events/binder/binder_transaction_received/enable" },
+ { REQ, "events/binder/binder_transaction/enable" },
+ { REQ, "events/binder/binder_transaction_received/enable" },
} },
{ "binder_lock", "Binder global lock trace", 0, {
- { REQ, "/sys/kernel/debug/tracing/events/binder/binder_lock/enable" },
- { REQ, "/sys/kernel/debug/tracing/events/binder/binder_locked/enable" },
- { REQ, "/sys/kernel/debug/tracing/events/binder/binder_unlock/enable" },
+ { REQ, "events/binder/binder_lock/enable" },
+ { REQ, "events/binder/binder_locked/enable" },
+ { REQ, "events/binder/binder_unlock/enable" },
} },
{ "pagecache", "Page cache", 0, {
- { REQ, "/sys/kernel/debug/tracing/events/filemap/enable" },
+ { REQ, "events/filemap/enable" },
} },
};
@@ -187,61 +207,65 @@
/* Global state */
static bool g_traceAborted = false;
static bool g_categoryEnables[NELEM(k_categories)] = {};
+static std::string g_traceFolder;
/* Sys file paths */
static const char* k_traceClockPath =
- "/sys/kernel/debug/tracing/trace_clock";
+ "trace_clock";
static const char* k_traceBufferSizePath =
- "/sys/kernel/debug/tracing/buffer_size_kb";
+ "buffer_size_kb";
+
+static const char* k_traceCmdlineSizePath =
+ "saved_cmdlines_size";
static const char* k_tracingOverwriteEnablePath =
- "/sys/kernel/debug/tracing/options/overwrite";
+ "options/overwrite";
static const char* k_currentTracerPath =
- "/sys/kernel/debug/tracing/current_tracer";
+ "current_tracer";
static const char* k_printTgidPath =
- "/sys/kernel/debug/tracing/options/print-tgid";
+ "options/print-tgid";
static const char* k_funcgraphAbsTimePath =
- "/sys/kernel/debug/tracing/options/funcgraph-abstime";
+ "options/funcgraph-abstime";
static const char* k_funcgraphCpuPath =
- "/sys/kernel/debug/tracing/options/funcgraph-cpu";
+ "options/funcgraph-cpu";
static const char* k_funcgraphProcPath =
- "/sys/kernel/debug/tracing/options/funcgraph-proc";
+ "options/funcgraph-proc";
static const char* k_funcgraphFlatPath =
- "/sys/kernel/debug/tracing/options/funcgraph-flat";
+ "options/funcgraph-flat";
static const char* k_funcgraphDurationPath =
- "/sys/kernel/debug/tracing/options/funcgraph-duration";
+ "options/funcgraph-duration";
static const char* k_ftraceFilterPath =
- "/sys/kernel/debug/tracing/set_ftrace_filter";
+ "set_ftrace_filter";
static const char* k_tracingOnPath =
- "/sys/kernel/debug/tracing/tracing_on";
+ "tracing_on";
static const char* k_tracePath =
- "/sys/kernel/debug/tracing/trace";
+ "trace";
static const char* k_traceStreamPath =
- "/sys/kernel/debug/tracing/trace_pipe";
+ "trace_pipe";
static const char* k_traceMarkerPath =
- "/sys/kernel/debug/tracing/trace_marker";
+ "trace_marker";
// Check whether a file exists.
static bool fileExists(const char* filename) {
- return access(filename, F_OK) != -1;
+ return access((g_traceFolder + filename).c_str(), F_OK) != -1;
}
// Check whether a file is writable.
static bool fileIsWritable(const char* filename) {
- return access(filename, W_OK) != -1;
+ return access((g_traceFolder + filename).c_str(), W_OK) != -1;
}
// Truncate a file.
@@ -250,9 +274,9 @@
// This uses creat rather than truncate because some of the debug kernel
// device nodes (e.g. k_ftraceFilterPath) currently aren't changed by
// calls to truncate, but they are cleared by calls to creat.
- int traceFD = creat(path, 0);
+ int traceFD = creat((g_traceFolder + path).c_str(), 0);
if (traceFD == -1) {
- fprintf(stderr, "error truncating %s: %s (%d)\n", path,
+ fprintf(stderr, "error truncating %s: %s (%d)\n", (g_traceFolder + path).c_str(),
strerror(errno), errno);
return false;
}
@@ -264,9 +288,10 @@
static bool _writeStr(const char* filename, const char* str, int flags)
{
- int fd = open(filename, flags);
+ std::string fullFilename = g_traceFolder + filename;
+ int fd = open(fullFilename.c_str(), flags);
if (fd == -1) {
- fprintf(stderr, "error opening %s: %s (%d)\n", filename,
+ fprintf(stderr, "error opening %s: %s (%d)\n", fullFilename.c_str(),
strerror(errno), errno);
return false;
}
@@ -274,7 +299,7 @@
bool ok = true;
ssize_t len = strlen(str);
if (write(fd, str, len) != len) {
- fprintf(stderr, "error writing to %s: %s (%d)\n", filename,
+ fprintf(stderr, "error writing to %s: %s (%d)\n", fullFilename.c_str(),
strerror(errno), errno);
ok = false;
}
@@ -300,7 +325,7 @@
{
char buffer[128];
int len = 0;
- int fd = open(k_traceMarkerPath, O_WRONLY);
+ int fd = open((g_traceFolder + k_traceMarkerPath).c_str(), O_WRONLY);
if (fd == -1) {
fprintf(stderr, "error opening %s: %s (%d)\n", k_traceMarkerPath,
strerror(errno), errno);
@@ -363,7 +388,7 @@
// Check whether the category would be supported on the device if the user
// were root. This function assumes that root is able to write to any file
// that exists. It performs the same logic as isCategorySupported, but it
-// uses file existance rather than writability in the /sys/ file checks.
+// uses file existence rather than writability in the /sys/ file checks.
static bool isCategorySupportedForRoot(const TracingCategory& category)
{
bool ok = category.tags != 0;
@@ -416,56 +441,40 @@
return writeStr(k_traceBufferSizePath, str);
}
-// Read the trace_clock sysfs file and return true if it matches the requested
-// value. The trace_clock file format is:
-// local [global] counter uptime perf
-static bool isTraceClock(const char *mode)
+// Set the default size of cmdline hashtable
+static bool setCmdlineSize()
{
- int fd = open(k_traceClockPath, O_RDONLY);
- if (fd == -1) {
- fprintf(stderr, "error opening %s: %s (%d)\n", k_traceClockPath,
- strerror(errno), errno);
- return false;
+ if (fileExists(k_traceCmdlineSizePath)) {
+ return writeStr(k_traceCmdlineSizePath, "8192");
}
-
- char buf[4097];
- ssize_t n = read(fd, buf, 4096);
- close(fd);
- if (n == -1) {
- fprintf(stderr, "error reading %s: %s (%d)\n", k_traceClockPath,
- strerror(errno), errno);
- return false;
- }
- buf[n] = '\0';
-
- char *start = strchr(buf, '[');
- if (start == NULL) {
- return false;
- }
- start++;
-
- char *end = strchr(start, ']');
- if (end == NULL) {
- return false;
- }
- *end = '\0';
-
- return strcmp(mode, start) == 0;
+ return true;
}
-// Enable or disable the kernel's use of the global clock. Disabling the global
-// clock will result in the kernel using a per-CPU local clock.
+// Set the clock to the best available option while tracing. Use 'boot' if it's
+// available; otherwise, use 'mono'. If neither are available use 'global'.
// Any write to the trace_clock sysfs file will reset the buffer, so only
// update it if the requested value is not the current value.
-static bool setGlobalClockEnable(bool enable)
+static bool setClock()
{
- const char *clock = enable ? "global" : "local";
+ std::ifstream clockFile((g_traceFolder + k_traceClockPath).c_str(), O_RDONLY);
+ std::string clockStr((std::istreambuf_iterator<char>(clockFile)),
+ std::istreambuf_iterator<char>());
- if (isTraceClock(clock)) {
- return true;
+ std::string newClock;
+ if (clockStr.find("boot") != std::string::npos) {
+ newClock = "boot";
+ } else if (clockStr.find("mono") != std::string::npos) {
+ newClock = "mono";
+ } else {
+ newClock = "global";
}
- return writeStr(k_traceClockPath, clock);
+ size_t begin = clockStr.find("[") + 1;
+ size_t end = clockStr.find("]");
+ if (newClock.compare(0, std::string::npos, clockStr, begin, end-begin) == 0) {
+ return true;
+ }
+ return writeStr(k_traceClockPath, newClock.c_str());
}
static bool setPrintTgidEnableIfPresent(bool enable)
@@ -503,6 +512,54 @@
return true;
}
+// Poke all the HAL processes in the system to get them to re-read
+// their system properties.
+static void pokeHalServices()
+{
+ using ::android::hidl::base::V1_0::IBase;
+ using ::android::hidl::manager::V1_0::IServiceManager;
+ using ::android::hardware::hidl_string;
+ using ::android::hardware::Return;
+
+ sp<IServiceManager> sm = ::android::hardware::defaultServiceManager();
+
+ if (sm == nullptr) {
+ fprintf(stderr, "failed to get IServiceManager to poke hal services\n");
+ return;
+ }
+
+ auto listRet = sm->list([&](const auto &interfaces) {
+ for (size_t i = 0; i < interfaces.size(); i++) {
+ string fqInstanceName = interfaces[i];
+ string::size_type n = fqInstanceName.find("/");
+ if (n == std::string::npos || interfaces[i].size() == n+1)
+ continue;
+ hidl_string fqInterfaceName = fqInstanceName.substr(0, n);
+ hidl_string instanceName = fqInstanceName.substr(n+1, std::string::npos);
+ Return<sp<IBase>> interfaceRet = sm->get(fqInterfaceName, instanceName);
+ if (!interfaceRet.isOk()) {
+ // ignore
+ continue;
+ }
+
+ sp<IBase> interface = interfaceRet;
+ if (interface == nullptr) {
+ // ignore
+ continue;
+ }
+
+ auto notifyRet = interface->notifySyspropsChanged();
+ if (!notifyRet.isOk()) {
+ // ignore
+ }
+ }
+ });
+ if (!listRet.isOk()) {
+ // TODO(b/34242478) fix this when we determine the correct ACL
+ //fprintf(stderr, "failed to list services: %s\n", listRet.description().c_str());
+ }
+}
+
// Set the trace tags that userland tracing uses, and poke the running
// processes to pick up the new value.
static bool setTagsProperty(uint64_t tags)
@@ -533,11 +590,11 @@
// Set the system property that indicates which apps should perform
// application-level tracing.
-static bool setAppCmdlineProperty(const char* cmdline)
+static bool setAppCmdlineProperty(char* cmdline)
{
char buf[PROPERTY_KEY_MAX];
int i = 0;
- const char* start = cmdline;
+ char* start = cmdline;
while (start != NULL) {
if (i == MAX_PACKAGES) {
fprintf(stderr, "error: only 16 packages could be traced at once\n");
@@ -587,24 +644,14 @@
// kernel.
static bool verifyKernelTraceFuncs(const char* funcs)
{
- int fd = open(k_ftraceFilterPath, O_RDONLY);
- if (fd == -1) {
- fprintf(stderr, "error opening %s: %s (%d)\n", k_ftraceFilterPath,
+ std::string buf;
+ if (!android::base::ReadFileToString(g_traceFolder + k_ftraceFilterPath, &buf)) {
+ fprintf(stderr, "error opening %s: %s (%d)\n", k_ftraceFilterPath,
strerror(errno), errno);
- return false;
+ return false;
}
- char buf[4097];
- ssize_t n = read(fd, buf, 4096);
- close(fd);
- if (n == -1) {
- fprintf(stderr, "error reading %s: %s (%d)\n", k_ftraceFilterPath,
- strerror(errno), errno);
- return false;
- }
-
- buf[n] = '\0';
- String8 funcList = String8::format("\n%s", buf);
+ String8 funcList = String8::format("\n%s",buf.c_str());
// Make sure that every function listed in funcs is in the list we just
// read from the kernel, except for wildcard inputs.
@@ -624,7 +671,6 @@
func = strtok(NULL, ",");
}
free(myFuncs);
-
return ok;
}
@@ -724,7 +770,9 @@
ok &= setCategoriesEnableFromFile(g_categoriesFile);
ok &= setTraceOverwriteEnable(g_traceOverwrite);
ok &= setTraceBufferSizeKB(g_traceBufferSizeKB);
- ok &= setGlobalClockEnable(true);
+ // TODO: Re-enable after stabilization
+ //ok &= setCmdlineSize();
+ ok &= setClock();
ok &= setPrintTgidEnableIfPresent(true);
ok &= setKernelTraceFuncs(g_kernelTraceFuncs);
@@ -754,8 +802,9 @@
}
packageList += value;
}
- ok &= setAppCmdlineProperty(packageList.data());
+ ok &= setAppCmdlineProperty(&packageList[0]);
ok &= pokeBinderServices();
+ pokeHalServices();
// Disable all the sysfs enables. This is done as a separate loop from
// the enables to allow the same enable to exist in multiple categories.
@@ -797,7 +846,6 @@
// Set the options back to their defaults.
setTraceOverwriteEnable(true);
setTraceBufferSizeKB(1);
- setGlobalClockEnable(false);
setPrintTgidEnableIfPresent(false);
setKernelTraceFuncs(NULL);
}
@@ -819,7 +867,7 @@
static void streamTrace()
{
char trace_data[4096];
- int traceFD = open(k_traceStreamPath, O_RDWR);
+ int traceFD = open((g_traceFolder + k_traceStreamPath).c_str(), O_RDWR);
if (traceFD == -1) {
fprintf(stderr, "error opening %s: %s (%d)\n", k_traceStreamPath,
strerror(errno), errno);
@@ -844,7 +892,7 @@
static void dumpTrace(int outFd)
{
ALOGI("Dumping trace");
- int traceFD = open(k_tracePath, O_RDWR);
+ int traceFD = open((g_traceFolder + k_tracePath).c_str(), O_RDWR);
if (traceFD == -1) {
fprintf(stderr, "error opening %s: %s (%d)\n", k_tracePath,
strerror(errno), errno);
@@ -853,30 +901,34 @@
if (g_compress) {
z_stream zs;
- uint8_t *in, *out;
- int result, flush;
-
memset(&zs, 0, sizeof(zs));
- result = deflateInit(&zs, Z_DEFAULT_COMPRESSION);
+
+ int result = deflateInit(&zs, Z_DEFAULT_COMPRESSION);
if (result != Z_OK) {
fprintf(stderr, "error initializing zlib: %d\n", result);
close(traceFD);
return;
}
- const size_t bufSize = 64*1024;
- in = (uint8_t*)malloc(bufSize);
- out = (uint8_t*)malloc(bufSize);
- flush = Z_NO_FLUSH;
+ constexpr size_t bufSize = 64*1024;
+ std::unique_ptr<uint8_t> in(new uint8_t[bufSize]);
+ std::unique_ptr<uint8_t> out(new uint8_t[bufSize]);
+ if (!in || !out) {
+ fprintf(stderr, "couldn't allocate buffers\n");
+ close(traceFD);
+ return;
+ }
- zs.next_out = out;
+ int flush = Z_NO_FLUSH;
+
+ zs.next_out = reinterpret_cast<Bytef*>(out.get());
zs.avail_out = bufSize;
do {
if (zs.avail_in == 0) {
// More input is needed.
- result = read(traceFD, in, bufSize);
+ result = read(traceFD, in.get(), bufSize);
if (result < 0) {
fprintf(stderr, "error reading trace: %s (%d)\n",
strerror(errno), errno);
@@ -885,14 +937,14 @@
} else if (result == 0) {
flush = Z_FINISH;
} else {
- zs.next_in = in;
+ zs.next_in = reinterpret_cast<Bytef*>(in.get());
zs.avail_in = result;
}
}
if (zs.avail_out == 0) {
// Need to write the output.
- result = write(outFd, out, bufSize);
+ result = write(outFd, out.get(), bufSize);
if ((size_t)result < bufSize) {
fprintf(stderr, "error writing deflated trace: %s (%d)\n",
strerror(errno), errno);
@@ -900,7 +952,7 @@
zs.avail_out = bufSize; // skip the final write
break;
}
- zs.next_out = out;
+ zs.next_out = reinterpret_cast<Bytef*>(out.get());
zs.avail_out = bufSize;
}
@@ -912,7 +964,7 @@
if (zs.avail_out < bufSize) {
size_t bytes = bufSize - zs.avail_out;
- result = write(outFd, out, bytes);
+ result = write(outFd, out.get(), bytes);
if ((size_t)result < bytes) {
fprintf(stderr, "error writing deflated trace: %s (%d)\n",
strerror(errno), errno);
@@ -923,9 +975,6 @@
if (result != Z_OK) {
fprintf(stderr, "error cleaning up zlib: %d\n", result);
}
-
- free(in);
- free(out);
} else {
ssize_t sent = 0;
while ((sent = sendfile(outFd, traceFD, NULL, 64*1024*1024)) > 0);
@@ -981,9 +1030,9 @@
" -k fname,... trace the listed kernel functions\n"
" -n ignore signals\n"
" -s N sleep for N seconds before tracing [default 0]\n"
- " -t N trace for N seconds [defualt 5]\n"
+ " -t N trace for N seconds [default 5]\n"
" -z compress the trace dump\n"
- " --async_start start circular trace and return immediatly\n"
+ " --async_start start circular trace and return immediately\n"
" --async_dump dump the current contents of circular trace buffer\n"
" --async_stop stop tracing and dump the current contents of circular\n"
" trace buffer\n"
@@ -998,6 +1047,29 @@
);
}
+bool findTraceFiles()
+{
+ static const std::string debugfs_path = "/sys/kernel/debug/tracing/";
+ static const std::string tracefs_path = "/sys/kernel/tracing/";
+ static const std::string trace_file = "trace_marker";
+
+ bool tracefs = access((tracefs_path + trace_file).c_str(), F_OK) != -1;
+ bool debugfs = access((debugfs_path + trace_file).c_str(), F_OK) != -1;
+
+ if (!tracefs && !debugfs) {
+ fprintf(stderr, "Error: Did not find trace folder\n");
+ return false;
+ }
+
+ if (tracefs) {
+ g_traceFolder = tracefs_path;
+ } else {
+ g_traceFolder = debugfs_path;
+ }
+
+ return true;
+}
+
int main(int argc, char **argv)
{
bool async = false;
@@ -1011,6 +1083,11 @@
exit(0);
}
+ if (!findTraceFiles()) {
+ fprintf(stderr, "No trace folder found\n");
+ exit(-1);
+ }
+
for (;;) {
int ret;
int option_index = 0;
@@ -1158,7 +1235,7 @@
fflush(stdout);
int outFd = STDOUT_FILENO;
if (g_outputFile) {
- outFd = open(g_outputFile, O_WRONLY | O_CREAT);
+ outFd = open(g_outputFile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
}
if (outFd == -1) {
printf("Failed to open '%s', err=%d", g_outputFile, errno);
diff --git a/cmds/atrace/atrace.rc b/cmds/atrace/atrace.rc
index 747cc69..d538145 100644
--- a/cmds/atrace/atrace.rc
+++ b/cmds/atrace/atrace.rc
@@ -4,63 +4,135 @@
# Allow writing to the kernel trace log.
chmod 0222 /sys/kernel/debug/tracing/trace_marker
+ chmod 0222 /sys/kernel/tracing/trace_marker
# Allow the shell group to enable (some) kernel tracing.
chown root shell /sys/kernel/debug/tracing/trace_clock
+ chown root shell /sys/kernel/tracing/trace_clock
chown root shell /sys/kernel/debug/tracing/buffer_size_kb
+ chown root shell /sys/kernel/tracing/buffer_size_kb
chown root shell /sys/kernel/debug/tracing/options/overwrite
+ chown root shell /sys/kernel/tracing/options/overwrite
chown root shell /sys/kernel/debug/tracing/options/print-tgid
+ chown root shell /sys/kernel/tracing/options/print-tgid
+ chown root shell /sys/kernel/debug/tracing/saved_cmdlines_size
+ chown root shell /sys/kernel/tracing/saved_cmdlines_size
chown root shell /sys/kernel/debug/tracing/events/sched/sched_switch/enable
+ chown root shell /sys/kernel/tracing/events/sched/sched_switch/enable
chown root shell /sys/kernel/debug/tracing/events/sched/sched_wakeup/enable
+ chown root shell /sys/kernel/tracing/events/sched/sched_wakeup/enable
chown root shell /sys/kernel/debug/tracing/events/sched/sched_blocked_reason/enable
+ chown root shell /sys/kernel/tracing/events/sched/sched_blocked_reason/enable
chown root shell /sys/kernel/debug/tracing/events/sched/sched_cpu_hotplug/enable
+ chown root shell /sys/kernel/tracing/events/sched/sched_cpu_hotplug/enable
chown root shell /sys/kernel/debug/tracing/events/power/cpu_frequency/enable
+ chown root shell /sys/kernel/tracing/events/power/cpu_frequency/enable
chown root shell /sys/kernel/debug/tracing/events/power/cpu_idle/enable
+ chown root shell /sys/kernel/tracing/events/power/cpu_idle/enable
chown root shell /sys/kernel/debug/tracing/events/power/clock_set_rate/enable
+ chown root shell /sys/kernel/tracing/events/power/clock_set_rate/enable
chown root shell /sys/kernel/debug/tracing/events/power/cpu_frequency_limits/enable
+ chown root shell /sys/kernel/tracing/events/power/cpu_frequency_limits/enable
chown root shell /sys/kernel/debug/tracing/events/cpufreq_interactive/enable
+ chown root shell /sys/kernel/tracing/events/cpufreq_interactive/enable
chown root shell /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_direct_reclaim_begin/enable
+ chown root shell /sys/kernel/tracing/events/vmscan/mm_vmscan_direct_reclaim_begin/enable
chown root shell /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_direct_reclaim_end/enable
+ chown root shell /sys/kernel/tracing/events/vmscan/mm_vmscan_direct_reclaim_end/enable
chown root shell /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_kswapd_wake/enable
+ chown root shell /sys/kernel/tracing/events/vmscan/mm_vmscan_kswapd_wake/enable
chown root shell /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_kswapd_sleep/enable
+ chown root shell /sys/kernel/tracing/events/vmscan/mm_vmscan_kswapd_sleep/enable
chown root shell /sys/kernel/debug/tracing/events/binder/binder_transaction/enable
+ chown root shell /sys/kernel/tracing/events/binder/binder_transaction/enable
chown root shell /sys/kernel/debug/tracing/events/binder/binder_transaction_received/enable
+ chown root shell /sys/kernel/tracing/events/binder/binder_transaction_received/enable
chown root shell /sys/kernel/debug/tracing/events/binder/binder_lock/enable
+ chown root shell /sys/kernel/tracing/events/binder/binder_lock/enable
chown root shell /sys/kernel/debug/tracing/events/binder/binder_locked/enable
+ chown root shell /sys/kernel/tracing/events/binder/binder_locked/enable
chown root shell /sys/kernel/debug/tracing/events/binder/binder_unlock/enable
+ chown root shell /sys/kernel/tracing/events/binder/binder_unlock/enable
chown root shell /sys/kernel/debug/tracing/tracing_on
+ chown root shell /sys/kernel/tracing/tracing_on
chmod 0664 /sys/kernel/debug/tracing/trace_clock
+ chmod 0664 /sys/kernel/tracing/trace_clock
chmod 0664 /sys/kernel/debug/tracing/buffer_size_kb
+ chmod 0664 /sys/kernel/tracing/buffer_size_kb
chmod 0664 /sys/kernel/debug/tracing/options/overwrite
+ chmod 0664 /sys/kernel/tracing/options/overwrite
chmod 0664 /sys/kernel/debug/tracing/options/print-tgid
+ chmod 0664 /sys/kernel/tracing/options/print-tgid
+ chmod 0664 /sys/kernel/debug/tracing/saved_cmdlines_size
+ chmod 0664 /sys/kernel/tracing/saved_cmdlines_size
chmod 0664 /sys/kernel/debug/tracing/events/sched/sched_switch/enable
+ chmod 0664 /sys/kernel/tracing/events/sched/sched_switch/enable
chmod 0664 /sys/kernel/debug/tracing/events/sched/sched_wakeup/enable
+ chmod 0664 /sys/kernel/tracing/events/sched/sched_wakeup/enable
chmod 0664 /sys/kernel/debug/tracing/events/sched/sched_blocked_reason/enable
+ chmod 0664 /sys/kernel/tracing/events/sched/sched_blocked_reason/enable
chmod 0664 /sys/kernel/debug/tracing/events/sched/sched_cpu_hotplug/enable
+ chmod 0664 /sys/kernel/tracing/events/sched/sched_cpu_hotplug/enable
chmod 0664 /sys/kernel/debug/tracing/events/power/cpu_frequency/enable
+ chmod 0664 /sys/kernel/tracing/events/power/cpu_frequency/enable
chmod 0664 /sys/kernel/debug/tracing/events/power/cpu_idle/enable
+ chmod 0664 /sys/kernel/tracing/events/power/cpu_idle/enable
chmod 0664 /sys/kernel/debug/tracing/events/power/clock_set_rate/enable
+ chmod 0664 /sys/kernel/tracing/events/power/clock_set_rate/enable
chmod 0664 /sys/kernel/debug/tracing/events/power/cpu_frequency_limits/enable
+ chmod 0664 /sys/kernel/tracing/events/power/cpu_frequency_limits/enable
chmod 0664 /sys/kernel/debug/tracing/events/cpufreq_interactive/enable
+ chmod 0664 /sys/kernel/tracing/events/cpufreq_interactive/enable
chmod 0664 /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_direct_reclaim_begin/enable
+ chmod 0664 /sys/kernel/tracing/events/vmscan/mm_vmscan_direct_reclaim_begin/enable
chmod 0664 /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_direct_reclaim_end/enable
+ chmod 0664 /sys/kernel/tracing/events/vmscan/mm_vmscan_direct_reclaim_end/enable
chmod 0664 /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_kswapd_wake/enable
+ chmod 0664 /sys/kernel/tracing/events/vmscan/mm_vmscan_kswapd_wake/enable
chmod 0664 /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_kswapd_sleep/enable
+ chmod 0664 /sys/kernel/tracing/events/vmscan/mm_vmscan_kswapd_sleep/enable
chmod 0664 /sys/kernel/debug/tracing/tracing_on
+ chmod 0664 /sys/kernel/tracing/tracing_on
chmod 0664 /sys/kernel/debug/tracing/events/binder/binder_transaction/enable
+ chmod 0664 /sys/kernel/tracing/events/binder/binder_transaction/enable
chmod 0664 /sys/kernel/debug/tracing/events/binder/binder_transaction_received/enable
+ chmod 0664 /sys/kernel/tracing/events/binder/binder_transaction_received/enable
chmod 0664 /sys/kernel/debug/tracing/events/binder/binder_lock/enable
+ chmod 0664 /sys/kernel/tracing/events/binder/binder_lock/enable
chmod 0664 /sys/kernel/debug/tracing/events/binder/binder_locked/enable
+ chmod 0664 /sys/kernel/tracing/events/binder/binder_locked/enable
chmod 0664 /sys/kernel/debug/tracing/events/binder/binder_unlock/enable
+ chmod 0664 /sys/kernel/tracing/events/binder/binder_unlock/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/i2c/enable
+ chmod 0664 /sys/kernel/tracing/events/i2c/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/i2c/i2c_read/enable
+ chmod 0664 /sys/kernel/tracing/events/i2c/i2c_read/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/i2c/i2c_write/enable
+ chmod 0664 /sys/kernel/tracing/events/i2c/i2c_write/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/i2c/i2c_result/enable
+ chmod 0664 /sys/kernel/tracing/events/i2c/i2c_result/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/i2c/i2c_reply/enable
+ chmod 0664 /sys/kernel/tracing/events/i2c/i2c_reply/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/i2c/smbus_read/enable
+ chmod 0664 /sys/kernel/tracing/events/i2c/smbus_read/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/i2c/smbus_write/enable
+ chmod 0664 /sys/kernel/tracing/events/i2c/smbus_write/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/i2c/smbus_result/enable
+ chmod 0664 /sys/kernel/tracing/events/i2c/smbus_result/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/i2c/smbus_reply/enable
+ chmod 0664 /sys/kernel/tracing/events/i2c/smbus_reply/enable
# Tracing disabled by default
write /sys/kernel/debug/tracing/tracing_on 0
+ write /sys/kernel/tracing/tracing_on 0
# Allow only the shell group to read and truncate the kernel trace.
chown root shell /sys/kernel/debug/tracing/trace
+ chown root shell /sys/kernel/tracing/trace
chmod 0660 /sys/kernel/debug/tracing/trace
+ chmod 0660 /sys/kernel/tracing/trace
on property:persist.debug.atrace.boottrace=1
start boottrace
diff --git a/cmds/atrace/atrace_userdebug.rc b/cmds/atrace/atrace_userdebug.rc
new file mode 100644
index 0000000..5fd28e2
--- /dev/null
+++ b/cmds/atrace/atrace_userdebug.rc
@@ -0,0 +1,47 @@
+## Permissions to allow additional system-wide tracing to the kernel trace buffer.
+## The default list of permissions is set in frameworks/native/cmds/atrace/atrace.rc
+
+# Allow the shell group to enable kernel tracepoints:
+
+on post-fs
+ chown root shell /sys/kernel/debug/tracing/events/sync/enable
+ chown root shell /sys/kernel/debug/tracing/events/workqueue/enable
+ chown root shell /sys/kernel/debug/tracing/events/regulator/enable
+ chown root shell /sys/kernel/debug/tracing/events/pagecache/enable
+
+ # irq
+ chown root shell /sys/kernel/debug/tracing/events/irq/enable
+ chown root shell /sys/kernel/debug/tracing/events/ipi/enable
+
+ # disk
+ chown root shell /sys/kernel/debug/tracing/events/f2fs/f2fs_sync_file_enter/enable
+ chown root shell /sys/kernel/debug/tracing/events/f2fs/f2fs_sync_file_exit/enable
+ chown root shell /sys/kernel/debug/tracing/events/f2fs/f2fs_write_begin/enable
+ chown root shell /sys/kernel/debug/tracing/events/f2fs/f2fs_write_end/enable
+ chown root shell /sys/kernel/debug/tracing/events/ext4/ext4_da_write_begin/enable
+ chown root shell /sys/kernel/debug/tracing/events/ext4/ext4_da_write_end/enable
+ chown root shell /sys/kernel/debug/tracing/events/ext4/ext4_sync_file_enter/enable
+ chown root shell /sys/kernel/debug/tracing/events/ext4/ext4_sync_file_exit/enable
+ chown root shell /sys/kernel/debug/tracing/events/block/block_rq_issue/enable
+ chown root shell /sys/kernel/debug/tracing/events/block/block_rq_complete/enable
+
+ chmod 0664 /sys/kernel/debug/tracing/events/sync/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/workqueue/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/regulator/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/pagecache/enable
+
+ # irq
+ chmod 0664 /sys/kernel/debug/tracing/events/irq/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/ipi/enable
+
+ # disk
+ chmod 0664 /sys/kernel/debug/tracing/events/f2fs/f2fs_sync_file_enter/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/f2fs/f2fs_sync_file_exit/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/f2fs/f2fs_write_begin/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/f2fs/f2fs_write_end/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/ext4/ext4_da_write_begin/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/ext4/ext4_da_write_end/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/ext4/ext4_sync_file_enter/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/ext4/ext4_sync_file_exit/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/block/block_rq_issue/enable
+ chmod 0664 /sys/kernel/debug/tracing/events/block/block_rq_complete/enable
diff --git a/cmds/bugreport/Android.bp b/cmds/bugreport/Android.bp
new file mode 100644
index 0000000..139e4b2
--- /dev/null
+++ b/cmds/bugreport/Android.bp
@@ -0,0 +1,6 @@
+cc_binary {
+ name: "bugreport",
+ srcs: ["bugreport.cpp"],
+ cflags: ["-Wall"],
+ shared_libs: ["libcutils"],
+}
diff --git a/cmds/bugreport/Android.mk b/cmds/bugreport/Android.mk
deleted file mode 100644
index ced5d30..0000000
--- a/cmds/bugreport/Android.mk
+++ /dev/null
@@ -1,12 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= bugreport.cpp
-
-LOCAL_MODULE:= bugreport
-
-LOCAL_CFLAGS := -Wall
-
-LOCAL_SHARED_LIBRARIES := libcutils
-
-include $(BUILD_EXECUTABLE)
diff --git a/cmds/bugreportz/readme.md b/cmds/bugreportz/readme.md
index 2697f09..eb0d898 100644
--- a/cmds/bugreportz/readme.md
+++ b/cmds/bugreportz/readme.md
@@ -17,3 +17,4 @@
- `OK:<path_to_bugreport_file>` in case of success.
- `FAIL:<error message>` in case of failure.
+
diff --git a/cmds/cmd/Android.mk b/cmds/cmd/Android.mk
index ac2f4c0..d565e57 100644
--- a/cmds/cmd/Android.mk
+++ b/cmds/cmd/Android.mk
@@ -7,8 +7,11 @@
LOCAL_SHARED_LIBRARIES := \
libutils \
liblog \
+ libselinux \
libbinder
-
+
+LOCAL_C_INCLUDES += \
+ $(JNI_H_INCLUDE)
ifeq ($(TARGET_OS),linux)
LOCAL_CFLAGS += -DXP_UNIX
diff --git a/cmds/cmd/cmd.cpp b/cmds/cmd/cmd.cpp
index ed740d3..7e05d72 100644
--- a/cmds/cmd/cmd.cpp
+++ b/cmds/cmd/cmd.cpp
@@ -21,7 +21,10 @@
#include <binder/ProcessState.h>
#include <binder/IResultReceiver.h>
#include <binder/IServiceManager.h>
+#include <binder/IShellCallback.h>
#include <binder/TextOutput.h>
+#include <utils/Condition.h>
+#include <utils/Mutex.h>
#include <utils/Vector.h>
#include <getopt.h>
@@ -29,7 +32,16 @@
#include <stdio.h>
#include <string.h>
#include <unistd.h>
+#include <fcntl.h>
#include <sys/time.h>
+#include <errno.h>
+
+#include "selinux/selinux.h"
+#include "selinux/android.h"
+
+#include <UniquePtr.h>
+
+#define DEBUG 0
using namespace android;
@@ -38,10 +50,72 @@
return lhs->compare(*rhs);
}
+struct SecurityContext_Delete {
+ void operator()(security_context_t p) const {
+ freecon(p);
+ }
+};
+typedef UniquePtr<char[], SecurityContext_Delete> Unique_SecurityContext;
+
+class MyShellCallback : public BnShellCallback
+{
+public:
+ bool mActive = true;
+
+ virtual int openOutputFile(const String16& path, const String16& seLinuxContext) {
+ String8 path8(path);
+ char cwd[256];
+ getcwd(cwd, 256);
+ String8 fullPath(cwd);
+ fullPath.appendPath(path8);
+ if (!mActive) {
+ aerr << "Open attempt after active for: " << fullPath << endl;
+ return -EPERM;
+ }
+ int fd = open(fullPath.string(), O_WRONLY|O_CREAT|O_TRUNC, S_IRWXU|S_IRWXG);
+ if (fd < 0) {
+ return fd;
+ }
+ if (is_selinux_enabled() && seLinuxContext.size() > 0) {
+ String8 seLinuxContext8(seLinuxContext);
+ security_context_t tmp = NULL;
+ int ret = getfilecon(fullPath.string(), &tmp);
+ Unique_SecurityContext context(tmp);
+ int accessGranted = selinux_check_access(seLinuxContext8.string(), context.get(),
+ "file", "write", NULL);
+ if (accessGranted != 0) {
+ close(fd);
+ aerr << "System server has no access to file context " << context.get()
+ << " (from path " << fullPath.string() << ", context "
+ << seLinuxContext8.string() << ")" << endl;
+ return -EPERM;
+ }
+ }
+ return fd;
+ }
+};
+
class MyResultReceiver : public BnResultReceiver
{
public:
- virtual void send(int32_t /*resultCode*/) {
+ Mutex mMutex;
+ Condition mCondition;
+ bool mHaveResult = false;
+ int32_t mResult = 0;
+
+ virtual void send(int32_t resultCode) {
+ AutoMutex _l(mMutex);
+ mResult = resultCode;
+ mHaveResult = true;
+ mCondition.signal();
+ }
+
+ int32_t waitForResult() {
+ AutoMutex _l(mMutex);
+ while (!mHaveResult) {
+ mCondition.wait(mMutex);
+ }
+ return mResult;
}
};
@@ -49,18 +123,25 @@
{
signal(SIGPIPE, SIG_IGN);
sp<ProcessState> proc = ProcessState::self();
+ // setThreadPoolMaxThreadCount(0) actually tells the kernel it's
+ // not allowed to spawn any additional threads, but we still spawn
+ // a binder thread from userspace when we call startThreadPool().
+ // This is safe because we only have 2 callbacks, neither of which
+ // block.
+ // See b/36066697 for rationale
+ proc->setThreadPoolMaxThreadCount(0);
proc->startThreadPool();
sp<IServiceManager> sm = defaultServiceManager();
fflush(stdout);
if (sm == NULL) {
- ALOGE("Unable to get default service manager!");
+ ALOGW("Unable to get default service manager!");
aerr << "cmd: Unable to get default service manager!" << endl;
return 20;
}
if (argc == 1) {
- aout << "cmd: no service specified; use -l to list all services" << endl;
+ aerr << "cmd: No service specified; use -l to list all services" << endl;
return 20;
}
@@ -85,12 +166,41 @@
String16 cmd = String16(argv[1]);
sp<IBinder> service = sm->checkService(cmd);
if (service == NULL) {
- aerr << "Can't find service: " << argv[1] << endl;
+ ALOGW("Can't find service %s", argv[1]);
+ aerr << "cmd: Can't find service: " << argv[1] << endl;
return 20;
}
+ sp<MyShellCallback> cb = new MyShellCallback();
+ sp<MyResultReceiver> result = new MyResultReceiver();
+
+#if DEBUG
+ ALOGD("cmd: Invoking %s in=%d, out=%d, err=%d", argv[1], STDIN_FILENO, STDOUT_FILENO,
+ STDERR_FILENO);
+#endif
+
// TODO: block until a result is returned to MyResultReceiver.
- IBinder::shellCommand(service, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, args,
- new MyResultReceiver());
- return 0;
+ status_t err = IBinder::shellCommand(service, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, args,
+ cb, result);
+ if (err < 0) {
+ const char* errstr;
+ switch (err) {
+ case BAD_TYPE: errstr = "Bad type"; break;
+ case FAILED_TRANSACTION: errstr = "Failed transaction"; break;
+ case FDS_NOT_ALLOWED: errstr = "File descriptors not allowed"; break;
+ case UNEXPECTED_NULL: errstr = "Unexpected null"; break;
+ default: errstr = strerror(-err); break;
+ }
+ ALOGW("Failure calling service %s: %s (%d)", argv[1], errstr, -err);
+ aout << "cmd: Failure calling service " << argv[1] << ": " << errstr << " ("
+ << (-err) << ")" << endl;
+ return err;
+ }
+
+ cb->mActive = false;
+ status_t res = result->waitForResult();
+#if DEBUG
+ ALOGD("result=%d", (int)res);
+#endif
+ return res;
}
diff --git a/cmds/dumpstate/.clang-format b/cmds/dumpstate/.clang-format
new file mode 100644
index 0000000..fc4eb1b
--- /dev/null
+++ b/cmds/dumpstate/.clang-format
@@ -0,0 +1,13 @@
+BasedOnStyle: Google
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+
+AccessModifierOffset: -2
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+IndentWidth: 4
+PointerAlignment: Left
+TabWidth: 4
+UseTab: Never
+PenaltyExcessCharacter: 32
diff --git a/cmds/dumpstate/Android.mk b/cmds/dumpstate/Android.mk
index 791a7c4..a407ea2 100644
--- a/cmds/dumpstate/Android.mk
+++ b/cmds/dumpstate/Android.mk
@@ -1,24 +1,178 @@
LOCAL_PATH:= $(call my-dir)
+
+# ================#
+# Common settings #
+# ================#
+# ZipArchive support, the order matters here to get all symbols.
+COMMON_ZIP_LIBRARIES := libziparchive libz libcrypto
+
+# TODO: ideally the tests should depend on a shared dumpstate library, but currently libdumpstate
+# is used to define the device-specific HAL library. Instead, both dumpstate and dumpstate_test
+# shares a lot of common settings
+COMMON_LOCAL_CFLAGS := \
+ -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter
+COMMON_SRC_FILES := \
+ DumpstateInternal.cpp \
+ utils.cpp
+COMMON_SHARED_LIBRARIES := \
+ android.hardware.dumpstate@1.0 \
+ android.hidl.manager@1.0 \
+ libhidlbase \
+ libbase \
+ libbinder \
+ libcutils \
+ libdebuggerd_client \
+ libdumpstateaidl \
+ libdumpstateutil \
+ liblog \
+ libselinux \
+ libutils \
+ $(COMMON_ZIP_LIBRARIES)
+
+# ====================#
+# libdumpstateutil #
+# ====================#
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := libdumpstate_default.cpp
-LOCAL_MODULE := libdumpstate.default
+
+LOCAL_MODULE := libdumpstateutil
+
+LOCAL_CFLAGS := $(COMMON_LOCAL_CFLAGS)
+LOCAL_C_INCLUDES := $(LOCAL_PATH)
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+LOCAL_SRC_FILES := \
+ DumpstateInternal.cpp \
+ DumpstateUtil.cpp
+LOCAL_SHARED_LIBRARIES := \
+ libbase \
+ liblog \
+
+include $(BUILD_SHARED_LIBRARY)
+
+# ====================#
+# libdumpstateheaders #
+# ====================#
+# TODO: this module is necessary so the device-specific libdumpstate implementations do not
+# need to add any other dependency (like libbase). Should go away once dumpstate HAL changes.
+include $(CLEAR_VARS)
+
+LOCAL_EXPORT_C_INCLUDE_DIRS = $(LOCAL_PATH)
+LOCAL_MODULE := libdumpstateheaders
+LOCAL_EXPORT_SHARED_LIBRARY_HEADERS := \
+ $(COMMON_SHARED_LIBRARIES)
+LOCAL_EXPORT_STATIC_LIBRARY_HEADERS := \
+ $(COMMON_STATIC_LIBRARIES)
+# Soong requires that whats is on LOCAL_EXPORTED_ is also on LOCAL_
+LOCAL_SHARED_LIBRARIES := $(LOCAL_EXPORT_SHARED_LIBRARY_HEADERS)
+LOCAL_STATIC_LIBRARIES := $(LOCAL_EXPORT_STATIC_LIBRARY_HEADERS)
+
include $(BUILD_STATIC_LIBRARY)
+# ================ #
+# libdumpstateaidl #
+# =================#
include $(CLEAR_VARS)
-ifdef BOARD_WLAN_DEVICE
-LOCAL_CFLAGS := -DFWDUMP_$(BOARD_WLAN_DEVICE)
-endif
+LOCAL_MODULE := libdumpstateaidl
-LOCAL_SRC_FILES := dumpstate.cpp utils.cpp
+LOCAL_CFLAGS := $(COMMON_LOCAL_CFLAGS)
+
+LOCAL_SHARED_LIBRARIES := \
+ libbinder \
+ libutils
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/binder
+LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/binder
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/binder
+LOCAL_SRC_FILES := \
+ binder/android/os/IDumpstate.aidl \
+ binder/android/os/IDumpstateListener.aidl \
+ binder/android/os/IDumpstateToken.aidl
+
+include $(BUILD_SHARED_LIBRARY)
+
+# ==========#
+# dumpstate #
+# ==========#
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(COMMON_SRC_FILES) \
+ DumpstateService.cpp \
+ dumpstate.cpp
LOCAL_MODULE := dumpstate
-LOCAL_SHARED_LIBRARIES := libcutils liblog libselinux libbase
-# ZipArchive support, the order matters here to get all symbols.
-LOCAL_STATIC_LIBRARIES := libziparchive libz libmincrypt
-LOCAL_HAL_STATIC_LIBRARIES := libdumpstate
-LOCAL_CFLAGS += -Wall -Werror -Wno-unused-parameter
+LOCAL_SHARED_LIBRARIES := $(COMMON_SHARED_LIBRARIES)
+
+LOCAL_STATIC_LIBRARIES := $(COMMON_STATIC_LIBRARIES)
+
+LOCAL_CFLAGS += $(COMMON_LOCAL_CFLAGS)
+
LOCAL_INIT_RC := dumpstate.rc
include $(BUILD_EXECUTABLE)
+
+# ===============#
+# dumpstate_test #
+# ===============#
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := dumpstate_test
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_CFLAGS := $(COMMON_LOCAL_CFLAGS)
+
+LOCAL_SRC_FILES := $(COMMON_SRC_FILES) \
+ DumpstateService.cpp \
+ tests/dumpstate_test.cpp
+
+LOCAL_STATIC_LIBRARIES := $(COMMON_STATIC_LIBRARIES) \
+ libgmock
+
+LOCAL_SHARED_LIBRARIES := $(COMMON_SHARED_LIBRARIES)
+
+include $(BUILD_NATIVE_TEST)
+
+# =======================#
+# dumpstate_test_fixture #
+# =======================#
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := dumpstate_test_fixture
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_CFLAGS := $(COMMON_LOCAL_CFLAGS)
+
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+
+LOCAL_SRC_FILES := \
+ tests/dumpstate_test_fixture.cpp
+
+LOCAL_MODULE_CLASS := NATIVE_TESTS
+
+dumpstate_tests_intermediates := $(local-intermediates-dir)/DATA
+dumpstate_tests_subpath_from_data := nativetest/dumpstate_test_fixture
+dumpstate_tests_root_in_device := /data/$(dumpstate_tests_subpath_from_data)
+dumpstate_tests_root_for_test_zip := $(dumpstate_tests_intermediates)/$(dumpstate_tests_subpath_from_data)
+testdata_files := $(call find-subdir-files, testdata/*)
+
+# Copy test data files to intermediates/DATA for use with LOCAL_PICKUP_FILES
+GEN := $(addprefix $(dumpstate_tests_root_for_test_zip)/, $(testdata_files))
+$(GEN): PRIVATE_PATH := $(LOCAL_PATH)
+$(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@
+$(GEN): $(dumpstate_tests_root_for_test_zip)/testdata/% : $(LOCAL_PATH)/testdata/%
+ $(transform-generated-source)
+LOCAL_GENERATED_SOURCES += $(GEN)
+
+# Copy test data files again to $OUT/data so the tests can be run with adb sync
+# TODO: the build system should do this automatically
+GEN := $(addprefix $(TARGET_OUT_DATA)/$(dumpstate_tests_subpath_from_data)/, $(testdata_files))
+$(GEN): PRIVATE_PATH := $(LOCAL_PATH)
+$(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@
+$(GEN): $(TARGET_OUT_DATA)/$(dumpstate_tests_subpath_from_data)/testdata/% : $(LOCAL_PATH)/testdata/%
+ $(transform-generated-source)
+LOCAL_GENERATED_SOURCES += $(GEN)
+
+LOCAL_PICKUP_FILES := $(dumpstate_tests_intermediates)
+
+include $(BUILD_NATIVE_TEST)
diff --git a/cmds/dumpstate/DumpstateInternal.cpp b/cmds/dumpstate/DumpstateInternal.cpp
new file mode 100644
index 0000000..0343277
--- /dev/null
+++ b/cmds/dumpstate/DumpstateInternal.cpp
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#define LOG_TAG "dumpstate"
+
+#include "DumpstateInternal.h"
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/capability.h>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <cutils/log.h>
+#include <private/android_filesystem_config.h>
+
+uint64_t Nanotime() {
+ timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return static_cast<uint64_t>(ts.tv_sec * NANOS_PER_SEC + ts.tv_nsec);
+}
+
+// Switches to non-root user and group.
+bool DropRootUser() {
+ if (getgid() == AID_SHELL && getuid() == AID_SHELL) {
+ MYLOGD("drop_root_user(): already running as Shell\n");
+ return true;
+ }
+ /* ensure we will keep capabilities when we drop root */
+ if (prctl(PR_SET_KEEPCAPS, 1) < 0) {
+ MYLOGE("prctl(PR_SET_KEEPCAPS) failed: %s\n", strerror(errno));
+ return false;
+ }
+
+ gid_t groups[] = {AID_LOG, AID_SDCARD_R, AID_SDCARD_RW, AID_MOUNT,
+ AID_INET, AID_NET_BW_STATS, AID_READPROC, AID_BLUETOOTH};
+ if (setgroups(sizeof(groups) / sizeof(groups[0]), groups) != 0) {
+ MYLOGE("Unable to setgroups, aborting: %s\n", strerror(errno));
+ return false;
+ }
+ if (setgid(AID_SHELL) != 0) {
+ MYLOGE("Unable to setgid, aborting: %s\n", strerror(errno));
+ return false;
+ }
+ if (setuid(AID_SHELL) != 0) {
+ MYLOGE("Unable to setuid, aborting: %s\n", strerror(errno));
+ return false;
+ }
+
+ struct __user_cap_header_struct capheader;
+ struct __user_cap_data_struct capdata[2];
+ memset(&capheader, 0, sizeof(capheader));
+ memset(&capdata, 0, sizeof(capdata));
+ capheader.version = _LINUX_CAPABILITY_VERSION_3;
+ capheader.pid = 0;
+
+ capdata[CAP_TO_INDEX(CAP_SYSLOG)].permitted = CAP_TO_MASK(CAP_SYSLOG);
+ capdata[CAP_TO_INDEX(CAP_SYSLOG)].effective = CAP_TO_MASK(CAP_SYSLOG);
+ capdata[0].inheritable = 0;
+ capdata[1].inheritable = 0;
+
+ if (capset(&capheader, &capdata[0]) < 0) {
+ MYLOGE("capset failed: %s\n", strerror(errno));
+ return false;
+ }
+
+ return true;
+}
+
+int DumpFileFromFdToFd(const std::string& title, const std::string& path_string, int fd, int out_fd,
+ bool dry_run) {
+ const char* path = path_string.c_str();
+ if (!title.empty()) {
+ dprintf(out_fd, "------ %s (%s", title.c_str(), path);
+
+ struct stat st;
+ // Only show the modification time of non-device files.
+ size_t path_len = strlen(path);
+ if ((path_len < 6 || memcmp(path, "/proc/", 6)) &&
+ (path_len < 5 || memcmp(path, "/sys/", 5)) &&
+ (path_len < 3 || memcmp(path, "/d/", 3)) && !fstat(fd, &st)) {
+ char stamp[80];
+ time_t mtime = st.st_mtime;
+ strftime(stamp, sizeof(stamp), "%Y-%m-%d %H:%M:%S", localtime(&mtime));
+ dprintf(out_fd, ": %s", stamp);
+ }
+ dprintf(out_fd, ") ------\n");
+ fsync(out_fd);
+ }
+ if (dry_run) {
+ if (out_fd != STDOUT_FILENO) {
+ // There is no title, but we should still print a dry-run message
+ dprintf(out_fd, "%s: skipped on dry run\n", path);
+ } else if (!title.empty()) {
+ dprintf(out_fd, "\t(skipped on dry run)\n");
+ }
+ fsync(out_fd);
+ return 0;
+ }
+ bool newline = false;
+ fd_set read_set;
+ timeval tm;
+ while (true) {
+ FD_ZERO(&read_set);
+ FD_SET(fd, &read_set);
+ /* Timeout if no data is read for 30 seconds. */
+ tm.tv_sec = 30;
+ tm.tv_usec = 0;
+ uint64_t elapsed = Nanotime();
+ int ret = TEMP_FAILURE_RETRY(select(fd + 1, &read_set, nullptr, nullptr, &tm));
+ if (ret == -1) {
+ dprintf(out_fd, "*** %s: select failed: %s\n", path, strerror(errno));
+ newline = true;
+ break;
+ } else if (ret == 0) {
+ elapsed = Nanotime() - elapsed;
+ dprintf(out_fd, "*** %s: Timed out after %.3fs\n", path, (float)elapsed / NANOS_PER_SEC);
+ newline = true;
+ break;
+ } else {
+ char buffer[65536];
+ ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer, sizeof(buffer)));
+ if (bytes_read > 0) {
+ android::base::WriteFully(out_fd, buffer, bytes_read);
+ newline = (buffer[bytes_read - 1] == '\n');
+ } else {
+ if (bytes_read == -1) {
+ dprintf(out_fd, "*** %s: Failed to read from fd: %s", path, strerror(errno));
+ newline = true;
+ }
+ break;
+ }
+ }
+ }
+ close(fd);
+
+ if (!newline) dprintf(out_fd, "\n");
+ if (!title.empty()) dprintf(out_fd, "\n");
+ return 0;
+}
diff --git a/cmds/dumpstate/DumpstateInternal.h b/cmds/dumpstate/DumpstateInternal.h
new file mode 100644
index 0000000..2f7704d
--- /dev/null
+++ b/cmds/dumpstate/DumpstateInternal.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+#ifndef FRAMEWORK_NATIVE_CMD_DUMPSTATE_INTERNAL_H_
+#define FRAMEWORK_NATIVE_CMD_DUMPSTATE_INTERNAL_H_
+
+#include <cstdint>
+#include <string>
+
+// TODO: rename macros to DUMPSTATE_LOGXXX
+#ifndef MYLOGD
+#define MYLOGD(...) \
+ fprintf(stderr, __VA_ARGS__); \
+ ALOGD(__VA_ARGS__);
+#endif
+
+#ifndef MYLOGI
+#define MYLOGI(...) \
+ fprintf(stderr, __VA_ARGS__); \
+ ALOGI(__VA_ARGS__);
+#endif
+
+#ifndef MYLOGE
+#define MYLOGE(...) \
+ fprintf(stderr, __VA_ARGS__); \
+ ALOGE(__VA_ARGS__);
+#endif
+
+// Internal functions used by .cpp files on multiple build targets.
+// TODO: move to android::os::dumpstate::internal namespace
+
+// TODO: use functions from <chrono> instead
+const uint64_t NANOS_PER_SEC = 1000000000;
+uint64_t Nanotime();
+
+// Switches to non-root user and group.
+bool DropRootUser();
+
+// TODO: move to .cpp as static once is not used by utils.cpp anymore.
+int DumpFileFromFdToFd(const std::string& title, const std::string& path_string, int fd, int out_fd,
+ bool dry_run = false);
+
+#endif // FRAMEWORK_NATIVE_CMD_DUMPSTATE_INTERNAL_H_
diff --git a/cmds/dumpstate/DumpstateService.cpp b/cmds/dumpstate/DumpstateService.cpp
new file mode 100644
index 0000000..efe0466
--- /dev/null
+++ b/cmds/dumpstate/DumpstateService.cpp
@@ -0,0 +1,106 @@
+/**
+ * Copyright (c) 2016, 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.
+ */
+
+#define LOG_TAG "dumpstate"
+
+#include "DumpstateService.h"
+
+#include <android-base/stringprintf.h>
+
+#include "android/os/BnDumpstate.h"
+
+#include "DumpstateInternal.h"
+
+namespace android {
+namespace os {
+
+namespace {
+class DumpstateToken : public BnDumpstateToken {};
+}
+
+DumpstateService::DumpstateService() : ds_(Dumpstate::GetInstance()) {
+}
+
+char const* DumpstateService::getServiceName() {
+ return "dumpstate";
+}
+
+status_t DumpstateService::Start() {
+ IPCThreadState::self()->disableBackgroundScheduling(true);
+ status_t ret = BinderService<DumpstateService>::publish();
+ if (ret != android::OK) {
+ return ret;
+ }
+ sp<ProcessState> ps(ProcessState::self());
+ ps->startThreadPool();
+ ps->giveThreadPoolName();
+ return android::OK;
+}
+
+binder::Status DumpstateService::setListener(const std::string& name,
+ const sp<IDumpstateListener>& listener,
+ sp<IDumpstateToken>* returned_token) {
+ *returned_token = nullptr;
+ if (name.empty()) {
+ MYLOGE("setListener(): name not set\n");
+ return binder::Status::ok();
+ }
+ if (listener == nullptr) {
+ MYLOGE("setListener(): listener not set\n");
+ return binder::Status::ok();
+ }
+ std::lock_guard<std::mutex> lock(lock_);
+ if (ds_.listener_ != nullptr) {
+ MYLOGE("setListener(%s): already set (%s)\n", name.c_str(), ds_.listener_name_.c_str());
+ return binder::Status::ok();
+ }
+
+ ds_.listener_name_ = name;
+ ds_.listener_ = listener;
+ *returned_token = new DumpstateToken();
+
+ return binder::Status::ok();
+}
+
+status_t DumpstateService::dump(int fd, const Vector<String16>&) {
+ dprintf(fd, "id: %d\n", ds_.id_);
+ dprintf(fd, "pid: %d\n", ds_.pid_);
+ dprintf(fd, "update_progress: %s\n", ds_.update_progress_ ? "true" : "false");
+ dprintf(fd, "update_progress_threshold: %d\n", ds_.update_progress_threshold_);
+ dprintf(fd, "last_updated_progress: %d\n", ds_.last_updated_progress_);
+ dprintf(fd, "progress:\n");
+ ds_.progress_->Dump(fd, " ");
+ dprintf(fd, "args: %s\n", ds_.args_.c_str());
+ dprintf(fd, "extra_options: %s\n", ds_.extra_options_.c_str());
+ dprintf(fd, "version: %s\n", ds_.version_.c_str());
+ dprintf(fd, "bugreport_dir: %s\n", ds_.bugreport_dir_.c_str());
+ dprintf(fd, "screenshot_path: %s\n", ds_.screenshot_path_.c_str());
+ dprintf(fd, "log_path: %s\n", ds_.log_path_.c_str());
+ dprintf(fd, "tmp_path: %s\n", ds_.tmp_path_.c_str());
+ dprintf(fd, "path: %s\n", ds_.path_.c_str());
+ dprintf(fd, "extra_options: %s\n", ds_.extra_options_.c_str());
+ dprintf(fd, "base_name: %s\n", ds_.base_name_.c_str());
+ dprintf(fd, "name: %s\n", ds_.name_.c_str());
+ dprintf(fd, "now: %ld\n", ds_.now_);
+ dprintf(fd, "is_zipping: %s\n", ds_.IsZipping() ? "true" : "false");
+ dprintf(fd, "listener: %s\n", ds_.listener_name_.c_str());
+ dprintf(fd, "notification title: %s\n", ds_.notification_title.c_str());
+ dprintf(fd, "notification description: %s\n", ds_.notification_description.c_str());
+
+ return NO_ERROR;
+}
+} // namespace os
+} // namespace android
diff --git a/cmds/dumpstate/DumpstateService.h b/cmds/dumpstate/DumpstateService.h
new file mode 100644
index 0000000..4352d3d
--- /dev/null
+++ b/cmds/dumpstate/DumpstateService.h
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2016, 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.
+ */
+
+#ifndef ANDROID_OS_DUMPSTATE_H_
+#define ANDROID_OS_DUMPSTATE_H_
+
+#include <mutex>
+#include <vector>
+
+#include <binder/BinderService.h>
+
+#include "android/os/BnDumpstate.h"
+#include "android/os/BnDumpstateToken.h"
+#include "dumpstate.h"
+
+namespace android {
+namespace os {
+
+class DumpstateService : public BinderService<DumpstateService>, public BnDumpstate {
+ public:
+ DumpstateService();
+
+ static status_t Start();
+ static char const* getServiceName();
+
+ status_t dump(int fd, const Vector<String16>& args) override;
+ binder::Status setListener(const std::string& name, const sp<IDumpstateListener>& listener,
+ sp<IDumpstateToken>* returned_token) override;
+
+ private:
+ Dumpstate& ds_;
+ std::mutex lock_;
+};
+
+} // namespace os
+} // namespace android
+
+#endif // ANDROID_OS_DUMPSTATE_H_
diff --git a/cmds/dumpstate/DumpstateUtil.cpp b/cmds/dumpstate/DumpstateUtil.cpp
new file mode 100644
index 0000000..26702c4
--- /dev/null
+++ b/cmds/dumpstate/DumpstateUtil.cpp
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#define LOG_TAG "dumpstate"
+
+#include "DumpstateUtil.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <cutils/log.h>
+
+#include "DumpstateInternal.h"
+
+namespace android {
+namespace os {
+namespace dumpstate {
+
+namespace {
+
+static constexpr const char* kSuPath = "/system/xbin/su";
+
+static bool waitpid_with_timeout(pid_t pid, int timeout_seconds, int* status) {
+ sigset_t child_mask, old_mask;
+ sigemptyset(&child_mask);
+ sigaddset(&child_mask, SIGCHLD);
+
+ if (sigprocmask(SIG_BLOCK, &child_mask, &old_mask) == -1) {
+ printf("*** sigprocmask failed: %s\n", strerror(errno));
+ return false;
+ }
+
+ timespec ts;
+ ts.tv_sec = timeout_seconds;
+ ts.tv_nsec = 0;
+ int ret = TEMP_FAILURE_RETRY(sigtimedwait(&child_mask, NULL, &ts));
+ int saved_errno = errno;
+ // Set the signals back the way they were.
+ if (sigprocmask(SIG_SETMASK, &old_mask, NULL) == -1) {
+ printf("*** sigprocmask failed: %s\n", strerror(errno));
+ if (ret == 0) {
+ return false;
+ }
+ }
+ if (ret == -1) {
+ errno = saved_errno;
+ if (errno == EAGAIN) {
+ errno = ETIMEDOUT;
+ } else {
+ printf("*** sigtimedwait failed: %s\n", strerror(errno));
+ }
+ return false;
+ }
+
+ pid_t child_pid = waitpid(pid, status, WNOHANG);
+ if (child_pid != pid) {
+ if (child_pid != -1) {
+ printf("*** Waiting for pid %d, got pid %d instead\n", pid, child_pid);
+ } else {
+ printf("*** waitpid failed: %s\n", strerror(errno));
+ }
+ return false;
+ }
+ return true;
+}
+} // unnamed namespace
+
+CommandOptions CommandOptions::DEFAULT = CommandOptions::WithTimeout(10).Build();
+CommandOptions CommandOptions::AS_ROOT = CommandOptions::WithTimeout(10).AsRoot().Build();
+
+CommandOptions::CommandOptionsBuilder::CommandOptionsBuilder(int64_t timeout) : values(timeout) {
+}
+
+CommandOptions::CommandOptionsBuilder& CommandOptions::CommandOptionsBuilder::Always() {
+ values.always_ = true;
+ return *this;
+}
+
+CommandOptions::CommandOptionsBuilder& CommandOptions::CommandOptionsBuilder::AsRoot() {
+ values.account_mode_ = SU_ROOT;
+ return *this;
+}
+
+CommandOptions::CommandOptionsBuilder& CommandOptions::CommandOptionsBuilder::DropRoot() {
+ values.account_mode_ = DROP_ROOT;
+ return *this;
+}
+
+CommandOptions::CommandOptionsBuilder& CommandOptions::CommandOptionsBuilder::RedirectStderr() {
+ values.output_mode_ = REDIRECT_TO_STDERR;
+ return *this;
+}
+
+CommandOptions::CommandOptionsBuilder& CommandOptions::CommandOptionsBuilder::Log(
+ const std::string& message) {
+ values.logging_message_ = message;
+ return *this;
+}
+
+CommandOptions CommandOptions::CommandOptionsBuilder::Build() {
+ return CommandOptions(values);
+}
+
+CommandOptions::CommandOptionsValues::CommandOptionsValues(int64_t timeout)
+ : timeout_(timeout),
+ always_(false),
+ account_mode_(DONT_DROP_ROOT),
+ output_mode_(NORMAL_OUTPUT),
+ logging_message_("") {
+}
+
+CommandOptions::CommandOptions(const CommandOptionsValues& values) : values(values) {
+}
+
+int64_t CommandOptions::Timeout() const {
+ return values.timeout_;
+}
+
+bool CommandOptions::Always() const {
+ return values.always_;
+}
+
+PrivilegeMode CommandOptions::PrivilegeMode() const {
+ return values.account_mode_;
+}
+
+OutputMode CommandOptions::OutputMode() const {
+ return values.output_mode_;
+}
+
+std::string CommandOptions::LoggingMessage() const {
+ return values.logging_message_;
+}
+
+CommandOptions::CommandOptionsBuilder CommandOptions::WithTimeout(int64_t timeout) {
+ return CommandOptions::CommandOptionsBuilder(timeout);
+}
+
+std::string PropertiesHelper::build_type_ = "";
+int PropertiesHelper::dry_run_ = -1;
+
+bool PropertiesHelper::IsUserBuild() {
+ if (build_type_.empty()) {
+ build_type_ = android::base::GetProperty("ro.build.type", "user");
+ }
+ return "user" == build_type_;
+}
+
+bool PropertiesHelper::IsDryRun() {
+ if (dry_run_ == -1) {
+ dry_run_ = android::base::GetBoolProperty("dumpstate.dry_run", false) ? 1 : 0;
+ }
+ return dry_run_ == 1;
+}
+
+int DumpFileToFd(int out_fd, const std::string& title, const std::string& path) {
+ int fd = TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC));
+ if (fd < 0) {
+ int err = errno;
+ if (title.empty()) {
+ dprintf(out_fd, "*** Error dumping %s: %s\n", path.c_str(), strerror(err));
+ } else {
+ dprintf(out_fd, "*** Error dumping %s (%s): %s\n", path.c_str(), title.c_str(),
+ strerror(err));
+ }
+ fsync(out_fd);
+ return -1;
+ }
+ return DumpFileFromFdToFd(title, path, fd, out_fd, PropertiesHelper::IsDryRun());
+}
+
+int RunCommandToFd(int fd, const std::string& title, const std::vector<std::string>& full_command,
+ const CommandOptions& options) {
+ if (full_command.empty()) {
+ MYLOGE("No arguments on RunCommandToFd(%s)\n", title.c_str());
+ return -1;
+ }
+
+ int size = full_command.size() + 1; // null terminated
+ int starting_index = 0;
+ if (options.PrivilegeMode() == SU_ROOT) {
+ starting_index = 2; // "su" "root"
+ size += starting_index;
+ }
+
+ std::vector<const char*> args;
+ args.resize(size);
+
+ std::string command_string;
+ if (options.PrivilegeMode() == SU_ROOT) {
+ args[0] = kSuPath;
+ command_string += kSuPath;
+ args[1] = "root";
+ command_string += " root ";
+ }
+ for (size_t i = 0; i < full_command.size(); i++) {
+ args[i + starting_index] = full_command[i].data();
+ command_string += args[i + starting_index];
+ if (i != full_command.size() - 1) {
+ command_string += " ";
+ }
+ }
+ args[size - 1] = nullptr;
+
+ const char* command = command_string.c_str();
+
+ if (options.PrivilegeMode() == SU_ROOT && PropertiesHelper::IsUserBuild()) {
+ dprintf(fd, "Skipping '%s' on user build.\n", command);
+ return 0;
+ }
+
+ if (!title.empty()) {
+ dprintf(fd, "------ %s (%s) ------\n", title.c_str(), command);
+ fsync(fd);
+ }
+
+ const std::string& logging_message = options.LoggingMessage();
+ if (!logging_message.empty()) {
+ MYLOGI(logging_message.c_str(), command_string.c_str());
+ }
+
+ bool silent = (options.OutputMode() == REDIRECT_TO_STDERR);
+ bool redirecting_to_fd = STDOUT_FILENO != fd;
+
+ if (PropertiesHelper::IsDryRun() && !options.Always()) {
+ if (!title.empty()) {
+ dprintf(fd, "\t(skipped on dry run)\n");
+ } else if (redirecting_to_fd) {
+ // There is no title, but we should still print a dry-run message
+ dprintf(fd, "%s: skipped on dry run\n", command_string.c_str());
+ }
+ fsync(fd);
+ return 0;
+ }
+
+ const char* path = args[0];
+
+ uint64_t start = Nanotime();
+ pid_t pid = fork();
+
+ /* handle error case */
+ if (pid < 0) {
+ if (!silent) dprintf(fd, "*** fork: %s\n", strerror(errno));
+ MYLOGE("*** fork: %s\n", strerror(errno));
+ return pid;
+ }
+
+ /* handle child case */
+ if (pid == 0) {
+ if (options.PrivilegeMode() == DROP_ROOT && !DropRootUser()) {
+ if (!silent) {
+ dprintf(fd, "*** failed to drop root before running %s: %s\n", command,
+ strerror(errno));
+ }
+ MYLOGE("*** could not drop root before running %s: %s\n", command, strerror(errno));
+ return -1;
+ }
+
+ if (silent) {
+ // Redirects stdout to stderr
+ TEMP_FAILURE_RETRY(dup2(STDERR_FILENO, STDOUT_FILENO));
+ } else if (redirecting_to_fd) {
+ // Redirect stdout to fd
+ TEMP_FAILURE_RETRY(dup2(fd, STDOUT_FILENO));
+ close(fd);
+ }
+
+ /* make sure the child dies when dumpstate dies */
+ prctl(PR_SET_PDEATHSIG, SIGKILL);
+
+ /* just ignore SIGPIPE, will go down with parent's */
+ struct sigaction sigact;
+ memset(&sigact, 0, sizeof(sigact));
+ sigact.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &sigact, NULL);
+
+ execvp(path, (char**)args.data());
+ // execvp's result will be handled after waitpid_with_timeout() below, but
+ // if it failed, it's safer to exit dumpstate.
+ MYLOGD("execvp on command '%s' failed (error: %s)\n", command, strerror(errno));
+ // Must call _exit (instead of exit), otherwise it will corrupt the zip
+ // file.
+ _exit(EXIT_FAILURE);
+ }
+
+ /* handle parent case */
+ int status;
+ bool ret = waitpid_with_timeout(pid, options.Timeout(), &status);
+ fsync(fd);
+
+ uint64_t elapsed = Nanotime() - start;
+ if (!ret) {
+ if (errno == ETIMEDOUT) {
+ if (!silent)
+ dprintf(fd, "*** command '%s' timed out after %.3fs (killing pid %d)\n", command,
+ static_cast<float>(elapsed) / NANOS_PER_SEC, pid);
+ MYLOGE("*** command '%s' timed out after %.3fs (killing pid %d)\n", command,
+ static_cast<float>(elapsed) / NANOS_PER_SEC, pid);
+ } else {
+ if (!silent)
+ dprintf(fd, "*** command '%s': Error after %.4fs (killing pid %d)\n", command,
+ static_cast<float>(elapsed) / NANOS_PER_SEC, pid);
+ MYLOGE("command '%s': Error after %.4fs (killing pid %d)\n", command,
+ static_cast<float>(elapsed) / NANOS_PER_SEC, pid);
+ }
+ kill(pid, SIGTERM);
+ if (!waitpid_with_timeout(pid, 5, nullptr)) {
+ kill(pid, SIGKILL);
+ if (!waitpid_with_timeout(pid, 5, nullptr)) {
+ if (!silent)
+ dprintf(fd, "could not kill command '%s' (pid %d) even with SIGKILL.\n",
+ command, pid);
+ MYLOGE("could not kill command '%s' (pid %d) even with SIGKILL.\n", command, pid);
+ }
+ }
+ return -1;
+ }
+
+ if (WIFSIGNALED(status)) {
+ if (!silent)
+ dprintf(fd, "*** command '%s' failed: killed by signal %d\n", command, WTERMSIG(status));
+ MYLOGE("*** command '%s' failed: killed by signal %d\n", command, WTERMSIG(status));
+ } else if (WIFEXITED(status) && WEXITSTATUS(status) > 0) {
+ status = WEXITSTATUS(status);
+ if (!silent) dprintf(fd, "*** command '%s' failed: exit code %d\n", command, status);
+ MYLOGE("*** command '%s' failed: exit code %d\n", command, status);
+ }
+
+ return status;
+}
+
+int GetPidByName(const std::string& ps_name) {
+ DIR* proc_dir;
+ struct dirent* ps;
+ unsigned int pid;
+ std::string cmdline;
+
+ if (!(proc_dir = opendir("/proc"))) {
+ MYLOGE("Can't open /proc\n");
+ return -1;
+ }
+
+ while ((ps = readdir(proc_dir))) {
+ if (!(pid = atoi(ps->d_name))) {
+ continue;
+ }
+ android::base::ReadFileToString("/proc/" + std::string(ps->d_name) + "/cmdline", &cmdline);
+ if (cmdline.find(ps_name) == std::string::npos) {
+ continue;
+ } else {
+ closedir(proc_dir);
+ return pid;
+ }
+ }
+ MYLOGE("can't find the pid\n");
+ closedir(proc_dir);
+ return -1;
+}
+
+} // namespace dumpstate
+} // namespace os
+} // namespace android
diff --git a/cmds/dumpstate/DumpstateUtil.h b/cmds/dumpstate/DumpstateUtil.h
new file mode 100644
index 0000000..5a8ce5b
--- /dev/null
+++ b/cmds/dumpstate/DumpstateUtil.h
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+#ifndef ANDROID_OS_DUMPSTATE_UTIL_H_
+#define ANDROID_OS_DUMPSTATE_UTIL_H_
+
+#include <cstdint>
+#include <string>
+
+namespace android {
+namespace os {
+namespace dumpstate {
+
+/*
+ * Defines the Linux account that should be executing a command.
+ */
+enum PrivilegeMode {
+ /* Explicitly change the `uid` and `gid` to be `shell`.*/
+ DROP_ROOT,
+ /* Don't change the `uid` and `gid`. */
+ DONT_DROP_ROOT,
+ /* Prefix the command with `/PATH/TO/su root`. Won't work non user builds. */
+ SU_ROOT
+};
+
+/*
+ * Defines what should happen with the main output stream (`stdout` or fd) of a command.
+ */
+enum OutputMode {
+ /* Don't change main output. */
+ NORMAL_OUTPUT,
+ /* Redirect main output to `stderr`. */
+ REDIRECT_TO_STDERR
+};
+
+/*
+ * Value object used to set command options.
+ *
+ * Typically constructed using a builder with chained setters. Examples:
+ *
+ * CommandOptions::WithTimeout(20).AsRoot().Build();
+ * CommandOptions::WithTimeout(10).Always().RedirectStderr().Build();
+ *
+ * Although the builder could be used to dynamically set values. Example:
+ *
+ * CommandOptions::CommandOptionsBuilder options =
+ * CommandOptions::WithTimeout(10);
+ * if (!is_user_build()) {
+ * options.AsRoot();
+ * }
+ * RunCommand("command", {"args"}, options.Build());
+ */
+class CommandOptions {
+ private:
+ class CommandOptionsValues {
+ private:
+ CommandOptionsValues(int64_t timeout);
+
+ int64_t timeout_;
+ bool always_;
+ PrivilegeMode account_mode_;
+ OutputMode output_mode_;
+ std::string logging_message_;
+
+ friend class CommandOptions;
+ friend class CommandOptionsBuilder;
+ };
+
+ CommandOptions(const CommandOptionsValues& values);
+
+ const CommandOptionsValues values;
+
+ public:
+ class CommandOptionsBuilder {
+ public:
+ /* Sets the command to always run, even on `dry-run` mode. */
+ CommandOptionsBuilder& Always();
+ /* Sets the command's PrivilegeMode as `SU_ROOT` */
+ CommandOptionsBuilder& AsRoot();
+ /* Sets the command's PrivilegeMode as `DROP_ROOT` */
+ CommandOptionsBuilder& DropRoot();
+ /* Sets the command's OutputMode as `REDIRECT_TO_STDERR` */
+ CommandOptionsBuilder& RedirectStderr();
+ /* When not empty, logs a message before executing the command.
+ * Must contain a `%s`, which will be replaced by the full command line, and end on `\n`. */
+ CommandOptionsBuilder& Log(const std::string& message);
+ /* Builds the command options. */
+ CommandOptions Build();
+
+ private:
+ CommandOptionsBuilder(int64_t timeout);
+ CommandOptionsValues values;
+ friend class CommandOptions;
+ };
+
+ /** Gets the command timeout, in seconds. */
+ int64_t Timeout() const;
+ /* Checks whether the command should always be run, even on dry-run mode. */
+ bool Always() const;
+ /** Gets the PrivilegeMode of the command. */
+ PrivilegeMode PrivilegeMode() const;
+ /** Gets the OutputMode of the command. */
+ OutputMode OutputMode() const;
+ /** Gets the logging message header, it any. */
+ std::string LoggingMessage() const;
+
+ /** Creates a builder with the requied timeout. */
+ static CommandOptionsBuilder WithTimeout(int64_t timeout);
+
+ // Common options.
+ static CommandOptions DEFAULT;
+ static CommandOptions AS_ROOT;
+};
+
+/*
+ * System properties helper.
+ */
+class PropertiesHelper {
+ friend class DumpstateBaseTest;
+
+ public:
+ /*
+ * Gets whether device is running a `user` build.
+ */
+ static bool IsUserBuild();
+
+ /*
+ * When running in dry-run mode, skips the real dumps and just print the section headers.
+ *
+ * Useful when debugging dumpstate or other bugreport-related activities.
+ *
+ * Dry-run mode is enabled by setting the system property `dumpstate.dry_run` to true.
+ */
+ static bool IsDryRun();
+
+ private:
+ static std::string build_type_;
+ static int dry_run_;
+};
+
+/*
+ * Forks a command, waits for it to finish, and returns its status.
+ *
+ * |fd| file descriptor that receives the command's 'stdout'.
+ * |title| description of the command printed on `stdout` (or empty to skip
+ * description).
+ * |full_command| array containing the command (first entry) and its arguments.
+ * Must contain at least one element.
+ * |options| optional argument defining the command's behavior.
+ */
+int RunCommandToFd(int fd, const std::string& title, const std::vector<std::string>& full_command,
+ const CommandOptions& options = CommandOptions::DEFAULT);
+
+/*
+ * Dumps the contents of a file into a file descriptor.
+ *
+ * |fd| file descriptor where the file is dumped into.
+ * |title| description of the command printed on `stdout` (or empty to skip
+ * description).
+ * |path| location of the file to be dumped.
+ */
+int DumpFileToFd(int fd, const std::string& title, const std::string& path);
+
+/*
+ * Finds the process id by process name.
+ * |ps_name| the process name we want to search for
+ */
+int GetPidByName(const std::string& ps_name);
+
+} // namespace dumpstate
+} // namespace os
+} // namespace android
+
+#endif // ANDROID_OS_DUMPSTATE_UTIL_H_
diff --git a/cmds/dumpstate/README.md b/cmds/dumpstate/README.md
new file mode 100644
index 0000000..0302ea5
--- /dev/null
+++ b/cmds/dumpstate/README.md
@@ -0,0 +1,103 @@
+# `dumpstate` development tips
+
+## To build `dumpstate`
+
+Do a full build first:
+
+```
+m -j dumpstate
+```
+
+Then incremental ones:
+
+```
+mmm -j frameworks/native/cmds/dumpstate
+```
+
+If you're working on device-specific code, you might need to build them as well. Example:
+
+```
+mmm -j frameworks/native/cmds/dumpstate device/acme/secret_device/dumpstate/ hardware/interfaces/dumpstate
+```
+
+## To build, deploy, and take a bugreport
+
+```
+mmm -j frameworks/native/cmds/dumpstate && adb push ${OUT}/system/bin/dumpstate system/bin && adb shell am bug-report
+```
+
+## To build, deploy, and run unit tests
+
+First create `/data/nativetest`:
+
+```
+adb shell mkdir /data/nativetest
+```
+
+Then run:
+
+```
+mmm -j frameworks/native/cmds/dumpstate/ && adb push ${OUT}/data/nativetest/dumpstate_test* /data/nativetest && adb shell /data/nativetest/dumpstate_test/dumpstate_test
+```
+
+And to run just one test (for example, `DumpstateTest.RunCommandNoArgs`):
+
+```
+mmm -j frameworks/native/cmds/dumpstate/ && adb push ${OUT}/data/nativetest/dumpstate_test* /data/nativetest && adb shell /data/nativetest/dumpstate_test/dumpstate_test --gtest_filter=DumpstateTest.RunCommandNoArgs
+```
+
+## To take quick bugreports
+
+```
+adb shell setprop dumpstate.dry_run true
+```
+
+## To change the `dumpstate` version
+
+```
+adb shell setprop dumpstate.version VERSION_NAME
+```
+
+Example:
+
+```
+adb shell setprop dumpstate.version split-dumpsys && adb shell dumpstate -v
+```
+
+
+Then to restore the default version:
+
+```
+adb shell setprop dumpstate.version default
+```
+
+## Code style and formatting
+
+Use the style defined at the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html)
+and make sure to run the following command prior to `repo upload`:
+
+```
+git clang-format --style=file HEAD~
+```
+
+## Useful Bash tricks
+
+```
+export BR_DIR=/bugreports
+
+alias br='adb shell cmd activity bug-report'
+alias ls_bugs='adb shell ls -l ${BR_DIR}/'
+
+unzip_bug() {
+ adb pull ${BR_DIR}/$1 && emacs $1 && mv $1 /tmp
+}
+
+less_bug() {
+ adb pull ${BR_DIR}/$1 && less $1 && mv $1 /tmp
+}
+
+rm_bugs() {
+ if [ -z "${BR_DIR}" ] ; then echo "Variable BR_DIR not set"; else adb shell rm -rf ${BR_DIR}/*; fi
+}
+
+```
diff --git a/cmds/dumpstate/binder/android/os/IDumpstate.aidl b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
new file mode 100644
index 0000000..4becccf
--- /dev/null
+++ b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2016, 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.
+ */
+
+package android.os;
+
+import android.os.IDumpstateListener;
+import android.os.IDumpstateToken;
+
+/**
+ * Binder interface for the currently running dumpstate process.
+ * {@hide}
+ */
+interface IDumpstate {
+
+ /*
+ * Sets the listener for this dumpstate progress.
+ *
+ * Returns a token used to monitor dumpstate death, or `nullptr` if the listener was already
+ * set (the listener behaves like a Highlander: There Can be Only One).
+ */
+ IDumpstateToken setListener(@utf8InCpp String name, IDumpstateListener listener);
+}
diff --git a/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl b/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl
new file mode 100644
index 0000000..32717f4
--- /dev/null
+++ b/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2016, 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.
+ */
+
+package android.os;
+
+/**
+ * Listener for dumpstate events.
+ *
+ * {@hide}
+ */
+interface IDumpstateListener {
+ void onProgressUpdated(int progress);
+ void onMaxProgressUpdated(int maxProgress);
+}
diff --git a/cmds/dumpstate/binder/android/os/IDumpstateToken.aidl b/cmds/dumpstate/binder/android/os/IDumpstateToken.aidl
new file mode 100644
index 0000000..7f74ceb
--- /dev/null
+++ b/cmds/dumpstate/binder/android/os/IDumpstateToken.aidl
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2016, 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.
+ */
+
+package android.os;
+
+/**
+ * Token used by the IDumpstateListener to watch for dumpstate death.
+ * {@hide}
+ */
+interface IDumpstateToken {
+}
diff --git a/cmds/dumpstate/bugreport-format.md b/cmds/dumpstate/bugreport-format.md
index ca7d574..b995b80 100644
--- a/cmds/dumpstate/bugreport-format.md
+++ b/cmds/dumpstate/bugreport-format.md
@@ -22,7 +22,7 @@
file as the `ACTION_SEND_MULTIPLE` attachment.
## Version 1.0 (Android N)
-On _Android N (TBD)_, `dumpstate` generates a zip file directly (unless there
+On _Android N (Nougat)_, `dumpstate` generates a zip file directly (unless there
is a failure, in which case it reverts to the flat file that is zipped by
**Shell** and hence the end result is the _v0_ format).
@@ -55,6 +55,10 @@
- `title.txt`: whose value is a single-line summary of the problem.
- `description.txt`: whose value is a multi-line, detailed description of the problem.
+## Android O versions
+On _Android O (OhMightyAndroidWhatsYourNextReleaseName?)_, the following changes were made:
+- The ANR traces are added to the `FS` folder, typically under `FS/data/anr` (version `2.0-dev-1`).
+
## Intermediate versions
During development, the versions will be suffixed with _-devX_ or
_-devX-EXPERIMENTAL_FEATURE_, where _X_ is a number that increases as the
@@ -63,8 +67,8 @@
For example, the initial version during _Android N_ development was
**1.0-dev1**. When `dumpsys` was split in 2 sections but not all tools were
ready to parse that format, the version was named **1.0-dev2**,
-which had to be passed do `dumpsys` explicitly (i.e., trhough a
-`-V 1.0-dev2` argument). Once that format became stable and tools
+which had to be passed to `dumpsys` explicitly (by setting the `dumpstate.version` system property).
+Once that format became stable and tools
knew how to parse it, the default version became **1.0-dev2**.
Similarly, if changes in the file format are made after the initial release of
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 0929d9b..f84d86d 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#define LOG_TAG "dumpstate"
+
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
@@ -35,38 +37,37 @@
#include <unistd.h>
#include <android-base/file.h>
+#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <android/hardware/dumpstate/1.0/IDumpstateDevice.h>
+#include <cutils/native_handle.h>
#include <cutils/properties.h>
+#include <openssl/sha.h>
+#include <private/android_filesystem_config.h>
+#include <private/android_logger.h>
-#include "private/android_filesystem_config.h"
-
-#define LOG_TAG "dumpstate"
-#include <cutils/log.h>
-
+#include "DumpstateInternal.h"
+#include "DumpstateService.h"
#include "dumpstate.h"
-#include "ScopedFd.h"
-#include "ziparchive/zip_writer.h"
-#include "mincrypt/sha256.h"
+using ::android::hardware::dumpstate::V1_0::IDumpstateDevice;
-using android::base::StringPrintf;
+// TODO: remove once moved to namespace
+using android::os::dumpstate::CommandOptions;
+using android::os::dumpstate::DumpFileToFd;
+using android::os::dumpstate::PropertiesHelper;
+using android::os::dumpstate::GetPidByName;
/* read before root is shed */
static char cmdline_buf[16384] = "(unknown)";
static const char *dump_traces_path = NULL;
-// TODO: variables below should be part of dumpstate object
-static unsigned long id;
-static char build_type[PROPERTY_VALUE_MAX];
-static time_t now;
-static std::unique_ptr<ZipWriter> zip_writer;
+// TODO: variables and functions below should be part of dumpstate object
+
static std::set<std::string> mount_points;
void add_mountinfo();
-int control_socket_fd = -1;
-/* suffix of the bugreport files - it's typically the date (when invoked with -d),
- * although it could be changed by the user using a system property */
-static std::string suffix;
#define PSTORE_LAST_KMSG "/sys/fs/pstore/console-ramoops"
#define ALT_PSTORE_LAST_KMSG "/sys/fs/pstore/console-ramoops-0"
@@ -91,41 +92,57 @@
static tombstone_data_t tombstone_data[NUM_TOMBSTONES];
-const std::string ZIP_ROOT_DIR = "FS";
-std::string bugreport_dir;
-
-/*
- * List of supported zip format versions.
- *
- * See bugreport-format.txt for more info.
- */
-static std::string VERSION_DEFAULT = "1.0";
-
-bool is_user_build() {
- return 0 == strncmp(build_type, "user", PROPERTY_VALUE_MAX - 1);
+// TODO: temporary variables and functions used during C++ refactoring
+static Dumpstate& ds = Dumpstate::GetInstance();
+static int RunCommand(const std::string& title, const std::vector<std::string>& fullCommand,
+ const CommandOptions& options = CommandOptions::DEFAULT) {
+ return ds.RunCommand(title, fullCommand, options);
+}
+static void RunDumpsys(const std::string& title, const std::vector<std::string>& dumpsysArgs,
+ const CommandOptions& options = Dumpstate::DEFAULT_DUMPSYS,
+ long dumpsysTimeout = 0) {
+ return ds.RunDumpsys(title, dumpsysArgs, options, dumpsysTimeout);
+}
+static int DumpFile(const std::string& title, const std::string& path) {
+ return ds.DumpFile(title, path);
}
-/* gets the tombstone data, according to the bugreport type: if zipped gets all tombstones,
- * otherwise gets just those modified in the last half an hour. */
+// Relative directory (inside the zip) for all files copied as-is into the bugreport.
+static const std::string ZIP_ROOT_DIR = "FS";
+
+// Must be hardcoded because dumpstate HAL implementation need SELinux access to it
+static const std::string kDumpstateBoardPath = "/bugreports/dumpstate_board.txt";
+static const std::string kLsHalDebugPath = "/bugreports/dumpstate_lshal.txt";
+
+static constexpr char PROPERTY_EXTRA_OPTIONS[] = "dumpstate.options";
+static constexpr char PROPERTY_LAST_ID[] = "dumpstate.last_id";
+static constexpr char PROPERTY_VERSION[] = "dumpstate.version";
+static constexpr char PROPERTY_EXTRA_TITLE[] = "dumpstate.options.title";
+static constexpr char PROPERTY_EXTRA_DESCRIPTION[] = "dumpstate.options.description";
+
+static const CommandOptions AS_ROOT_20 = CommandOptions::WithTimeout(20).AsRoot().Build();
+
+/* gets the tombstone data, according to the bugreport type: if zipped, gets all tombstones;
+ * otherwise, gets just those modified in the last half an hour. */
static void get_tombstone_fds(tombstone_data_t data[NUM_TOMBSTONES]) {
- time_t thirty_minutes_ago = now - 60*30;
+ time_t thirty_minutes_ago = ds.now_ - 60 * 30;
for (size_t i = 0; i < NUM_TOMBSTONES; i++) {
snprintf(data[i].name, sizeof(data[i].name), "%s%02zu", TOMBSTONE_FILE_PREFIX, i);
int fd = TEMP_FAILURE_RETRY(open(data[i].name,
O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK));
struct stat st;
- if (fstat(fd, &st) == 0 && S_ISREG(st.st_mode) &&
- (zip_writer || (time_t) st.st_mtime >= thirty_minutes_ago)) {
- data[i].fd = fd;
+ if (fstat(fd, &st) == 0 && S_ISREG(st.st_mode) && st.st_size > 0 &&
+ (ds.IsZipping() || st.st_mtime >= thirty_minutes_ago)) {
+ data[i].fd = fd;
} else {
- close(fd);
+ close(fd);
data[i].fd = -1;
}
}
}
// for_each_pid() callback to get mount info about a process.
-void do_mountinfo(int pid, const char *name) {
+void do_mountinfo(int pid, const char* name __attribute__((unused))) {
char path[PATH_MAX];
// Gets the the content of the /proc/PID/ns/mnt link, so only unique mount points
@@ -142,7 +159,7 @@
if (mount_points.find(linkname) == mount_points.end()) {
// First time this mount point was found: add it
snprintf(path, sizeof(path), "/proc/%d/mountinfo", pid);
- if (add_zip_entry(ZIP_ROOT_DIR + path, path)) {
+ if (ds.AddZipEntry(ZIP_ROOT_DIR + path, path)) {
mount_points.insert(linkname);
} else {
MYLOGE("Unable to add mountinfo %s to zip file\n", path);
@@ -151,12 +168,12 @@
}
void add_mountinfo() {
- if (!is_zipping()) return;
- const char *title = "MOUNT INFO";
+ if (!ds.IsZipping()) return;
+ std::string title = "MOUNT INFO";
mount_points.clear();
- DurationReporter duration_reporter(title, NULL);
- for_each_pid(do_mountinfo, NULL);
- MYLOGD("%s: %d entries added to zip file\n", title, (int) mount_points.size());
+ DurationReporter duration_reporter(title, true);
+ for_each_pid(do_mountinfo, nullptr);
+ MYLOGD("%s: %d entries added to zip file\n", title.c_str(), (int)mount_points.size());
}
static void dump_dev_files(const char *title, const char *driverpath, const char *filename)
@@ -175,40 +192,13 @@
continue;
}
snprintf(path, sizeof(path), "%s/%s/%s", driverpath, de->d_name, filename);
- dump_file(title, path);
+ DumpFile(title, path);
}
closedir(d);
}
-// return pid of a userspace process. If not found or error, return 0.
-static unsigned int pid_of_process(const char* ps_name) {
- DIR *proc_dir;
- struct dirent *ps;
- unsigned int pid;
- std::string cmdline;
- if (!(proc_dir = opendir("/proc"))) {
- MYLOGE("Can't open /proc\n");
- return 0;
- }
-
- while ((ps = readdir(proc_dir))) {
- if (!(pid = atoi(ps->d_name))) {
- continue;
- }
- android::base::ReadFileToString("/proc/"
- + std::string(ps->d_name) + "/cmdline", &cmdline);
- if (cmdline.find(ps_name) == std::string::npos) {
- continue;
- } else {
- closedir(proc_dir);
- return pid;
- }
- }
- closedir(proc_dir);
- return 0;
-}
// dump anrd's trace and add to the zip file.
// 1. check if anrd is running on this device.
@@ -225,13 +215,13 @@
long long cur_size = 0;
const char *trace_path = "/data/misc/anrd/";
- if (!zip_writer) {
- MYLOGE("Not dumping anrd trace because zip_writer is not set\n");
+ if (!ds.IsZipping()) {
+ MYLOGE("Not dumping anrd trace because it's not a zipped bugreport\n");
return false;
}
// find anrd's pid if it is running.
- pid = pid_of_process("/system/xbin/anrd");
+ pid = GetPidByName("/system/xbin/anrd");
if (pid > 0) {
if (stat(trace_path, &st) == 0) {
@@ -243,7 +233,8 @@
// send SIGUSR1 to the anrd to generate a trace.
sprintf(buf, "%u", pid);
- if (run_command("ANRD_DUMP", 1, "kill", "-SIGUSR1", buf, NULL)) {
+ if (RunCommand("ANRD_DUMP", {"kill", "-SIGUSR1", buf},
+ CommandOptions::WithTimeout(1).Build())) {
MYLOGE("anrd signal timed out. Please manually collect trace\n");
return false;
}
@@ -296,7 +287,7 @@
}
}
// Add to the zip file.
- if (!add_zip_entry("anrd_trace.txt", path)) {
+ if (!ds.AddZipEntry("anrd_trace.txt", path)) {
MYLOGE("Unable to add anrd_trace file %s to zip file\n", path);
} else {
if (remove(path)) {
@@ -312,11 +303,11 @@
}
static void dump_systrace() {
- if (!is_zipping()) {
- MYLOGD("Not dumping systrace because dumpstate is not zipping\n");
+ if (!ds.IsZipping()) {
+ MYLOGD("Not dumping systrace because it's not a zipped bugreport\n");
return;
}
- std::string systrace_path = bugreport_dir + "/systrace-" + suffix + ".txt";
+ std::string systrace_path = ds.GetPath("-systrace.txt");
if (systrace_path.empty()) {
MYLOGE("Not dumping systrace because path is empty\n");
return;
@@ -333,17 +324,17 @@
MYLOGD("Running '/system/bin/atrace --async_dump -o %s', which can take several minutes",
systrace_path.c_str());
- if (run_command("SYSTRACE", 120, "/system/bin/atrace", "--async_dump", "-o",
- systrace_path.c_str(), NULL)) {
+ if (RunCommand("SYSTRACE", {"/system/bin/atrace", "--async_dump", "-o", systrace_path},
+ CommandOptions::WithTimeout(120).Build())) {
MYLOGE("systrace timed out, its zip entry will be incomplete\n");
- // TODO: run_command tries to kill the process, but atrace doesn't die peacefully; ideally,
- // we should call strace to stop itself, but there is no such option yet (just a
- // --async_stop, which stops and dump
- // if (run_command("SYSTRACE", 10, "/system/bin/atrace", "--kill", NULL)) {
- // MYLOGE("could not stop systrace ");
- // }
+ // TODO: RunCommand tries to kill the process, but atrace doesn't die
+ // peacefully; ideally, we should call strace to stop itself, but there is no such option
+ // yet (just a --async_stop, which stops and dump
+ // if (RunCommand("SYSTRACE", {"/system/bin/atrace", "--kill"})) {
+ // MYLOGE("could not stop systrace ");
+ // }
}
- if (!add_zip_entry("systrace.txt", systrace_path)) {
+ if (!ds.AddZipEntry("systrace.txt", systrace_path)) {
MYLOGE("Unable to add systrace file %s to zip file\n", systrace_path.c_str());
} else {
if (remove(systrace_path.c_str())) {
@@ -353,13 +344,13 @@
}
static void dump_raft() {
- if (is_user_build()) {
+ if (PropertiesHelper::IsUserBuild()) {
return;
}
- std::string raft_log_path = bugreport_dir + "/raft_log.txt";
- if (raft_log_path.empty()) {
- MYLOGD("raft_log_path is empty\n");
+ std::string raft_path = ds.GetPath("-raft_log.txt");
+ if (raft_path.empty()) {
+ MYLOGD("raft_path is empty\n");
return;
}
@@ -369,29 +360,30 @@
return;
}
- if (!is_zipping()) {
- // Write compressed and encoded raft logs to stdout if not zip_writer.
- run_command("RAFT LOGS", 600, "logcompressor", "-r", RAFT_DIR, NULL);
+ CommandOptions options = CommandOptions::WithTimeout(600).Build();
+ if (!ds.IsZipping()) {
+ // Write compressed and encoded raft logs to stdout if it's not a zipped bugreport.
+ RunCommand("RAFT LOGS", {"logcompressor", "-r", RAFT_DIR}, options);
return;
}
- run_command("RAFT LOGS", 600, "logcompressor", "-n", "-r", RAFT_DIR,
- "-o", raft_log_path.c_str(), NULL);
- if (!add_zip_entry("raft_log.txt", raft_log_path)) {
- MYLOGE("Unable to add raft log %s to zip file\n", raft_log_path.c_str());
+ RunCommand("RAFT LOGS", {"logcompressor", "-n", "-r", RAFT_DIR, "-o", raft_path}, options);
+ if (!ds.AddZipEntry("raft_log.txt", raft_path)) {
+ MYLOGE("Unable to add raft log %s to zip file\n", raft_path.c_str());
} else {
- if (remove(raft_log_path.c_str())) {
- MYLOGE("Error removing raft file %s: %s\n", raft_log_path.c_str(), strerror(errno));
+ if (remove(raft_path.c_str())) {
+ MYLOGE("Error removing raft file %s: %s\n", raft_path.c_str(), strerror(errno));
}
}
}
/**
- * Finds the last modified file in the directory dir whose name starts with file_prefix
+ * Finds the last modified file in the directory dir whose name starts with file_prefix.
+ *
* Function returns empty string when it does not find a file
*/
-static std::string get_last_modified_file_matching_prefix(const std::string& dir,
- const std::string& file_prefix) {
+static std::string GetLastModifiedFileWithPrefix(const std::string& dir,
+ const std::string& file_prefix) {
std::unique_ptr<DIR, decltype(&closedir)> d(opendir(dir.c_str()), closedir);
if (d == nullptr) {
MYLOGD("Error %d opening %s\n", errno, dir.c_str());
@@ -400,7 +392,7 @@
// Find the newest file matching the file_prefix in dir
struct dirent *de;
- time_t last_modified = 0;
+ time_t last_modified_time = 0;
std::string last_modified_file = "";
struct stat s;
@@ -412,39 +404,43 @@
file = dir + "/" + file;
int ret = stat(file.c_str(), &s);
- if ((ret == 0) && (s.st_mtime > last_modified)) {
+ if ((ret == 0) && (s.st_mtime > last_modified_time)) {
last_modified_file = file;
- last_modified = s.st_mtime;
+ last_modified_time = s.st_mtime;
}
}
return last_modified_file;
}
-void dump_modem_logs() {
- DurationReporter duration_reporter("dump_modem_logs");
- if (is_user_build()) {
+static void DumpModemLogs() {
+ DurationReporter durationReporter("DUMP MODEM LOGS");
+ if (PropertiesHelper::IsUserBuild()) {
return;
}
- if (!is_zipping()) {
+ if (!ds.IsZipping()) {
MYLOGD("Not dumping modem logs. dumpstate is not generating a zipping bugreport\n");
return;
}
- char property[PROPERTY_VALUE_MAX];
- property_get("ro.radio.log_prefix", property, "");
- std::string file_prefix = std::string(property);
+ std::string file_prefix = android::base::GetProperty("ro.radio.log_prefix", "");
+
if(file_prefix.empty()) {
MYLOGD("No modem log : file_prefix is empty\n");
return;
}
- MYLOGD("dump_modem_logs: directory is %s and file_prefix is %s\n",
- bugreport_dir.c_str(), file_prefix.c_str());
+ // TODO: b/33820081 we need to provide a right way to dump modem logs.
+ std::string radio_bugreport_dir = android::base::GetProperty("ro.radio.log_loc", "");
+ if (radio_bugreport_dir.empty()) {
+ radio_bugreport_dir = dirname(ds.GetPath("").c_str());
+ }
- std::string modem_log_file =
- get_last_modified_file_matching_prefix(bugreport_dir, file_prefix);
+ MYLOGD("DumpModemLogs: directory is %s and file_prefix is %s\n",
+ radio_bugreport_dir.c_str(), file_prefix.c_str());
+
+ std::string modem_log_file = GetLastModifiedFileWithPrefix(radio_bugreport_dir, file_prefix);
struct stat s;
if (modem_log_file.empty() || stat(modem_log_file.c_str(), &s) != 0) {
@@ -453,7 +449,7 @@
}
std::string filename = basename(modem_log_file.c_str());
- if (!add_zip_entry(filename, modem_log_file)) {
+ if (!ds.AddZipEntry(filename, modem_log_file)) {
MYLOGE("Unable to add modem log %s to zip file\n", modem_log_file.c_str());
} else {
MYLOGD("Modem Log %s is added to zip\n", modem_log_file.c_str());
@@ -472,7 +468,7 @@
return strcmp(path + len - sizeof(stat) + 1, stat); /* .../stat? */
}
-static bool skip_none(const char *path) {
+static bool skip_none(const char* path __attribute__((unused))) {
return false;
}
@@ -631,11 +627,10 @@
/ fields[__STAT_IO_TICKS];
if (!write_perf && !write_ios) {
- printf("%s: perf(ios) rd: %luKB/s(%lu/s) q: %u\n",
- path, read_perf, read_ios, queue);
+ printf("%s: perf(ios) rd: %luKB/s(%lu/s) q: %u\n", path, read_perf, read_ios, queue);
} else {
- printf("%s: perf(ios) rd: %luKB/s(%lu/s) wr: %luKB/s(%lu/s) q: %u\n",
- path, read_perf, read_ios, write_perf, write_ios, queue);
+ printf("%s: perf(ios) rd: %luKB/s(%lu/s) wr: %luKB/s(%lu/s) q: %u\n", path, read_perf,
+ read_ios, write_perf, write_ios, queue);
}
/* bugreport timeout factor adjustment */
@@ -646,132 +641,43 @@
return 0;
}
-/* Copied policy from system/core/logd/LogBuffer.cpp */
-
-#define LOG_BUFFER_SIZE (256 * 1024)
-#define LOG_BUFFER_MIN_SIZE (64 * 1024UL)
-#define LOG_BUFFER_MAX_SIZE (256 * 1024 * 1024UL)
-
-static bool valid_size(unsigned long value) {
- if ((value < LOG_BUFFER_MIN_SIZE) || (LOG_BUFFER_MAX_SIZE < value)) {
- return false;
- }
-
- long pages = sysconf(_SC_PHYS_PAGES);
- if (pages < 1) {
- return true;
- }
-
- long pagesize = sysconf(_SC_PAGESIZE);
- if (pagesize <= 1) {
- pagesize = PAGE_SIZE;
- }
-
- // maximum memory impact a somewhat arbitrary ~3%
- pages = (pages + 31) / 32;
- unsigned long maximum = pages * pagesize;
-
- if ((maximum < LOG_BUFFER_MIN_SIZE) || (LOG_BUFFER_MAX_SIZE < maximum)) {
- return true;
- }
-
- return value <= maximum;
-}
-
-static unsigned long property_get_size(const char *key) {
- unsigned long value;
- char *cp, property[PROPERTY_VALUE_MAX];
-
- property_get(key, property, "");
- value = strtoul(property, &cp, 10);
-
- switch(*cp) {
- case 'm':
- case 'M':
- value *= 1024;
- /* FALLTHRU */
- case 'k':
- case 'K':
- value *= 1024;
- /* FALLTHRU */
- case '\0':
- break;
-
- default:
- value = 0;
- }
-
- if (!valid_size(value)) {
- value = 0;
- }
-
- return value;
-}
-
/* timeout in ms */
static unsigned long logcat_timeout(const char *name) {
- static const char global_tuneable[] = "persist.logd.size"; // Settings App
- static const char global_default[] = "ro.logd.size"; // BoardConfig.mk
- char key[PROP_NAME_MAX];
- unsigned long property_size, default_size;
-
- default_size = property_get_size(global_tuneable);
- if (!default_size) {
- default_size = property_get_size(global_default);
- }
-
- snprintf(key, sizeof(key), "%s.%s", global_tuneable, name);
- property_size = property_get_size(key);
-
- if (!property_size) {
- snprintf(key, sizeof(key), "%s.%s", global_default, name);
- property_size = property_get_size(key);
- }
-
- if (!property_size) {
- property_size = default_size;
- }
-
- if (!property_size) {
- property_size = LOG_BUFFER_SIZE;
- }
-
+ log_id_t id = android_name_to_log_id(name);
+ unsigned long property_size = __android_logger_get_buffer_size(id);
/* Engineering margin is ten-fold our guess */
return 10 * (property_size + worst_write_perf) / worst_write_perf;
}
-/* End copy from system/core/logd/LogBuffer.cpp */
+void Dumpstate::PrintHeader() const {
+ std::string build, fingerprint, radio, bootloader, network;
+ char date[80];
-/* dumps the current system state to stdout */
-static void print_header(std::string version) {
- char build[PROPERTY_VALUE_MAX], fingerprint[PROPERTY_VALUE_MAX];
- char radio[PROPERTY_VALUE_MAX], bootloader[PROPERTY_VALUE_MAX];
- char network[PROPERTY_VALUE_MAX], date[80];
-
- property_get("ro.build.display.id", build, "(unknown)");
- property_get("ro.build.fingerprint", fingerprint, "(unknown)");
- property_get("ro.build.type", build_type, "(unknown)");
- property_get("gsm.version.baseband", radio, "(unknown)");
- property_get("ro.bootloader", bootloader, "(unknown)");
- property_get("gsm.operator.alpha", network, "(unknown)");
- strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", localtime(&now));
+ build = android::base::GetProperty("ro.build.display.id", "(unknown)");
+ fingerprint = android::base::GetProperty("ro.build.fingerprint", "(unknown)");
+ radio = android::base::GetProperty("gsm.version.baseband", "(unknown)");
+ bootloader = android::base::GetProperty("ro.bootloader", "(unknown)");
+ network = android::base::GetProperty("gsm.operator.alpha", "(unknown)");
+ strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", localtime(&now_));
printf("========================================================\n");
printf("== dumpstate: %s\n", date);
printf("========================================================\n");
printf("\n");
- printf("Build: %s\n", build);
- printf("Build fingerprint: '%s'\n", fingerprint); /* format is important for other tools */
- printf("Bootloader: %s\n", bootloader);
- printf("Radio: %s\n", radio);
- printf("Network: %s\n", network);
+ printf("Build: %s\n", build.c_str());
+ // NOTE: fingerprint entry format is important for other tools.
+ printf("Build fingerprint: '%s'\n", fingerprint.c_str());
+ printf("Bootloader: %s\n", bootloader.c_str());
+ printf("Radio: %s\n", radio.c_str());
+ printf("Network: %s\n", network.c_str());
printf("Kernel: ");
- dump_file(NULL, "/proc/version");
+ DumpFileToFd(STDOUT_FILENO, "", "/proc/version");
printf("Command line: %s\n", strtok(cmdline_buf, "\n"));
- printf("Bugreport format version: %s\n", version.c_str());
- printf("Dumpstate info: id=%lu pid=%d\n", id, getpid());
+ printf("Bugreport format version: %s\n", version_.c_str());
+ printf("Dumpstate info: id=%d pid=%d dry_run=%d args=%s extra_options=%s\n", id_, pid_,
+ PropertiesHelper::IsDryRun(), args_.c_str(), extra_options_.c_str());
printf("\n");
}
@@ -783,10 +689,10 @@
".shb", ".sys", ".vb", ".vbe", ".vbs", ".vxd", ".wsc", ".wsf", ".wsh"
};
-bool add_zip_entry_from_fd(const std::string& entry_name, int fd) {
- if (!is_zipping()) {
- MYLOGD("Not adding entry %s from fd because dumpstate is not zipping\n",
- entry_name.c_str());
+bool Dumpstate::AddZipEntryFromFd(const std::string& entry_name, int fd) {
+ if (!IsZipping()) {
+ MYLOGD("Not adding zip entry %s from fd because it's not a zipped bugreport\n",
+ entry_name.c_str());
return false;
}
std::string valid_name = entry_name;
@@ -804,250 +710,305 @@
// Logging statement below is useful to time how long each entry takes, but it's too verbose.
// MYLOGD("Adding zip entry %s\n", entry_name.c_str());
- int32_t err = zip_writer->StartEntryWithTime(valid_name.c_str(),
- ZipWriter::kCompress, get_mtime(fd, now));
- if (err) {
- MYLOGE("zip_writer->StartEntryWithTime(%s): %s\n", valid_name.c_str(),
- ZipWriter::ErrorCodeString(err));
+ int32_t err = zip_writer_->StartEntryWithTime(valid_name.c_str(), ZipWriter::kCompress,
+ get_mtime(fd, ds.now_));
+ if (err != 0) {
+ MYLOGE("zip_writer_->StartEntryWithTime(%s): %s\n", valid_name.c_str(),
+ ZipWriter::ErrorCodeString(err));
return false;
}
std::vector<uint8_t> buffer(65536);
while (1) {
- ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer.data(), sizeof(buffer)));
+ ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer.data(), buffer.size()));
if (bytes_read == 0) {
break;
} else if (bytes_read == -1) {
MYLOGE("read(%s): %s\n", entry_name.c_str(), strerror(errno));
return false;
}
- err = zip_writer->WriteBytes(buffer.data(), bytes_read);
+ err = zip_writer_->WriteBytes(buffer.data(), bytes_read);
if (err) {
- MYLOGE("zip_writer->WriteBytes(): %s\n", ZipWriter::ErrorCodeString(err));
+ MYLOGE("zip_writer_->WriteBytes(): %s\n", ZipWriter::ErrorCodeString(err));
return false;
}
}
- err = zip_writer->FinishEntry();
- if (err) {
- MYLOGE("zip_writer->FinishEntry(): %s\n", ZipWriter::ErrorCodeString(err));
+ err = zip_writer_->FinishEntry();
+ if (err != 0) {
+ MYLOGE("zip_writer_->FinishEntry(): %s\n", ZipWriter::ErrorCodeString(err));
return false;
}
return true;
}
-bool add_zip_entry(const std::string& entry_name, const std::string& entry_path) {
- ScopedFd fd(TEMP_FAILURE_RETRY(open(entry_path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC)));
- if (fd.get() == -1) {
+bool Dumpstate::AddZipEntry(const std::string& entry_name, const std::string& entry_path) {
+ android::base::unique_fd fd(
+ TEMP_FAILURE_RETRY(open(entry_path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC)));
+ if (fd == -1) {
MYLOGE("open(%s): %s\n", entry_path.c_str(), strerror(errno));
return false;
}
- return add_zip_entry_from_fd(entry_name, fd.get());
+ return AddZipEntryFromFd(entry_name, fd.get());
}
/* adds a file to the existing zipped bugreport */
-static int _add_file_from_fd(const char *title, const char *path, int fd) {
- return add_zip_entry_from_fd(ZIP_ROOT_DIR + path, fd) ? 0 : 1;
+static int _add_file_from_fd(const char* title __attribute__((unused)), const char* path, int fd) {
+ return ds.AddZipEntryFromFd(ZIP_ROOT_DIR + path, fd) ? 0 : 1;
}
-// TODO: move to util.cpp
-void add_dir(const char *dir, bool recursive) {
- if (!is_zipping()) {
- MYLOGD("Not adding dir %s because dumpstate is not zipping\n", dir);
+void Dumpstate::AddDir(const std::string& dir, bool recursive) {
+ if (!IsZipping()) {
+ MYLOGD("Not adding dir %s because it's not a zipped bugreport\n", dir.c_str());
return;
}
- MYLOGD("Adding dir %s (recursive: %d)\n", dir, recursive);
- DurationReporter duration_reporter(dir, NULL);
- dump_files(NULL, dir, recursive ? skip_none : is_dir, _add_file_from_fd);
+ MYLOGD("Adding dir %s (recursive: %d)\n", dir.c_str(), recursive);
+ DurationReporter duration_reporter(dir, true);
+ dump_files("", dir.c_str(), recursive ? skip_none : is_dir, _add_file_from_fd);
}
-bool is_zipping() {
- return zip_writer != nullptr;
-}
-
-/* adds a text entry entry to the existing zip file. */
-static bool add_text_zip_entry(const std::string& entry_name, const std::string& content) {
- if (!is_zipping()) {
- MYLOGD("Not adding text entry %s because dumpstate is not zipping\n", entry_name.c_str());
+bool Dumpstate::AddTextZipEntry(const std::string& entry_name, const std::string& content) {
+ if (!IsZipping()) {
+ MYLOGD("Not adding text zip entry %s because it's not a zipped bugreport\n",
+ entry_name.c_str());
return false;
}
MYLOGD("Adding zip text entry %s\n", entry_name.c_str());
- int32_t err = zip_writer->StartEntryWithTime(entry_name.c_str(), ZipWriter::kCompress, now);
- if (err) {
- MYLOGE("zip_writer->StartEntryWithTime(%s): %s\n", entry_name.c_str(),
- ZipWriter::ErrorCodeString(err));
+ int32_t err = zip_writer_->StartEntryWithTime(entry_name.c_str(), ZipWriter::kCompress, ds.now_);
+ if (err != 0) {
+ MYLOGE("zip_writer_->StartEntryWithTime(%s): %s\n", entry_name.c_str(),
+ ZipWriter::ErrorCodeString(err));
return false;
}
- err = zip_writer->WriteBytes(content.c_str(), content.length());
- if (err) {
- MYLOGE("zip_writer->WriteBytes(%s): %s\n", entry_name.c_str(),
- ZipWriter::ErrorCodeString(err));
+ err = zip_writer_->WriteBytes(content.c_str(), content.length());
+ if (err != 0) {
+ MYLOGE("zip_writer_->WriteBytes(%s): %s\n", entry_name.c_str(),
+ ZipWriter::ErrorCodeString(err));
return false;
}
- err = zip_writer->FinishEntry();
- if (err) {
- MYLOGE("zip_writer->FinishEntry(): %s\n", ZipWriter::ErrorCodeString(err));
+ err = zip_writer_->FinishEntry();
+ if (err != 0) {
+ MYLOGE("zip_writer_->FinishEntry(): %s\n", ZipWriter::ErrorCodeString(err));
return false;
}
return true;
}
-static void dump_iptables() {
- run_command("IPTABLES", 10, "iptables", "-L", "-nvx", NULL);
- run_command("IP6TABLES", 10, "ip6tables", "-L", "-nvx", NULL);
- run_command("IPTABLES NAT", 10, "iptables", "-t", "nat", "-L", "-nvx", NULL);
- /* no ip6 nat */
- run_command("IPTABLES MANGLE", 10, "iptables", "-t", "mangle", "-L", "-nvx", NULL);
- run_command("IP6TABLES MANGLE", 10, "ip6tables", "-t", "mangle", "-L", "-nvx", NULL);
- run_command("IPTABLES RAW", 10, "iptables", "-t", "raw", "-L", "-nvx", NULL);
- run_command("IP6TABLES RAW", 10, "ip6tables", "-t", "raw", "-L", "-nvx", NULL);
-}
-
-static void do_kmsg() {
+static void DoKmsg() {
struct stat st;
if (!stat(PSTORE_LAST_KMSG, &st)) {
/* Also TODO: Make console-ramoops CAP_SYSLOG protected. */
- dump_file("LAST KMSG", PSTORE_LAST_KMSG);
+ DumpFile("LAST KMSG", PSTORE_LAST_KMSG);
} else if (!stat(ALT_PSTORE_LAST_KMSG, &st)) {
- dump_file("LAST KMSG", ALT_PSTORE_LAST_KMSG);
+ DumpFile("LAST KMSG", ALT_PSTORE_LAST_KMSG);
} else {
/* TODO: Make last_kmsg CAP_SYSLOG protected. b/5555691 */
- dump_file("LAST KMSG", "/proc/last_kmsg");
+ DumpFile("LAST KMSG", "/proc/last_kmsg");
}
}
-static void do_logcat() {
+static void DoLogcat() {
unsigned long timeout;
- // dump_file("EVENT LOG TAGS", "/etc/event-log-tags");
+ // DumpFile("EVENT LOG TAGS", "/etc/event-log-tags");
// calculate timeout
timeout = logcat_timeout("main") + logcat_timeout("system") + logcat_timeout("crash");
if (timeout < 20000) {
timeout = 20000;
}
- run_command("SYSTEM LOG", timeout / 1000, "logcat", "-v", "threadtime",
- "-v", "printable",
- "-d",
- "*:v", NULL);
+ RunCommand("SYSTEM LOG",
+ {"logcat", "-v", "threadtime", "-v", "printable", "-v", "uid",
+ "-d", "*:v"},
+ CommandOptions::WithTimeout(timeout / 1000).Build());
timeout = logcat_timeout("events");
if (timeout < 20000) {
timeout = 20000;
}
- run_command("EVENT LOG", timeout / 1000, "logcat", "-b", "events",
- "-v", "threadtime",
- "-v", "printable",
- "-d",
- "*:v", NULL);
+ RunCommand("EVENT LOG",
+ {"logcat", "-b", "events", "-v", "threadtime", "-v", "printable", "-v", "uid",
+ "-d", "*:v"},
+ CommandOptions::WithTimeout(timeout / 1000).Build());
timeout = logcat_timeout("radio");
if (timeout < 20000) {
timeout = 20000;
}
- run_command("RADIO LOG", timeout / 1000, "logcat", "-b", "radio",
- "-v", "threadtime",
- "-v", "printable",
- "-d",
- "*:v", NULL);
+ RunCommand("RADIO LOG",
+ {"logcat", "-b", "radio", "-v", "threadtime", "-v", "printable", "-v", "uid",
+ "-d", "*:v"},
+ CommandOptions::WithTimeout(timeout / 1000).Build());
- run_command("LOG STATISTICS", 10, "logcat", "-b", "all", "-S", NULL);
+ RunCommand("LOG STATISTICS", {"logcat", "-b", "all", "-S"});
/* kernels must set CONFIG_PSTORE_PMSG, slice up pstore with device tree */
- run_command("LAST LOGCAT", 10, "logcat", "-L",
- "-b", "all",
- "-v", "threadtime",
- "-v", "printable",
- "-d",
- "*:v", NULL);
+ RunCommand("LAST LOGCAT",
+ {"logcat", "-L", "-b", "all", "-v", "threadtime", "-v", "printable", "-v", "uid",
+ "-d", "*:v"});
}
-static void dumpstate(const std::string& screenshot_path, const std::string& version) {
+static void DumpIpTables() {
+ RunCommand("IPTABLES", {"iptables", "-L", "-nvx"});
+ RunCommand("IP6TABLES", {"ip6tables", "-L", "-nvx"});
+ RunCommand("IPTABLES NAT", {"iptables", "-t", "nat", "-L", "-nvx"});
+ /* no ip6 nat */
+ RunCommand("IPTABLES MANGLE", {"iptables", "-t", "mangle", "-L", "-nvx"});
+ RunCommand("IP6TABLES MANGLE", {"ip6tables", "-t", "mangle", "-L", "-nvx"});
+ RunCommand("IPTABLES RAW", {"iptables", "-t", "raw", "-L", "-nvx"});
+ RunCommand("IP6TABLES RAW", {"ip6tables", "-t", "raw", "-L", "-nvx"});
+}
+
+static void AddAnrTraceFiles() {
+ bool add_to_zip = ds.IsZipping() && ds.version_ == VERSION_SPLIT_ANR;
+ std::string dump_traces_dir;
+
+ /* show the traces we collected in main(), if that was done */
+ if (dump_traces_path != nullptr) {
+ if (add_to_zip) {
+ dump_traces_dir = dirname(dump_traces_path);
+ MYLOGD("Adding ANR traces (directory %s) to the zip file\n", dump_traces_dir.c_str());
+ ds.AddDir(dump_traces_dir, true);
+ } else {
+ MYLOGD("Dumping current ANR traces (%s) to the main bugreport entry\n",
+ dump_traces_path);
+ ds.DumpFile("VM TRACES JUST NOW", dump_traces_path);
+ }
+ }
+
+ std::string anr_traces_path = android::base::GetProperty("dalvik.vm.stack-trace-file", "");
+ std::string anr_traces_dir = dirname(anr_traces_path.c_str());
+
+ // Make sure directory is not added twice.
+ // TODO: this is an overzealous check because it's relying on dump_traces_path - which is
+ // generated by dump_traces() - and anr_traces_path - which is retrieved from a system
+ // property - but in reality they're the same path (although the former could be nullptr).
+ // Anyways, once dump_traces() is refactored as a private Dumpstate function, this logic should
+ // be revisited.
+ bool already_dumped = anr_traces_dir == dump_traces_dir;
+
+ MYLOGD("AddAnrTraceFiles(): dump_traces_dir=%s, anr_traces_dir=%s, already_dumped=%d\n",
+ dump_traces_dir.c_str(), anr_traces_dir.c_str(), already_dumped);
+
+ if (anr_traces_path.empty()) {
+ printf("*** NO VM TRACES FILE DEFINED (dalvik.vm.stack-trace-file)\n\n");
+ } else {
+ int fd = TEMP_FAILURE_RETRY(
+ open(anr_traces_path.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK));
+ if (fd < 0) {
+ printf("*** NO ANR VM TRACES FILE (%s): %s\n\n", anr_traces_path.c_str(),
+ strerror(errno));
+ } else {
+ if (add_to_zip) {
+ if (!already_dumped) {
+ MYLOGD("Adding dalvik ANR traces (directory %s) to the zip file\n",
+ anr_traces_dir.c_str());
+ ds.AddDir(anr_traces_dir, true);
+ already_dumped = true;
+ }
+ } else {
+ MYLOGD("Dumping last ANR traces (%s) to the main bugreport entry\n",
+ anr_traces_path.c_str());
+ dump_file_from_fd("VM TRACES AT LAST ANR", anr_traces_path.c_str(), fd);
+ }
+ }
+ }
+
+ if (add_to_zip && already_dumped) {
+ MYLOGD("Already dumped directory %s to the zip file\n", anr_traces_dir.c_str());
+ return;
+ }
+
+ /* slow traces for slow operations */
+ struct stat st;
+ if (!anr_traces_path.empty()) {
+ int tail = anr_traces_path.size() - 1;
+ while (tail > 0 && anr_traces_path.at(tail) != '/') {
+ tail--;
+ }
+ int i = 0;
+ while (1) {
+ anr_traces_path = anr_traces_path.substr(0, tail + 1) +
+ android::base::StringPrintf("slow%02d.txt", i);
+ if (stat(anr_traces_path.c_str(), &st)) {
+ // No traces file at this index, done with the files.
+ break;
+ }
+ ds.DumpFile("VM TRACES WHEN SLOW", anr_traces_path.c_str());
+ i++;
+ }
+ }
+}
+
+static void dumpstate() {
DurationReporter duration_reporter("DUMPSTATE");
dump_dev_files("TRUSTY VERSION", "/sys/bus/platform/drivers/trusty", "trusty_version");
- run_command("UPTIME", 10, "uptime", NULL);
+ RunCommand("UPTIME", {"uptime"});
dump_files("UPTIME MMC PERF", mmcblk0, skip_not_stat, dump_stat_from_fd);
dump_emmc_ecsd("/d/mmc0/mmc0:0001/ext_csd");
- dump_file("MEMORY INFO", "/proc/meminfo");
- run_command("CPU INFO", 10, "top", "-n", "1", "-d", "1", "-m", "30", "-H", NULL);
- run_command("PROCRANK", 20, SU_PATH, "root", "procrank", NULL);
- dump_file("VIRTUAL MEMORY STATS", "/proc/vmstat");
- dump_file("VMALLOC INFO", "/proc/vmallocinfo");
- dump_file("SLAB INFO", "/proc/slabinfo");
- dump_file("ZONEINFO", "/proc/zoneinfo");
- dump_file("PAGETYPEINFO", "/proc/pagetypeinfo");
- dump_file("BUDDYINFO", "/proc/buddyinfo");
- dump_file("FRAGMENTATION INFO", "/d/extfrag/unusable_index");
+ DumpFile("MEMORY INFO", "/proc/meminfo");
+ RunCommand("CPU INFO", {"top", "-b", "-n", "1", "-H", "-s", "6", "-o",
+ "pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name"});
+ RunCommand("PROCRANK", {"procrank"}, AS_ROOT_20);
+ DumpFile("VIRTUAL MEMORY STATS", "/proc/vmstat");
+ DumpFile("VMALLOC INFO", "/proc/vmallocinfo");
+ DumpFile("SLAB INFO", "/proc/slabinfo");
+ DumpFile("ZONEINFO", "/proc/zoneinfo");
+ DumpFile("PAGETYPEINFO", "/proc/pagetypeinfo");
+ DumpFile("BUDDYINFO", "/proc/buddyinfo");
+ DumpFile("FRAGMENTATION INFO", "/d/extfrag/unusable_index");
- dump_file("KERNEL WAKE SOURCES", "/d/wakeup_sources");
- dump_file("KERNEL CPUFREQ", "/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state");
- dump_file("KERNEL SYNC", "/d/sync");
+ DumpFile("KERNEL WAKE SOURCES", "/d/wakeup_sources");
+ DumpFile("KERNEL CPUFREQ", "/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state");
+ DumpFile("KERNEL SYNC", "/d/sync");
- run_command("PROCESSES AND THREADS", 10, "ps", "-Z", "-t", "-p", "-P", NULL);
- run_command("LIBRANK", 10, SU_PATH, "root", "librank", NULL);
+ RunCommand("PROCESSES AND THREADS",
+ {"ps", "-A", "-T", "-Z", "-O", "pri,nice,rtprio,sched,pcy"});
+ RunCommand("LIBRANK", {"librank"}, CommandOptions::AS_ROOT);
- run_command("PRINTENV", 10, "printenv", NULL);
- run_command("NETSTAT", 10, "netstat", "-n", NULL);
- run_command("LSMOD", 10, "lsmod", NULL);
+ if (ds.IsZipping()) {
+ RunCommand(
+ "HARDWARE HALS",
+ {"lshal", std::string("--debug=") + kLsHalDebugPath},
+ CommandOptions::AS_ROOT);
+
+ ds.AddZipEntry("lshal-debug.txt", kLsHalDebugPath);
+
+ unlink(kLsHalDebugPath.c_str());
+ } else {
+ RunCommand(
+ "HARDWARE HALS", {"lshal", "--debug"}, CommandOptions::AS_ROOT);
+ }
+
+ RunCommand("PRINTENV", {"printenv"});
+ RunCommand("NETSTAT", {"netstat", "-nW"});
+ struct stat s;
+ if (stat("/proc/modules", &s) != 0) {
+ MYLOGD("Skipping 'lsmod' because /proc/modules does not exist\n");
+ } else {
+ RunCommand("LSMOD", {"lsmod"});
+ }
do_dmesg();
- run_command("LIST OF OPEN FILES", 10, SU_PATH, "root", "lsof", NULL);
+ RunCommand("LIST OF OPEN FILES", {"lsof"}, CommandOptions::AS_ROOT);
for_each_pid(do_showmap, "SMAPS OF ALL PROCESSES");
for_each_tid(show_wchan, "BLOCKED PROCESS WAIT-CHANNELS");
for_each_pid(show_showtime, "PROCESS TIMES (pid cmd user system iowait+percentage)");
/* Dump Bluetooth HCI logs */
- add_dir("/data/misc/bluetooth/logs", true);
+ ds.AddDir("/data/misc/bluetooth/logs", true);
- if (!screenshot_path.empty()) {
+ if (!ds.do_early_screenshot_) {
MYLOGI("taking late screenshot\n");
- take_screenshot(screenshot_path);
- MYLOGI("wrote screenshot: %s\n", screenshot_path.c_str());
+ ds.TakeScreenshot();
}
- do_logcat();
+ DoLogcat();
- /* show the traces we collected in main(), if that was done */
- if (dump_traces_path != NULL) {
- dump_file("VM TRACES JUST NOW", dump_traces_path);
- }
-
- /* only show ANR traces if they're less than 15 minutes old */
- struct stat st;
- char anr_traces_path[PATH_MAX];
- property_get("dalvik.vm.stack-trace-file", anr_traces_path, "");
- if (!anr_traces_path[0]) {
- printf("*** NO VM TRACES FILE DEFINED (dalvik.vm.stack-trace-file)\n\n");
- } else {
- int fd = TEMP_FAILURE_RETRY(open(anr_traces_path,
- O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK));
- if (fd < 0) {
- printf("*** NO ANR VM TRACES FILE (%s): %s\n\n", anr_traces_path, strerror(errno));
- } else {
- dump_file_from_fd("VM TRACES AT LAST ANR", anr_traces_path, fd);
- }
- }
-
- /* slow traces for slow operations */
- if (anr_traces_path[0] != 0) {
- int tail = strlen(anr_traces_path)-1;
- while (tail > 0 && anr_traces_path[tail] != '/') {
- tail--;
- }
- int i = 0;
- while (1) {
- sprintf(anr_traces_path+tail+1, "slow%02d.txt", i);
- if (stat(anr_traces_path, &st)) {
- // No traces file at this index, done with the files.
- break;
- }
- dump_file("VM TRACES WHEN SLOW", anr_traces_path);
- i++;
- }
- }
+ AddAnrTraceFiles();
int dumped = 0;
for (size_t i = 0; i < NUM_TOMBSTONES; i++) {
@@ -1055,8 +1016,8 @@
const char *name = tombstone_data[i].name;
int fd = tombstone_data[i].fd;
dumped = 1;
- if (zip_writer) {
- if (!add_zip_entry_from_fd(ZIP_ROOT_DIR + name, fd)) {
+ if (ds.IsZipping()) {
+ if (!ds.AddZipEntryFromFd(ZIP_ROOT_DIR + name, fd)) {
MYLOGE("Unable to add tombstone %s to zip file\n", name);
}
} else {
@@ -1070,229 +1031,294 @@
printf("*** NO TOMBSTONES to dump in %s\n\n", TOMBSTONE_DIR);
}
- dump_file("NETWORK DEV INFO", "/proc/net/dev");
- dump_file("QTAGUID NETWORK INTERFACES INFO", "/proc/net/xt_qtaguid/iface_stat_all");
- dump_file("QTAGUID NETWORK INTERFACES INFO (xt)", "/proc/net/xt_qtaguid/iface_stat_fmt");
- dump_file("QTAGUID CTRL INFO", "/proc/net/xt_qtaguid/ctrl");
- dump_file("QTAGUID STATS INFO", "/proc/net/xt_qtaguid/stats");
+ DumpFile("NETWORK DEV INFO", "/proc/net/dev");
+ DumpFile("QTAGUID NETWORK INTERFACES INFO", "/proc/net/xt_qtaguid/iface_stat_all");
+ DumpFile("QTAGUID NETWORK INTERFACES INFO (xt)", "/proc/net/xt_qtaguid/iface_stat_fmt");
+ DumpFile("QTAGUID CTRL INFO", "/proc/net/xt_qtaguid/ctrl");
+ DumpFile("QTAGUID STATS INFO", "/proc/net/xt_qtaguid/stats");
- do_kmsg();
+ DoKmsg();
/* The following have a tendency to get wedged when wifi drivers/fw goes belly-up. */
- run_command("NETWORK INTERFACES", 10, "ip", "link", NULL);
+ RunCommand("NETWORK INTERFACES", {"ip", "link"});
- run_command("IPv4 ADDRESSES", 10, "ip", "-4", "addr", "show", NULL);
- run_command("IPv6 ADDRESSES", 10, "ip", "-6", "addr", "show", NULL);
+ RunCommand("IPv4 ADDRESSES", {"ip", "-4", "addr", "show"});
+ RunCommand("IPv6 ADDRESSES", {"ip", "-6", "addr", "show"});
- run_command("IP RULES", 10, "ip", "rule", "show", NULL);
- run_command("IP RULES v6", 10, "ip", "-6", "rule", "show", NULL);
+ RunCommand("IP RULES", {"ip", "rule", "show"});
+ RunCommand("IP RULES v6", {"ip", "-6", "rule", "show"});
dump_route_tables();
- run_command("ARP CACHE", 10, "ip", "-4", "neigh", "show", NULL);
- run_command("IPv6 ND CACHE", 10, "ip", "-6", "neigh", "show", NULL);
- run_command("MULTICAST ADDRESSES", 10, "ip", "maddr", NULL);
- run_command("WIFI NETWORKS", 20, "wpa_cli", "IFNAME=wlan0", "list_networks", NULL);
+ RunCommand("ARP CACHE", {"ip", "-4", "neigh", "show"});
+ RunCommand("IPv6 ND CACHE", {"ip", "-6", "neigh", "show"});
+ RunCommand("MULTICAST ADDRESSES", {"ip", "maddr"});
+ RunCommand("WIFI NETWORKS", {"wpa_cli", "IFNAME=wlan0", "list_networks"},
+ CommandOptions::WithTimeout(20).Build());
-#ifdef FWDUMP_bcmdhd
- run_command("ND OFFLOAD TABLE", 5,
- SU_PATH, "root", WLUTIL, "nd_hostip", NULL);
+ RunDumpsys("NETWORK DIAGNOSTICS", {"connectivity", "--diag"},
+ CommandOptions::WithTimeout(10).Build());
- run_command("DUMP WIFI INTERNAL COUNTERS (1)", 20,
- SU_PATH, "root", WLUTIL, "counters", NULL);
+ RunCommand("SYSTEM PROPERTIES", {"getprop"});
- run_command("ND OFFLOAD STATUS (1)", 5,
- SU_PATH, "root", WLUTIL, "nd_status", NULL);
+ RunCommand("VOLD DUMP", {"vdc", "dump"});
+ RunCommand("SECURE CONTAINERS", {"vdc", "asec", "list"});
-#endif
- dump_file("INTERRUPTS (1)", "/proc/interrupts");
+ RunCommand("STORAGED TASKIOINFO", {"storaged", "-u"}, CommandOptions::WithTimeout(10).Build());
- run_command("NETWORK DIAGNOSTICS", 10, "dumpsys", "-t", "10", "connectivity", "--diag", NULL);
+ RunCommand("FILESYSTEMS & FREE SPACE", {"df"});
-#ifdef FWDUMP_bcmdhd
- run_command("DUMP WIFI STATUS", 20,
- SU_PATH, "root", "dhdutil", "-i", "wlan0", "dump", NULL);
-
- run_command("DUMP WIFI INTERNAL COUNTERS (2)", 20,
- SU_PATH, "root", WLUTIL, "counters", NULL);
-
- run_command("ND OFFLOAD STATUS (2)", 5,
- SU_PATH, "root", WLUTIL, "nd_status", NULL);
-#endif
- dump_file("INTERRUPTS (2)", "/proc/interrupts");
-
- print_properties();
-
- run_command("VOLD DUMP", 10, "vdc", "dump", NULL);
- run_command("SECURE CONTAINERS", 10, "vdc", "asec", "list", NULL);
-
- run_command("FILESYSTEMS & FREE SPACE", 10, "df", NULL);
-
- run_command("LAST RADIO LOG", 10, "parse_radio_log", "/proc/last_radio_log", NULL);
+ RunCommand("LAST RADIO LOG", {"parse_radio_log", "/proc/last_radio_log"});
printf("------ BACKLIGHTS ------\n");
printf("LCD brightness=");
- dump_file(NULL, "/sys/class/leds/lcd-backlight/brightness");
+ DumpFile("", "/sys/class/leds/lcd-backlight/brightness");
printf("Button brightness=");
- dump_file(NULL, "/sys/class/leds/button-backlight/brightness");
+ DumpFile("", "/sys/class/leds/button-backlight/brightness");
printf("Keyboard brightness=");
- dump_file(NULL, "/sys/class/leds/keyboard-backlight/brightness");
+ DumpFile("", "/sys/class/leds/keyboard-backlight/brightness");
printf("ALS mode=");
- dump_file(NULL, "/sys/class/leds/lcd-backlight/als");
+ DumpFile("", "/sys/class/leds/lcd-backlight/als");
printf("LCD driver registers:\n");
- dump_file(NULL, "/sys/class/leds/lcd-backlight/registers");
+ DumpFile("", "/sys/class/leds/lcd-backlight/registers");
printf("\n");
/* Binder state is expensive to look at as it uses a lot of memory. */
- dump_file("BINDER FAILED TRANSACTION LOG", "/sys/kernel/debug/binder/failed_transaction_log");
- dump_file("BINDER TRANSACTION LOG", "/sys/kernel/debug/binder/transaction_log");
- dump_file("BINDER TRANSACTIONS", "/sys/kernel/debug/binder/transactions");
- dump_file("BINDER STATS", "/sys/kernel/debug/binder/stats");
- dump_file("BINDER STATE", "/sys/kernel/debug/binder/state");
+ DumpFile("BINDER FAILED TRANSACTION LOG", "/sys/kernel/debug/binder/failed_transaction_log");
+ DumpFile("BINDER TRANSACTION LOG", "/sys/kernel/debug/binder/transaction_log");
+ DumpFile("BINDER TRANSACTIONS", "/sys/kernel/debug/binder/transactions");
+ DumpFile("BINDER STATS", "/sys/kernel/debug/binder/stats");
+ DumpFile("BINDER STATE", "/sys/kernel/debug/binder/state");
- printf("========================================================\n");
- printf("== Board\n");
- printf("========================================================\n");
+ ds.DumpstateBoard();
- dumpstate_board();
- printf("\n");
-
- /* Migrate the ril_dumpstate to a dumpstate_board()? */
- char ril_dumpstate_timeout[PROPERTY_VALUE_MAX] = {0};
- property_get("ril.dumpstate.timeout", ril_dumpstate_timeout, "30");
- if (strnlen(ril_dumpstate_timeout, PROPERTY_VALUE_MAX - 1) > 0) {
- if (is_user_build()) {
- // su does not exist on user builds, so try running without it.
- // This way any implementations of vril-dump that do not require
- // root can run on user builds.
- run_command("DUMP VENDOR RIL LOGS", atoi(ril_dumpstate_timeout),
- "vril-dump", NULL);
- } else {
- run_command("DUMP VENDOR RIL LOGS", atoi(ril_dumpstate_timeout),
- SU_PATH, "root", "vril-dump", NULL);
+ /* Migrate the ril_dumpstate to a device specific dumpstate? */
+ int rilDumpstateTimeout = android::base::GetIntProperty("ril.dumpstate.timeout", 0);
+ if (rilDumpstateTimeout > 0) {
+ // su does not exist on user builds, so try running without it.
+ // This way any implementations of vril-dump that do not require
+ // root can run on user builds.
+ CommandOptions::CommandOptionsBuilder options =
+ CommandOptions::WithTimeout(rilDumpstateTimeout);
+ if (!PropertiesHelper::IsUserBuild()) {
+ options.AsRoot();
}
+ RunCommand("DUMP VENDOR RIL LOGS", {"vril-dump"}, options.Build());
}
printf("========================================================\n");
printf("== Android Framework Services\n");
printf("========================================================\n");
- run_command("DUMPSYS", 60, "dumpsys", "-t", "60", "--skip", "meminfo", "cpuinfo", NULL);
+ RunDumpsys("DUMPSYS", {"--skip", "meminfo", "cpuinfo"}, CommandOptions::WithTimeout(90).Build(),
+ 10);
printf("========================================================\n");
printf("== Checkins\n");
printf("========================================================\n");
- run_command("CHECKIN BATTERYSTATS", 30, "dumpsys", "-t", "30", "batterystats", "-c", NULL);
- run_command("CHECKIN MEMINFO", 30, "dumpsys", "-t", "30", "meminfo", "--checkin", NULL);
- run_command("CHECKIN NETSTATS", 30, "dumpsys", "-t", "30", "netstats", "--checkin", NULL);
- run_command("CHECKIN PROCSTATS", 30, "dumpsys", "-t", "30", "procstats", "-c", NULL);
- run_command("CHECKIN USAGESTATS", 30, "dumpsys", "-t", "30", "usagestats", "-c", NULL);
- run_command("CHECKIN PACKAGE", 30, "dumpsys", "-t", "30", "package", "--checkin", NULL);
+ RunDumpsys("CHECKIN BATTERYSTATS", {"batterystats", "-c"});
+ RunDumpsys("CHECKIN MEMINFO", {"meminfo", "--checkin"});
+ RunDumpsys("CHECKIN NETSTATS", {"netstats", "--checkin"});
+ RunDumpsys("CHECKIN PROCSTATS", {"procstats", "-c"});
+ RunDumpsys("CHECKIN USAGESTATS", {"usagestats", "-c"});
+ RunDumpsys("CHECKIN PACKAGE", {"package", "--checkin"});
printf("========================================================\n");
printf("== Running Application Activities\n");
printf("========================================================\n");
- run_command("APP ACTIVITIES", 30, "dumpsys", "-t", "30", "activity", "all", NULL);
+ RunDumpsys("APP ACTIVITIES", {"activity", "-v", "all"});
printf("========================================================\n");
printf("== Running Application Services\n");
printf("========================================================\n");
- run_command("APP SERVICES", 30, "dumpsys", "-t", "30", "activity", "service", "all", NULL);
+ RunDumpsys("APP SERVICES", {"activity", "service", "all"});
printf("========================================================\n");
printf("== Running Application Providers\n");
printf("========================================================\n");
- run_command("APP PROVIDERS", 30, "dumpsys", "-t", "30", "activity", "provider", "all", NULL);
+ RunDumpsys("APP PROVIDERS", {"activity", "provider", "all"});
- // dump_modem_logs adds the modem logs if available to the bugreport.
+ printf("========================================================\n");
+ printf("== Dropbox crashes\n");
+ printf("========================================================\n");
+
+ RunDumpsys("DROPBOX SYSTEM SERVER CRASHES", {"dropbox", "-p", "system_server_crash"});
+ RunDumpsys("DROPBOX SYSTEM APP CRASHES", {"dropbox", "-p", "system_app_crash"});
+
+ // DumpModemLogs adds the modem logs if available to the bugreport.
// Do this at the end to allow for sufficient time for the modem logs to be
// collected.
- dump_modem_logs();
+ DumpModemLogs();
printf("========================================================\n");
- printf("== Final progress (pid %d): %d/%d (originally %d)\n",
- getpid(), progress, weight_total, WEIGHT_TOTAL);
+ printf("== Final progress (pid %d): %d/%d (estimated %d)\n", ds.pid_, ds.progress_->Get(),
+ ds.progress_->GetMax(), ds.progress_->GetInitialMax());
printf("========================================================\n");
- printf("== dumpstate: done\n");
+ printf("== dumpstate: done (id %d)\n", ds.id_);
printf("========================================================\n");
}
-static void usage() {
- fprintf(stderr,
- "usage: dumpstate [-h] [-b soundfile] [-e soundfile] [-o file [-d] [-p] [-t]"
- "[-z] [-s] [-S] [-q] [-B] [-P] [-R] [-V version]\n"
- " -h: display this help message\n"
- " -b: play sound file instead of vibrate, at beginning of job\n"
- " -e: play sound file instead of vibrate, at end of job\n"
- " -o: write to file (instead of stdout)\n"
- " -d: append date to filename (requires -o)\n"
- " -p: capture screenshot to filename.png (requires -o)\n"
- " -t: only captures telephony sections\n"
- " -z: generate zipped file (requires -o)\n"
- " -s: write output to control socket (for init)\n"
- " -S: write file location to control socket (for init; requires -o and -z)"
- " -q: disable vibrate\n"
- " -B: send broadcast when finished (requires -o)\n"
- " -P: send broadcast when started and update system properties on "
- "progress (requires -o and -B)\n"
- " -R: take bugreport in remote mode (requires -o, -z, -d and -B, "
- "shouldn't be used with -P)\n"
- " -V: sets the bugreport format version (valid values: %s)\n",
- VERSION_DEFAULT.c_str());
+void Dumpstate::DumpstateBoard() {
+ DurationReporter duration_reporter("dumpstate_board()");
+ printf("========================================================\n");
+ printf("== Board\n");
+ printf("========================================================\n");
+
+ ::android::sp<IDumpstateDevice> dumpstate_device(IDumpstateDevice::getService());
+ if (dumpstate_device == nullptr) {
+ MYLOGE("No IDumpstateDevice implementation\n");
+ return;
+ }
+
+ if (!IsZipping()) {
+ MYLOGD("Not dumping board info because it's not a zipped bugreport\n");
+ return;
+ }
+
+ std::string path = kDumpstateBoardPath;
+ MYLOGI("Calling IDumpstateDevice implementation using path %s\n", path.c_str());
+
+ int fd =
+ TEMP_FAILURE_RETRY(open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
+ if (fd < 0) {
+ MYLOGE("Could not open file %s: %s\n", path.c_str(), strerror(errno));
+ return;
+ }
+
+ native_handle_t* handle = native_handle_create(1, 0);
+ if (handle == nullptr) {
+ MYLOGE("Could not create native_handle\n");
+ return;
+ }
+ handle->data[0] = fd;
+
+ // TODO: need a timeout mechanism so dumpstate does not hang on device implementation call.
+ android::hardware::Return<void> status = dumpstate_device->dumpstateBoard(handle);
+ if (!status.isOk()) {
+ MYLOGE("dumpstateBoard failed: %s\n", status.description().c_str());
+ native_handle_close(handle);
+ native_handle_delete(handle);
+ return;
+ }
+
+ AddZipEntry("dumpstate-board.txt", path);
+ printf("*** See dumpstate-board.txt entry ***\n");
+
+ native_handle_close(handle);
+ native_handle_delete(handle);
+
+ if (remove(path.c_str()) != 0) {
+ MYLOGE("Could not remove(%s): %s\n", path.c_str(), strerror(errno));
+ }
}
-static void sigpipe_handler(int n) {
- // don't complain to stderr or stdout
+static void ShowUsageAndExit(int exitCode = 1) {
+ fprintf(stderr,
+ "usage: dumpstate [-h] [-b soundfile] [-e soundfile] [-o file] [-d] [-p] "
+ "[-z]] [-s] [-S] [-q] [-B] [-P] [-R] [-V version]\n"
+ " -h: display this help message\n"
+ " -b: play sound file instead of vibrate, at beginning of job\n"
+ " -e: play sound file instead of vibrate, at end of job\n"
+ " -o: write to file (instead of stdout)\n"
+ " -d: append date to filename (requires -o)\n"
+ " -p: capture screenshot to filename.png (requires -o)\n"
+ " -z: generate zipped file (requires -o)\n"
+ " -s: write output to control socket (for init)\n"
+ " -S: write file location to control socket (for init; requires -o and -z)"
+ " -q: disable vibrate\n"
+ " -B: send broadcast when finished (requires -o)\n"
+ " -P: send broadcast when started and update system properties on "
+ "progress (requires -o and -B)\n"
+ " -R: take bugreport in remote mode (requires -o, -z, -d and -B, "
+ "shouldn't be used with -P)\n"
+ " -v: prints the dumpstate header and exit\n");
+ exit(exitCode);
+}
+
+static void ExitOnInvalidArgs() {
+ fprintf(stderr, "invalid combination of args\n");
+ ShowUsageAndExit();
+}
+
+static void sig_handler(int) {
_exit(EXIT_FAILURE);
}
-/* adds the temporary report to the existing .zip file, closes the .zip file, and removes the
- temporary file.
- */
-static bool finish_zip_file(const std::string& bugreport_name, const std::string& bugreport_path,
- time_t now) {
- if (!add_zip_entry(bugreport_name, bugreport_path)) {
+static void register_sig_handler() {
+ struct sigaction sa;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = sig_handler;
+ sigaction(SIGPIPE, &sa, NULL); // broken pipe
+ sigaction(SIGSEGV, &sa, NULL); // segment fault
+ sigaction(SIGINT, &sa, NULL); // ctrl-c
+ sigaction(SIGTERM, &sa, NULL); // killed
+ sigaction(SIGQUIT, &sa, NULL); // quit
+}
+
+bool Dumpstate::FinishZipFile() {
+ std::string entry_name = base_name_ + "-" + name_ + ".txt";
+ MYLOGD("Adding main entry (%s) from %s to .zip bugreport\n", entry_name.c_str(),
+ tmp_path_.c_str());
+ // Final timestamp
+ char date[80];
+ time_t the_real_now_please_stand_up = time(nullptr);
+ strftime(date, sizeof(date), "%Y/%m/%d %H:%M:%S", localtime(&the_real_now_please_stand_up));
+ MYLOGD("dumpstate id %d finished around %s (%ld s)\n", ds.id_, date,
+ the_real_now_please_stand_up - ds.now_);
+
+ if (!ds.AddZipEntry(entry_name, tmp_path_)) {
MYLOGE("Failed to add text entry to .zip file\n");
return false;
}
- if (!add_text_zip_entry("main_entry.txt", bugreport_name)) {
+ if (!AddTextZipEntry("main_entry.txt", entry_name)) {
MYLOGE("Failed to add main_entry.txt to .zip file\n");
return false;
}
- int32_t err = zip_writer->Finish();
- if (err) {
- MYLOGE("zip_writer->Finish(): %s\n", ZipWriter::ErrorCodeString(err));
+ // Add log file (which contains stderr output) to zip...
+ fprintf(stderr, "dumpstate_log.txt entry on zip file logged up to here\n");
+ if (!ds.AddZipEntry("dumpstate_log.txt", ds.log_path_.c_str())) {
+ MYLOGE("Failed to add dumpstate log to .zip file\n");
+ return false;
+ }
+ // ... and re-opens it for further logging.
+ redirect_to_existing_file(stderr, const_cast<char*>(ds.log_path_.c_str()));
+ fprintf(stderr, "\n");
+
+ int32_t err = zip_writer_->Finish();
+ if (err != 0) {
+ MYLOGE("zip_writer_->Finish(): %s\n", ZipWriter::ErrorCodeString(err));
return false;
}
- if (is_user_build()) {
- MYLOGD("Removing temporary file %s\n", bugreport_path.c_str())
- if (remove(bugreport_path.c_str())) {
- ALOGW("remove(%s): %s\n", bugreport_path.c_str(), strerror(errno));
- }
- } else {
- MYLOGD("Keeping temporary file %s on non-user build\n", bugreport_path.c_str())
+ // TODO: remove once FinishZipFile() is automatically handled by Dumpstate's destructor.
+ ds.zip_file.reset(nullptr);
+
+ MYLOGD("Removing temporary file %s\n", tmp_path_.c_str())
+ if (remove(tmp_path_.c_str()) != 0) {
+ MYLOGE("Failed to remove temporary file (%s): %s\n", tmp_path_.c_str(), strerror(errno));
}
return true;
}
static std::string SHA256_file_hash(std::string filepath) {
- ScopedFd fd(TEMP_FAILURE_RETRY(open(filepath.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC
- | O_NOFOLLOW)));
- if (fd.get() == -1) {
+ android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(filepath.c_str(), O_RDONLY | O_NONBLOCK
+ | O_CLOEXEC | O_NOFOLLOW)));
+ if (fd == -1) {
MYLOGE("open(%s): %s\n", filepath.c_str(), strerror(errno));
return NULL;
}
SHA256_CTX ctx;
- SHA256_init(&ctx);
+ SHA256_Init(&ctx);
std::vector<uint8_t> buffer(65536);
while (1) {
@@ -1304,21 +1330,48 @@
return NULL;
}
- SHA256_update(&ctx, buffer.data(), bytes_read);
+ SHA256_Update(&ctx, buffer.data(), bytes_read);
}
- uint8_t hash[SHA256_DIGEST_SIZE];
- memcpy(hash, SHA256_final(&ctx), SHA256_DIGEST_SIZE);
- char hash_buffer[SHA256_DIGEST_SIZE * 2 + 1];
- for(size_t i = 0; i < SHA256_DIGEST_SIZE; i++) {
+ uint8_t hash[SHA256_DIGEST_LENGTH];
+ SHA256_Final(hash, &ctx);
+
+ char hash_buffer[SHA256_DIGEST_LENGTH * 2 + 1];
+ for(size_t i = 0; i < SHA256_DIGEST_LENGTH; i++) {
sprintf(hash_buffer + (i * 2), "%02x", hash[i]);
}
hash_buffer[sizeof(hash_buffer) - 1] = 0;
return std::string(hash_buffer);
}
+static void SendBroadcast(const std::string& action, const std::vector<std::string>& args) {
+ // clang-format off
+ std::vector<std::string> am = {"/system/bin/cmd", "activity", "broadcast", "--user", "0",
+ "--receiver-foreground", "--receiver-include-background", "-a", action};
+ // clang-format on
+
+ am.insert(am.end(), args.begin(), args.end());
+
+ RunCommand("", am,
+ CommandOptions::WithTimeout(20)
+ .Log("Sending broadcast: '%s'\n")
+ .Always()
+ .DropRoot()
+ .RedirectStderr()
+ .Build());
+}
+
+static void Vibrate(int duration_ms) {
+ // clang-format off
+ RunCommand("", {"cmd", "vibrator", "vibrate", std::to_string(duration_ms), "dumpstate"},
+ CommandOptions::WithTimeout(10)
+ .Log("Vibrate: '%s'\n")
+ .Always()
+ .Build());
+ // clang-format on
+}
+
int main(int argc, char *argv[]) {
- struct sigaction sigact;
int do_add_date = 0;
int do_zip_file = 0;
int do_vibrate = 1;
@@ -1327,33 +1380,15 @@
int use_control_socket = 0;
int do_fb = 0;
int do_broadcast = 0;
- int do_early_screenshot = 0;
int is_remote_mode = 0;
+ bool show_header_only = false;
+ bool do_start_service = false;
bool telephony_only = false;
- std::string version = VERSION_DEFAULT;
-
- now = time(NULL);
-
- MYLOGI("begin\n");
-
- /* gets the sequential id */
- char last_id[PROPERTY_VALUE_MAX];
- property_get("dumpstate.last_id", last_id, "0");
- id = strtoul(last_id, NULL, 10) + 1;
- snprintf(last_id, sizeof(last_id), "%lu", id);
- property_set("dumpstate.last_id", last_id);
- MYLOGI("dumpstate id: %lu\n", id);
-
- /* clear SIGPIPE handler */
- memset(&sigact, 0, sizeof(sigact));
- sigact.sa_handler = sigpipe_handler;
- sigaction(SIGPIPE, &sigact, NULL);
-
/* set as high priority, and protect from OOM killer */
setpriority(PRIO_PROCESS, 0, -20);
- FILE *oom_adj = fopen("/proc/self/oom_score_adj", "we");
+ FILE* oom_adj = fopen("/proc/self/oom_score_adj", "we");
if (oom_adj) {
fputs("-1000", oom_adj);
fclose(oom_adj);
@@ -1367,60 +1402,146 @@
}
/* parse arguments */
- std::string args;
- format_args(argc, const_cast<const char **>(argv), &args);
- MYLOGD("Dumpstate command line: %s\n", args.c_str());
int c;
- while ((c = getopt(argc, argv, "dho:svqzptPBRSV:")) != -1) {
+ while ((c = getopt(argc, argv, "dho:svqzpPBRSV:")) != -1) {
switch (c) {
- case 'd': do_add_date = 1; break;
- case 't': telephony_only = true; break;
- case 'z': do_zip_file = 1; break;
- case 'o': use_outfile = optarg; break;
- case 's': use_socket = 1; break;
- case 'S': use_control_socket = 1; break;
- case 'v': break; // compatibility no-op
- case 'q': do_vibrate = 0; break;
- case 'p': do_fb = 1; break;
- case 'P': do_update_progress = 1; break;
- case 'R': is_remote_mode = 1; break;
- case 'B': do_broadcast = 1; break;
- case 'V': version = optarg; break;
- case '?': printf("\n");
+ // clang-format off
+ case 'd': do_add_date = 1; break;
+ case 'z': do_zip_file = 1; break;
+ case 'o': use_outfile = optarg; break;
+ case 's': use_socket = 1; break;
+ case 'S': use_control_socket = 1; break;
+ case 'v': show_header_only = true; break;
+ case 'q': do_vibrate = 0; break;
+ case 'p': do_fb = 1; break;
+ case 'P': ds.update_progress_ = true; break;
+ case 'R': is_remote_mode = 1; break;
+ case 'B': do_broadcast = 1; break;
+ case 'V': break; // compatibility no-op
case 'h':
- usage();
- exit(1);
+ ShowUsageAndExit(0);
+ break;
+ default:
+ fprintf(stderr, "Invalid option: %c\n", c);
+ ShowUsageAndExit();
+ // clang-format on
}
}
- if ((do_zip_file || do_add_date || do_update_progress || do_broadcast) && !use_outfile) {
- usage();
- exit(1);
+ // TODO: use helper function to convert argv into a string
+ for (int i = 0; i < argc; i++) {
+ ds.args_ += argv[i];
+ if (i < argc - 1) {
+ ds.args_ += " ";
+ }
+ }
+
+ ds.extra_options_ = android::base::GetProperty(PROPERTY_EXTRA_OPTIONS, "");
+ if (!ds.extra_options_.empty()) {
+ // Framework uses a system property to override some command-line args.
+ // Currently, it contains the type of the requested bugreport.
+ if (ds.extra_options_ == "bugreportplus") {
+ // Currently, the dumpstate binder is only used by Shell to update progress.
+ do_start_service = true;
+ ds.update_progress_ = true;
+ do_fb = 0;
+ } else if (ds.extra_options_ == "bugreportremote") {
+ do_vibrate = 0;
+ is_remote_mode = 1;
+ do_fb = 0;
+ } else if (ds.extra_options_ == "bugreportwear") {
+ ds.update_progress_ = true;
+ } else if (ds.extra_options_ == "bugreporttelephony") {
+ telephony_only = true;
+ } else {
+ MYLOGE("Unknown extra option: %s\n", ds.extra_options_.c_str());
+ }
+ // Reset the property
+ android::base::SetProperty(PROPERTY_EXTRA_OPTIONS, "");
+ }
+
+ ds.notification_title = android::base::GetProperty(PROPERTY_EXTRA_TITLE, "");
+ if (!ds.notification_title.empty()) {
+ // Reset the property
+ android::base::SetProperty(PROPERTY_EXTRA_TITLE, "");
+
+ ds.notification_description = android::base::GetProperty(PROPERTY_EXTRA_DESCRIPTION, "");
+ if (!ds.notification_description.empty()) {
+ // Reset the property
+ android::base::SetProperty(PROPERTY_EXTRA_DESCRIPTION, "");
+ }
+ MYLOGD("notification (title: %s, description: %s)\n",
+ ds.notification_title.c_str(), ds.notification_description.c_str());
+ }
+
+ if ((do_zip_file || do_add_date || ds.update_progress_ || do_broadcast) && !use_outfile) {
+ ExitOnInvalidArgs();
}
if (use_control_socket && !do_zip_file) {
- usage();
+ ExitOnInvalidArgs();
+ }
+
+ if (ds.update_progress_ && !do_broadcast) {
+ ExitOnInvalidArgs();
+ }
+
+ if (is_remote_mode && (ds.update_progress_ || !do_broadcast || !do_zip_file || !do_add_date)) {
+ ExitOnInvalidArgs();
+ }
+
+ if (ds.version_ == VERSION_DEFAULT) {
+ ds.version_ = VERSION_CURRENT;
+ }
+
+ if (ds.version_ != VERSION_CURRENT && ds.version_ != VERSION_SPLIT_ANR) {
+ MYLOGE("invalid version requested ('%s'); suppported values are: ('%s', '%s', '%s')\n",
+ ds.version_.c_str(), VERSION_DEFAULT.c_str(), VERSION_CURRENT.c_str(),
+ VERSION_SPLIT_ANR.c_str());
exit(1);
}
- if (do_update_progress && !do_broadcast) {
- usage();
- exit(1);
+ if (show_header_only) {
+ ds.PrintHeader();
+ exit(0);
}
- if (is_remote_mode && (do_update_progress || !do_broadcast || !do_zip_file || !do_add_date)) {
- usage();
- exit(1);
+ /* redirect output if needed */
+ bool is_redirecting = !use_socket && use_outfile;
+
+ // TODO: temporarily set progress until it's part of the Dumpstate constructor
+ std::string stats_path =
+ is_redirecting ? android::base::StringPrintf("%s/dumpstate-stats.txt", dirname(use_outfile))
+ : "";
+ ds.progress_.reset(new Progress(stats_path));
+
+ /* gets the sequential id */
+ uint32_t last_id = android::base::GetIntProperty(PROPERTY_LAST_ID, 0);
+ ds.id_ = ++last_id;
+ android::base::SetProperty(PROPERTY_LAST_ID, std::to_string(last_id));
+
+ MYLOGI("begin\n");
+
+ register_sig_handler();
+
+ if (do_start_service) {
+ MYLOGI("Starting 'dumpstate' service\n");
+ android::status_t ret;
+ if ((ret = android::os::DumpstateService::Start()) != android::OK) {
+ MYLOGE("Unable to start DumpstateService: %d\n", ret);
+ }
}
- if (version != VERSION_DEFAULT) {
- usage();
- exit(1);
+ if (PropertiesHelper::IsDryRun()) {
+ MYLOGI("Running on dry-run mode (to disable it, call 'setprop dumpstate.dry_run false')\n");
}
- MYLOGI("bugreport format version: %s\n", version.c_str());
+ MYLOGI("dumpstate info: id=%d, args='%s', extra_options= %s)\n", ds.id_, ds.args_.c_str(),
+ ds.extra_options_.c_str());
- do_early_screenshot = do_update_progress;
+ MYLOGI("bugreport format version: %s\n", ds.version_.c_str());
+
+ ds.do_early_screenshot_ = ds.update_progress_;
// If we are going to use a socket, do it as early as possible
// to avoid timeouts from bugreport.
@@ -1430,98 +1551,74 @@
if (use_control_socket) {
MYLOGD("Opening control socket\n");
- control_socket_fd = open_socket("dumpstate");
- do_update_progress = 1;
+ ds.control_socket_fd_ = open_socket("dumpstate");
+ ds.update_progress_ = 1;
}
- /* full path of the temporary file containing the bugreport */
- std::string tmp_path;
-
- /* full path of the file containing the dumpstate logs*/
- std::string log_path;
-
- /* full path of the systrace file, when enabled */
- std::string systrace_path;
-
- /* full path of the temporary file containing the screenshot (when requested) */
- std::string screenshot_path;
-
- /* base name (without suffix or extensions) of the bugreport files */
- std::string base_name;
-
- /* pointer to the actual path, be it zip or text */
- std::string path;
-
- /* pointer to the zipped file */
- std::unique_ptr<FILE, int(*)(FILE*)> zip_file(NULL, fclose);
-
- /* redirect output if needed */
- bool is_redirecting = !use_socket && use_outfile;
-
if (is_redirecting) {
- bugreport_dir = dirname(use_outfile);
- base_name = basename(use_outfile);
+ ds.bugreport_dir_ = dirname(use_outfile);
+ std::string build_id = android::base::GetProperty("ro.build.id", "UNKNOWN_BUILD");
+ std::string device_name = android::base::GetProperty("ro.product.name", "UNKNOWN_DEVICE");
+ ds.base_name_ = android::base::StringPrintf("%s-%s-%s", basename(use_outfile),
+ device_name.c_str(), build_id.c_str());
if (do_add_date) {
char date[80];
- strftime(date, sizeof(date), "%Y-%m-%d-%H-%M-%S", localtime(&now));
- suffix = date;
+ strftime(date, sizeof(date), "%Y-%m-%d-%H-%M-%S", localtime(&ds.now_));
+ ds.name_ = date;
} else {
- suffix = "undated";
+ ds.name_ = "undated";
}
- char build_id[PROPERTY_VALUE_MAX];
- property_get("ro.build.id", build_id, "UNKNOWN_BUILD");
- base_name = base_name + "-" + build_id;
- if (telephony_only) {
- base_name = base_name + "-telephony";
- }
- if (do_fb) {
- // TODO: if dumpstate was an object, the paths could be internal variables and then
- // we could have a function to calculate the derived values, such as:
- // screenshot_path = GetPath(".png");
- screenshot_path = bugreport_dir + "/" + base_name + "-" + suffix + ".png";
- }
- tmp_path = bugreport_dir + "/" + base_name + "-" + suffix + ".tmp";
- log_path = bugreport_dir + "/dumpstate_log-" + suffix + "-"
- + std::to_string(getpid()) + ".txt";
- MYLOGD("Bugreport dir: %s\n"
- "Base name: %s\n"
- "Suffix: %s\n"
- "Log path: %s\n"
- "Temporary path: %s\n"
- "Screenshot path: %s\n",
- bugreport_dir.c_str(), base_name.c_str(), suffix.c_str(),
- log_path.c_str(), tmp_path.c_str(), screenshot_path.c_str());
+ if (telephony_only) {
+ ds.base_name_ += "-telephony";
+ }
+
+ if (do_fb) {
+ ds.screenshot_path_ = ds.GetPath(".png");
+ }
+ ds.tmp_path_ = ds.GetPath(".tmp");
+ ds.log_path_ = ds.GetPath("-dumpstate_log-" + std::to_string(ds.pid_) + ".txt");
+
+ MYLOGD(
+ "Bugreport dir: %s\n"
+ "Base name: %s\n"
+ "Suffix: %s\n"
+ "Log path: %s\n"
+ "Temporary path: %s\n"
+ "Screenshot path: %s\n",
+ ds.bugreport_dir_.c_str(), ds.base_name_.c_str(), ds.name_.c_str(),
+ ds.log_path_.c_str(), ds.tmp_path_.c_str(), ds.screenshot_path_.c_str());
if (do_zip_file) {
- path = bugreport_dir + "/" + base_name + "-" + suffix + ".zip";
- MYLOGD("Creating initial .zip file (%s)\n", path.c_str());
- create_parent_dirs(path.c_str());
- zip_file.reset(fopen(path.c_str(), "wb"));
- if (!zip_file) {
- MYLOGE("fopen(%s, 'wb'): %s\n", path.c_str(), strerror(errno));
+ ds.path_ = ds.GetPath(".zip");
+ MYLOGD("Creating initial .zip file (%s)\n", ds.path_.c_str());
+ create_parent_dirs(ds.path_.c_str());
+ ds.zip_file.reset(fopen(ds.path_.c_str(), "wb"));
+ if (ds.zip_file == nullptr) {
+ MYLOGE("fopen(%s, 'wb'): %s\n", ds.path_.c_str(), strerror(errno));
do_zip_file = 0;
} else {
- zip_writer.reset(new ZipWriter(zip_file.get()));
+ ds.zip_writer_.reset(new ZipWriter(ds.zip_file.get()));
}
- add_text_zip_entry("version.txt", version);
+ ds.AddTextZipEntry("version.txt", ds.version_);
}
- if (do_update_progress) {
+ if (ds.update_progress_) {
if (do_broadcast) {
// clang-format off
+
std::vector<std::string> am_args = {
- "--receiver-permission", "android.permission.DUMP", "--receiver-foreground",
- "--es", "android.intent.extra.NAME", suffix,
- "--ei", "android.intent.extra.ID", std::to_string(id),
- "--ei", "android.intent.extra.PID", std::to_string(getpid()),
- "--ei", "android.intent.extra.MAX", std::to_string(WEIGHT_TOTAL),
+ "--receiver-permission", "android.permission.DUMP",
+ "--es", "android.intent.extra.NAME", ds.name_,
+ "--ei", "android.intent.extra.ID", std::to_string(ds.id_),
+ "--ei", "android.intent.extra.PID", std::to_string(ds.pid_),
+ "--ei", "android.intent.extra.MAX", std::to_string(ds.progress_->GetMax()),
};
// clang-format on
- send_broadcast("android.intent.action.BUGREPORT_STARTED", am_args);
+ SendBroadcast("com.android.internal.intent.action.BUGREPORT_STARTED", am_args);
}
if (use_control_socket) {
- dprintf(control_socket_fd, "BEGIN:%s\n", path.c_str());
+ dprintf(ds.control_socket_fd_, "BEGIN:%s\n", ds.path_.c_str());
}
}
}
@@ -1533,66 +1630,61 @@
fclose(cmdline);
}
- /* open the vibrator before dropping root */
- std::unique_ptr<FILE, int(*)(FILE*)> vibrator(NULL, fclose);
if (do_vibrate) {
- vibrator.reset(fopen("/sys/class/timed_output/vibrator/enable", "we"));
- if (vibrator) {
- vibrate(vibrator.get(), 150);
- }
+ Vibrate(150);
}
- if (do_fb && do_early_screenshot) {
- if (screenshot_path.empty()) {
+ if (do_fb && ds.do_early_screenshot_) {
+ if (ds.screenshot_path_.empty()) {
// should not have happened
MYLOGE("INTERNAL ERROR: skipping early screenshot because path was not set\n");
} else {
MYLOGI("taking early screenshot\n");
- take_screenshot(screenshot_path);
- MYLOGI("wrote screenshot: %s\n", screenshot_path.c_str());
- if (chown(screenshot_path.c_str(), AID_SHELL, AID_SHELL)) {
- MYLOGE("Unable to change ownership of screenshot file %s: %s\n",
- screenshot_path.c_str(), strerror(errno));
- }
+ ds.TakeScreenshot();
}
}
if (do_zip_file) {
- if (chown(path.c_str(), AID_SHELL, AID_SHELL)) {
- MYLOGE("Unable to change ownership of zip file %s: %s\n", path.c_str(), strerror(errno));
+ if (chown(ds.path_.c_str(), AID_SHELL, AID_SHELL)) {
+ MYLOGE("Unable to change ownership of zip file %s: %s\n", ds.path_.c_str(),
+ strerror(errno));
}
}
if (is_redirecting) {
- redirect_to_file(stderr, const_cast<char*>(log_path.c_str()));
- if (chown(log_path.c_str(), AID_SHELL, AID_SHELL)) {
+ redirect_to_file(stderr, const_cast<char*>(ds.log_path_.c_str()));
+ if (chown(ds.log_path_.c_str(), AID_SHELL, AID_SHELL)) {
MYLOGE("Unable to change ownership of dumpstate log file %s: %s\n",
- log_path.c_str(), strerror(errno));
+ ds.log_path_.c_str(), strerror(errno));
}
/* TODO: rather than generating a text file now and zipping it later,
it would be more efficient to redirect stdout to the zip entry
directly, but the libziparchive doesn't support that option yet. */
- redirect_to_file(stdout, const_cast<char*>(tmp_path.c_str()));
- if (chown(tmp_path.c_str(), AID_SHELL, AID_SHELL)) {
+ redirect_to_file(stdout, const_cast<char*>(ds.tmp_path_.c_str()));
+ if (chown(ds.tmp_path_.c_str(), AID_SHELL, AID_SHELL)) {
MYLOGE("Unable to change ownership of temporary bugreport file %s: %s\n",
- tmp_path.c_str(), strerror(errno));
+ ds.tmp_path_.c_str(), strerror(errno));
}
}
+
+ // Don't buffer stdout
+ setvbuf(stdout, nullptr, _IONBF, 0);
+
// NOTE: there should be no stdout output until now, otherwise it would break the header.
// In particular, DurationReport objects should be created passing 'title, NULL', so their
// duration is logged into MYLOG instead.
- print_header(version);
+ ds.PrintHeader();
if (telephony_only) {
- dump_iptables();
- if (!drop_root_user()) {
+ DumpIpTables();
+ if (!DropRootUser()) {
return -1;
}
do_dmesg();
- do_logcat();
- do_kmsg();
- dumpstate_board();
- dump_modem_logs();
+ DoLogcat();
+ DoKmsg();
+ ds.DumpstateBoard();
+ DumpModemLogs();
} else {
// Dumps systrace right away, otherwise it will be filled with unnecessary events.
// First try to dump anrd trace if the daemon is running. Otherwise, dump
@@ -1601,40 +1693,44 @@
dump_systrace();
}
- // TODO: Drop root user and move into dumpstate() once b/28633932 is fixed.
- dump_raft();
-
// Invoking the following dumpsys calls before dump_traces() to try and
// keep the system stats as close to its initial state as possible.
- run_command_as_shell("DUMPSYS MEMINFO", 30, "dumpsys", "-t", "30", "meminfo", "-a", NULL);
- run_command_as_shell("DUMPSYS CPUINFO", 10, "dumpsys", "-t", "10", "cpuinfo", "-a", NULL);
+ RunDumpsys("DUMPSYS MEMINFO", {"meminfo", "-a"},
+ CommandOptions::WithTimeout(90).DropRoot().Build());
+ RunDumpsys("DUMPSYS CPUINFO", {"cpuinfo", "-a"},
+ CommandOptions::WithTimeout(10).DropRoot().Build());
+
+ // TODO: Drop root user and move into dumpstate() once b/28633932 is fixed.
+ dump_raft();
/* collect stack traces from Dalvik and native processes (needs root) */
dump_traces_path = dump_traces();
/* Run some operations that require root. */
get_tombstone_fds(tombstone_data);
- add_dir(RECOVERY_DIR, true);
- add_dir(RECOVERY_DATA_DIR, true);
- add_dir(LOGPERSIST_DATA_DIR, false);
- if (!is_user_build()) {
- add_dir(PROFILE_DATA_DIR_CUR, true);
- add_dir(PROFILE_DATA_DIR_REF, true);
+ ds.AddDir(RECOVERY_DIR, true);
+ ds.AddDir(RECOVERY_DATA_DIR, true);
+ ds.AddDir(LOGPERSIST_DATA_DIR, false);
+ if (!PropertiesHelper::IsUserBuild()) {
+ ds.AddDir(PROFILE_DATA_DIR_CUR, true);
+ ds.AddDir(PROFILE_DATA_DIR_REF, true);
}
add_mountinfo();
- dump_iptables();
+ DumpIpTables();
// Capture any IPSec policies in play. No keys are exposed here.
- run_command("IP XFRM POLICY", 10, "ip", "xfrm", "policy", nullptr);
+ RunCommand("IP XFRM POLICY", {"ip", "xfrm", "policy"},
+ CommandOptions::WithTimeout(10).Build());
// Run ss as root so we can see socket marks.
- run_command("DETAILED SOCKET STATE", 10, "ss", "-eionptu", NULL);
+ RunCommand("DETAILED SOCKET STATE", {"ss", "-eionptu"},
+ CommandOptions::WithTimeout(10).Build());
- if (!drop_root_user()) {
+ if (!DropRootUser()) {
return -1;
}
- dumpstate(do_early_screenshot ? "": screenshot_path, version);
+ dumpstate();
}
/* close output if needed */
@@ -1646,125 +1742,136 @@
if (use_outfile) {
/* check if user changed the suffix using system properties */
- char key[PROPERTY_KEY_MAX];
- char value[PROPERTY_VALUE_MAX];
- snprintf(key, sizeof(key), "dumpstate.%d.name", getpid());
- property_get(key, value, "");
+ std::string name = android::base::GetProperty(
+ android::base::StringPrintf("dumpstate.%d.name", ds.pid_), "");
bool change_suffix= false;
- if (value[0]) {
+ if (!name.empty()) {
/* must whitelist which characters are allowed, otherwise it could cross directories */
std::regex valid_regex("^[-_a-zA-Z0-9]+$");
- if (std::regex_match(value, valid_regex)) {
+ if (std::regex_match(name.c_str(), valid_regex)) {
change_suffix = true;
} else {
- MYLOGE("invalid suffix provided by user: %s\n", value);
+ MYLOGE("invalid suffix provided by user: %s\n", name.c_str());
}
}
if (change_suffix) {
- MYLOGI("changing suffix from %s to %s\n", suffix.c_str(), value);
- suffix = value;
- if (!screenshot_path.empty()) {
- std::string new_screenshot_path =
- bugreport_dir + "/" + base_name + "-" + suffix + ".png";
- if (rename(screenshot_path.c_str(), new_screenshot_path.c_str())) {
- MYLOGE("rename(%s, %s): %s\n", screenshot_path.c_str(),
- new_screenshot_path.c_str(), strerror(errno));
+ MYLOGI("changing suffix from %s to %s\n", ds.name_.c_str(), name.c_str());
+ ds.name_ = name;
+ if (!ds.screenshot_path_.empty()) {
+ std::string new_screenshot_path = ds.GetPath(".png");
+ if (rename(ds.screenshot_path_.c_str(), new_screenshot_path.c_str())) {
+ MYLOGE("rename(%s, %s): %s\n", ds.screenshot_path_.c_str(),
+ new_screenshot_path.c_str(), strerror(errno));
} else {
- screenshot_path = new_screenshot_path;
+ ds.screenshot_path_ = new_screenshot_path;
}
}
}
bool do_text_file = true;
if (do_zip_file) {
- std::string entry_name = base_name + "-" + suffix + ".txt";
- MYLOGD("Adding main entry (%s) to .zip bugreport\n", entry_name.c_str());
- if (!finish_zip_file(entry_name, tmp_path, now)) {
+ if (!ds.FinishZipFile()) {
MYLOGE("Failed to finish zip file; sending text bugreport instead\n");
do_text_file = true;
} else {
do_text_file = false;
// Since zip file is already created, it needs to be renamed.
- std::string new_path = bugreport_dir + "/" + base_name + "-" + suffix + ".zip";
- if (path != new_path) {
- MYLOGD("Renaming zip file from %s to %s\n", path.c_str(), new_path.c_str());
- if (rename(path.c_str(), new_path.c_str())) {
- MYLOGE("rename(%s, %s): %s\n", path.c_str(),
- new_path.c_str(), strerror(errno));
+ std::string new_path = ds.GetPath(".zip");
+ if (ds.path_ != new_path) {
+ MYLOGD("Renaming zip file from %s to %s\n", ds.path_.c_str(), new_path.c_str());
+ if (rename(ds.path_.c_str(), new_path.c_str())) {
+ MYLOGE("rename(%s, %s): %s\n", ds.path_.c_str(), new_path.c_str(),
+ strerror(errno));
} else {
- path = new_path;
+ ds.path_ = new_path;
}
}
}
}
if (do_text_file) {
- path = bugreport_dir + "/" + base_name + "-" + suffix + ".txt";
- MYLOGD("Generating .txt bugreport at %s from %s\n", path.c_str(), tmp_path.c_str());
- if (rename(tmp_path.c_str(), path.c_str())) {
- MYLOGE("rename(%s, %s): %s\n", tmp_path.c_str(), path.c_str(), strerror(errno));
- path.clear();
+ ds.path_ = ds.GetPath(".txt");
+ MYLOGD("Generating .txt bugreport at %s from %s\n", ds.path_.c_str(),
+ ds.tmp_path_.c_str());
+ if (rename(ds.tmp_path_.c_str(), ds.path_.c_str())) {
+ MYLOGE("rename(%s, %s): %s\n", ds.tmp_path_.c_str(), ds.path_.c_str(),
+ strerror(errno));
+ ds.path_.clear();
}
}
if (use_control_socket) {
if (do_text_file) {
- dprintf(control_socket_fd, "FAIL:could not create zip file, check %s "
- "for more details\n", log_path.c_str());
+ dprintf(ds.control_socket_fd_,
+ "FAIL:could not create zip file, check %s "
+ "for more details\n",
+ ds.log_path_.c_str());
} else {
- dprintf(control_socket_fd, "OK:%s\n", path.c_str());
+ dprintf(ds.control_socket_fd_, "OK:%s\n", ds.path_.c_str());
}
}
}
/* vibrate a few but shortly times to let user know it's finished */
- if (vibrator) {
- for (int i = 0; i < 3; i++) {
- vibrate(vibrator.get(), 75);
- usleep((75 + 50) * 1000);
- }
+ for (int i = 0; i < 3; i++) {
+ Vibrate(75);
+ usleep((75 + 50) * 1000);
}
/* tell activity manager we're done */
if (do_broadcast) {
- if (!path.empty()) {
- MYLOGI("Final bugreport path: %s\n", path.c_str());
+ if (!ds.path_.empty()) {
+ MYLOGI("Final bugreport path: %s\n", ds.path_.c_str());
// clang-format off
+
std::vector<std::string> am_args = {
- "--receiver-permission", "android.permission.DUMP", "--receiver-foreground",
- "--ei", "android.intent.extra.ID", std::to_string(id),
- "--ei", "android.intent.extra.PID", std::to_string(getpid()),
- "--ei", "android.intent.extra.MAX", std::to_string(weight_total),
- "--es", "android.intent.extra.BUGREPORT", path,
- "--es", "android.intent.extra.DUMPSTATE_LOG", log_path
+ "--receiver-permission", "android.permission.DUMP",
+ "--ei", "android.intent.extra.ID", std::to_string(ds.id_),
+ "--ei", "android.intent.extra.PID", std::to_string(ds.pid_),
+ "--ei", "android.intent.extra.MAX", std::to_string(ds.progress_->GetMax()),
+ "--es", "android.intent.extra.BUGREPORT", ds.path_,
+ "--es", "android.intent.extra.DUMPSTATE_LOG", ds.log_path_
};
// clang-format on
if (do_fb) {
am_args.push_back("--es");
am_args.push_back("android.intent.extra.SCREENSHOT");
- am_args.push_back(screenshot_path);
+ am_args.push_back(ds.screenshot_path_);
+ }
+ if (!ds.notification_title.empty()) {
+ am_args.push_back("--es");
+ am_args.push_back("android.intent.extra.TITLE");
+ am_args.push_back(ds.notification_title);
+ if (!ds.notification_description.empty()) {
+ am_args.push_back("--es");
+ am_args.push_back("android.intent.extra.DESCRIPTION");
+ am_args.push_back(ds.notification_description);
+ }
}
if (is_remote_mode) {
am_args.push_back("--es");
am_args.push_back("android.intent.extra.REMOTE_BUGREPORT_HASH");
- am_args.push_back(SHA256_file_hash(path));
- send_broadcast("android.intent.action.REMOTE_BUGREPORT_FINISHED", am_args);
+ am_args.push_back(SHA256_file_hash(ds.path_));
+ SendBroadcast("com.android.internal.intent.action.REMOTE_BUGREPORT_FINISHED",
+ am_args);
} else {
- send_broadcast("android.intent.action.BUGREPORT_FINISHED", am_args);
+ SendBroadcast("com.android.internal.intent.action.BUGREPORT_FINISHED", am_args);
}
} else {
MYLOGE("Skipping finished broadcast because bugreport could not be generated\n");
}
}
- MYLOGD("Final progress: %d/%d (originally %d)\n", progress, weight_total, WEIGHT_TOTAL);
- MYLOGI("done\n");
+ MYLOGD("Final progress: %d/%d (estimated %d)\n", ds.progress_->Get(), ds.progress_->GetMax(),
+ ds.progress_->GetInitialMax());
+ ds.progress_->Save();
+ MYLOGI("done (id %d)\n", ds.id_);
if (is_redirecting) {
fclose(stderr);
}
- if (use_control_socket && control_socket_fd != -1) {
- MYLOGD("Closing control socket\n");
- close(control_socket_fd);
+ if (use_control_socket && ds.control_socket_fd_ != -1) {
+ MYLOGD("Closing control socket\n");
+ close(ds.control_socket_fd_);
}
return 0;
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 514af59..f02303b 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -14,93 +14,341 @@
* limitations under the License.
*/
-#ifndef _DUMPSTATE_H_
-#define _DUMPSTATE_H_
-
-/* When defined, skips the real dumps and just print the section headers.
- Useful when debugging dumpstate itself. */
-//#define _DUMPSTATE_DRY_RUN_
-
-#ifdef _DUMPSTATE_DRY_RUN_
-#define ON_DRY_RUN_RETURN(X) return X
-#define ON_DRY_RUN(code) code
-#else
-#define ON_DRY_RUN_RETURN(X)
-#define ON_DRY_RUN(code)
-#endif
-
-#ifndef MYLOGD
-#define MYLOGD(...) fprintf(stderr, __VA_ARGS__); ALOGD(__VA_ARGS__);
-#endif
-
-#ifndef MYLOGI
-#define MYLOGI(...) fprintf(stderr, __VA_ARGS__); ALOGI(__VA_ARGS__);
-#endif
-
-#ifndef MYLOGE
-#define MYLOGE(...) fprintf(stderr, __VA_ARGS__); ALOGE(__VA_ARGS__);
-#endif
+#ifndef FRAMEWORK_NATIVE_CMD_DUMPSTATE_H_
+#define FRAMEWORK_NATIVE_CMD_DUMPSTATE_H_
#include <time.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdio.h>
+
+#include <string>
#include <vector>
-#define SU_PATH "/system/xbin/su"
+#include <android-base/macros.h>
+#include <ziparchive/zip_writer.h>
+#include "DumpstateUtil.h"
+#include "android/os/BnDumpstate.h"
+
+// Workaround for const char *args[MAX_ARGS_ARRAY_SIZE] variables until they're converted to
+// std::vector<std::string>
+// TODO: remove once not used
+#define MAX_ARGS_ARRAY_SIZE 1000
+
+// TODO: move everything under this namespace
+// TODO: and then remove explicitly android::os::dumpstate:: prefixes
+namespace android {
+namespace os {
+namespace dumpstate {
+
+class DumpstateTest;
+class ProgressTest;
+
+} // namespace dumpstate
+} // namespace os
+} // namespace android
+
+// TODO: remove once moved to HAL
#ifdef __cplusplus
extern "C" {
#endif
-typedef void (for_each_pid_func)(int, const char *);
-typedef void (for_each_tid_func)(int, int, const char *);
-
-/* Estimated total weight of bugreport generation.
+/*
+ * Helper class used to report how long it takes for a section to finish.
*
- * Each section contributes to the total weight by an individual weight, so the overall progress
- * can be calculated by dividing the all completed weight by the total weight.
+ * Typical usage:
*
- * This value is defined empirically and it need to be adjusted as more sections are added.
+ * DurationReporter duration_reporter(title);
*
- * It does not need to match the exact sum of all sections, but ideally it should to be slight more
- * than such sum: a value too high will cause the bugreport to finish before the user expected (for
- * example, jumping from 70% to 100%), while a value too low will cause the progress to get stuck
- * at an almost-finished value (like 99%) for a while.
*/
-static const int WEIGHT_TOTAL = 6500;
+class DurationReporter {
+ public:
+ DurationReporter(const std::string& title, bool log_only = false);
-/* Most simple commands have 10 as timeout, so 5 is a good estimate */
-static const int WEIGHT_FILE = 5;
+ ~DurationReporter();
+
+ private:
+ std::string title_;
+ bool log_only_;
+ uint64_t started_;
+
+ DISALLOW_COPY_AND_ASSIGN(DurationReporter);
+};
/*
- * TODO: the dumpstate internal state is getting fragile; for example, this variable is defined
- * here, declared at utils.cpp, and used on utils.cpp and dumpstate.cpp.
- * It would be better to take advantage of the C++ migration and encapsulate the state in an object,
- * but that will be better handled in a major C++ refactoring, which would also get rid of other C
- * idioms (like using std::string instead of char*, removing varargs, etc...) */
-extern int do_update_progress, progress, weight_total, control_socket_fd;
+ * Keeps track of current progress and estimated max, saving stats on file to tune up future runs.
+ *
+ * Each `dumpstate` section contributes to the total weight by an individual weight, so the overall
+ * progress can be calculated by dividing the estimate max progress by the current progress.
+ *
+ * The estimated max progress is initially set to a value (`kDefaultMax) defined empirically, but
+ * it's adjusted after each dumpstate run by storing the average duration in a file.
+ *
+ */
+class Progress {
+ friend class android::os::dumpstate::ProgressTest;
+ friend class android::os::dumpstate::DumpstateTest;
-/* full path of the directory where the bugreport files will be written */
-extern std::string bugreport_dir;
+ public:
+ /*
+ * Default estimation of the max duration of a bugreport generation.
+ *
+ * It does not need to match the exact sum of all sections, but ideally it should to be slight
+ * more than such sum: a value too high will cause the bugreport to finish before the user
+ * expected (for example, jumping from 70% to 100%), while a value too low will cause the
+ * progress to get stuck at an almost-finished value (like 99%) for a while.
+ *
+ * This constant is only used when the average duration from previous runs cannot be used.
+ */
+ static const int kDefaultMax;
-/* root dir for all files copied as-is into the bugreport. */
-extern const std::string ZIP_ROOT_DIR;
+ Progress(const std::string& path = "");
-/* Checkes whether dumpstate is generating a zipped bugreport. */
-bool is_zipping();
+ // Gets the current progress.
+ int32_t Get() const;
-/* adds a new entry to the existing zip file. */
-bool add_zip_entry(const std::string& entry_name, const std::string& entry_path);
+ // Gets the current estimated max progress.
+ int32_t GetMax() const;
-/* adds a new entry to the existing zip file. */
-bool add_zip_entry_from_fd(const std::string& entry_name, int fd);
+ // Gets the initial estimated max progress.
+ int32_t GetInitialMax() const;
-/* adds all files from a directory to the zipped bugreport file */
-void add_dir(const char *dir, bool recursive);
+ // Increments progress (ignored if not positive).
+ // Returns `true` if the max progress increased as well.
+ bool Inc(int32_t delta);
-/* prints the contents of a file */
-int dump_file(const char *title, const char *path);
+ // Persist the stats.
+ void Save();
+
+ void Dump(int fd, const std::string& prefix) const;
+
+ private:
+ Progress(int32_t initial_max, float growth_factor,
+ const std::string& path = ""); // Used by test cases.
+ Progress(int32_t initial_max, int32_t progress, float growth_factor); // Used by test cases.
+ void Load();
+ int32_t initial_max_;
+ int32_t progress_;
+ int32_t max_;
+ float growth_factor_;
+ int32_t n_runs_;
+ int32_t average_max_;
+ const std::string& path_;
+};
+
+/*
+ * List of supported zip format versions.
+ *
+ * See bugreport-format.md for more info.
+ */
+static std::string VERSION_CURRENT = "1.0";
+
+/*
+ * Temporary version that adds a anr-traces.txt entry. Once tools support it, the current version
+ * will be bumped to 2.0-dev-1.
+ */
+static std::string VERSION_SPLIT_ANR = "2.0-dev-1";
+
+/*
+ * "Alias" for the current version.
+ */
+static std::string VERSION_DEFAULT = "default";
+
+/*
+ * Main class driving a bugreport generation.
+ *
+ * Currently, it only contains variables that are accessed externally, but gradually the functions
+ * that are spread accross utils.cpp and dumpstate.cpp will be moved to it.
+ */
+class Dumpstate {
+ friend class DumpstateTest;
+
+ public:
+ static android::os::dumpstate::CommandOptions DEFAULT_DUMPSYS;
+
+ static Dumpstate& GetInstance();
+
+ /* Checkes whether dumpstate is generating a zipped bugreport. */
+ bool IsZipping() const;
+
+ /*
+ * Forks a command, waits for it to finish, and returns its status.
+ *
+ * |title| description of the command printed on `stdout` (or empty to skip
+ * description).
+ * |full_command| array containing the command (first entry) and its arguments.
+ * Must contain at least one element.
+ * |options| optional argument defining the command's behavior.
+ */
+ int RunCommand(const std::string& title, const std::vector<std::string>& fullCommand,
+ const android::os::dumpstate::CommandOptions& options =
+ android::os::dumpstate::CommandOptions::DEFAULT);
+
+ /*
+ * Runs `dumpsys` with the given arguments, automatically setting its timeout
+ * (`-t` argument)
+ * according to the command options.
+ *
+ * |title| description of the command printed on `stdout` (or empty to skip
+ * description).
+ * |dumpsys_args| `dumpsys` arguments (except `-t`).
+ * |options| optional argument defining the command's behavior.
+ * |dumpsys_timeout| when > 0, defines the value passed to `dumpsys -t` (otherwise it uses the
+ * timeout from `options`)
+ */
+ void RunDumpsys(const std::string& title, const std::vector<std::string>& dumpsys_args,
+ const android::os::dumpstate::CommandOptions& options = DEFAULT_DUMPSYS,
+ long dumpsys_timeout = 0);
+
+ /*
+ * Prints the contents of a file.
+ *
+ * |title| description of the command printed on `stdout` (or empty to skip
+ * description).
+ * |path| location of the file to be dumped.
+ */
+ int DumpFile(const std::string& title, const std::string& path);
+
+ /*
+ * Adds a new entry to the existing zip file.
+ * */
+ bool AddZipEntry(const std::string& entry_name, const std::string& entry_path);
+
+ /*
+ * Adds a new entry to the existing zip file.
+ */
+ bool AddZipEntryFromFd(const std::string& entry_name, int fd);
+
+ /*
+ * Adds a text entry entry to the existing zip file.
+ */
+ bool AddTextZipEntry(const std::string& entry_name, const std::string& content);
+
+ /*
+ * Adds all files from a directory to the zipped bugreport file.
+ */
+ void AddDir(const std::string& dir, bool recursive);
+
+ /*
+ * Takes a screenshot and save it to the given `path`.
+ *
+ * If `path` is empty, uses a standard path based on the bugreport name.
+ */
+ void TakeScreenshot(const std::string& path = "");
+
+ /////////////////////////////////////////////////////////////////////
+ // TODO: members below should be private once refactor is finished //
+ /////////////////////////////////////////////////////////////////////
+
+ // TODO: temporary method until Dumpstate object is properly set
+ void SetProgress(std::unique_ptr<Progress> progress);
+
+ void DumpstateBoard();
+
+ /*
+ * Updates the overall progress of the bugreport generation by the given weight increment.
+ */
+ void UpdateProgress(int32_t delta);
+
+ /* Prints the dumpstate header on `stdout`. */
+ void PrintHeader() const;
+
+ /*
+ * Adds the temporary report to the existing .zip file, closes the .zip file, and removes the
+ * temporary file.
+ */
+ bool FinishZipFile();
+
+ /* Gets the path of a bugreport file with the given suffix. */
+ std::string GetPath(const std::string& suffix) const;
+
+ // TODO: initialize fields on constructor
+
+ // dumpstate id - unique after each device reboot.
+ uint32_t id_;
+
+ // dumpstate pid
+ pid_t pid_;
+
+ // Whether progress updates should be published.
+ bool update_progress_ = false;
+
+ // How frequently the progess should be updated;the listener will only be notificated when the
+ // delta from the previous update is more than the threshold.
+ int32_t update_progress_threshold_ = 100;
+
+ // Last progress that triggered a listener updated
+ int32_t last_updated_progress_;
+
+ // Whether it should take an screenshot earlier in the process.
+ bool do_early_screenshot_ = false;
+
+ std::unique_ptr<Progress> progress_;
+
+ // When set, defines a socket file-descriptor use to report progress to bugreportz.
+ int control_socket_fd_ = -1;
+
+ // Bugreport format version;
+ std::string version_ = VERSION_CURRENT;
+
+ // Command-line arguments as string
+ std::string args_;
+
+ // Extra options passed as system property.
+ std::string extra_options_;
+
+ // Full path of the directory where the bugreport files will be written.
+ std::string bugreport_dir_;
+
+ // Full path of the temporary file containing the screenshot (when requested).
+ std::string screenshot_path_;
+
+ time_t now_;
+
+ // Base name (without suffix or extensions) of the bugreport files, typically
+ // `bugreport-BUILD_ID`.
+ std::string base_name_;
+
+ // Name is the suffix part of the bugreport files - it's typically the date (when invoked with
+ // `-d`), but it could be changed by the user..
+ std::string name_;
+
+ // Full path of the temporary file containing the bugreport.
+ std::string tmp_path_;
+
+ // Full path of the file containing the dumpstate logs.
+ std::string log_path_;
+
+ // Pointer to the actual path, be it zip or text.
+ std::string path_;
+
+ // Pointer to the zipped file.
+ std::unique_ptr<FILE, int (*)(FILE*)> zip_file{nullptr, fclose};
+
+ // Pointer to the zip structure.
+ std::unique_ptr<ZipWriter> zip_writer_;
+
+ // Binder object listing to progress.
+ android::sp<android::os::IDumpstateListener> listener_;
+ std::string listener_name_;
+
+ // Notification title and description
+ std::string notification_title;
+ std::string notification_description;
+
+ private:
+ // Used by GetInstance() only.
+ Dumpstate(const std::string& version = VERSION_CURRENT);
+
+ DISALLOW_COPY_AND_ASSIGN(Dumpstate);
+};
+
+// for_each_pid_func = void (*)(int, const char*);
+// for_each_tid_func = void (*)(int, int, const char*);
+
+typedef void(for_each_pid_func)(int, const char*);
+typedef void(for_each_tid_func)(int, int, const char*);
/* saves the the contents of a file as a long */
int read_file_as_long(const char *path, long int *output);
@@ -116,37 +364,8 @@
* to false when set to NULL. dump_from_fd will always be
* called with title NULL.
*/
-int dump_files(const char *title, const char *dir,
- bool (*skip)(const char *path),
- int (*dump_from_fd)(const char *title, const char *path, int fd));
-
-// TODO: need to refactor all those run_command variations; there shold be just one, receiving an
-// optional CommandOptions objects with values such as run_always, drop_root, etc...
-
-/* forks a command and waits for it to finish -- terminate args with NULL */
-int run_command_as_shell(const char *title, int timeout_seconds, const char *command, ...);
-int run_command(const char *title, int timeout_seconds, const char *command, ...);
-
-enum RootMode { DROP_ROOT, DONT_DROP_ROOT };
-enum StdoutMode { NORMAL_STDOUT, REDIRECT_TO_STDERR };
-
-/* forks a command and waits for it to finish
- first element of args is the command, and last must be NULL.
- command is always ran, even when _DUMPSTATE_DRY_RUN_ is defined. */
-int run_command_always(const char *title, RootMode root_mode, StdoutMode stdout_mode,
- int timeout_seconds, const char *args[]);
-
-/* switch to non-root user and group */
-bool drop_root_user();
-
-/* sends a broadcast using Activity Manager */
-void send_broadcast(const std::string& action, const std::vector<std::string>& args);
-
-/* updates the overall progress of dumpstate by the given weight increment */
-void update_progress(int weight);
-
-/* prints all the system properties */
-void print_properties();
+int dump_files(const std::string& title, const char* dir, bool (*skip)(const char* path),
+ int (*dump_from_fd)(const char* title, const char* path, int fd));
/** opens a socket and returns its file descriptor */
int open_socket(const char *service);
@@ -154,9 +373,12 @@
/* redirect output to a service control socket */
void redirect_to_socket(FILE *redirect, const char *service);
-/* redirect output to a file */
+/* redirect output to a new file */
void redirect_to_file(FILE *redirect, char *path);
+/* redirect output to an existing file */
+void redirect_to_existing_file(FILE *redirect, char *path);
+
/* create leading directories, if necessary */
void create_parent_dirs(const char *path);
@@ -187,15 +409,6 @@
/* Play a sound via Stagefright */
void play_sound(const char *path);
-/* Implemented by libdumpstate_board to dump board-specific info */
-void dumpstate_board();
-
-/* Takes a screenshot and save it to the given file */
-void take_screenshot(const std::string& path);
-
-/* Vibrates for a given durating (in milliseconds). */
-void vibrate(FILE* vibrator, int ms);
-
/* Checks if a given path is a directory. */
bool is_dir(const char* pathname);
@@ -208,34 +421,8 @@
/** Gets command-line arguments. */
void format_args(int argc, const char *argv[], std::string *args);
-/** Tells if the device is running a user build. */
-bool is_user_build();
-
-/*
- * Helper class used to report how long it takes for a section to finish.
- *
- * Typical usage:
- *
- * DurationReporter duration_reporter(title);
- *
- */
-class DurationReporter {
-public:
- DurationReporter(const char *title);
- DurationReporter(const char *title, FILE* out);
-
- ~DurationReporter();
-
- static uint64_t nanotime();
-
-private:
- const char* title_;
- FILE* out_;
- uint64_t started_;
-};
-
#ifdef __cplusplus
}
#endif
-#endif /* _DUMPSTATE_H_ */
+#endif /* FRAMEWORK_NATIVE_CMD_DUMPSTATE_H_ */
diff --git a/cmds/dumpstate/dumpstate.rc b/cmds/dumpstate/dumpstate.rc
index 336db9f..2e72574 100644
--- a/cmds/dumpstate/dumpstate.rc
+++ b/cmds/dumpstate/dumpstate.rc
@@ -17,40 +17,3 @@
class main
disabled
oneshot
-
-# bugreportplus is an enhanced version of bugreport that provides a better
-# user interface (like displaying progress and allowing user to enter details).
-# It's typically triggered by the power button or developer settings.
-service bugreportplus /system/bin/dumpstate -d -B -P -z \
- -o /data/user_de/0/com.android.shell/files/bugreports/bugreport
- class main
- disabled
- oneshot
-
-# bugreportremote is an altered version of bugreport that is supposed to be
-# called not by human user of the device, but by DevicePolicyManagerService only when the
-# Device Owner explicitly requests it, and shared with the Device Policy Controller (DPC) app only
-# if the user consents
-# it will disable vibrations, screenshot taking and will not track progress or
-# allow user to enter any details
-service bugreportremote /system/bin/dumpstate -d -q -B -R -z \
- -o /data/user_de/0/com.android.shell/files/bugreports/remote/bugreport
- class main
- disabled
- oneshot
-
-# bugreportwear is a wearable version of bugreport that displays progress and takes early
-# screenshot.
-service bugreportwear /system/bin/dumpstate -d -B -P -p -z \
- -o /data/user_de/0/com.android.shell/files/bugreports/bugreport
- class main
- disabled
- oneshot
-
-# bugreportelefony is a lightweight version of bugreport that only includes a few, urgent
-# sections used to report telephony bugs.
-service bugreportelefony /system/bin/dumpstate -t -d -B -z \
- -o /data/user_de/0/com.android.shell/files/bugreports/bugreport
- class main
- disabled
- oneshot
diff --git a/cmds/dumpstate/libdumpstate_default.cpp b/cmds/dumpstate/libdumpstate_default.cpp
deleted file mode 100644
index fd840df..0000000
--- a/cmds/dumpstate/libdumpstate_default.cpp
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2013 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.
- */
-
-#include "dumpstate.h"
-
-void dumpstate_board(void)
-{
-}
-
diff --git a/cmds/dumpstate/testdata/empty-file.txt b/cmds/dumpstate/testdata/empty-file.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cmds/dumpstate/testdata/empty-file.txt
diff --git a/cmds/dumpstate/testdata/multiple-lines-with-newline.txt b/cmds/dumpstate/testdata/multiple-lines-with-newline.txt
new file mode 100644
index 0000000..7b7a187
--- /dev/null
+++ b/cmds/dumpstate/testdata/multiple-lines-with-newline.txt
@@ -0,0 +1,3 @@
+I AM LINE1
+I AM LINE2
+I AM LINE3
diff --git a/cmds/dumpstate/testdata/multiple-lines.txt b/cmds/dumpstate/testdata/multiple-lines.txt
new file mode 100644
index 0000000..bead103
--- /dev/null
+++ b/cmds/dumpstate/testdata/multiple-lines.txt
@@ -0,0 +1,3 @@
+I AM LINE1
+I AM LINE2
+I AM LINE3
\ No newline at end of file
diff --git a/cmds/dumpstate/testdata/single-line-with-newline.txt b/cmds/dumpstate/testdata/single-line-with-newline.txt
new file mode 100644
index 0000000..cb48c82
--- /dev/null
+++ b/cmds/dumpstate/testdata/single-line-with-newline.txt
@@ -0,0 +1 @@
+I AM LINE1
diff --git a/cmds/dumpstate/testdata/single-line.txt b/cmds/dumpstate/testdata/single-line.txt
new file mode 100644
index 0000000..2f64046
--- /dev/null
+++ b/cmds/dumpstate/testdata/single-line.txt
@@ -0,0 +1 @@
+I AM LINE1
\ No newline at end of file
diff --git a/cmds/dumpstate/testdata/stats-invalid-1st-NAN.txt b/cmds/dumpstate/testdata/stats-invalid-1st-NAN.txt
new file mode 100644
index 0000000..dad9fe8
--- /dev/null
+++ b/cmds/dumpstate/testdata/stats-invalid-1st-NAN.txt
@@ -0,0 +1 @@
+SIX_SIX_SIX 42
diff --git a/cmds/dumpstate/testdata/stats-invalid-1st-negative.txt b/cmds/dumpstate/testdata/stats-invalid-1st-negative.txt
new file mode 100644
index 0000000..4facef9
--- /dev/null
+++ b/cmds/dumpstate/testdata/stats-invalid-1st-negative.txt
@@ -0,0 +1 @@
+-666 42
diff --git a/cmds/dumpstate/testdata/stats-invalid-1st-too-big.txt b/cmds/dumpstate/testdata/stats-invalid-1st-too-big.txt
new file mode 100644
index 0000000..42508f1
--- /dev/null
+++ b/cmds/dumpstate/testdata/stats-invalid-1st-too-big.txt
@@ -0,0 +1 @@
+4815162342 42
diff --git a/cmds/dumpstate/testdata/stats-invalid-2nd-NAN.txt b/cmds/dumpstate/testdata/stats-invalid-2nd-NAN.txt
new file mode 100644
index 0000000..a23ba2c
--- /dev/null
+++ b/cmds/dumpstate/testdata/stats-invalid-2nd-NAN.txt
@@ -0,0 +1 @@
+666 FORTY_TWO
diff --git a/cmds/dumpstate/testdata/stats-invalid-2nd-negative.txt b/cmds/dumpstate/testdata/stats-invalid-2nd-negative.txt
new file mode 100644
index 0000000..dd529b4
--- /dev/null
+++ b/cmds/dumpstate/testdata/stats-invalid-2nd-negative.txt
@@ -0,0 +1 @@
+666 -42
diff --git a/cmds/dumpstate/testdata/stats-invalid-2nd-too-big.txt b/cmds/dumpstate/testdata/stats-invalid-2nd-too-big.txt
new file mode 100644
index 0000000..b148b46
--- /dev/null
+++ b/cmds/dumpstate/testdata/stats-invalid-2nd-too-big.txt
@@ -0,0 +1 @@
+666 4815162342
diff --git a/cmds/dumpstate/testdata/stats-invalid-both-NAN.txt b/cmds/dumpstate/testdata/stats-invalid-both-NAN.txt
new file mode 100644
index 0000000..4a9466d
--- /dev/null
+++ b/cmds/dumpstate/testdata/stats-invalid-both-NAN.txt
@@ -0,0 +1 @@
+N_RUNS AVERAGE
\ No newline at end of file
diff --git a/cmds/dumpstate/testdata/stats-one-run-no-newline.txt b/cmds/dumpstate/testdata/stats-one-run-no-newline.txt
new file mode 100644
index 0000000..0aef60c
--- /dev/null
+++ b/cmds/dumpstate/testdata/stats-one-run-no-newline.txt
@@ -0,0 +1 @@
+1 10
\ No newline at end of file
diff --git a/cmds/dumpstate/testdata/stats-two-runs.txt b/cmds/dumpstate/testdata/stats-two-runs.txt
new file mode 100644
index 0000000..9af1233
--- /dev/null
+++ b/cmds/dumpstate/testdata/stats-two-runs.txt
@@ -0,0 +1 @@
+2 15
diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp
new file mode 100644
index 0000000..1c19268
--- /dev/null
+++ b/cmds/dumpstate/tests/dumpstate_test.cpp
@@ -0,0 +1,1157 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#define LOG_TAG "dumpstate"
+#include <cutils/log.h>
+
+#include "DumpstateInternal.h"
+#include "DumpstateService.h"
+#include "android/os/BnDumpstate.h"
+#include "dumpstate.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <fcntl.h>
+#include <libgen.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <thread>
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+namespace android {
+namespace os {
+namespace dumpstate {
+
+using ::testing::EndsWith;
+using ::testing::HasSubstr;
+using ::testing::IsNull;
+using ::testing::IsEmpty;
+using ::testing::NotNull;
+using ::testing::StrEq;
+using ::testing::StartsWith;
+using ::testing::Test;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+class DumpstateListenerMock : public IDumpstateListener {
+ public:
+ MOCK_METHOD1(onProgressUpdated, binder::Status(int32_t progress));
+ MOCK_METHOD1(onMaxProgressUpdated, binder::Status(int32_t max_progress));
+
+ protected:
+ MOCK_METHOD0(onAsBinder, IBinder*());
+};
+
+static int calls_;
+
+// Base class for all tests in this file
+class DumpstateBaseTest : public Test {
+ public:
+ virtual void SetUp() override {
+ calls_++;
+ SetDryRun(false);
+ }
+
+ void SetDryRun(bool dry_run) const {
+ PropertiesHelper::dry_run_ = dry_run;
+ }
+
+ void SetBuildType(const std::string& build_type) const {
+ PropertiesHelper::build_type_ = build_type;
+ }
+
+ bool IsStandalone() const {
+ return calls_ == 1;
+ }
+
+ void DropRoot() const {
+ DropRootUser();
+ uid_t uid = getuid();
+ ASSERT_EQ(2000, (int)uid);
+ }
+
+ protected:
+ const std::string kTestPath = dirname(android::base::GetExecutablePath().c_str());
+ const std::string kFixturesPath = kTestPath + "/../dumpstate_test_fixture/";
+ const std::string kTestDataPath = kFixturesPath + "/testdata/";
+ const std::string kSimpleCommand = kFixturesPath + "dumpstate_test_fixture";
+ const std::string kEchoCommand = "/system/bin/echo";
+
+ /*
+ * Copies a text file fixture to a temporary file, returning it's path.
+ *
+ * Useful in cases where the test case changes the content of the tile.
+ */
+ std::string CopyTextFileFixture(const std::string& relative_name) {
+ std::string from = kTestDataPath + relative_name;
+ // Not using TemporaryFile because it's deleted at the end, and it's useful to keep it
+ // around for poking when the test fails.
+ std::string to = kTestDataPath + relative_name + ".tmp";
+ ALOGD("CopyTextFileFixture: from %s to %s\n", from.c_str(), to.c_str());
+ android::base::RemoveFileIfExists(to);
+ CopyTextFile(from, to);
+ return to.c_str();
+ }
+
+ // Need functions that returns void to use assertions -
+ // https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md#assertion-placement
+ void ReadFileToString(const std::string& path, std::string* content) {
+ ASSERT_TRUE(android::base::ReadFileToString(path, content))
+ << "could not read contents from " << path;
+ }
+ void WriteStringToFile(const std::string& content, const std::string& path) {
+ ASSERT_TRUE(android::base::WriteStringToFile(content, path))
+ << "could not write contents to " << path;
+ }
+
+ private:
+ void CopyTextFile(const std::string& from, const std::string& to) {
+ std::string content;
+ ReadFileToString(from, &content);
+ WriteStringToFile(content, to);
+ }
+};
+
+class DumpstateTest : public DumpstateBaseTest {
+ public:
+ void SetUp() {
+ DumpstateBaseTest::SetUp();
+ SetDryRun(false);
+ SetBuildType(android::base::GetProperty("ro.build.type", "(unknown)"));
+ ds.progress_.reset(new Progress());
+ ds.update_progress_ = false;
+ ds.update_progress_threshold_ = 0;
+ }
+
+ // Runs a command and capture `stdout` and `stderr`.
+ int RunCommand(const std::string& title, const std::vector<std::string>& full_command,
+ const CommandOptions& options = CommandOptions::DEFAULT) {
+ CaptureStdout();
+ CaptureStderr();
+ int status = ds.RunCommand(title, full_command, options);
+ out = GetCapturedStdout();
+ err = GetCapturedStderr();
+ return status;
+ }
+
+ // Dumps a file and capture `stdout` and `stderr`.
+ int DumpFile(const std::string& title, const std::string& path) {
+ CaptureStdout();
+ CaptureStderr();
+ int status = ds.DumpFile(title, path);
+ out = GetCapturedStdout();
+ err = GetCapturedStderr();
+ return status;
+ }
+
+ void SetProgress(long progress, long initial_max, long threshold = 0) {
+ ds.update_progress_ = true;
+ ds.update_progress_threshold_ = threshold;
+ ds.last_updated_progress_ = 0;
+ ds.progress_.reset(new Progress(initial_max, progress, 1.2));
+ }
+
+ std::string GetProgressMessage(const std::string& listener_name, int progress, int max,
+ int old_max = 0, bool update_progress = true) {
+ EXPECT_EQ(progress, ds.progress_->Get()) << "invalid progress";
+ EXPECT_EQ(max, ds.progress_->GetMax()) << "invalid max";
+
+ bool max_increased = old_max > 0;
+
+ std::string message = "";
+ if (max_increased) {
+ message =
+ android::base::StringPrintf("Adjusting max progress from %d to %d\n", old_max, max);
+ }
+
+ if (update_progress) {
+ message += android::base::StringPrintf("Setting progress (%s): %d/%d\n",
+ listener_name.c_str(), progress, max);
+ }
+
+ return message;
+ }
+
+ // `stdout` and `stderr` from the last command ran.
+ std::string out, err;
+
+ Dumpstate& ds = Dumpstate::GetInstance();
+};
+
+TEST_F(DumpstateTest, RunCommandNoArgs) {
+ EXPECT_EQ(-1, RunCommand("", {}));
+}
+
+TEST_F(DumpstateTest, RunCommandNoTitle) {
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand}));
+ EXPECT_THAT(out, StrEq("stdout\n"));
+ EXPECT_THAT(err, StrEq("stderr\n"));
+}
+
+TEST_F(DumpstateTest, RunCommandWithTitle) {
+ EXPECT_EQ(0, RunCommand("I AM GROOT", {kSimpleCommand}));
+ EXPECT_THAT(err, StrEq("stderr\n"));
+ // We don't know the exact duration, so we check the prefix and suffix
+ EXPECT_THAT(out,
+ StartsWith("------ I AM GROOT (" + kSimpleCommand + ") ------\nstdout\n------"));
+ EXPECT_THAT(out, EndsWith("s was the duration of 'I AM GROOT' ------\n"));
+}
+
+TEST_F(DumpstateTest, RunCommandWithLoggingMessage) {
+ EXPECT_EQ(
+ 0, RunCommand("", {kSimpleCommand},
+ CommandOptions::WithTimeout(10).Log("COMMAND, Y U NO LOG FIRST?").Build()));
+ EXPECT_THAT(out, StrEq("stdout\n"));
+ EXPECT_THAT(err, StrEq("COMMAND, Y U NO LOG FIRST?stderr\n"));
+}
+
+TEST_F(DumpstateTest, RunCommandRedirectStderr) {
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand},
+ CommandOptions::WithTimeout(10).RedirectStderr().Build()));
+ EXPECT_THAT(out, IsEmpty());
+ EXPECT_THAT(err, StrEq("stdout\nstderr\n"));
+}
+
+TEST_F(DumpstateTest, RunCommandWithOneArg) {
+ EXPECT_EQ(0, RunCommand("", {kEchoCommand, "one"}));
+ EXPECT_THAT(err, IsEmpty());
+ EXPECT_THAT(out, StrEq("one\n"));
+}
+
+TEST_F(DumpstateTest, RunCommandWithMultipleArgs) {
+ EXPECT_EQ(0, RunCommand("", {kEchoCommand, "one", "is", "the", "loniest", "number"}));
+ EXPECT_THAT(err, IsEmpty());
+ EXPECT_THAT(out, StrEq("one is the loniest number\n"));
+}
+
+TEST_F(DumpstateTest, RunCommandDryRun) {
+ SetDryRun(true);
+ EXPECT_EQ(0, RunCommand("I AM GROOT", {kSimpleCommand}));
+ // We don't know the exact duration, so we check the prefix and suffix
+ EXPECT_THAT(out, StartsWith("------ I AM GROOT (" + kSimpleCommand +
+ ") ------\n\t(skipped on dry run)\n------"));
+ EXPECT_THAT(out, EndsWith("s was the duration of 'I AM GROOT' ------\n"));
+ EXPECT_THAT(err, IsEmpty());
+}
+
+TEST_F(DumpstateTest, RunCommandDryRunNoTitle) {
+ SetDryRun(true);
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand}));
+ EXPECT_THAT(out, IsEmpty());
+ EXPECT_THAT(err, IsEmpty());
+}
+
+TEST_F(DumpstateTest, RunCommandDryRunAlways) {
+ SetDryRun(true);
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand}, CommandOptions::WithTimeout(10).Always().Build()));
+ EXPECT_THAT(out, StrEq("stdout\n"));
+ EXPECT_THAT(err, StrEq("stderr\n"));
+}
+
+TEST_F(DumpstateTest, RunCommandNotFound) {
+ EXPECT_NE(0, RunCommand("", {"/there/cannot/be/such/command"}));
+ EXPECT_THAT(out, StartsWith("*** command '/there/cannot/be/such/command' failed: exit code"));
+ EXPECT_THAT(err, StartsWith("execvp on command '/there/cannot/be/such/command' failed"));
+}
+
+TEST_F(DumpstateTest, RunCommandFails) {
+ EXPECT_EQ(42, RunCommand("", {kSimpleCommand, "--exit", "42"}));
+ EXPECT_THAT(out, StrEq("stdout\n*** command '" + kSimpleCommand +
+ " --exit 42' failed: exit code 42\n"));
+ EXPECT_THAT(err, StrEq("stderr\n*** command '" + kSimpleCommand +
+ " --exit 42' failed: exit code 42\n"));
+}
+
+TEST_F(DumpstateTest, RunCommandCrashes) {
+ EXPECT_NE(0, RunCommand("", {kSimpleCommand, "--crash"}));
+ // We don't know the exit code, so check just the prefix.
+ EXPECT_THAT(
+ out, StartsWith("stdout\n*** command '" + kSimpleCommand + " --crash' failed: exit code"));
+ EXPECT_THAT(
+ err, StartsWith("stderr\n*** command '" + kSimpleCommand + " --crash' failed: exit code"));
+}
+
+TEST_F(DumpstateTest, RunCommandTimesout) {
+ EXPECT_EQ(-1, RunCommand("", {kSimpleCommand, "--sleep", "2"},
+ CommandOptions::WithTimeout(1).Build()));
+ EXPECT_THAT(out, StartsWith("stdout line1\n*** command '" + kSimpleCommand +
+ " --sleep 2' timed out after 1"));
+ EXPECT_THAT(err, StartsWith("sleeping for 2s\n*** command '" + kSimpleCommand +
+ " --sleep 2' timed out after 1"));
+}
+
+TEST_F(DumpstateTest, RunCommandIsKilled) {
+ CaptureStdout();
+ CaptureStderr();
+
+ std::thread t([=]() {
+ EXPECT_EQ(SIGTERM, ds.RunCommand("", {kSimpleCommand, "--pid", "--sleep", "20"},
+ CommandOptions::WithTimeout(100).Always().Build()));
+ });
+
+ // Capture pid and pre-sleep output.
+ sleep(1); // Wait a little bit to make sure pid and 1st line were printed.
+ std::string err = GetCapturedStderr();
+ EXPECT_THAT(err, StrEq("sleeping for 20s\n"));
+
+ std::string out = GetCapturedStdout();
+ std::vector<std::string> lines = android::base::Split(out, "\n");
+ ASSERT_EQ(3, (int)lines.size()) << "Invalid lines before sleep: " << out;
+
+ int pid = atoi(lines[0].c_str());
+ EXPECT_THAT(lines[1], StrEq("stdout line1"));
+ EXPECT_THAT(lines[2], IsEmpty()); // \n
+
+ // Then kill the process.
+ CaptureStdout();
+ CaptureStderr();
+ ASSERT_EQ(0, kill(pid, SIGTERM)) << "failed to kill pid " << pid;
+ t.join();
+
+ // Finally, check output after murder.
+ out = GetCapturedStdout();
+ err = GetCapturedStderr();
+
+ EXPECT_THAT(out, StrEq("*** command '" + kSimpleCommand +
+ " --pid --sleep 20' failed: killed by signal 15\n"));
+ EXPECT_THAT(err, StrEq("*** command '" + kSimpleCommand +
+ " --pid --sleep 20' failed: killed by signal 15\n"));
+}
+
+TEST_F(DumpstateTest, RunCommandProgress) {
+ sp<DumpstateListenerMock> listener(new DumpstateListenerMock());
+ ds.listener_ = listener;
+ ds.listener_name_ = "FoxMulder";
+ SetProgress(0, 30);
+
+ EXPECT_CALL(*listener, onProgressUpdated(20));
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand}, CommandOptions::WithTimeout(20).Build()));
+ std::string progress_message = GetProgressMessage(ds.listener_name_, 20, 30);
+ EXPECT_THAT(out, StrEq("stdout\n"));
+ EXPECT_THAT(err, StrEq("stderr\n" + progress_message));
+
+ EXPECT_CALL(*listener, onProgressUpdated(30));
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand}, CommandOptions::WithTimeout(10).Build()));
+ progress_message = GetProgressMessage(ds.listener_name_, 30, 30);
+ EXPECT_THAT(out, StrEq("stdout\n"));
+ EXPECT_THAT(err, StrEq("stderr\n" + progress_message));
+
+ // Run a command that will increase maximum timeout.
+ EXPECT_CALL(*listener, onProgressUpdated(31));
+ EXPECT_CALL(*listener, onMaxProgressUpdated(37));
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand}, CommandOptions::WithTimeout(1).Build()));
+ progress_message = GetProgressMessage(ds.listener_name_, 31, 37, 30); // 20% increase
+ EXPECT_THAT(out, StrEq("stdout\n"));
+ EXPECT_THAT(err, StrEq("stderr\n" + progress_message));
+
+ // Make sure command ran while in dry_run is counted.
+ SetDryRun(true);
+ EXPECT_CALL(*listener, onProgressUpdated(35));
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand}, CommandOptions::WithTimeout(4).Build()));
+ progress_message = GetProgressMessage(ds.listener_name_, 35, 37);
+ EXPECT_THAT(out, IsEmpty());
+ EXPECT_THAT(err, StrEq(progress_message));
+
+ ds.listener_.clear();
+}
+
+TEST_F(DumpstateTest, RunCommandProgressIgnoreThreshold) {
+ sp<DumpstateListenerMock> listener(new DumpstateListenerMock());
+ ds.listener_ = listener;
+ ds.listener_name_ = "FoxMulder";
+ SetProgress(0, 8, 5); // 8 max, 5 threshold
+
+ // First update should always be sent.
+ EXPECT_CALL(*listener, onProgressUpdated(1));
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand}, CommandOptions::WithTimeout(1).Build()));
+ std::string progress_message = GetProgressMessage(ds.listener_name_, 1, 8);
+ EXPECT_THAT(out, StrEq("stdout\n"));
+ EXPECT_THAT(err, StrEq("stderr\n" + progress_message));
+
+ // Fourth update should be ignored because it's between the threshold (5 -1 = 4 < 5).
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand}, CommandOptions::WithTimeout(4).Build()));
+ EXPECT_THAT(out, StrEq("stdout\n"));
+ EXPECT_THAT(err, StrEq("stderr\n"));
+
+ // Third update should be sent because it reaches threshold (6 - 1 = 5).
+ EXPECT_CALL(*listener, onProgressUpdated(6));
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand}, CommandOptions::WithTimeout(1).Build()));
+ progress_message = GetProgressMessage(ds.listener_name_, 6, 8);
+ EXPECT_THAT(out, StrEq("stdout\n"));
+ EXPECT_THAT(err, StrEq("stderr\n" + progress_message));
+
+ // Fourth update should be ignored because it's between the threshold (9 - 6 = 3 < 5).
+ // But max update should be sent.
+ EXPECT_CALL(*listener, onMaxProgressUpdated(10)); // 9 * 120% = 10.8 = 10
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand}, CommandOptions::WithTimeout(3).Build()));
+ progress_message = GetProgressMessage(ds.listener_name_, 9, 10, 8, false);
+ EXPECT_THAT(out, StrEq("stdout\n"));
+ EXPECT_THAT(err, StrEq("stderr\n" + progress_message));
+
+ ds.listener_.clear();
+}
+
+TEST_F(DumpstateTest, RunCommandDropRoot) {
+ if (!IsStandalone()) {
+ // TODO: temporarily disabled because it might cause other tests to fail after dropping
+ // to Shell - need to refactor tests to avoid this problem)
+ MYLOGE("Skipping DumpstateTest.RunCommandDropRoot() on test suite\n")
+ return;
+ }
+ // First check root case - only available when running with 'adb root'.
+ uid_t uid = getuid();
+ if (uid == 0) {
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand, "--uid"}));
+ EXPECT_THAT(out, StrEq("0\nstdout\n"));
+ EXPECT_THAT(err, StrEq("stderr\n"));
+ return;
+ }
+ // Then run dropping root.
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand, "--uid"},
+ CommandOptions::WithTimeout(1).DropRoot().Build()));
+ EXPECT_THAT(out, StrEq("2000\nstdout\n"));
+ EXPECT_THAT(err, StrEq("drop_root_user(): already running as Shell\nstderr\n"));
+}
+
+TEST_F(DumpstateTest, RunCommandAsRootUserBuild) {
+ if (!IsStandalone()) {
+ // TODO: temporarily disabled because it might cause other tests to fail after dropping
+ // to Shell - need to refactor tests to avoid this problem)
+ MYLOGE("Skipping DumpstateTest.RunCommandAsRootUserBuild() on test suite\n")
+ return;
+ }
+ if (!PropertiesHelper::IsUserBuild()) {
+ // Emulates user build if necessarily.
+ SetBuildType("user");
+ }
+
+ DropRoot();
+
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand}, CommandOptions::WithTimeout(1).AsRoot().Build()));
+
+ // We don't know the exact path of su, so we just check for the 'root ...' commands
+ EXPECT_THAT(out, StartsWith("Skipping"));
+ EXPECT_THAT(out, EndsWith("root " + kSimpleCommand + "' on user build.\n"));
+ EXPECT_THAT(err, IsEmpty());
+}
+
+TEST_F(DumpstateTest, RunCommandAsRootNonUserBuild) {
+ if (!IsStandalone()) {
+ // TODO: temporarily disabled because it might cause other tests to fail after dropping
+ // to Shell - need to refactor tests to avoid this problem)
+ MYLOGE("Skipping DumpstateTest.RunCommandAsRootNonUserBuild() on test suite\n")
+ return;
+ }
+ if (PropertiesHelper::IsUserBuild()) {
+ ALOGI("Skipping RunCommandAsRootNonUserBuild on user builds\n");
+ return;
+ }
+
+ DropRoot();
+
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand, "--uid"},
+ CommandOptions::WithTimeout(1).AsRoot().Build()));
+
+ EXPECT_THAT(out, StrEq("0\nstdout\n"));
+ EXPECT_THAT(err, StrEq("stderr\n"));
+}
+
+TEST_F(DumpstateTest, DumpFileNotFoundNoTitle) {
+ EXPECT_EQ(-1, DumpFile("", "/I/cant/believe/I/exist"));
+ EXPECT_THAT(out,
+ StrEq("*** Error dumping /I/cant/believe/I/exist: No such file or directory\n"));
+ EXPECT_THAT(err, IsEmpty());
+}
+
+TEST_F(DumpstateTest, DumpFileNotFoundWithTitle) {
+ EXPECT_EQ(-1, DumpFile("Y U NO EXIST?", "/I/cant/believe/I/exist"));
+ EXPECT_THAT(err, IsEmpty());
+ // We don't know the exact duration, so we check the prefix and suffix
+ EXPECT_THAT(out, StartsWith("*** Error dumping /I/cant/believe/I/exist (Y U NO EXIST?): No "
+ "such file or directory\n"));
+ EXPECT_THAT(out, EndsWith("s was the duration of 'Y U NO EXIST?' ------\n"));
+}
+
+TEST_F(DumpstateTest, DumpFileSingleLine) {
+ EXPECT_EQ(0, DumpFile("", kTestDataPath + "single-line.txt"));
+ EXPECT_THAT(err, IsEmpty());
+ EXPECT_THAT(out, StrEq("I AM LINE1\n")); // dumpstate adds missing newline
+}
+
+TEST_F(DumpstateTest, DumpFileSingleLineWithNewLine) {
+ EXPECT_EQ(0, DumpFile("", kTestDataPath + "single-line-with-newline.txt"));
+ EXPECT_THAT(err, IsEmpty());
+ EXPECT_THAT(out, StrEq("I AM LINE1\n"));
+}
+
+TEST_F(DumpstateTest, DumpFileMultipleLines) {
+ EXPECT_EQ(0, DumpFile("", kTestDataPath + "multiple-lines.txt"));
+ EXPECT_THAT(err, IsEmpty());
+ EXPECT_THAT(out, StrEq("I AM LINE1\nI AM LINE2\nI AM LINE3\n"));
+}
+
+TEST_F(DumpstateTest, DumpFileMultipleLinesWithNewLine) {
+ EXPECT_EQ(0, DumpFile("", kTestDataPath + "multiple-lines-with-newline.txt"));
+ EXPECT_THAT(err, IsEmpty());
+ EXPECT_THAT(out, StrEq("I AM LINE1\nI AM LINE2\nI AM LINE3\n"));
+}
+
+TEST_F(DumpstateTest, DumpFileOnDryRunNoTitle) {
+ SetDryRun(true);
+ EXPECT_EQ(0, DumpFile("", kTestDataPath + "single-line.txt"));
+ EXPECT_THAT(err, IsEmpty());
+ EXPECT_THAT(out, IsEmpty());
+}
+
+TEST_F(DumpstateTest, DumpFileOnDryRun) {
+ SetDryRun(true);
+ EXPECT_EQ(0, DumpFile("Might as well dump. Dump!", kTestDataPath + "single-line.txt"));
+ EXPECT_THAT(err, IsEmpty());
+ EXPECT_THAT(
+ out, StartsWith("------ Might as well dump. Dump! (" + kTestDataPath + "single-line.txt:"));
+ EXPECT_THAT(out, HasSubstr("\n\t(skipped on dry run)\n------"));
+ EXPECT_THAT(out, EndsWith("s was the duration of 'Might as well dump. Dump!' ------\n"));
+}
+
+TEST_F(DumpstateTest, DumpFileUpdateProgress) {
+ sp<DumpstateListenerMock> listener(new DumpstateListenerMock());
+ ds.listener_ = listener;
+ ds.listener_name_ = "FoxMulder";
+ SetProgress(0, 30);
+
+ EXPECT_CALL(*listener, onProgressUpdated(5));
+ EXPECT_EQ(0, DumpFile("", kTestDataPath + "single-line.txt"));
+
+ std::string progress_message =
+ GetProgressMessage(ds.listener_name_, 5, 30); // TODO: unhardcode WEIGHT_FILE (5)?
+ EXPECT_THAT(err, StrEq(progress_message));
+ EXPECT_THAT(out, StrEq("I AM LINE1\n")); // dumpstate adds missing newline
+
+ ds.listener_.clear();
+}
+
+class DumpstateServiceTest : public DumpstateBaseTest {
+ public:
+ DumpstateService dss;
+};
+
+TEST_F(DumpstateServiceTest, SetListenerNoName) {
+ sp<DumpstateListenerMock> listener(new DumpstateListenerMock());
+ sp<IDumpstateToken> token;
+ EXPECT_TRUE(dss.setListener("", listener, &token).isOk());
+ ASSERT_THAT(token, IsNull());
+}
+
+TEST_F(DumpstateServiceTest, SetListenerNoPointer) {
+ sp<IDumpstateToken> token;
+ EXPECT_TRUE(dss.setListener("whatever", nullptr, &token).isOk());
+ ASSERT_THAT(token, IsNull());
+}
+
+TEST_F(DumpstateServiceTest, SetListenerTwice) {
+ sp<DumpstateListenerMock> listener(new DumpstateListenerMock());
+ sp<IDumpstateToken> token;
+ EXPECT_TRUE(dss.setListener("whatever", listener, &token).isOk());
+ ASSERT_THAT(token, NotNull());
+ EXPECT_THAT(Dumpstate::GetInstance().listener_name_, StrEq("whatever"));
+
+ token.clear();
+ EXPECT_TRUE(dss.setListener("whatsoever", listener, &token).isOk());
+ ASSERT_THAT(token, IsNull());
+ EXPECT_THAT(Dumpstate::GetInstance().listener_name_, StrEq("whatever"));
+}
+
+class ProgressTest : public DumpstateBaseTest {
+ public:
+ Progress GetInstance(int32_t max, double growth_factor, const std::string& path = "") {
+ return Progress(max, growth_factor, path);
+ }
+
+ void AssertStats(const std::string& path, int32_t expected_runs, int32_t expected_average) {
+ std::string expected_content =
+ android::base::StringPrintf("%d %d\n", expected_runs, expected_average);
+ std::string actual_content;
+ ReadFileToString(path, &actual_content);
+ ASSERT_THAT(actual_content, StrEq(expected_content)) << "invalid stats on " << path;
+ }
+};
+
+TEST_F(ProgressTest, SimpleTest) {
+ Progress progress;
+ EXPECT_EQ(0, progress.Get());
+ EXPECT_EQ(Progress::kDefaultMax, progress.GetInitialMax());
+ EXPECT_EQ(Progress::kDefaultMax, progress.GetMax());
+
+ bool max_increased = progress.Inc(1);
+ EXPECT_EQ(1, progress.Get());
+ EXPECT_EQ(Progress::kDefaultMax, progress.GetInitialMax());
+ EXPECT_EQ(Progress::kDefaultMax, progress.GetMax());
+ EXPECT_FALSE(max_increased);
+
+ // Ignore negative increase.
+ max_increased = progress.Inc(-1);
+ EXPECT_EQ(1, progress.Get());
+ EXPECT_EQ(Progress::kDefaultMax, progress.GetInitialMax());
+ EXPECT_EQ(Progress::kDefaultMax, progress.GetMax());
+ EXPECT_FALSE(max_increased);
+}
+
+TEST_F(ProgressTest, MaxGrowsInsideNewRange) {
+ Progress progress = GetInstance(10, 1.2); // 20% growth factor
+ EXPECT_EQ(0, progress.Get());
+ EXPECT_EQ(10, progress.GetInitialMax());
+ EXPECT_EQ(10, progress.GetMax());
+
+ // No increase
+ bool max_increased = progress.Inc(10);
+ EXPECT_EQ(10, progress.Get());
+ EXPECT_EQ(10, progress.GetMax());
+ EXPECT_FALSE(max_increased);
+
+ // Increase, with new value < max*20%
+ max_increased = progress.Inc(1);
+ EXPECT_EQ(11, progress.Get());
+ EXPECT_EQ(13, progress.GetMax()); // 11 average * 20% growth = 13.2 = 13
+ EXPECT_TRUE(max_increased);
+}
+
+TEST_F(ProgressTest, MaxGrowsOutsideNewRange) {
+ Progress progress = GetInstance(10, 1.2); // 20% growth factor
+ EXPECT_EQ(0, progress.Get());
+ EXPECT_EQ(10, progress.GetInitialMax());
+ EXPECT_EQ(10, progress.GetMax());
+
+ // No increase
+ bool max_increased = progress.Inc(10);
+ EXPECT_EQ(10, progress.Get());
+ EXPECT_EQ(10, progress.GetMax());
+ EXPECT_FALSE(max_increased);
+
+ // Increase, with new value > max*20%
+ max_increased = progress.Inc(5);
+ EXPECT_EQ(15, progress.Get());
+ EXPECT_EQ(18, progress.GetMax()); // 15 average * 20% growth = 18
+ EXPECT_TRUE(max_increased);
+}
+
+TEST_F(ProgressTest, InvalidPath) {
+ Progress progress("/devil/null");
+ EXPECT_EQ(Progress::kDefaultMax, progress.GetMax());
+}
+
+TEST_F(ProgressTest, EmptyFile) {
+ Progress progress(CopyTextFileFixture("empty-file.txt"));
+ EXPECT_EQ(Progress::kDefaultMax, progress.GetMax());
+}
+
+TEST_F(ProgressTest, InvalidLine1stEntryNAN) {
+ Progress progress(CopyTextFileFixture("stats-invalid-1st-NAN.txt"));
+ EXPECT_EQ(Progress::kDefaultMax, progress.GetMax());
+}
+
+TEST_F(ProgressTest, InvalidLine2ndEntryNAN) {
+ Progress progress(CopyTextFileFixture("stats-invalid-2nd-NAN.txt"));
+ EXPECT_EQ(Progress::kDefaultMax, progress.GetMax());
+}
+
+TEST_F(ProgressTest, InvalidLineBothNAN) {
+ Progress progress(CopyTextFileFixture("stats-invalid-both-NAN.txt"));
+ EXPECT_EQ(Progress::kDefaultMax, progress.GetMax());
+}
+
+TEST_F(ProgressTest, InvalidLine1stEntryNegative) {
+ Progress progress(CopyTextFileFixture("stats-invalid-1st-negative.txt"));
+ EXPECT_EQ(Progress::kDefaultMax, progress.GetMax());
+}
+
+TEST_F(ProgressTest, InvalidLine2ndEntryNegative) {
+ Progress progress(CopyTextFileFixture("stats-invalid-2nd-negative.txt"));
+ EXPECT_EQ(Progress::kDefaultMax, progress.GetMax());
+}
+
+TEST_F(ProgressTest, InvalidLine1stEntryTooBig) {
+ Progress progress(CopyTextFileFixture("stats-invalid-1st-too-big.txt"));
+ EXPECT_EQ(Progress::kDefaultMax, progress.GetMax());
+}
+
+TEST_F(ProgressTest, InvalidLine2ndEntryTooBig) {
+ Progress progress(CopyTextFileFixture("stats-invalid-2nd-too-big.txt"));
+ EXPECT_EQ(Progress::kDefaultMax, progress.GetMax());
+}
+
+// Tests stats are properly saved when the file does not exists.
+TEST_F(ProgressTest, FirstTime) {
+ if (!IsStandalone()) {
+ // TODO: temporarily disabled because it's failing when running as suite
+ MYLOGE("Skipping ProgressTest.FirstTime() on test suite\n")
+ return;
+ }
+
+ std::string path = kTestDataPath + "FirstTime.txt";
+ android::base::RemoveFileIfExists(path);
+
+ Progress run1(path);
+ EXPECT_EQ(0, run1.Get());
+ EXPECT_EQ(Progress::kDefaultMax, run1.GetInitialMax());
+ EXPECT_EQ(Progress::kDefaultMax, run1.GetMax());
+
+ bool max_increased = run1.Inc(20);
+ EXPECT_EQ(20, run1.Get());
+ EXPECT_EQ(Progress::kDefaultMax, run1.GetMax());
+ EXPECT_FALSE(max_increased);
+
+ run1.Save();
+ AssertStats(path, 1, 20);
+}
+
+// Tests what happens when the persistent settings contains the average duration of 1 run.
+// Data on file is 1 run and 109 average.
+TEST_F(ProgressTest, SecondTime) {
+ std::string path = CopyTextFileFixture("stats-one-run-no-newline.txt");
+
+ Progress run1 = GetInstance(-42, 1.2, path);
+ EXPECT_EQ(0, run1.Get());
+ EXPECT_EQ(10, run1.GetInitialMax());
+ EXPECT_EQ(10, run1.GetMax());
+
+ bool max_increased = run1.Inc(20);
+ EXPECT_EQ(20, run1.Get());
+ EXPECT_EQ(24, run1.GetMax());
+ EXPECT_TRUE(max_increased);
+
+ // Average now is 2 runs and (10 + 20)/ 2 = 15
+ run1.Save();
+ AssertStats(path, 2, 15);
+
+ Progress run2 = GetInstance(-42, 1.2, path);
+ EXPECT_EQ(0, run2.Get());
+ EXPECT_EQ(15, run2.GetInitialMax());
+ EXPECT_EQ(15, run2.GetMax());
+
+ max_increased = run2.Inc(25);
+ EXPECT_EQ(25, run2.Get());
+ EXPECT_EQ(30, run2.GetMax());
+ EXPECT_TRUE(max_increased);
+
+ // Average now is 3 runs and (15 * 2 + 25)/ 3 = 18.33 = 18
+ run2.Save();
+ AssertStats(path, 3, 18);
+
+ Progress run3 = GetInstance(-42, 1.2, path);
+ EXPECT_EQ(0, run3.Get());
+ EXPECT_EQ(18, run3.GetInitialMax());
+ EXPECT_EQ(18, run3.GetMax());
+
+ // Make sure average decreases as well
+ max_increased = run3.Inc(5);
+ EXPECT_EQ(5, run3.Get());
+ EXPECT_EQ(18, run3.GetMax());
+ EXPECT_FALSE(max_increased);
+
+ // Average now is 4 runs and (18 * 3 + 5)/ 4 = 14.75 = 14
+ run3.Save();
+ AssertStats(path, 4, 14);
+}
+
+// Tests what happens when the persistent settings contains the average duration of 2 runs.
+// Data on file is 2 runs and 15 average.
+TEST_F(ProgressTest, ThirdTime) {
+ std::string path = CopyTextFileFixture("stats-two-runs.txt");
+ AssertStats(path, 2, 15); // Sanity check
+
+ Progress run1 = GetInstance(-42, 1.2, path);
+ EXPECT_EQ(0, run1.Get());
+ EXPECT_EQ(15, run1.GetInitialMax());
+ EXPECT_EQ(15, run1.GetMax());
+
+ bool max_increased = run1.Inc(20);
+ EXPECT_EQ(20, run1.Get());
+ EXPECT_EQ(24, run1.GetMax());
+ EXPECT_TRUE(max_increased);
+
+ // Average now is 3 runs and (15 * 2 + 20)/ 3 = 16.66 = 16
+ run1.Save();
+ AssertStats(path, 3, 16);
+}
+
+class DumpstateUtilTest : public DumpstateBaseTest {
+ public:
+ void SetUp() {
+ DumpstateBaseTest::SetUp();
+ SetDryRun(false);
+ }
+
+ void CaptureFdOut() {
+ ReadFileToString(path_, &out);
+ }
+
+ void CreateFd(const std::string& name) {
+ path_ = kTestDataPath + name;
+ MYLOGD("Creating fd for file %s\n", path_.c_str());
+
+ fd = TEMP_FAILURE_RETRY(open(path_.c_str(),
+ O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
+ ASSERT_GE(fd, 0) << "could not create FD for path " << path_;
+ }
+
+ // Runs a command into the `fd` and capture `stderr`.
+ int RunCommand(const std::string& title, const std::vector<std::string>& full_command,
+ const CommandOptions& options = CommandOptions::DEFAULT) {
+ CaptureStderr();
+ int status = RunCommandToFd(fd, title, full_command, options);
+ close(fd);
+
+ CaptureFdOut();
+ err = GetCapturedStderr();
+ return status;
+ }
+
+ // Dumps a file and into the `fd` and `stderr`.
+ int DumpFile(const std::string& title, const std::string& path) {
+ CaptureStderr();
+ int status = DumpFileToFd(fd, title, path);
+ close(fd);
+
+ CaptureFdOut();
+ err = GetCapturedStderr();
+ return status;
+ }
+
+ // Find out the pid of the process_name
+ int FindPidOfProcess(const std::string& process_name) {
+ CaptureStderr();
+ int status = GetPidByName(process_name);
+ err = GetCapturedStderr();
+ return status;
+ }
+
+ int fd;
+
+ // 'fd` output and `stderr` from the last command ran.
+ std::string out, err;
+
+ private:
+ std::string path_;
+};
+
+TEST_F(DumpstateUtilTest, RunCommandNoArgs) {
+ CreateFd("RunCommandNoArgs.txt");
+ EXPECT_EQ(-1, RunCommand("", {}));
+}
+
+TEST_F(DumpstateUtilTest, RunCommandNoTitle) {
+ CreateFd("RunCommandWithNoArgs.txt");
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand}));
+ EXPECT_THAT(out, StrEq("stdout\n"));
+ EXPECT_THAT(err, StrEq("stderr\n"));
+}
+
+TEST_F(DumpstateUtilTest, RunCommandWithTitle) {
+ CreateFd("RunCommandWithNoArgs.txt");
+ EXPECT_EQ(0, RunCommand("I AM GROOT", {kSimpleCommand}));
+ EXPECT_THAT(out, StrEq("------ I AM GROOT (" + kSimpleCommand + ") ------\nstdout\n"));
+ EXPECT_THAT(err, StrEq("stderr\n"));
+}
+
+TEST_F(DumpstateUtilTest, RunCommandWithOneArg) {
+ CreateFd("RunCommandWithOneArg.txt");
+ EXPECT_EQ(0, RunCommand("", {kEchoCommand, "one"}));
+ EXPECT_THAT(err, IsEmpty());
+ EXPECT_THAT(out, StrEq("one\n"));
+}
+
+TEST_F(DumpstateUtilTest, RunCommandWithMultipleArgs) {
+ CreateFd("RunCommandWithMultipleArgs.txt");
+ EXPECT_EQ(0, RunCommand("", {kEchoCommand, "one", "is", "the", "loniest", "number"}));
+ EXPECT_THAT(err, IsEmpty());
+ EXPECT_THAT(out, StrEq("one is the loniest number\n"));
+}
+
+TEST_F(DumpstateUtilTest, RunCommandWithLoggingMessage) {
+ CreateFd("RunCommandWithLoggingMessage.txt");
+ EXPECT_EQ(
+ 0, RunCommand("", {kSimpleCommand},
+ CommandOptions::WithTimeout(10).Log("COMMAND, Y U NO LOG FIRST?").Build()));
+ EXPECT_THAT(out, StrEq("stdout\n"));
+ EXPECT_THAT(err, StrEq("COMMAND, Y U NO LOG FIRST?stderr\n"));
+}
+
+TEST_F(DumpstateUtilTest, RunCommandRedirectStderr) {
+ CreateFd("RunCommandRedirectStderr.txt");
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand},
+ CommandOptions::WithTimeout(10).RedirectStderr().Build()));
+ EXPECT_THAT(out, IsEmpty());
+ EXPECT_THAT(err, StrEq("stdout\nstderr\n"));
+}
+
+TEST_F(DumpstateUtilTest, RunCommandDryRun) {
+ CreateFd("RunCommandDryRun.txt");
+ SetDryRun(true);
+ EXPECT_EQ(0, RunCommand("I AM GROOT", {kSimpleCommand}));
+ EXPECT_THAT(out, StrEq(android::base::StringPrintf(
+ "------ I AM GROOT (%s) ------\n\t(skipped on dry run)\n",
+ kSimpleCommand.c_str())));
+ EXPECT_THAT(err, IsEmpty());
+}
+
+TEST_F(DumpstateUtilTest, RunCommandDryRunNoTitle) {
+ CreateFd("RunCommandDryRun.txt");
+ SetDryRun(true);
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand}));
+ EXPECT_THAT(
+ out, StrEq(android::base::StringPrintf("%s: skipped on dry run\n", kSimpleCommand.c_str())));
+ EXPECT_THAT(err, IsEmpty());
+}
+
+TEST_F(DumpstateUtilTest, RunCommandDryRunAlways) {
+ CreateFd("RunCommandDryRunAlways.txt");
+ SetDryRun(true);
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand}, CommandOptions::WithTimeout(10).Always().Build()));
+ EXPECT_THAT(out, StrEq("stdout\n"));
+ EXPECT_THAT(err, StrEq("stderr\n"));
+}
+
+TEST_F(DumpstateUtilTest, RunCommandNotFound) {
+ CreateFd("RunCommandNotFound.txt");
+ EXPECT_NE(0, RunCommand("", {"/there/cannot/be/such/command"}));
+ EXPECT_THAT(out, StartsWith("*** command '/there/cannot/be/such/command' failed: exit code"));
+ EXPECT_THAT(err, StartsWith("execvp on command '/there/cannot/be/such/command' failed"));
+}
+
+TEST_F(DumpstateUtilTest, RunCommandFails) {
+ CreateFd("RunCommandFails.txt");
+ EXPECT_EQ(42, RunCommand("", {kSimpleCommand, "--exit", "42"}));
+ EXPECT_THAT(out, StrEq("stdout\n*** command '" + kSimpleCommand +
+ " --exit 42' failed: exit code 42\n"));
+ EXPECT_THAT(err, StrEq("stderr\n*** command '" + kSimpleCommand +
+ " --exit 42' failed: exit code 42\n"));
+}
+
+TEST_F(DumpstateUtilTest, RunCommandCrashes) {
+ CreateFd("RunCommandCrashes.txt");
+ EXPECT_NE(0, RunCommand("", {kSimpleCommand, "--crash"}));
+ // We don't know the exit code, so check just the prefix.
+ EXPECT_THAT(
+ out, StartsWith("stdout\n*** command '" + kSimpleCommand + " --crash' failed: exit code"));
+ EXPECT_THAT(
+ err, StartsWith("stderr\n*** command '" + kSimpleCommand + " --crash' failed: exit code"));
+}
+
+TEST_F(DumpstateUtilTest, RunCommandTimesout) {
+ CreateFd("RunCommandTimesout.txt");
+ EXPECT_EQ(-1, RunCommand("", {kSimpleCommand, "--sleep", "2"},
+ CommandOptions::WithTimeout(1).Build()));
+ EXPECT_THAT(out, StartsWith("stdout line1\n*** command '" + kSimpleCommand +
+ " --sleep 2' timed out after 1"));
+ EXPECT_THAT(err, StartsWith("sleeping for 2s\n*** command '" + kSimpleCommand +
+ " --sleep 2' timed out after 1"));
+}
+
+TEST_F(DumpstateUtilTest, RunCommandIsKilled) {
+ CreateFd("RunCommandIsKilled.txt");
+ CaptureStderr();
+
+ std::thread t([=]() {
+ EXPECT_EQ(SIGTERM, RunCommandToFd(fd, "", {kSimpleCommand, "--pid", "--sleep", "20"},
+ CommandOptions::WithTimeout(100).Always().Build()));
+ });
+
+ // Capture pid and pre-sleep output.
+ sleep(1); // Wait a little bit to make sure pid and 1st line were printed.
+ std::string err = GetCapturedStderr();
+ EXPECT_THAT(err, StrEq("sleeping for 20s\n"));
+
+ CaptureFdOut();
+ std::vector<std::string> lines = android::base::Split(out, "\n");
+ ASSERT_EQ(3, (int)lines.size()) << "Invalid lines before sleep: " << out;
+
+ int pid = atoi(lines[0].c_str());
+ EXPECT_THAT(lines[1], StrEq("stdout line1"));
+ EXPECT_THAT(lines[2], IsEmpty()); // \n
+
+ // Then kill the process.
+ CaptureFdOut();
+ CaptureStderr();
+ ASSERT_EQ(0, kill(pid, SIGTERM)) << "failed to kill pid " << pid;
+ t.join();
+
+ // Finally, check output after murder.
+ CaptureFdOut();
+ err = GetCapturedStderr();
+
+ // out starts with the pid, which is an unknown
+ EXPECT_THAT(out, EndsWith("stdout line1\n*** command '" + kSimpleCommand +
+ " --pid --sleep 20' failed: killed by signal 15\n"));
+ EXPECT_THAT(err, StrEq("*** command '" + kSimpleCommand +
+ " --pid --sleep 20' failed: killed by signal 15\n"));
+}
+
+TEST_F(DumpstateUtilTest, RunCommandAsRootUserBuild) {
+ if (!IsStandalone()) {
+ // TODO: temporarily disabled because it might cause other tests to fail after dropping
+ // to Shell - need to refactor tests to avoid this problem)
+ MYLOGE("Skipping DumpstateUtilTest.RunCommandAsRootUserBuild() on test suite\n")
+ return;
+ }
+ CreateFd("RunCommandAsRootUserBuild.txt");
+ if (!PropertiesHelper::IsUserBuild()) {
+ // Emulates user build if necessarily.
+ SetBuildType("user");
+ }
+
+ DropRoot();
+
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand}, CommandOptions::WithTimeout(1).AsRoot().Build()));
+
+ // We don't know the exact path of su, so we just check for the 'root ...' commands
+ EXPECT_THAT(out, StartsWith("Skipping"));
+ EXPECT_THAT(out, EndsWith("root " + kSimpleCommand + "' on user build.\n"));
+ EXPECT_THAT(err, IsEmpty());
+}
+
+TEST_F(DumpstateUtilTest, RunCommandAsRootNonUserBuild) {
+ if (!IsStandalone()) {
+ // TODO: temporarily disabled because it might cause other tests to fail after dropping
+ // to Shell - need to refactor tests to avoid this problem)
+ MYLOGE("Skipping DumpstateUtilTest.RunCommandAsRootNonUserBuild() on test suite\n")
+ return;
+ }
+ CreateFd("RunCommandAsRootNonUserBuild.txt");
+ if (PropertiesHelper::IsUserBuild()) {
+ ALOGI("Skipping RunCommandAsRootNonUserBuild on user builds\n");
+ return;
+ }
+
+ DropRoot();
+
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand, "--uid"},
+ CommandOptions::WithTimeout(1).AsRoot().Build()));
+
+ EXPECT_THAT(out, StrEq("0\nstdout\n"));
+ EXPECT_THAT(err, StrEq("stderr\n"));
+}
+
+TEST_F(DumpstateUtilTest, RunCommandDropRoot) {
+ if (!IsStandalone()) {
+ // TODO: temporarily disabled because it might cause other tests to fail after dropping
+ // to Shell - need to refactor tests to avoid this problem)
+ MYLOGE("Skipping DumpstateUtilTest.RunCommandDropRoot() on test suite\n")
+ return;
+ }
+ CreateFd("RunCommandDropRoot.txt");
+ // First check root case - only available when running with 'adb root'.
+ uid_t uid = getuid();
+ if (uid == 0) {
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand, "--uid"}));
+ EXPECT_THAT(out, StrEq("0\nstdout\n"));
+ EXPECT_THAT(err, StrEq("stderr\n"));
+ return;
+ }
+ // Then run dropping root.
+ EXPECT_EQ(0, RunCommand("", {kSimpleCommand, "--uid"},
+ CommandOptions::WithTimeout(1).DropRoot().Build()));
+ EXPECT_THAT(out, StrEq("2000\nstdout\n"));
+ EXPECT_THAT(err, StrEq("drop_root_user(): already running as Shell\nstderr\n"));
+}
+
+TEST_F(DumpstateUtilTest, DumpFileNotFoundNoTitle) {
+ CreateFd("DumpFileNotFound.txt");
+ EXPECT_EQ(-1, DumpFile("", "/I/cant/believe/I/exist"));
+ EXPECT_THAT(out,
+ StrEq("*** Error dumping /I/cant/believe/I/exist: No such file or directory\n"));
+ EXPECT_THAT(err, IsEmpty());
+}
+
+TEST_F(DumpstateUtilTest, DumpFileNotFoundWithTitle) {
+ CreateFd("DumpFileNotFound.txt");
+ EXPECT_EQ(-1, DumpFile("Y U NO EXIST?", "/I/cant/believe/I/exist"));
+ EXPECT_THAT(out, StrEq("*** Error dumping /I/cant/believe/I/exist (Y U NO EXIST?): No such "
+ "file or directory\n"));
+ EXPECT_THAT(err, IsEmpty());
+}
+
+TEST_F(DumpstateUtilTest, DumpFileSingleLine) {
+ CreateFd("DumpFileSingleLine.txt");
+ EXPECT_EQ(0, DumpFile("", kTestDataPath + "single-line.txt"));
+ EXPECT_THAT(err, IsEmpty());
+ EXPECT_THAT(out, StrEq("I AM LINE1\n")); // dumpstate adds missing newline
+}
+
+TEST_F(DumpstateUtilTest, DumpFileSingleLineWithNewLine) {
+ CreateFd("DumpFileSingleLineWithNewLine.txt");
+ EXPECT_EQ(0, DumpFile("", kTestDataPath + "single-line-with-newline.txt"));
+ EXPECT_THAT(err, IsEmpty());
+ EXPECT_THAT(out, StrEq("I AM LINE1\n"));
+}
+
+TEST_F(DumpstateUtilTest, DumpFileMultipleLines) {
+ CreateFd("DumpFileMultipleLines.txt");
+ EXPECT_EQ(0, DumpFile("", kTestDataPath + "multiple-lines.txt"));
+ EXPECT_THAT(err, IsEmpty());
+ EXPECT_THAT(out, StrEq("I AM LINE1\nI AM LINE2\nI AM LINE3\n"));
+}
+
+TEST_F(DumpstateUtilTest, DumpFileMultipleLinesWithNewLine) {
+ CreateFd("DumpFileMultipleLinesWithNewLine.txt");
+ EXPECT_EQ(0, DumpFile("", kTestDataPath + "multiple-lines-with-newline.txt"));
+ EXPECT_THAT(err, IsEmpty());
+ EXPECT_THAT(out, StrEq("I AM LINE1\nI AM LINE2\nI AM LINE3\n"));
+}
+
+TEST_F(DumpstateUtilTest, DumpFileOnDryRunNoTitle) {
+ CreateFd("DumpFileOnDryRun.txt");
+ SetDryRun(true);
+ std::string path = kTestDataPath + "single-line.txt";
+ EXPECT_EQ(0, DumpFile("", kTestDataPath + "single-line.txt"));
+ EXPECT_THAT(err, IsEmpty());
+ EXPECT_THAT(out, StrEq(path + ": skipped on dry run\n"));
+}
+
+TEST_F(DumpstateUtilTest, DumpFileOnDryRun) {
+ CreateFd("DumpFileOnDryRun.txt");
+ SetDryRun(true);
+ std::string path = kTestDataPath + "single-line.txt";
+ EXPECT_EQ(0, DumpFile("Might as well dump. Dump!", kTestDataPath + "single-line.txt"));
+ EXPECT_THAT(err, IsEmpty());
+ EXPECT_THAT(
+ out, StartsWith("------ Might as well dump. Dump! (" + kTestDataPath + "single-line.txt:"));
+ EXPECT_THAT(out, EndsWith("skipped on dry run\n"));
+}
+
+TEST_F(DumpstateUtilTest, FindingPidWithExistingProcess) {
+ // init process always has pid 1.
+ EXPECT_EQ(1, FindPidOfProcess("init"));
+ EXPECT_THAT(err, IsEmpty());
+}
+
+TEST_F(DumpstateUtilTest, FindingPidWithNotExistingProcess) {
+ // find the process with abnormal name.
+ EXPECT_EQ(-1, FindPidOfProcess("abcdef12345-543"));
+ EXPECT_THAT(err, StrEq("can't find the pid\n"));
+}
+
+} // namespace dumpstate
+} // namespace os
+} // namespace android
diff --git a/cmds/dumpstate/tests/dumpstate_test_fixture.cpp b/cmds/dumpstate/tests/dumpstate_test_fixture.cpp
new file mode 100644
index 0000000..5be4719
--- /dev/null
+++ b/cmds/dumpstate/tests/dumpstate_test_fixture.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#define LOG_TAG "dumpstate"
+#include <cutils/log.h>
+
+void PrintDefaultOutput() {
+ fprintf(stdout, "stdout\n");
+ fflush(stdout);
+ fprintf(stderr, "stderr\n");
+ fflush(stderr);
+}
+
+/*
+ * Binary used to on RunCommand tests.
+ *
+ * Usage:
+ *
+ * - Unless stated otherwise this command:
+ *
+ * 1.Prints `stdout\n` on `stdout` and flushes it.
+ * 2.Prints `stderr\n` on `stderr` and flushes it.
+ * 3.Exit with status 0.
+ *
+ * - If 1st argument is '--pid', it first prints its pid on `stdout`.
+ *
+ * - If 1st argument is '--uid', it first prints its uid on `stdout`.
+ *
+ * - If 1st argument is '--crash', it uses ALOGF to crash and returns 666.
+ *
+ * - With argument '--exit' 'CODE', returns CODE;
+ *
+ * - With argument '--sleep 'TIME':
+ *
+ * 1.Prints `stdout line1\n` on `stdout` and `sleeping TIME s\n` on `stderr`
+ * 2.Sleeps for TIME s
+ * 3.Prints `stdout line2\n` on `stdout` and `woke up\n` on `stderr`
+ */
+int main(int argc, char* const argv[]) {
+ if (argc == 2) {
+ if (strcmp(argv[1], "--crash") == 0) {
+ PrintDefaultOutput();
+ LOG_FATAL("D'OH\n");
+ return 666;
+ }
+ }
+ if (argc == 3) {
+ if (strcmp(argv[1], "--exit") == 0) {
+ PrintDefaultOutput();
+ return atoi(argv[2]);
+ }
+ }
+
+ if (argc > 1) {
+ int index = 1;
+
+ // First check arguments that can shift the index.
+ if (strcmp(argv[1], "--pid") == 0) {
+ index++;
+ fprintf(stdout, "%d\n", getpid());
+ fflush(stdout);
+ } else if (strcmp(argv[1], "--uid") == 0) {
+ index++;
+ fprintf(stdout, "%d\n", getuid());
+ fflush(stdout);
+ }
+
+ // Then the "common" arguments, if any.
+ if (argc > index + 1) {
+ if (strcmp(argv[index], "--sleep") == 0) {
+ int napTime = atoi(argv[index + 1]);
+ fprintf(stdout, "stdout line1\n");
+ fflush(stdout);
+ fprintf(stderr, "sleeping for %ds\n", napTime);
+ fflush(stderr);
+ sleep(napTime);
+ fprintf(stdout, "stdout line2\n");
+ fflush(stdout);
+ fprintf(stderr, "woke up\n");
+ fflush(stderr);
+ return 0;
+ }
+ }
+ }
+
+ PrintDefaultOutput();
+ return 0;
+}
diff --git a/cmds/dumpstate/utils.cpp b/cmds/dumpstate/utils.cpp
index af6c666..eefdcbd 100644
--- a/cmds/dumpstate/utils.cpp
+++ b/cmds/dumpstate/utils.cpp
@@ -14,43 +14,65 @@
* limitations under the License.
*/
+#define LOG_TAG "dumpstate"
+
+#include "dumpstate.h"
+
#include <dirent.h>
-#include <errno.h>
#include <fcntl.h>
-#include <limits.h>
+#include <libgen.h>
+#include <math.h>
#include <poll.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
-#include <string>
#include <string.h>
#include <sys/capability.h>
#include <sys/inotify.h>
+#include <sys/klog.h>
+#include <sys/prctl.h>
#include <sys/stat.h>
-#include <sys/sysconf.h>
#include <sys/time.h>
#include <sys/wait.h>
-#include <sys/klog.h>
#include <time.h>
#include <unistd.h>
-#include <vector>
-#include <sys/prctl.h>
-#define LOG_TAG "dumpstate"
+#include <set>
+#include <string>
+#include <vector>
#include <android-base/file.h>
-#include <cutils/debugger.h>
-#include <cutils/log.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android/hidl/manager/1.0/IServiceManager.h>
#include <cutils/properties.h>
#include <cutils/sockets.h>
+#include <debuggerd/client.h>
+#include <log/log.h>
#include <private/android_filesystem_config.h>
-#include <selinux/android.h>
+#include "DumpstateInternal.h"
-#include "dumpstate.h"
+// TODO: remove once moved to namespace
+using android::os::dumpstate::CommandOptions;
+using android::os::dumpstate::DumpFileToFd;
+using android::os::dumpstate::PropertiesHelper;
-static const int64_t NANOS_PER_SEC = 1000000000;
+// Keep in sync with
+// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
+static const int TRACE_DUMP_TIMEOUT_MS = 10000; // 10 seconds
+
+/* Most simple commands have 10 as timeout, so 5 is a good estimate */
+static const int32_t WEIGHT_FILE = 5;
+
+// TODO: temporary variables and functions used during C++ refactoring
+static Dumpstate& ds = Dumpstate::GetInstance();
+static int RunCommand(const std::string& title, const std::vector<std::string>& full_command,
+ const CommandOptions& options = CommandOptions::DEFAULT) {
+ return ds.RunCommand(title, full_command, options);
+}
/* list of native processes to include in the native dumps */
// This matches the /proc/pid/exe link instead of /proc/pid/cmdline.
@@ -58,47 +80,190 @@
"/system/bin/audioserver",
"/system/bin/cameraserver",
"/system/bin/drmserver",
- "/system/bin/mediacodec", // media.codec
"/system/bin/mediadrmserver",
"/system/bin/mediaextractor", // media.extractor
"/system/bin/mediaserver",
"/system/bin/sdcard",
"/system/bin/surfaceflinger",
"/system/bin/vehicle_network_service",
+ "/vendor/bin/hw/android.hardware.media.omx@1.0-service", // media.codec
NULL,
};
-DurationReporter::DurationReporter(const char *title) : DurationReporter(title, stdout) {}
+/* list of hal interface to dump containing process during native dumps */
+static const char* hal_interfaces_to_dump[] {
+ "android.hardware.audio@2.0::IDevicesFactory",
+ "android.hardware.bluetooth@1.0::IBluetoothHci",
+ "android.hardware.camera.provider@2.4::ICameraProvider",
+ "android.hardware.graphics.composer@2.1::IComposer",
+ "android.hardware.vr@1.0::IVr",
+ "android.hardware.media.omx@1.0::IOmx",
+ NULL,
+};
-DurationReporter::DurationReporter(const char *title, FILE *out) {
- title_ = title;
- if (title) {
- started_ = DurationReporter::nanotime();
+// Reasonable value for max stats.
+static const int STATS_MAX_N_RUNS = 1000;
+static const long STATS_MAX_AVERAGE = 100000;
+
+CommandOptions Dumpstate::DEFAULT_DUMPSYS = CommandOptions::WithTimeout(30).Build();
+
+Dumpstate::Dumpstate(const std::string& version)
+ : pid_(getpid()), version_(version), now_(time(nullptr)) {
+}
+
+Dumpstate& Dumpstate::GetInstance() {
+ static Dumpstate singleton_(android::base::GetProperty("dumpstate.version", VERSION_CURRENT));
+ return singleton_;
+}
+
+DurationReporter::DurationReporter(const std::string& title, bool log_only)
+ : title_(title), log_only_(log_only) {
+ if (!title_.empty()) {
+ started_ = Nanotime();
}
- out_ = out;
}
DurationReporter::~DurationReporter() {
- if (title_) {
- uint64_t elapsed = DurationReporter::nanotime() - started_;
- // Use "Yoda grammar" to make it easier to grep|sort sections.
- if (out_) {
- fprintf(out_, "------ %.3fs was the duration of '%s' ------\n",
- (float) elapsed / NANOS_PER_SEC, title_);
+ if (!title_.empty()) {
+ uint64_t elapsed = Nanotime() - started_;
+ if (log_only_) {
+ MYLOGD("Duration of '%s': %.3fs\n", title_.c_str(), (float)elapsed / NANOS_PER_SEC);
} else {
- MYLOGD("Duration of '%s': %.3fs\n", title_, (float) elapsed / NANOS_PER_SEC);
+ // Use "Yoda grammar" to make it easier to grep|sort sections.
+ printf("------ %.3fs was the duration of '%s' ------\n", (float)elapsed / NANOS_PER_SEC,
+ title_.c_str());
}
}
}
-uint64_t DurationReporter::DurationReporter::nanotime() {
- struct timespec ts;
- clock_gettime(CLOCK_MONOTONIC, &ts);
- return (uint64_t) ts.tv_sec * NANOS_PER_SEC + ts.tv_nsec;
+const int32_t Progress::kDefaultMax = 5000;
+
+Progress::Progress(const std::string& path) : Progress(Progress::kDefaultMax, 1.1, path) {
+}
+
+Progress::Progress(int32_t initial_max, int32_t progress, float growth_factor)
+ : Progress(initial_max, growth_factor, "") {
+ progress_ = progress;
+}
+
+Progress::Progress(int32_t initial_max, float growth_factor, const std::string& path)
+ : initial_max_(initial_max),
+ progress_(0),
+ max_(initial_max),
+ growth_factor_(growth_factor),
+ n_runs_(0),
+ average_max_(0),
+ path_(path) {
+ if (!path_.empty()) {
+ Load();
+ }
+}
+
+void Progress::Load() {
+ MYLOGD("Loading stats from %s\n", path_.c_str());
+ std::string content;
+ if (!android::base::ReadFileToString(path_, &content)) {
+ MYLOGI("Could not read stats from %s; using max of %d\n", path_.c_str(), max_);
+ return;
+ }
+ if (content.empty()) {
+ MYLOGE("No stats (empty file) on %s; using max of %d\n", path_.c_str(), max_);
+ return;
+ }
+ std::vector<std::string> lines = android::base::Split(content, "\n");
+
+ if (lines.size() < 1) {
+ MYLOGE("Invalid stats on file %s: not enough lines (%d). Using max of %d\n", path_.c_str(),
+ (int)lines.size(), max_);
+ return;
+ }
+ char* ptr;
+ n_runs_ = strtol(lines[0].c_str(), &ptr, 10);
+ average_max_ = strtol(ptr, nullptr, 10);
+ if (n_runs_ <= 0 || average_max_ <= 0 || n_runs_ > STATS_MAX_N_RUNS ||
+ average_max_ > STATS_MAX_AVERAGE) {
+ MYLOGE("Invalid stats line on file %s: %s\n", path_.c_str(), lines[0].c_str());
+ initial_max_ = Progress::kDefaultMax;
+ } else {
+ initial_max_ = average_max_;
+ }
+ max_ = initial_max_;
+
+ MYLOGI("Average max progress: %d in %d runs; estimated max: %d\n", average_max_, n_runs_, max_);
+}
+
+void Progress::Save() {
+ int32_t total = n_runs_ * average_max_ + progress_;
+ int32_t runs = n_runs_ + 1;
+ int32_t average = floor(((float)total) / runs);
+ MYLOGI("Saving stats (total=%d, runs=%d, average=%d) on %s\n", total, runs, average,
+ path_.c_str());
+ if (path_.empty()) {
+ return;
+ }
+
+ std::string content = android::base::StringPrintf("%d %d\n", runs, average);
+ if (!android::base::WriteStringToFile(content, path_)) {
+ MYLOGE("Could not save stats on %s\n", path_.c_str());
+ }
+}
+
+int32_t Progress::Get() const {
+ return progress_;
+}
+
+bool Progress::Inc(int32_t delta) {
+ bool changed = false;
+ if (delta >= 0) {
+ progress_ += delta;
+ if (progress_ > max_) {
+ int32_t old_max = max_;
+ max_ = floor((float)progress_ * growth_factor_);
+ MYLOGD("Adjusting max progress from %d to %d\n", old_max, max_);
+ changed = true;
+ }
+ }
+ return changed;
+}
+
+int32_t Progress::GetMax() const {
+ return max_;
+}
+
+int32_t Progress::GetInitialMax() const {
+ return initial_max_;
+}
+
+void Progress::Dump(int fd, const std::string& prefix) const {
+ const char* pr = prefix.c_str();
+ dprintf(fd, "%sprogress: %d\n", pr, progress_);
+ dprintf(fd, "%smax: %d\n", pr, max_);
+ dprintf(fd, "%sinitial_max: %d\n", pr, initial_max_);
+ dprintf(fd, "%sgrowth_factor: %0.2f\n", pr, growth_factor_);
+ dprintf(fd, "%spath: %s\n", pr, path_.c_str());
+ dprintf(fd, "%sn_runs: %d\n", pr, n_runs_);
+ dprintf(fd, "%saverage_max: %d\n", pr, average_max_);
+}
+
+bool Dumpstate::IsZipping() const {
+ return zip_writer_ != nullptr;
+}
+
+std::string Dumpstate::GetPath(const std::string& suffix) const {
+ return android::base::StringPrintf("%s/%s-%s%s", bugreport_dir_.c_str(), base_name_.c_str(),
+ name_.c_str(), suffix.c_str());
+}
+
+void Dumpstate::SetProgress(std::unique_ptr<Progress> progress) {
+ progress_ = std::move(progress);
}
void for_each_userid(void (*func)(int), const char *header) {
- ON_DRY_RUN_RETURN();
+ std::string title = header == nullptr ? "for_each_userid" : android::base::StringPrintf(
+ "for_each_userid(%s)", header);
+ DurationReporter duration_reporter(title);
+ if (PropertiesHelper::IsDryRun()) return;
+
DIR *d;
struct dirent *de;
@@ -180,8 +345,12 @@
}
void for_each_pid(for_each_pid_func func, const char *header) {
- ON_DRY_RUN_RETURN();
- __for_each_pid(for_each_pid_helper, header, (void *)func);
+ std::string title = header == nullptr ? "for_each_pid"
+ : android::base::StringPrintf("for_each_pid(%s)", header);
+ DurationReporter duration_reporter(title);
+ if (PropertiesHelper::IsDryRun()) return;
+
+ __for_each_pid(for_each_pid_helper, header, (void *) func);
}
static void for_each_tid_helper(int pid, const char *cmdline, void *arg) {
@@ -233,12 +402,18 @@
}
void for_each_tid(for_each_tid_func func, const char *header) {
- ON_DRY_RUN_RETURN();
+ std::string title = header == nullptr ? "for_each_tid"
+ : android::base::StringPrintf("for_each_tid(%s)", header);
+ DurationReporter duration_reporter(title);
+
+ if (PropertiesHelper::IsDryRun()) return;
+
__for_each_pid(for_each_tid_helper, header, (void *) func);
}
void show_wchan(int pid, int tid, const char *name) {
- ON_DRY_RUN_RETURN();
+ if (PropertiesHelper::IsDryRun()) return;
+
char path[255];
char buffer[255];
int fd, ret, save_errno;
@@ -304,7 +479,8 @@
}
void show_showtime(int pid, const char *name) {
- ON_DRY_RUN_RETURN();
+ if (PropertiesHelper::IsDryRun()) return;
+
char path[255];
char buffer[1023];
int fd, ret, save_errno;
@@ -361,7 +537,7 @@
if (iotime) {
snprdec(buffer, sizeof(buffer), 79, permille);
}
- puts(buffer); // adds a trailing newline
+ puts(buffer); // adds a trailing newline
return;
}
@@ -371,7 +547,8 @@
DurationReporter duration_reporter(title);
printf("------ %s ------\n", title);
- ON_DRY_RUN_RETURN();
+ if (PropertiesHelper::IsDryRun()) return;
+
/* Get size of kernel buffer */
int size = klogctl(KLOG_SIZE_BUFFER, NULL, 0);
if (size <= 0) {
@@ -401,84 +578,17 @@
snprintf(title, sizeof(title), "SHOW MAP %d (%s)", pid, name);
snprintf(arg, sizeof(arg), "%d", pid);
- run_command(title, 10, SU_PATH, "root", "showmap", "-q", arg, NULL);
+ RunCommand(title, {"showmap", "-q", arg}, CommandOptions::AS_ROOT);
}
-static int _dump_file_from_fd(const char *title, const char *path, int fd) {
- if (title) {
- printf("------ %s (%s", title, path);
-
- struct stat st;
- // Only show the modification time of non-device files.
- size_t path_len = strlen(path);
- if ((path_len < 6 || memcmp(path, "/proc/", 6)) &&
- (path_len < 5 || memcmp(path, "/sys/", 5)) &&
- (path_len < 3 || memcmp(path, "/d/", 3)) &&
- !fstat(fd, &st)) {
- char stamp[80];
- time_t mtime = st.st_mtime;
- strftime(stamp, sizeof(stamp), "%Y-%m-%d %H:%M:%S", localtime(&mtime));
- printf(": %s", stamp);
- }
- printf(") ------\n");
- }
- ON_DRY_RUN({ update_progress(WEIGHT_FILE); close(fd); return 0; });
-
- bool newline = false;
- fd_set read_set;
- struct timeval tm;
- while (1) {
- FD_ZERO(&read_set);
- FD_SET(fd, &read_set);
- /* Timeout if no data is read for 30 seconds. */
- tm.tv_sec = 30;
- tm.tv_usec = 0;
- uint64_t elapsed = DurationReporter::nanotime();
- int ret = TEMP_FAILURE_RETRY(select(fd + 1, &read_set, NULL, NULL, &tm));
- if (ret == -1) {
- printf("*** %s: select failed: %s\n", path, strerror(errno));
- newline = true;
- break;
- } else if (ret == 0) {
- elapsed = DurationReporter::nanotime() - elapsed;
- printf("*** %s: Timed out after %.3fs\n", path,
- (float) elapsed / NANOS_PER_SEC);
- newline = true;
- break;
- } else {
- char buffer[65536];
- ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer, sizeof(buffer)));
- if (bytes_read > 0) {
- fwrite(buffer, bytes_read, 1, stdout);
- newline = (buffer[bytes_read-1] == '\n');
- } else {
- if (bytes_read == -1) {
- printf("*** %s: Failed to read from fd: %s", path, strerror(errno));
- newline = true;
- }
- break;
- }
- }
- }
- update_progress(WEIGHT_FILE);
- close(fd);
-
- if (!newline) printf("\n");
- if (title) printf("\n");
- return 0;
-}
-
-/* prints the contents of a file */
-int dump_file(const char *title, const char *path) {
+int Dumpstate::DumpFile(const std::string& title, const std::string& path) {
DurationReporter duration_reporter(title);
- int fd = TEMP_FAILURE_RETRY(open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC));
- if (fd < 0) {
- int err = errno;
- printf("*** %s: %s\n", path, strerror(err));
- if (title) printf("\n");
- return -1;
- }
- return _dump_file_from_fd(title, path, fd);
+
+ int status = DumpFileToFd(STDOUT_FILENO, title, path);
+
+ UpdateProgress(WEIGHT_FILE);
+
+ return status;
}
int read_file_as_long(const char *path, long int *output) {
@@ -508,9 +618,8 @@
* to false when set to NULL. dump_from_fd will always be
* called with title NULL.
*/
-int dump_files(const char *title, const char *dir,
- bool (*skip)(const char *path),
- int (*dump_from_fd)(const char *title, const char *path, int fd)) {
+int dump_files(const std::string& title, const char* dir, bool (*skip)(const char* path),
+ int (*dump_from_fd)(const char* title, const char* path, int fd)) {
DurationReporter duration_reporter(title);
DIR *dirp;
struct dirent *d;
@@ -518,10 +627,10 @@
const char *slash = "/";
int fd, retval = 0;
- if (title) {
- printf("------ %s (%s) ------\n", title, dir);
+ if (!title.empty()) {
+ printf("------ %s (%s) ------\n", title.c_str(), dir);
}
- ON_DRY_RUN_RETURN(0);
+ if (PropertiesHelper::IsDryRun()) return 0;
if (dir[strlen(dir) - 1] == '/') {
++slash;
@@ -552,7 +661,7 @@
continue;
}
if (d->d_type == DT_DIR) {
- int ret = dump_files(NULL, newpath, skip, dump_from_fd);
+ int ret = dump_files("", newpath, skip, dump_from_fd);
if (ret < 0) {
retval = ret;
}
@@ -567,7 +676,7 @@
(*dump_from_fd)(NULL, newpath, fd);
}
closedir(dirp);
- if (title) {
+ if (!title.empty()) {
printf("\n");
}
return retval;
@@ -578,6 +687,8 @@
* stuck.
*/
int dump_file_from_fd(const char *title, const char *path, int fd) {
+ if (PropertiesHelper::IsDryRun()) return 0;
+
int flags = fcntl(fd, F_GETFL);
if (flags == -1) {
printf("*** %s: failed to get flags on fd %d: %s\n", path, fd, strerror(errno));
@@ -588,332 +699,30 @@
close(fd);
return -1;
}
- return _dump_file_from_fd(title, path, fd);
+ return DumpFileFromFdToFd(title, path, fd, STDOUT_FILENO, PropertiesHelper::IsDryRun());
}
-bool waitpid_with_timeout(pid_t pid, int timeout_seconds, int* status) {
- sigset_t child_mask, old_mask;
- sigemptyset(&child_mask);
- sigaddset(&child_mask, SIGCHLD);
-
- if (sigprocmask(SIG_BLOCK, &child_mask, &old_mask) == -1) {
- printf("*** sigprocmask failed: %s\n", strerror(errno));
- return false;
- }
-
- struct timespec ts;
- ts.tv_sec = timeout_seconds;
- ts.tv_nsec = 0;
- int ret = TEMP_FAILURE_RETRY(sigtimedwait(&child_mask, NULL, &ts));
- int saved_errno = errno;
- // Set the signals back the way they were.
- if (sigprocmask(SIG_SETMASK, &old_mask, NULL) == -1) {
- printf("*** sigprocmask failed: %s\n", strerror(errno));
- if (ret == 0) {
- return false;
- }
- }
- if (ret == -1) {
- errno = saved_errno;
- if (errno == EAGAIN) {
- errno = ETIMEDOUT;
- } else {
- printf("*** sigtimedwait failed: %s\n", strerror(errno));
- }
- return false;
- }
-
- pid_t child_pid = waitpid(pid, status, WNOHANG);
- if (child_pid != pid) {
- if (child_pid != -1) {
- printf("*** Waiting for pid %d, got pid %d instead\n", pid, child_pid);
- } else {
- printf("*** waitpid failed: %s\n", strerror(errno));
- }
- return false;
- }
- return true;
-}
-
-// TODO: refactor all those commands that convert args
-void format_args(const char* command, const char *args[], std::string *string);
-
-int run_command(const char *title, int timeout_seconds, const char *command, ...) {
+int Dumpstate::RunCommand(const std::string& title, const std::vector<std::string>& full_command,
+ const CommandOptions& options) {
DurationReporter duration_reporter(title);
- fflush(stdout);
- const char *args[1024] = {command};
- size_t arg;
- va_list ap;
- va_start(ap, command);
- if (title) printf("------ %s (%s", title, command);
- bool null_terminated = false;
- for (arg = 1; arg < sizeof(args) / sizeof(args[0]); ++arg) {
- args[arg] = va_arg(ap, const char *);
- if (args[arg] == nullptr) {
- null_terminated = true;
- break;
- }
- // TODO: null_terminated check is not really working; line below would crash dumpstate if
- // nullptr is missing
- if (title) printf(" %s", args[arg]);
- }
- if (title) printf(") ------\n");
- fflush(stdout);
- if (!null_terminated) {
- // Fail now, otherwise execvp() call on run_command_always() might hang.
- std::string cmd;
- format_args(command, args, &cmd);
- MYLOGE("skipping command %s because its args were not NULL-terminated", cmd.c_str());
- return -1;
- }
+ int status = RunCommandToFd(STDOUT_FILENO, title, full_command, options);
- ON_DRY_RUN({ update_progress(timeout_seconds); va_end(ap); return 0; });
+ /* TODO: for now we're simplifying the progress calculation by using the
+ * timeout as the weight. It's a good approximation for most cases, except when calling dumpsys,
+ * where its weight should be much higher proportionally to its timeout.
+ * Ideally, it should use a options.EstimatedDuration() instead...*/
+ UpdateProgress(options.Timeout());
- int status = run_command_always(title, DONT_DROP_ROOT, NORMAL_STDOUT, timeout_seconds, args);
- va_end(ap);
return status;
}
-int run_command_as_shell(const char *title, int timeout_seconds, const char *command, ...) {
- DurationReporter duration_reporter(title);
- fflush(stdout);
-
- const char *args[1024] = {command};
- size_t arg;
- va_list ap;
- va_start(ap, command);
- if (title) printf("------ %s (%s", title, command);
- bool null_terminated = false;
- for (arg = 1; arg < sizeof(args) / sizeof(args[0]); ++arg) {
- args[arg] = va_arg(ap, const char *);
- if (args[arg] == nullptr) {
- null_terminated = true;
- break;
- }
- // TODO: null_terminated check is not really working; line below would crash dumpstate if
- // nullptr is missing
- if (title) printf(" %s", args[arg]);
- }
- if (title) printf(") ------\n");
- fflush(stdout);
- if (!null_terminated) {
- // Fail now, otherwise execvp() call on run_command_always() might hang.
- std::string cmd;
- format_args(command, args, &cmd);
- MYLOGE("skipping command %s because its args were not NULL-terminated", cmd.c_str());
- return -1;
- }
-
- ON_DRY_RUN({ update_progress(timeout_seconds); va_end(ap); return 0; });
-
- int status = run_command_always(title, DROP_ROOT, NORMAL_STDOUT, timeout_seconds, args);
- va_end(ap);
- return status;
-}
-
-/* forks a command and waits for it to finish */
-int run_command_always(const char *title, RootMode root_mode, StdoutMode stdout_mode,
- int timeout_seconds, const char *args[]) {
- bool silent = (stdout_mode == REDIRECT_TO_STDERR);
- // TODO: need to check if args is null-terminated, otherwise execvp will crash dumpstate
-
- /* TODO: for now we're simplifying the progress calculation by using the timeout as the weight.
- * It's a good approximation for most cases, except when calling dumpsys, where its weight
- * should be much higher proportionally to its timeout. */
- int weight = timeout_seconds;
-
- const char *command = args[0];
- uint64_t start = DurationReporter::nanotime();
- pid_t pid = fork();
-
- /* handle error case */
- if (pid < 0) {
- if (!silent) printf("*** fork: %s\n", strerror(errno));
- MYLOGE("*** fork: %s\n", strerror(errno));
- return pid;
- }
-
- /* handle child case */
- if (pid == 0) {
- if (root_mode == DROP_ROOT && !drop_root_user()) {
- if (!silent) printf("*** fail todrop root before running %s: %s\n", command,
- strerror(errno));
- MYLOGE("*** could not drop root before running %s: %s\n", command, strerror(errno));
- return -1;
- }
-
- if (silent) {
- // Redirect stderr to stdout
- dup2(STDERR_FILENO, STDOUT_FILENO);
- }
-
- /* make sure the child dies when dumpstate dies */
- prctl(PR_SET_PDEATHSIG, SIGKILL);
-
- /* just ignore SIGPIPE, will go down with parent's */
- struct sigaction sigact;
- memset(&sigact, 0, sizeof(sigact));
- sigact.sa_handler = SIG_IGN;
- sigaction(SIGPIPE, &sigact, NULL);
-
- execvp(command, (char**) args);
- // execvp's result will be handled after waitpid_with_timeout() below, but if it failed,
- // it's safer to exit dumpstate.
- MYLOGD("execvp on command '%s' failed (error: %s)", command, strerror(errno));
- fflush(stdout);
- // Must call _exit (instead of exit), otherwise it will corrupt the zip file.
- _exit(EXIT_FAILURE);
- }
-
- /* handle parent case */
- int status;
- bool ret = waitpid_with_timeout(pid, timeout_seconds, &status);
- uint64_t elapsed = DurationReporter::nanotime() - start;
- std::string cmd; // used to log command and its args
- if (!ret) {
- if (errno == ETIMEDOUT) {
- format_args(command, args, &cmd);
- if (!silent) printf("*** command '%s' timed out after %.3fs (killing pid %d)\n",
- cmd.c_str(), (float) elapsed / NANOS_PER_SEC, pid);
- MYLOGE("command '%s' timed out after %.3fs (killing pid %d)\n", cmd.c_str(),
- (float) elapsed / NANOS_PER_SEC, pid);
- } else {
- format_args(command, args, &cmd);
- if (!silent) printf("*** command '%s': Error after %.4fs (killing pid %d)\n",
- cmd.c_str(), (float) elapsed / NANOS_PER_SEC, pid);
- MYLOGE("command '%s': Error after %.4fs (killing pid %d)\n", cmd.c_str(),
- (float) elapsed / NANOS_PER_SEC, pid);
- }
- kill(pid, SIGTERM);
- if (!waitpid_with_timeout(pid, 5, NULL)) {
- kill(pid, SIGKILL);
- if (!waitpid_with_timeout(pid, 5, NULL)) {
- if (!silent) printf("could not kill command '%s' (pid %d) even with SIGKILL.\n",
- command, pid);
- MYLOGE("could not kill command '%s' (pid %d) even with SIGKILL.\n", command, pid);
- }
- }
- return -1;
- } else if (status) {
- format_args(command, args, &cmd);
- if (!silent) printf("*** command '%s' failed: %s\n", cmd.c_str(), strerror(errno));
- MYLOGE("command '%s' failed: %s\n", cmd.c_str(), strerror(errno));
- return -2;
- }
-
- if (WIFSIGNALED(status)) {
- if (!silent) printf("*** %s: Killed by signal %d\n", command, WTERMSIG(status));
- MYLOGE("*** %s: Killed by signal %d\n", command, WTERMSIG(status));
- } else if (WIFEXITED(status) && WEXITSTATUS(status) > 0) {
- if (!silent) printf("*** %s: Exit code %d\n", command, WEXITSTATUS(status));
- MYLOGE("*** %s: Exit code %d\n", command, WEXITSTATUS(status));
- }
-
- if (weight > 0) {
- update_progress(weight);
- }
- return status;
-}
-
-bool drop_root_user() {
- if (getgid() == AID_SHELL && getuid() == AID_SHELL) {
- MYLOGD("drop_root_user(): already running as Shell");
- return true;
- }
- /* ensure we will keep capabilities when we drop root */
- if (prctl(PR_SET_KEEPCAPS, 1) < 0) {
- MYLOGE("prctl(PR_SET_KEEPCAPS) failed: %s\n", strerror(errno));
- return false;
- }
-
- gid_t groups[] = { AID_LOG, AID_SDCARD_R, AID_SDCARD_RW,
- AID_MOUNT, AID_INET, AID_NET_BW_STATS, AID_READPROC,
- AID_BLUETOOTH };
- if (setgroups(sizeof(groups)/sizeof(groups[0]), groups) != 0) {
- MYLOGE("Unable to setgroups, aborting: %s\n", strerror(errno));
- return false;
- }
- if (setgid(AID_SHELL) != 0) {
- MYLOGE("Unable to setgid, aborting: %s\n", strerror(errno));
- return false;
- }
- if (setuid(AID_SHELL) != 0) {
- MYLOGE("Unable to setuid, aborting: %s\n", strerror(errno));
- return false;
- }
-
- struct __user_cap_header_struct capheader;
- struct __user_cap_data_struct capdata[2];
- memset(&capheader, 0, sizeof(capheader));
- memset(&capdata, 0, sizeof(capdata));
- capheader.version = _LINUX_CAPABILITY_VERSION_3;
- capheader.pid = 0;
-
- capdata[CAP_TO_INDEX(CAP_SYSLOG)].permitted = CAP_TO_MASK(CAP_SYSLOG);
- capdata[CAP_TO_INDEX(CAP_SYSLOG)].effective = CAP_TO_MASK(CAP_SYSLOG);
- capdata[0].inheritable = 0;
- capdata[1].inheritable = 0;
-
- if (capset(&capheader, &capdata[0]) < 0) {
- MYLOGE("capset failed: %s\n", strerror(errno));
- return false;
- }
-
- return true;
-}
-
-void send_broadcast(const std::string& action, const std::vector<std::string>& args) {
- if (args.size() > 1000) {
- MYLOGE("send_broadcast: too many arguments (%d)\n", (int) args.size());
- return;
- }
- const char *am_args[1024] = { "/system/bin/am", "broadcast", "--user", "0", "-a",
- action.c_str() };
- size_t am_index = 5; // Starts at the index of last initial value above.
- for (const std::string& arg : args) {
- am_args[++am_index] = arg.c_str();
- }
- // Always terminate with NULL.
- am_args[am_index + 1] = NULL;
- std::string args_string;
- format_args(am_index + 1, am_args, &args_string);
- MYLOGD("send_broadcast command: %s\n", args_string.c_str());
- run_command_always(NULL, DROP_ROOT, REDIRECT_TO_STDERR, 20, am_args);
-}
-
-size_t num_props = 0;
-static char* props[2000];
-
-static void print_prop(const char *key, const char *name, void *user) {
- (void) user;
- if (num_props < sizeof(props) / sizeof(props[0])) {
- char buf[PROPERTY_KEY_MAX + PROPERTY_VALUE_MAX + 10];
- snprintf(buf, sizeof(buf), "[%s]: [%s]\n", key, name);
- props[num_props++] = strdup(buf);
- }
-}
-
-static int compare_prop(const void *a, const void *b) {
- return strcmp(*(char * const *) a, *(char * const *) b);
-}
-
-/* prints all the system properties */
-void print_properties() {
- const char* title = "SYSTEM PROPERTIES";
- DurationReporter duration_reporter(title);
- printf("------ %s ------\n", title);
- ON_DRY_RUN_RETURN();
- size_t i;
- num_props = 0;
- property_list(print_prop, NULL);
- qsort(&props, num_props, sizeof(props[0]), compare_prop);
-
- for (i = 0; i < num_props; ++i) {
- fputs(props[i], stdout);
- free(props[i]);
- }
- printf("\n");
+void Dumpstate::RunDumpsys(const std::string& title, const std::vector<std::string>& dumpsys_args,
+ const CommandOptions& options, long dumpsysTimeout) {
+ long timeout = dumpsysTimeout > 0 ? dumpsysTimeout : options.Timeout();
+ std::vector<std::string> dumpsys = {"/system/bin/dumpsys", "-t", std::to_string(timeout)};
+ dumpsys.insert(dumpsys.end(), dumpsys_args.begin(), dumpsys_args.end());
+ RunCommand(title, dumpsys, options);
}
int open_socket(const char *service) {
@@ -974,11 +783,11 @@
}
}
-/* redirect output to a file */
-void redirect_to_file(FILE *redirect, char *path) {
+void _redirect_to_file(FILE *redirect, char *path, int truncate_flag) {
create_parent_dirs(path);
- int fd = TEMP_FAILURE_RETRY(open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW,
+ int fd = TEMP_FAILURE_RETRY(open(path,
+ O_WRONLY | O_CREAT | truncate_flag | O_CLOEXEC | O_NOFOLLOW,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
if (fd < 0) {
MYLOGE("%s: %s\n", path, strerror(errno));
@@ -989,6 +798,23 @@
close(fd);
}
+void redirect_to_file(FILE *redirect, char *path) {
+ _redirect_to_file(redirect, path, O_TRUNC);
+}
+
+void redirect_to_existing_file(FILE *redirect, char *path) {
+ _redirect_to_file(redirect, path, O_APPEND);
+}
+
+static bool should_dump_hal_interface(const char* interface) {
+ for (const char** i = hal_interfaces_to_dump; *i; i++) {
+ if (!strcmp(*i, interface)) {
+ return true;
+ }
+ }
+ return false;
+}
+
static bool should_dump_native_traces(const char* path) {
for (const char** p = native_processes_to_dump; *p; p++) {
if (!strcmp(*p, path)) {
@@ -998,42 +824,70 @@
return false;
}
+std::set<int> get_interesting_hal_pids() {
+ using android::hidl::manager::V1_0::IServiceManager;
+ using android::sp;
+ using android::hardware::Return;
+
+ sp<IServiceManager> manager = IServiceManager::getService();
+ std::set<int> pids;
+
+ Return<void> ret = manager->debugDump([&](auto& hals) {
+ for (const auto &info : hals) {
+ if (info.pid == static_cast<int>(IServiceManager::PidConstant::NO_PID)) {
+ continue;
+ }
+
+ if (!should_dump_hal_interface(info.interfaceName.c_str())) {
+ continue;
+ }
+
+ pids.insert(info.pid);
+ }
+ });
+
+ if (!ret.isOk()) {
+ MYLOGE("Could not get list of HAL PIDs: %s\n", ret.description().c_str());
+ }
+
+ return pids; // whether it was okay or not
+}
+
/* dump Dalvik and native stack traces, return the trace file location (NULL if none) */
const char *dump_traces() {
- DurationReporter duration_reporter("DUMP TRACES", NULL);
- ON_DRY_RUN_RETURN(NULL);
- const char* result = NULL;
+ DurationReporter duration_reporter("DUMP TRACES");
- char traces_path[PROPERTY_VALUE_MAX] = "";
- property_get("dalvik.vm.stack-trace-file", traces_path, "");
- if (!traces_path[0]) return NULL;
+ const char* result = nullptr;
+
+ std::string traces_path = android::base::GetProperty("dalvik.vm.stack-trace-file", "");
+ if (traces_path.empty()) return nullptr;
/* move the old traces.txt (if any) out of the way temporarily */
- char anr_traces_path[PATH_MAX];
- strlcpy(anr_traces_path, traces_path, sizeof(anr_traces_path));
- strlcat(anr_traces_path, ".anr", sizeof(anr_traces_path));
- if (rename(traces_path, anr_traces_path) && errno != ENOENT) {
- MYLOGE("rename(%s, %s): %s\n", traces_path, anr_traces_path, strerror(errno));
- return NULL; // Can't rename old traces.txt -- no permission? -- leave it alone instead
+ std::string anrtraces_path = traces_path + ".anr";
+ if (rename(traces_path.c_str(), anrtraces_path.c_str()) && errno != ENOENT) {
+ MYLOGE("rename(%s, %s): %s\n", traces_path.c_str(), anrtraces_path.c_str(), strerror(errno));
+ return nullptr; // Can't rename old traces.txt -- no permission? -- leave it alone instead
}
/* create a new, empty traces.txt file to receive stack dumps */
- int fd = TEMP_FAILURE_RETRY(open(traces_path, O_CREAT | O_WRONLY | O_TRUNC | O_NOFOLLOW | O_CLOEXEC,
- 0666)); /* -rw-rw-rw- */
+ int fd = TEMP_FAILURE_RETRY(
+ open(traces_path.c_str(), O_CREAT | O_WRONLY | O_APPEND | O_TRUNC | O_NOFOLLOW | O_CLOEXEC,
+ 0666)); /* -rw-rw-rw- */
if (fd < 0) {
- MYLOGE("%s: %s\n", traces_path, strerror(errno));
- return NULL;
+ MYLOGE("%s: %s\n", traces_path.c_str(), strerror(errno));
+ return nullptr;
}
int chmod_ret = fchmod(fd, 0666);
if (chmod_ret < 0) {
- MYLOGE("fchmod on %s failed: %s\n", traces_path, strerror(errno));
+ MYLOGE("fchmod on %s failed: %s\n", traces_path.c_str(), strerror(errno));
close(fd);
- return NULL;
+ return nullptr;
}
/* Variables below must be initialized before 'goto' statements */
int dalvik_found = 0;
int ifd, wfd = -1;
+ std::set<int> hal_pids = get_interesting_hal_pids();
/* walk /proc and kill -QUIT all Dalvik processes */
DIR *proc = opendir("/proc");
@@ -1049,9 +903,9 @@
goto error_close_fd;
}
- wfd = inotify_add_watch(ifd, traces_path, IN_CLOSE_WRITE);
+ wfd = inotify_add_watch(ifd, traces_path.c_str(), IN_CLOSE_WRITE);
if (wfd < 0) {
- MYLOGE("inotify_add_watch(%s): %s\n", traces_path, strerror(errno));
+ MYLOGE("inotify_add_watch(%s): %s\n", traces_path.c_str(), strerror(errno));
goto error_close_ifd;
}
@@ -1084,7 +938,7 @@
}
++dalvik_found;
- uint64_t start = DurationReporter::nanotime();
+ uint64_t start = Nanotime();
if (kill(pid, SIGQUIT)) {
MYLOGE("kill(%d, SIGQUIT): %s\n", pid, strerror(errno));
continue;
@@ -1092,7 +946,7 @@
/* wait for the writable-close notification from inotify */
struct pollfd pfd = { ifd, POLLIN, 0 };
- int ret = poll(&pfd, 1, 5000); /* 5 sec timeout */
+ int ret = poll(&pfd, 1, TRACE_DUMP_TIMEOUT_MS);
if (ret < 0) {
MYLOGE("poll: %s\n", strerror(errno));
} else if (ret == 0) {
@@ -1105,16 +959,17 @@
if (lseek(fd, 0, SEEK_END) < 0) {
MYLOGE("lseek: %s\n", strerror(errno));
} else {
- dprintf(fd, "[dump dalvik stack %d: %.3fs elapsed]\n",
- pid, (float)(DurationReporter::nanotime() - start) / NANOS_PER_SEC);
+ dprintf(fd, "[dump dalvik stack %d: %.3fs elapsed]\n", pid,
+ (float)(Nanotime() - start) / NANOS_PER_SEC);
}
- } else if (should_dump_native_traces(data)) {
+ } else if (should_dump_native_traces(data) ||
+ hal_pids.find(pid) != hal_pids.end()) {
/* dump native process if appropriate */
if (lseek(fd, 0, SEEK_END) < 0) {
MYLOGE("lseek: %s\n", strerror(errno));
} else {
static uint16_t timeout_failures = 0;
- uint64_t start = DurationReporter::nanotime();
+ uint64_t start = Nanotime();
/* If 3 backtrace dumps fail in a row, consider debuggerd dead. */
if (timeout_failures == 3) {
@@ -1125,8 +980,8 @@
} else {
timeout_failures = 0;
}
- dprintf(fd, "[dump native stack %d: %.3fs elapsed]\n",
- pid, (float)(DurationReporter::nanotime() - start) / NANOS_PER_SEC);
+ dprintf(fd, "[dump native stack %d: %.3fs elapsed]\n", pid,
+ (float)(Nanotime() - start) / NANOS_PER_SEC);
}
}
}
@@ -1135,17 +990,17 @@
MYLOGE("Warning: no Dalvik processes found to dump stacks\n");
}
- static char dump_traces_path[PATH_MAX];
- strlcpy(dump_traces_path, traces_path, sizeof(dump_traces_path));
- strlcat(dump_traces_path, ".bugreport", sizeof(dump_traces_path));
- if (rename(traces_path, dump_traces_path)) {
- MYLOGE("rename(%s, %s): %s\n", traces_path, dump_traces_path, strerror(errno));
+ static std::string dumptraces_path = android::base::StringPrintf(
+ "%s/bugreport-%s", dirname(traces_path.c_str()), basename(traces_path.c_str()));
+ if (rename(traces_path.c_str(), dumptraces_path.c_str())) {
+ MYLOGE("rename(%s, %s): %s\n", traces_path.c_str(), dumptraces_path.c_str(),
+ strerror(errno));
goto error_close_ifd;
}
- result = dump_traces_path;
+ result = dumptraces_path.c_str();
/* replace the saved [ANR] traces.txt file */
- rename(anr_traces_path, traces_path);
+ rename(anrtraces_path.c_str(), traces_path.c_str());
error_close_ifd:
close(ifd);
@@ -1156,9 +1011,9 @@
void dump_route_tables() {
DurationReporter duration_reporter("DUMP ROUTE TABLES");
- ON_DRY_RUN_RETURN();
+ if (PropertiesHelper::IsDryRun()) return;
const char* const RT_TABLES_PATH = "/data/misc/net/rt_tables";
- dump_file("RT_TABLES", RT_TABLES_PATH);
+ ds.DumpFile("RT_TABLES", RT_TABLES_PATH);
FILE* fp = fopen(RT_TABLES_PATH, "re");
if (!fp) {
printf("*** %s: %s\n", RT_TABLES_PATH, strerror(errno));
@@ -1169,72 +1024,67 @@
// need the table number. It's a 32-bit unsigned number, so max 10 chars. Skip the table name.
// Add a fixed max limit so this doesn't go awry.
for (int i = 0; i < 64 && fscanf(fp, " %10s %*s", table) == 1; ++i) {
- run_command("ROUTE TABLE IPv4", 10, "ip", "-4", "route", "show", "table", table, NULL);
- run_command("ROUTE TABLE IPv6", 10, "ip", "-6", "route", "show", "table", table, NULL);
+ RunCommand("ROUTE TABLE IPv4", {"ip", "-4", "route", "show", "table", table});
+ RunCommand("ROUTE TABLE IPv6", {"ip", "-6", "route", "show", "table", table});
}
fclose(fp);
}
-/* overall progress */
-int progress = 0;
-int do_update_progress = 0; // Set by dumpstate.cpp
-int weight_total = WEIGHT_TOTAL;
-
// TODO: make this function thread safe if sections are generated in parallel.
-void update_progress(int delta) {
- if (!do_update_progress) return;
+void Dumpstate::UpdateProgress(int32_t delta) {
+ if (progress_ == nullptr) {
+ MYLOGE("UpdateProgress: progress_ not set\n");
+ return;
+ }
- progress += delta;
+ // Always update progess so stats can be tuned...
+ bool max_changed = progress_->Inc(delta);
- char key[PROPERTY_KEY_MAX];
- char value[PROPERTY_VALUE_MAX];
+ // ...but only notifiy listeners when necessary.
+ if (!update_progress_) return;
+
+ int progress = progress_->Get();
+ int max = progress_->GetMax();
// adjusts max on the fly
- if (progress > weight_total) {
- int new_total = weight_total * 1.2;
- MYLOGD("Adjusting total weight from %d to %d\n", weight_total, new_total);
- weight_total = new_total;
- snprintf(key, sizeof(key), "dumpstate.%d.max", getpid());
- snprintf(value, sizeof(value), "%d", weight_total);
- int status = property_set(key, value);
- if (status) {
- MYLOGE("Could not update max weight by setting system property %s to %s: %d\n",
- key, value, status);
+ if (max_changed && listener_ != nullptr) {
+ listener_->onMaxProgressUpdated(max);
+ }
+
+ int32_t last_update_delta = progress - last_updated_progress_;
+ if (last_updated_progress_ > 0 && last_update_delta < update_progress_threshold_) {
+ return;
+ }
+ last_updated_progress_ = progress;
+
+ if (control_socket_fd_ >= 0) {
+ dprintf(control_socket_fd_, "PROGRESS:%d/%d\n", progress, max);
+ fsync(control_socket_fd_);
+ }
+
+ if (listener_ != nullptr) {
+ if (progress % 100 == 0) {
+ // We don't want to spam logcat, so only log multiples of 100.
+ MYLOGD("Setting progress (%s): %d/%d\n", listener_name_.c_str(), progress, max);
+ } else {
+ // stderr is ignored on normal invocations, but useful when calling
+ // /system/bin/dumpstate directly for debuggging.
+ fprintf(stderr, "Setting progress (%s): %d/%d\n", listener_name_.c_str(), progress, max);
}
+ listener_->onProgressUpdated(progress);
}
+}
- snprintf(key, sizeof(key), "dumpstate.%d.progress", getpid());
- snprintf(value, sizeof(value), "%d", progress);
-
- if (progress % 100 == 0) {
- // We don't want to spam logcat, so only log multiples of 100.
- MYLOGD("Setting progress (%s): %s/%d\n", key, value, weight_total);
+void Dumpstate::TakeScreenshot(const std::string& path) {
+ const std::string& real_path = path.empty() ? screenshot_path_ : path;
+ int status =
+ RunCommand("", {"/system/bin/screencap", "-p", real_path},
+ CommandOptions::WithTimeout(10).Always().DropRoot().RedirectStderr().Build());
+ if (status == 0) {
+ MYLOGD("Screenshot saved on %s\n", real_path.c_str());
} else {
- // stderr is ignored on normal invocations, but useful when calling /system/bin/dumpstate
- // directly for debuggging.
- fprintf(stderr, "Setting progress (%s): %s/%d\n", key, value, weight_total);
+ MYLOGE("Failed to take screenshot on %s\n", real_path.c_str());
}
-
- if (control_socket_fd >= 0) {
- dprintf(control_socket_fd, "PROGRESS:%d/%d\n", progress, weight_total);
- fsync(control_socket_fd);
- }
-
- int status = property_set(key, value);
- if (status) {
- MYLOGE("Could not update progress by setting system property %s to %s: %d\n",
- key, value, status);
- }
-}
-
-void take_screenshot(const std::string& path) {
- const char *args[] = { "/system/bin/screencap", "-p", path.c_str(), NULL };
- run_command_always(NULL, DONT_DROP_ROOT, REDIRECT_TO_STDERR, 10, args);
-}
-
-void vibrate(FILE* vibrator, int ms) {
- fprintf(vibrator, "%d\n", ms);
- fflush(vibrator);
}
bool is_dir(const char* pathname) {
@@ -1278,19 +1128,16 @@
int ext_csd_rev = 0;
std::string sub = buffer.substr(EXT_CSD_REV, sizeof(hex));
if (sscanf(sub.c_str(), "%2x", &ext_csd_rev) != 1) {
- printf("*** %s: EXT_CSD_REV parse error \"%s\"\n\n",
- ext_csd_path, sub.c_str());
+ printf("*** %s: EXT_CSD_REV parse error \"%s\"\n\n", ext_csd_path, sub.c_str());
return;
}
static const char *ver_str[] = {
"4.0", "4.1", "4.2", "4.3", "Obsolete", "4.41", "4.5", "5.0"
};
- printf("rev 1.%d (MMC %s)\n",
- ext_csd_rev,
- (ext_csd_rev < (int)(sizeof(ver_str) / sizeof(ver_str[0]))) ?
- ver_str[ext_csd_rev] :
- "Unknown");
+ printf("rev 1.%d (MMC %s)\n", ext_csd_rev,
+ (ext_csd_rev < (int)(sizeof(ver_str) / sizeof(ver_str[0]))) ? ver_str[ext_csd_rev]
+ : "Unknown");
if (ext_csd_rev < 7) {
printf("\n");
return;
@@ -1304,8 +1151,7 @@
int ext_pre_eol_info = 0;
sub = buffer.substr(EXT_PRE_EOL_INFO, sizeof(hex));
if (sscanf(sub.c_str(), "%2x", &ext_pre_eol_info) != 1) {
- printf("*** %s: PRE_EOL_INFO parse error \"%s\"\n\n",
- ext_csd_path, sub.c_str());
+ printf("*** %s: PRE_EOL_INFO parse error \"%s\"\n\n", ext_csd_path, sub.c_str());
return;
}
@@ -1315,11 +1161,10 @@
"Warning (consumed 80% of reserve)",
"Urgent (consumed 90% of reserve)"
};
- printf("PRE_EOL_INFO %d (MMC %s)\n",
- ext_pre_eol_info,
- eol_str[(ext_pre_eol_info < (int)
- (sizeof(eol_str) / sizeof(eol_str[0]))) ?
- ext_pre_eol_info : 0]);
+ printf(
+ "PRE_EOL_INFO %d (MMC %s)\n", ext_pre_eol_info,
+ eol_str[(ext_pre_eol_info < (int)(sizeof(eol_str) / sizeof(eol_str[0]))) ? ext_pre_eol_info
+ : 0]);
for (size_t lifetime = EXT_DEVICE_LIFE_TIME_EST_TYP_A;
lifetime <= EXT_DEVICE_LIFE_TIME_EST_TYP_B;
@@ -1348,48 +1193,18 @@
ext_device_life_time_est = 0;
sub = buffer.substr(lifetime, sizeof(hex));
if (sscanf(sub.c_str(), "%2x", &ext_device_life_time_est) != 1) {
- printf("*** %s: DEVICE_LIFE_TIME_EST_TYP_%c parse error \"%s\"\n",
- ext_csd_path,
- (unsigned)((lifetime - EXT_DEVICE_LIFE_TIME_EST_TYP_A) /
- sizeof(hex)) + 'A',
+ printf("*** %s: DEVICE_LIFE_TIME_EST_TYP_%c parse error \"%s\"\n", ext_csd_path,
+ (unsigned)((lifetime - EXT_DEVICE_LIFE_TIME_EST_TYP_A) / sizeof(hex)) + 'A',
sub.c_str());
continue;
}
printf("DEVICE_LIFE_TIME_EST_TYP_%c %d (MMC %s)\n",
- (unsigned)((lifetime - EXT_DEVICE_LIFE_TIME_EST_TYP_A) /
- sizeof(hex)) + 'A',
+ (unsigned)((lifetime - EXT_DEVICE_LIFE_TIME_EST_TYP_A) / sizeof(hex)) + 'A',
ext_device_life_time_est,
- est_str[(ext_device_life_time_est < (int)
- (sizeof(est_str) / sizeof(est_str[0]))) ?
- ext_device_life_time_est : 0]);
+ est_str[(ext_device_life_time_est < (int)(sizeof(est_str) / sizeof(est_str[0])))
+ ? ext_device_life_time_est
+ : 0]);
}
printf("\n");
}
-
-// TODO: refactor all those commands that convert args
-void format_args(int argc, const char *argv[], std::string *args) {
- LOG_ALWAYS_FATAL_IF(args == nullptr);
- for (int i = 0; i < argc; i++) {
- args->append(argv[i]);
- if (i < argc -1) {
- args->append(" ");
- }
- }
-}
-void format_args(const char* command, const char *args[], std::string *string) {
- LOG_ALWAYS_FATAL_IF(args == nullptr || command == nullptr);
- string->append(command);
- if (args[0] == nullptr) return;
- string->append(" ");
-
- for (int arg = 1; arg <= 1000; ++arg) {
- if (args[arg] == nullptr) return;
- string->append(args[arg]);
- if (args[arg+1] != nullptr) {
- string->append(" ");
- }
- }
- // TODO: not really working: if NULL is missing, it will crash dumpstate.
- MYLOGE("internal error: missing NULL entry on %s", string->c_str());
-}
diff --git a/cmds/dumpsys/.clang-format b/cmds/dumpsys/.clang-format
new file mode 100644
index 0000000..fc4eb1b
--- /dev/null
+++ b/cmds/dumpsys/.clang-format
@@ -0,0 +1,13 @@
+BasedOnStyle: Google
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+
+AccessModifierOffset: -2
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+IndentWidth: 4
+PointerAlignment: Left
+TabWidth: 4
+UseTab: Never
+PenaltyExcessCharacter: 32
diff --git a/cmds/dumpsys/Android.bp b/cmds/dumpsys/Android.bp
new file mode 100644
index 0000000..3476964
--- /dev/null
+++ b/cmds/dumpsys/Android.bp
@@ -0,0 +1,50 @@
+cc_defaults {
+ name: "dumpsys_defaults",
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ srcs: [
+ "dumpsys.cpp",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libutils",
+ "liblog",
+ "libbinder",
+ ],
+
+ clang: true,
+}
+
+//
+// Static library used in testing and executable
+//
+
+cc_library_static {
+ name: "libdumpsys",
+
+ defaults: ["dumpsys_defaults"],
+
+ export_include_dirs: ["."],
+}
+
+
+//
+// Executable
+//
+
+cc_binary {
+ name: "dumpsys",
+
+ defaults: ["dumpsys_defaults"],
+
+ srcs: [
+ "main.cpp",
+ ],
+}
+
+subdirs = ["tests"]
diff --git a/cmds/dumpsys/Android.mk b/cmds/dumpsys/Android.mk
deleted file mode 100644
index 8335c14..0000000
--- a/cmds/dumpsys/Android.mk
+++ /dev/null
@@ -1,21 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= \
- dumpsys.cpp
-
-LOCAL_SHARED_LIBRARIES := \
- libbase \
- libutils \
- liblog \
- libbinder
-
-
-ifeq ($(TARGET_OS),linux)
- LOCAL_CFLAGS += -DXP_UNIX
- #LOCAL_SHARED_LIBRARIES += librt
-endif
-
-LOCAL_MODULE:= dumpsys
-
-include $(BUILD_EXECUTABLE)
diff --git a/cmds/dumpsys/dumpsys.cpp b/cmds/dumpsys/dumpsys.cpp
index d19e98a..f0e7200 100644
--- a/cmds/dumpsys/dumpsys.cpp
+++ b/cmds/dumpsys/dumpsys.cpp
@@ -1,10 +1,19 @@
/*
- * Command that dumps interesting system state to the log.
+ * Copyright (C) 2009 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.
*/
-#define LOG_TAG "dumpsys"
-
#include <algorithm>
#include <chrono>
#include <thread>
@@ -12,7 +21,6 @@
#include <android-base/file.h>
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
-#include <binder/IServiceManager.h>
#include <binder/Parcel.h>
#include <binder/ProcessState.h>
#include <binder/TextOutput.h>
@@ -30,6 +38,8 @@
#include <sys/types.h>
#include <unistd.h>
+#include "dumpsys.h"
+
using namespace android;
using android::base::StringPrintf;
using android::base::unique_fd;
@@ -53,7 +63,7 @@
" SERVICE [ARGS]: dumps only service SERVICE, optionally passing ARGS to it\n");
}
-bool IsSkipped(const Vector<String16>& skipped, const String16& service) {
+static bool IsSkipped(const Vector<String16>& skipped, const String16& service) {
for (const auto& candidate : skipped) {
if (candidate == service) {
return true;
@@ -62,17 +72,7 @@
return false;
}
-int main(int argc, char* const argv[])
-{
- signal(SIGPIPE, SIG_IGN);
- sp<IServiceManager> sm = defaultServiceManager();
- fflush(stdout);
- if (sm == NULL) {
- ALOGE("Unable to get default service manager!");
- aerr << "dumpsys: Unable to get default service manager!" << endl;
- return 20;
- }
-
+int Dumpsys::main(int argc, char* const argv[]) {
Vector<String16> services;
Vector<String16> args;
Vector<String16> skippedServices;
@@ -85,6 +85,9 @@
{ 0, 0, 0, 0 }
};
+ // Must reset optind, otherwise subsequent calls will fail (wouldn't happen on main.cpp, but
+ // happens on test cases).
+ optind = 1;
while (1) {
int c;
int optionIndex = 0;
@@ -147,7 +150,7 @@
if (services.empty() || showListOnly) {
// gets all services
- services = sm->listServices();
+ services = sm_->listServices();
services.sort(sort_func);
args.add(String16("-a"));
}
@@ -159,8 +162,9 @@
aout << "Currently running services:" << endl;
for (size_t i=0; i<N; i++) {
- sp<IBinder> service = sm->checkService(services[i]);
- if (service != NULL) {
+ sp<IBinder> service = sm_->checkService(services[i]);
+
+ if (service != nullptr) {
bool skipped = IsSkipped(skippedServices, services[i]);
aout << " " << services[i] << (skipped ? " (skipped)" : "") << endl;
}
@@ -175,8 +179,8 @@
String16 service_name = std::move(services[i]);
if (IsSkipped(skippedServices, service_name)) continue;
- sp<IBinder> service = sm->checkService(service_name);
- if (service != NULL) {
+ sp<IBinder> service = sm_->checkService(service_name);
+ if (service != nullptr) {
int sfd[2];
if (pipe(sfd) != 0) {
@@ -203,7 +207,7 @@
// call returns, to terminate our reads if the other end closes their copy of the
// file descriptor, but then hangs for some reason. There doesn't seem to be a good
// way to do this, though.
- remote_end.clear();
+ remote_end.reset();
if (err != 0) {
aerr << "Error dumping service info: (" << strerror(err) << ") " << service_name
@@ -262,7 +266,10 @@
}
if (timed_out) {
- aout << endl << "*** SERVICE DUMP TIMEOUT EXPIRED ***" << endl << endl;
+ aout << endl
+ << "*** SERVICE '" << service_name << "' DUMP TIMEOUT (" << timeoutArg
+ << "s) EXPIRED ***" << endl
+ << endl;
}
if (timed_out || error) {
diff --git a/cmds/dumpsys/dumpsys.h b/cmds/dumpsys/dumpsys.h
new file mode 100644
index 0000000..2534dde
--- /dev/null
+++ b/cmds/dumpsys/dumpsys.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef FRAMEWORK_NATIVE_CMD_DUMPSYS_H_
+#define FRAMEWORK_NATIVE_CMD_DUMPSYS_H_
+
+#include <binder/IServiceManager.h>
+
+namespace android {
+
+class Dumpsys {
+ public:
+ Dumpsys(android::IServiceManager* sm) : sm_(sm) {
+ }
+ int main(int argc, char* const argv[]);
+
+ private:
+ android::IServiceManager* sm_;
+};
+}
+
+#endif // FRAMEWORK_NATIVE_CMD_DUMPSYS_H_
diff --git a/cmds/dumpsys/main.cpp b/cmds/dumpsys/main.cpp
new file mode 100644
index 0000000..8ba0eba
--- /dev/null
+++ b/cmds/dumpsys/main.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2009 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.
+ */
+
+/*
+ * Command that dumps interesting system state to the log.
+ */
+
+#include "dumpsys.h"
+
+#include <binder/IServiceManager.h>
+#include <binder/TextOutput.h>
+
+#include <signal.h>
+#include <stdio.h>
+
+using namespace android;
+
+int main(int argc, char* const argv[]) {
+ signal(SIGPIPE, SIG_IGN);
+ sp<IServiceManager> sm = defaultServiceManager();
+ fflush(stdout);
+ if (sm == nullptr) {
+ ALOGE("Unable to get default service manager!");
+ aerr << "dumpsys: Unable to get default service manager!" << endl;
+ return 20;
+ }
+
+ Dumpsys dumpsys(sm.get());
+ return dumpsys.main(argc, argv);
+}
diff --git a/cmds/dumpsys/tests/Android.bp b/cmds/dumpsys/tests/Android.bp
new file mode 100644
index 0000000..7698ed5
--- /dev/null
+++ b/cmds/dumpsys/tests/Android.bp
@@ -0,0 +1,19 @@
+// Build the unit tests for dumpsys
+cc_test {
+ name: "dumpsys_test",
+
+ srcs: ["dumpsys_test.cpp"],
+
+ shared_libs: [
+ "libbase",
+ "libbinder",
+ "libutils",
+ ],
+
+ static_libs: [
+ "libdumpsys",
+ "libgmock",
+ ],
+
+ clang: true,
+}
diff --git a/cmds/dumpsys/tests/dumpsys_test.cpp b/cmds/dumpsys/tests/dumpsys_test.cpp
new file mode 100644
index 0000000..66beb6d
--- /dev/null
+++ b/cmds/dumpsys/tests/dumpsys_test.cpp
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "../dumpsys.h"
+
+#include <vector>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <android-base/file.h>
+#include <utils/String16.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+using namespace android;
+
+using ::testing::_;
+using ::testing::Action;
+using ::testing::ActionInterface;
+using ::testing::DoAll;
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::MakeAction;
+using ::testing::Not;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::WithArg;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+class ServiceManagerMock : public IServiceManager {
+ public:
+ MOCK_CONST_METHOD1(getService, sp<IBinder>(const String16&));
+ MOCK_CONST_METHOD1(checkService, sp<IBinder>(const String16&));
+ MOCK_METHOD3(addService, status_t(const String16&, const sp<IBinder>&, bool));
+ MOCK_METHOD0(listServices, Vector<String16>());
+
+ protected:
+ MOCK_METHOD0(onAsBinder, IBinder*());
+};
+
+class BinderMock : public BBinder {
+ public:
+ BinderMock() {
+ }
+
+ MOCK_METHOD2(dump, status_t(int, const Vector<String16>&));
+};
+
+// gmock black magic to provide a WithArg<0>(WriteOnFd(output)) matcher
+typedef void WriteOnFdFunction(int);
+
+class WriteOnFdAction : public ActionInterface<WriteOnFdFunction> {
+ public:
+ explicit WriteOnFdAction(const std::string& output) : output_(output) {
+ }
+ virtual Result Perform(const ArgumentTuple& args) {
+ int fd = ::std::tr1::get<0>(args);
+ android::base::WriteStringToFd(output_, fd);
+ }
+
+ private:
+ std::string output_;
+};
+
+// Matcher used to emulate dump() by writing on its file descriptor.
+Action<WriteOnFdFunction> WriteOnFd(const std::string& output) {
+ return MakeAction(new WriteOnFdAction(output));
+}
+
+// Matcher for args using Android's Vector<String16> format
+// TODO: move it to some common testing library
+MATCHER_P(AndroidElementsAre, expected, "") {
+ std::ostringstream errors;
+ if (arg.size() != expected.size()) {
+ errors << " sizes do not match (expected " << expected.size() << ", got " << arg.size()
+ << ")\n";
+ }
+ int i = 0;
+ std::ostringstream actual_stream, expected_stream;
+ for (String16 actual : arg) {
+ std::string actual_str = String8(actual).c_str();
+ std::string expected_str = expected[i];
+ actual_stream << "'" << actual_str << "' ";
+ expected_stream << "'" << expected_str << "' ";
+ if (actual_str != expected_str) {
+ errors << " element mismatch at index " << i << "\n";
+ }
+ i++;
+ }
+
+ if (!errors.str().empty()) {
+ errors << "\nExpected args: " << expected_stream.str()
+ << "\nActual args: " << actual_stream.str();
+ *result_listener << errors.str();
+ return false;
+ }
+ return true;
+}
+
+// Custom action to sleep for timeout seconds
+ACTION_P(Sleep, timeout) {
+ sleep(timeout);
+}
+
+class DumpsysTest : public Test {
+ public:
+ DumpsysTest() : sm_(), dump_(&sm_), stdout_(), stderr_() {
+ }
+
+ void ExpectListServices(std::vector<std::string> services) {
+ Vector<String16> services16;
+ for (auto& service : services) {
+ services16.add(String16(service.c_str()));
+ }
+ EXPECT_CALL(sm_, listServices()).WillRepeatedly(Return(services16));
+ }
+
+ sp<BinderMock> ExpectCheckService(const char* name, bool running = true) {
+ sp<BinderMock> binder_mock;
+ if (running) {
+ binder_mock = new BinderMock;
+ }
+ EXPECT_CALL(sm_, checkService(String16(name))).WillRepeatedly(Return(binder_mock));
+ return binder_mock;
+ }
+
+ void ExpectDump(const char* name, const std::string& output) {
+ sp<BinderMock> binder_mock = ExpectCheckService(name);
+ EXPECT_CALL(*binder_mock, dump(_, _))
+ .WillRepeatedly(DoAll(WithArg<0>(WriteOnFd(output)), Return(0)));
+ }
+
+ void ExpectDumpWithArgs(const char* name, std::vector<std::string> args,
+ const std::string& output) {
+ sp<BinderMock> binder_mock = ExpectCheckService(name);
+ EXPECT_CALL(*binder_mock, dump(_, AndroidElementsAre(args)))
+ .WillRepeatedly(DoAll(WithArg<0>(WriteOnFd(output)), Return(0)));
+ }
+
+ void ExpectDumpAndHang(const char* name, int timeout_s, const std::string& output) {
+ sp<BinderMock> binder_mock = ExpectCheckService(name);
+ EXPECT_CALL(*binder_mock, dump(_, _))
+ .WillRepeatedly(DoAll(Sleep(timeout_s), WithArg<0>(WriteOnFd(output)), Return(0)));
+ }
+
+ void CallMain(const std::vector<std::string>& args) {
+ const char* argv[1024] = {"/some/virtual/dir/dumpsys"};
+ int argc = (int)args.size() + 1;
+ int i = 1;
+ for (const std::string& arg : args) {
+ argv[i++] = arg.c_str();
+ }
+ CaptureStdout();
+ CaptureStderr();
+ int status = dump_.main(argc, const_cast<char**>(argv));
+ stdout_ = GetCapturedStdout();
+ stderr_ = GetCapturedStderr();
+ EXPECT_THAT(status, Eq(0));
+ }
+
+ void AssertRunningServices(const std::vector<std::string>& services) {
+ std::string expected("Currently running services:\n");
+ for (const std::string& service : services) {
+ expected.append(" ").append(service).append("\n");
+ }
+ EXPECT_THAT(stdout_, HasSubstr(expected));
+ }
+
+ void AssertOutput(const std::string& expected) {
+ EXPECT_THAT(stdout_, StrEq(expected));
+ }
+
+ void AssertOutputContains(const std::string& expected) {
+ EXPECT_THAT(stdout_, HasSubstr(expected));
+ }
+
+ void AssertDumped(const std::string& service, const std::string& dump) {
+ EXPECT_THAT(stdout_, HasSubstr("DUMP OF SERVICE " + service + ":\n" + dump));
+ }
+
+ void AssertNotDumped(const std::string& dump) {
+ EXPECT_THAT(stdout_, Not(HasSubstr(dump)));
+ }
+
+ void AssertStopped(const std::string& service) {
+ EXPECT_THAT(stderr_, HasSubstr("Can't find service: " + service + "\n"));
+ }
+
+ ServiceManagerMock sm_;
+ Dumpsys dump_;
+
+ private:
+ std::string stdout_, stderr_;
+};
+
+// Tests 'dumpsys -l' when all services are running
+TEST_F(DumpsysTest, ListAllServices) {
+ ExpectListServices({"Locksmith", "Valet"});
+ ExpectCheckService("Locksmith");
+ ExpectCheckService("Valet");
+
+ CallMain({"-l"});
+
+ AssertRunningServices({"Locksmith", "Valet"});
+}
+
+// Tests 'dumpsys -l' when a service is not running
+TEST_F(DumpsysTest, ListRunningServices) {
+ ExpectListServices({"Locksmith", "Valet"});
+ ExpectCheckService("Locksmith");
+ ExpectCheckService("Valet", false);
+
+ CallMain({"-l"});
+
+ AssertRunningServices({"Locksmith"});
+ AssertNotDumped({"Valet"});
+}
+
+// Tests 'dumpsys service_name' on a service is running
+TEST_F(DumpsysTest, DumpRunningService) {
+ ExpectDump("Valet", "Here's your car");
+
+ CallMain({"Valet"});
+
+ AssertOutput("Here's your car");
+}
+
+// Tests 'dumpsys -t 1 service_name' on a service that times out after 2s
+TEST_F(DumpsysTest, DumpRunningServiceTimeout) {
+ ExpectDumpAndHang("Valet", 2, "Here's your car");
+
+ CallMain({"-t", "1", "Valet"});
+
+ AssertOutputContains("SERVICE 'Valet' DUMP TIMEOUT (1s) EXPIRED");
+ AssertNotDumped("Here's your car");
+
+ // Must wait so binder mock is deleted, otherwise test will fail with a leaked object
+ sleep(1);
+}
+
+// Tests 'dumpsys service_name Y U NO HAVE ARGS' on a service that is running
+TEST_F(DumpsysTest, DumpWithArgsRunningService) {
+ ExpectDumpWithArgs("SERVICE", {"Y", "U", "NO", "HANDLE", "ARGS"}, "I DO!");
+
+ CallMain({"SERVICE", "Y", "U", "NO", "HANDLE", "ARGS"});
+
+ AssertOutput("I DO!");
+}
+
+// Tests 'dumpsys' with no arguments
+TEST_F(DumpsysTest, DumpMultipleServices) {
+ ExpectListServices({"running1", "stopped2", "running3"});
+ ExpectDump("running1", "dump1");
+ ExpectCheckService("stopped2", false);
+ ExpectDump("running3", "dump3");
+
+ CallMain({});
+
+ AssertRunningServices({"running1", "running3"});
+ AssertDumped("running1", "dump1");
+ AssertStopped("stopped2");
+ AssertDumped("running3", "dump3");
+}
+
+// Tests 'dumpsys --skip skipped3 skipped5', which should skip these services
+TEST_F(DumpsysTest, DumpWithSkip) {
+ ExpectListServices({"running1", "stopped2", "skipped3", "running4", "skipped5"});
+ ExpectDump("running1", "dump1");
+ ExpectCheckService("stopped2", false);
+ ExpectDump("skipped3", "dump3");
+ ExpectDump("running4", "dump4");
+ ExpectDump("skipped5", "dump5");
+
+ CallMain({"--skip", "skipped3", "skipped5"});
+
+ AssertRunningServices({"running1", "running4", "skipped3 (skipped)", "skipped5 (skipped)"});
+ AssertDumped("running1", "dump1");
+ AssertDumped("running4", "dump4");
+ AssertStopped("stopped2");
+ AssertNotDumped("dump3");
+ AssertNotDumped("dump5");
+}
diff --git a/cmds/flatland/GLHelper.cpp b/cmds/flatland/GLHelper.cpp
index ddf3aa8..dfc3e58 100644
--- a/cmds/flatland/GLHelper.cpp
+++ b/cmds/flatland/GLHelper.cpp
@@ -25,7 +25,6 @@
namespace android {
GLHelper::GLHelper() :
- mGraphicBufferAlloc(new GraphicBufferAlloc()),
mDisplay(EGL_NO_DISPLAY),
mContext(EGL_NO_CONTEXT),
mDummySurface(EGL_NO_SURFACE),
@@ -203,7 +202,7 @@
sp<GLConsumer>* glConsumer, EGLSurface* surface) {
sp<IGraphicBufferProducer> producer;
sp<IGraphicBufferConsumer> consumer;
- BufferQueue::createBufferQueue(&producer, &consumer, mGraphicBufferAlloc);
+ BufferQueue::createBufferQueue(&producer, &consumer);
sp<GLConsumer> glc = new GLConsumer(consumer, name,
GL_TEXTURE_EXTERNAL_OES, false, true);
glc->setDefaultBufferSize(w, h);
@@ -365,6 +364,7 @@
if (!result) {
fprintf(stderr, "Shader source:\n");
printShaderSource(lines);
+ delete[] src;
return false;
}
delete[] src;
diff --git a/cmds/flatland/GLHelper.h b/cmds/flatland/GLHelper.h
index 7a9e9e3..d09463a 100644
--- a/cmds/flatland/GLHelper.h
+++ b/cmds/flatland/GLHelper.h
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-#include <gui/GraphicBufferAlloc.h>
#include <gui/GLConsumer.h>
#include <gui/Surface.h>
#include <gui/SurfaceControl.h>
@@ -75,8 +74,6 @@
bool setUpShaders(const ShaderDesc* shaderDescs, size_t numShaders);
- sp<GraphicBufferAlloc> mGraphicBufferAlloc;
-
EGLDisplay mDisplay;
EGLContext mContext;
EGLSurface mDummySurface;
diff --git a/cmds/flatland/Main.cpp b/cmds/flatland/Main.cpp
index c47b0c8..ec1e543 100644
--- a/cmds/flatland/Main.cpp
+++ b/cmds/flatland/Main.cpp
@@ -16,7 +16,6 @@
#define ATRACE_TAG ATRACE_TAG_ALWAYS
-#include <gui/GraphicBufferAlloc.h>
#include <gui/Surface.h>
#include <gui/SurfaceControl.h>
#include <gui/GLConsumer.h>
diff --git a/cmds/installd/Android.bp b/cmds/installd/Android.bp
new file mode 100644
index 0000000..33db6db
--- /dev/null
+++ b/cmds/installd/Android.bp
@@ -0,0 +1,75 @@
+cc_defaults {
+ name: "installd_defaults",
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ srcs: [
+ "CacheItem.cpp",
+ "CacheTracker.cpp",
+ "InstalldNativeService.cpp",
+ "dexopt.cpp",
+ "globals.cpp",
+ "utils.cpp",
+ "binder/android/os/IInstalld.aidl",
+ ],
+ shared_libs: [
+ "libbase",
+ "libbinder",
+ "libcutils",
+ "liblog",
+ "liblogwrap",
+ "libselinux",
+ "libutils",
+ ],
+
+ clang: true,
+}
+
+//
+// Static library used in testing and executable
+//
+
+cc_library_static {
+ name: "libinstalld",
+ defaults: ["installd_defaults"],
+
+ export_include_dirs: ["."],
+ aidl: {
+ export_aidl_headers: true,
+ },
+}
+
+//
+// Executable
+//
+
+cc_binary {
+ name: "installd",
+ defaults: ["installd_defaults"],
+ srcs: ["installd.cpp"],
+
+ static_libs: ["libdiskusage"],
+
+ init_rc: ["installd.rc"],
+}
+
+// OTA chroot tool
+
+cc_binary {
+ name: "otapreopt_chroot",
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ clang: true,
+
+ srcs: ["otapreopt_chroot.cpp"],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ ],
+}
+
+subdirs = ["tests"]
diff --git a/cmds/installd/Android.mk b/cmds/installd/Android.mk
index 86df596..1d21b3c 100644
--- a/cmds/installd/Android.mk
+++ b/cmds/installd/Android.mk
@@ -1,56 +1,12 @@
LOCAL_PATH := $(call my-dir)
-common_src_files := commands.cpp globals.cpp utils.cpp
-common_cflags := -Wall -Werror
-
-#
-# Static library used in testing and executable
-#
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := libinstalld
-LOCAL_MODULE_TAGS := eng tests
-LOCAL_SRC_FILES := $(common_src_files)
-LOCAL_CFLAGS := $(common_cflags)
-LOCAL_SHARED_LIBRARIES := \
- libbase \
- liblogwrap \
- libselinux \
-
-LOCAL_ADDITIONAL_DEPENDENCIES += $(LOCAL_PATH)/Android.mk
-LOCAL_CLANG := true
-include $(BUILD_STATIC_LIBRARY)
-
-#
-# Executable
-#
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := installd
-LOCAL_MODULE_TAGS := optional
-LOCAL_CFLAGS := $(common_cflags)
-LOCAL_SRC_FILES := installd.cpp $(common_src_files)
-LOCAL_SHARED_LIBRARIES := \
- libbase \
- libcutils \
- liblog \
- liblogwrap \
- libselinux \
-
-LOCAL_STATIC_LIBRARIES := libdiskusage
-LOCAL_ADDITIONAL_DEPENDENCIES += $(LOCAL_PATH)/Android.mk
-LOCAL_INIT_RC := installd.rc
-LOCAL_CLANG := true
-include $(BUILD_EXECUTABLE)
-
#
# OTA Executable
#
include $(CLEAR_VARS)
LOCAL_MODULE := otapreopt
-LOCAL_MODULE_TAGS := optional
-LOCAL_CFLAGS := $(common_cflags)
+LOCAL_CFLAGS := -Wall -Werror
# Base & ASLR boundaries for boot image creation.
ifndef LIBART_IMG_HOST_MIN_BASE_ADDRESS_DELTA
@@ -67,32 +23,17 @@
LOCAL_CFLAGS += -DART_BASE_ADDRESS_MIN_DELTA=$(LOCAL_LIBART_IMG_HOST_MIN_BASE_ADDRESS_DELTA)
LOCAL_CFLAGS += -DART_BASE_ADDRESS_MAX_DELTA=$(LOCAL_LIBART_IMG_HOST_MAX_BASE_ADDRESS_DELTA)
-LOCAL_SRC_FILES := otapreopt.cpp $(common_src_files)
+LOCAL_SRC_FILES := otapreopt.cpp globals.cpp utils.cpp dexopt.cpp
+LOCAL_HEADER_LIBRARIES := dex2oat_headers
LOCAL_SHARED_LIBRARIES := \
libbase \
libcutils \
liblog \
liblogwrap \
libselinux \
+ libutils \
LOCAL_STATIC_LIBRARIES := libdiskusage
-LOCAL_ADDITIONAL_DEPENDENCIES += $(LOCAL_PATH)/Android.mk
-LOCAL_CLANG := true
-include $(BUILD_EXECUTABLE)
-
-# OTA chroot tool
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := otapreopt_chroot
-LOCAL_MODULE_TAGS := optional
-LOCAL_CFLAGS := $(common_cflags)
-
-LOCAL_SRC_FILES := otapreopt_chroot.cpp
-LOCAL_SHARED_LIBRARIES := \
- libbase \
- liblog \
-
-LOCAL_ADDITIONAL_DEPENDENCIES += $(LOCAL_PATH)/Android.mk
LOCAL_CLANG := true
include $(BUILD_EXECUTABLE)
@@ -120,7 +61,3 @@
LOCAL_REQUIRED_MODULES := otapreopt otapreopt_chroot otapreopt_slot
include $(BUILD_PREBUILT)
-
-# Tests.
-
-include $(LOCAL_PATH)/tests/Android.mk
\ No newline at end of file
diff --git a/cmds/installd/CacheItem.cpp b/cmds/installd/CacheItem.cpp
new file mode 100644
index 0000000..515f915
--- /dev/null
+++ b/cmds/installd/CacheItem.cpp
@@ -0,0 +1,136 @@
+/*
+ * 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.
+ */
+
+#include "CacheItem.h"
+
+#include <inttypes.h>
+#include <stdint.h>
+#include <sys/xattr.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include "utils.h"
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace installd {
+
+CacheItem::CacheItem(FTSENT* p) {
+ level = p->fts_level;
+ directory = S_ISDIR(p->fts_statp->st_mode);
+ size = p->fts_statp->st_blocks * 512;
+ modified = p->fts_statp->st_mtime;
+
+ mParent = static_cast<CacheItem*>(p->fts_parent->fts_pointer);
+ if (mParent) {
+ group = mParent->group;
+ tombstone = mParent->tombstone;
+ mName = p->fts_name;
+ mName.insert(0, "/");
+ } else {
+ group = false;
+ tombstone = false;
+ mName = p->fts_path;
+ }
+}
+
+CacheItem::~CacheItem() {
+}
+
+std::string CacheItem::toString() {
+ return StringPrintf("%s size=%" PRId64 " mod=%ld", buildPath().c_str(), size, modified);
+}
+
+std::string CacheItem::buildPath() {
+ std::string res = mName;
+ CacheItem* parent = mParent;
+ while (parent) {
+ res.insert(0, parent->mName);
+ parent = parent->mParent;
+ }
+ return res;
+}
+
+int CacheItem::purge() {
+ int res = 0;
+ auto path = buildPath();
+ if (directory) {
+ FTS *fts;
+ FTSENT *p;
+ char *argv[] = { (char*) path.c_str(), nullptr };
+ if (!(fts = fts_open(argv, FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV, NULL))) {
+ PLOG(WARNING) << "Failed to fts_open " << path;
+ return -1;
+ }
+ while ((p = fts_read(fts)) != nullptr) {
+ switch (p->fts_info) {
+ case FTS_D:
+ if (p->fts_level == 0) {
+ p->fts_number = tombstone;
+ } else {
+ p->fts_number = p->fts_parent->fts_number
+ | (getxattr(p->fts_path, kXattrCacheTombstone, nullptr, 0) >= 0);
+ }
+ break;
+ case FTS_F:
+ if (p->fts_parent->fts_number) {
+ if (truncate(p->fts_path, 0) != 0) {
+ PLOG(WARNING) << "Failed to truncate " << p->fts_path;
+ res = -1;
+ }
+ } else {
+ if (unlink(p->fts_path) != 0) {
+ PLOG(WARNING) << "Failed to unlink " << p->fts_path;
+ res = -1;
+ }
+ }
+ break;
+ case FTS_DEFAULT:
+ case FTS_SL:
+ case FTS_SLNONE:
+ if (unlink(p->fts_path) != 0) {
+ PLOG(WARNING) << "Failed to unlink " << p->fts_path;
+ res = -1;
+ }
+ break;
+ case FTS_DP:
+ if (rmdir(p->fts_path) != 0) {
+ PLOG(WARNING) << "Failed to rmdir " << p->fts_path;
+ res = -1;
+ }
+ break;
+ }
+ }
+ } else {
+ if (tombstone) {
+ if (truncate(path.c_str(), 0) != 0) {
+ PLOG(WARNING) << "Failed to truncate " << path;
+ res = -1;
+ }
+ } else {
+ if (unlink(path.c_str()) != 0) {
+ PLOG(WARNING) << "Failed to unlink " << path;
+ res = -1;
+ }
+ }
+ }
+ return res;
+}
+
+} // namespace installd
+} // namespace android
diff --git a/cmds/installd/CacheItem.h b/cmds/installd/CacheItem.h
new file mode 100644
index 0000000..84b77aa
--- /dev/null
+++ b/cmds/installd/CacheItem.h
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_INSTALLD_CACHE_ITEM_H
+#define ANDROID_INSTALLD_CACHE_ITEM_H
+
+#include <memory>
+#include <string>
+
+#include <fts.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <android-base/macros.h>
+
+namespace android {
+namespace installd {
+
+/**
+ * Single cache item that can be purged to free up space. This may be an
+ * isolated file, or an entire directory tree that should be deleted as a
+ * group.
+ */
+class CacheItem {
+public:
+ CacheItem(FTSENT* p);
+ ~CacheItem();
+
+ std::string toString();
+ std::string buildPath();
+
+ int purge();
+
+ short level;
+ bool directory;
+ bool group;
+ bool tombstone;
+ int64_t size;
+ time_t modified;
+
+private:
+ CacheItem* mParent;
+ std::string mName;
+
+ DISALLOW_COPY_AND_ASSIGN(CacheItem);
+};
+
+} // namespace installd
+} // namespace android
+
+#endif // ANDROID_INSTALLD_CACHE_ITEM_H
diff --git a/cmds/installd/CacheTracker.cpp b/cmds/installd/CacheTracker.cpp
new file mode 100644
index 0000000..e293948
--- /dev/null
+++ b/cmds/installd/CacheTracker.cpp
@@ -0,0 +1,212 @@
+/*
+ * 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.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_PACKAGE_MANAGER
+
+#include "CacheTracker.h"
+
+#include <fts.h>
+#include <sys/quota.h>
+#include <sys/xattr.h>
+#include <utils/Trace.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+#include "utils.h"
+
+using android::base::StringPrintf;
+
+namespace android {
+namespace installd {
+
+CacheTracker::CacheTracker(userid_t userId, appid_t appId, const std::string& quotaDevice) :
+ cacheUsed(0), cacheQuota(0), mUserId(userId), mAppId(appId), mQuotaDevice(quotaDevice),
+ mItemsLoaded(false) {
+}
+
+CacheTracker::~CacheTracker() {
+}
+
+std::string CacheTracker::toString() {
+ return StringPrintf("UID=%d used=%" PRId64 " quota=%" PRId64 " ratio=%d",
+ multiuser_get_uid(mUserId, mAppId), cacheUsed, cacheQuota, getCacheRatio());
+}
+
+void CacheTracker::addDataPath(const std::string& dataPath) {
+ mDataPaths.push_back(dataPath);
+}
+
+void CacheTracker::loadStats() {
+ ATRACE_BEGIN("loadStats quota");
+ cacheUsed = 0;
+ if (loadQuotaStats()) {
+ return;
+ }
+ ATRACE_END();
+
+ ATRACE_BEGIN("loadStats tree");
+ cacheUsed = 0;
+ for (auto path : mDataPaths) {
+ auto cachePath = read_path_inode(path, "cache", kXattrInodeCache);
+ auto codeCachePath = read_path_inode(path, "code_cache", kXattrInodeCodeCache);
+ calculate_tree_size(cachePath, &cacheUsed);
+ calculate_tree_size(codeCachePath, &cacheUsed);
+ }
+ ATRACE_END();
+}
+
+bool CacheTracker::loadQuotaStats() {
+ int cacheGid = multiuser_get_cache_gid(mUserId, mAppId);
+ int extCacheGid = multiuser_get_ext_cache_gid(mUserId, mAppId);
+ if (!mQuotaDevice.empty() && cacheGid != -1 && extCacheGid != -1) {
+ struct dqblk dq;
+ if (quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), mQuotaDevice.c_str(), cacheGid,
+ reinterpret_cast<char*>(&dq)) != 0) {
+ if (errno != ESRCH) {
+ PLOG(ERROR) << "Failed to quotactl " << mQuotaDevice << " for GID " << cacheGid;
+ }
+ return false;
+ } else {
+ cacheUsed += dq.dqb_curspace;
+ }
+
+ if (quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), mQuotaDevice.c_str(), extCacheGid,
+ reinterpret_cast<char*>(&dq)) != 0) {
+ if (errno != ESRCH) {
+ PLOG(ERROR) << "Failed to quotactl " << mQuotaDevice << " for GID " << cacheGid;
+ }
+ return false;
+ } else {
+ cacheUsed += dq.dqb_curspace;
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void CacheTracker::loadItemsFrom(const std::string& path) {
+ FTS *fts;
+ FTSENT *p;
+ char *argv[] = { (char*) path.c_str(), nullptr };
+ if (!(fts = fts_open(argv, FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV, NULL))) {
+ PLOG(WARNING) << "Failed to fts_open " << path;
+ return;
+ }
+ while ((p = fts_read(fts)) != nullptr) {
+ if (p->fts_level == 0) continue;
+
+ // Create tracking nodes for everything we encounter
+ switch (p->fts_info) {
+ case FTS_D:
+ case FTS_DEFAULT:
+ case FTS_F:
+ case FTS_SL:
+ case FTS_SLNONE: {
+ auto item = std::shared_ptr<CacheItem>(new CacheItem(p));
+ p->fts_pointer = static_cast<void*>(item.get());
+ items.push_back(item);
+ }
+ }
+
+ switch (p->fts_info) {
+ case FTS_D: {
+ auto item = static_cast<CacheItem*>(p->fts_pointer);
+ item->group |= (getxattr(p->fts_path, kXattrCacheGroup, nullptr, 0) >= 0);
+ item->tombstone |= (getxattr(p->fts_path, kXattrCacheTombstone, nullptr, 0) >= 0);
+
+ // When group, immediately collect all files under tree
+ if (item->group) {
+ while ((p = fts_read(fts)) != nullptr) {
+ if (p->fts_info == FTS_DP && p->fts_level == item->level) break;
+ switch (p->fts_info) {
+ case FTS_D:
+ case FTS_DEFAULT:
+ case FTS_F:
+ case FTS_SL:
+ case FTS_SLNONE:
+ item->size += p->fts_statp->st_blocks * 512;
+ item->modified = std::max(item->modified, p->fts_statp->st_mtime);
+ }
+ }
+ }
+ }
+ }
+
+ // Bubble up modified time to parent
+ switch (p->fts_info) {
+ case FTS_DP:
+ case FTS_DEFAULT:
+ case FTS_F:
+ case FTS_SL:
+ case FTS_SLNONE: {
+ auto item = static_cast<CacheItem*>(p->fts_pointer);
+ auto parent = static_cast<CacheItem*>(p->fts_parent->fts_pointer);
+ if (parent) {
+ parent->modified = std::max(parent->modified, item->modified);
+ }
+ }
+ }
+ }
+ fts_close(fts);
+}
+
+void CacheTracker::loadItems() {
+ items.clear();
+
+ ATRACE_BEGIN("loadItems");
+ for (auto path : mDataPaths) {
+ loadItemsFrom(read_path_inode(path, "cache", kXattrInodeCache));
+ loadItemsFrom(read_path_inode(path, "code_cache", kXattrInodeCodeCache));
+ }
+ ATRACE_END();
+
+ ATRACE_BEGIN("sortItems");
+ auto cmp = [](std::shared_ptr<CacheItem> left, std::shared_ptr<CacheItem> right) {
+ // TODO: sort dotfiles last
+ // TODO: sort code_cache last
+ if (left->modified != right->modified) {
+ return (left->modified > right->modified);
+ }
+ if (left->level != right->level) {
+ return (left->level < right->level);
+ }
+ return left->directory;
+ };
+ std::stable_sort(items.begin(), items.end(), cmp);
+ ATRACE_END();
+}
+
+void CacheTracker::ensureItems() {
+ if (mItemsLoaded) {
+ return;
+ } else {
+ loadItems();
+ mItemsLoaded = true;
+ }
+}
+
+int CacheTracker::getCacheRatio() {
+ if (cacheQuota == 0) {
+ return 0;
+ } else {
+ return (cacheUsed * 10000) / cacheQuota;
+ }
+}
+
+} // namespace installd
+} // namespace android
diff --git a/cmds/installd/CacheTracker.h b/cmds/installd/CacheTracker.h
new file mode 100644
index 0000000..44359b4
--- /dev/null
+++ b/cmds/installd/CacheTracker.h
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_INSTALLD_CACHE_TRACKER_H
+#define ANDROID_INSTALLD_CACHE_TRACKER_H
+
+#include <memory>
+#include <string>
+#include <queue>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <android-base/macros.h>
+#include <cutils/multiuser.h>
+
+#include "CacheItem.h"
+
+namespace android {
+namespace installd {
+
+/**
+ * Cache tracker for a single UID. Each tracker is used in two modes: first
+ * for loading lightweight "stats", and then by loading detailed "items"
+ * which can then be purged to free up space.
+ */
+class CacheTracker {
+public:
+ CacheTracker(userid_t userId, appid_t appId, const std::string& quotaDevice);
+ ~CacheTracker();
+
+ std::string toString();
+
+ void addDataPath(const std::string& dataPath);
+
+ void loadStats();
+ void loadItems();
+
+ void ensureItems();
+
+ int getCacheRatio();
+
+ int64_t cacheUsed;
+ int64_t cacheQuota;
+
+ std::vector<std::shared_ptr<CacheItem>> items;
+
+private:
+ userid_t mUserId;
+ appid_t mAppId;
+ std::string mQuotaDevice;
+ bool mItemsLoaded;
+
+ std::vector<std::string> mDataPaths;
+
+ bool loadQuotaStats();
+ void loadItemsFrom(const std::string& path);
+
+ DISALLOW_COPY_AND_ASSIGN(CacheTracker);
+};
+
+} // namespace installd
+} // namespace android
+
+#endif // ANDROID_INSTALLD_CACHE_TRACKER_H
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp
new file mode 100644
index 0000000..60c89a9
--- /dev/null
+++ b/cmds/installd/InstalldNativeService.cpp
@@ -0,0 +1,2357 @@
+/*
+** Copyright 2008, 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.
+*/
+
+#include "InstalldNativeService.h"
+
+#define ATRACE_TAG ATRACE_TAG_PACKAGE_MANAGER
+
+#include <errno.h>
+#include <inttypes.h>
+#include <fstream>
+#include <fts.h>
+#include <regex>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/capability.h>
+#include <sys/file.h>
+#include <sys/resource.h>
+#include <sys/quota.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <unistd.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <cutils/fs.h>
+#include <cutils/properties.h>
+#include <cutils/sched_policy.h>
+#include <log/log.h> // TODO: Move everything to base/logging.
+#include <logwrap/logwrap.h>
+#include <private/android_filesystem_config.h>
+#include <selinux/android.h>
+#include <system/thread_defs.h>
+#include <utils/Trace.h>
+
+#include "dexopt.h"
+#include "globals.h"
+#include "installd_deps.h"
+#include "otapreopt_utils.h"
+#include "utils.h"
+
+#include "CacheTracker.h"
+#include "MatchExtensionGen.h"
+
+#ifndef LOG_TAG
+#define LOG_TAG "installd"
+#endif
+
+using android::base::StringPrintf;
+using std::endl;
+
+namespace android {
+namespace installd {
+
+static constexpr const char* kCpPath = "/system/bin/cp";
+static constexpr const char* kXattrDefault = "user.default";
+
+static constexpr const int MIN_RESTRICTED_HOME_SDK_VERSION = 24; // > M
+
+static constexpr const char* PKG_LIB_POSTFIX = "/lib";
+static constexpr const char* CACHE_DIR_POSTFIX = "/cache";
+static constexpr const char* CODE_CACHE_DIR_POSTFIX = "/code_cache";
+
+static constexpr const char *kIdMapPath = "/system/bin/idmap";
+static constexpr const char* IDMAP_PREFIX = "/data/resource-cache/";
+static constexpr const char* IDMAP_SUFFIX = "@idmap";
+
+// NOTE: keep in sync with Installer
+static constexpr int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
+static constexpr int FLAG_CLEAR_CODE_CACHE_ONLY = 1 << 9;
+static constexpr int FLAG_USE_QUOTA = 1 << 12;
+static constexpr int FLAG_FREE_CACHE_V2 = 1 << 13;
+static constexpr int FLAG_FREE_CACHE_V2_DEFY_QUOTA = 1 << 14;
+static constexpr int FLAG_FREE_CACHE_NOOP = 1 << 15;
+static constexpr int FLAG_FORCE = 1 << 16;
+
+namespace {
+
+constexpr const char* kDump = "android.permission.DUMP";
+
+static binder::Status ok() {
+ return binder::Status::ok();
+}
+
+static binder::Status exception(uint32_t code, const std::string& msg) {
+ return binder::Status::fromExceptionCode(code, String8(msg.c_str()));
+}
+
+static binder::Status error() {
+ return binder::Status::fromServiceSpecificError(errno);
+}
+
+static binder::Status error(const std::string& msg) {
+ PLOG(ERROR) << msg;
+ return binder::Status::fromServiceSpecificError(errno, String8(msg.c_str()));
+}
+
+static binder::Status error(uint32_t code, const std::string& msg) {
+ LOG(ERROR) << msg << " (" << code << ")";
+ return binder::Status::fromServiceSpecificError(code, String8(msg.c_str()));
+}
+
+binder::Status checkPermission(const char* permission) {
+ pid_t pid;
+ uid_t uid;
+
+ if (checkCallingPermission(String16(permission), reinterpret_cast<int32_t*>(&pid),
+ reinterpret_cast<int32_t*>(&uid))) {
+ return ok();
+ } else {
+ return exception(binder::Status::EX_SECURITY,
+ StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, permission));
+ }
+}
+
+binder::Status checkUid(uid_t expectedUid) {
+ uid_t uid = IPCThreadState::self()->getCallingUid();
+ if (uid == expectedUid || uid == AID_ROOT) {
+ return ok();
+ } else {
+ return exception(binder::Status::EX_SECURITY,
+ StringPrintf("UID %d is not expected UID %d", uid, expectedUid));
+ }
+}
+
+binder::Status checkArgumentUuid(const std::unique_ptr<std::string>& uuid) {
+ if (!uuid || is_valid_filename(*uuid)) {
+ return ok();
+ } else {
+ return exception(binder::Status::EX_ILLEGAL_ARGUMENT,
+ StringPrintf("UUID %s is malformed", uuid->c_str()));
+ }
+}
+
+binder::Status checkArgumentPackageName(const std::string& packageName) {
+ if (is_valid_package_name(packageName.c_str())) {
+ return ok();
+ } else {
+ return exception(binder::Status::EX_ILLEGAL_ARGUMENT,
+ StringPrintf("Package name %s is malformed", packageName.c_str()));
+ }
+}
+
+#define ENFORCE_UID(uid) { \
+ binder::Status status = checkUid((uid)); \
+ if (!status.isOk()) { \
+ return status; \
+ } \
+}
+
+#define CHECK_ARGUMENT_UUID(uuid) { \
+ binder::Status status = checkArgumentUuid((uuid)); \
+ if (!status.isOk()) { \
+ return status; \
+ } \
+}
+
+#define CHECK_ARGUMENT_PACKAGE_NAME(packageName) { \
+ binder::Status status = \
+ checkArgumentPackageName((packageName)); \
+ if (!status.isOk()) { \
+ return status; \
+ } \
+}
+
+} // namespace
+
+status_t InstalldNativeService::start() {
+ IPCThreadState::self()->disableBackgroundScheduling(true);
+ status_t ret = BinderService<InstalldNativeService>::publish();
+ if (ret != android::OK) {
+ return ret;
+ }
+ sp<ProcessState> ps(ProcessState::self());
+ ps->startThreadPool();
+ ps->giveThreadPoolName();
+ return android::OK;
+}
+
+status_t InstalldNativeService::dump(int fd, const Vector<String16> & /* args */) {
+ auto out = std::fstream(StringPrintf("/proc/self/fd/%d", fd));
+ const binder::Status dump_permission = checkPermission(kDump);
+ if (!dump_permission.isOk()) {
+ out << dump_permission.toString8() << endl;
+ return PERMISSION_DENIED;
+ }
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ out << "installd is happy!" << endl;
+
+ {
+ std::lock_guard<std::recursive_mutex> lock(mMountsLock);
+ out << endl << "Storage mounts:" << endl;
+ for (const auto& n : mStorageMounts) {
+ out << " " << n.first << " = " << n.second << endl;
+ }
+
+ out << endl << "Quota reverse mounts:" << endl;
+ for (const auto& n : mQuotaReverseMounts) {
+ out << " " << n.first << " = " << n.second << endl;
+ }
+ }
+
+ {
+ std::lock_guard<std::recursive_mutex> lock(mQuotasLock);
+ out << endl << "Per-UID cache quotas:" << endl;
+ for (const auto& n : mCacheQuotas) {
+ out << " " << n.first << " = " << n.second << endl;
+ }
+ }
+
+ out << endl;
+ out.flush();
+
+ return NO_ERROR;
+}
+
+/**
+ * Perform restorecon of the given path, but only perform recursive restorecon
+ * if the label of that top-level file actually changed. This can save us
+ * significant time by avoiding no-op traversals of large filesystem trees.
+ */
+static int restorecon_app_data_lazy(const std::string& path, const std::string& seInfo, uid_t uid,
+ bool existing) {
+ int res = 0;
+ char* before = nullptr;
+ char* after = nullptr;
+
+ // Note that SELINUX_ANDROID_RESTORECON_DATADATA flag is set by
+ // libselinux. Not needed here.
+
+ if (lgetfilecon(path.c_str(), &before) < 0) {
+ PLOG(ERROR) << "Failed before getfilecon for " << path;
+ goto fail;
+ }
+ if (selinux_android_restorecon_pkgdir(path.c_str(), seInfo.c_str(), uid, 0) < 0) {
+ PLOG(ERROR) << "Failed top-level restorecon for " << path;
+ goto fail;
+ }
+ if (lgetfilecon(path.c_str(), &after) < 0) {
+ PLOG(ERROR) << "Failed after getfilecon for " << path;
+ goto fail;
+ }
+
+ // If the initial top-level restorecon above changed the label, then go
+ // back and restorecon everything recursively
+ if (strcmp(before, after)) {
+ if (existing) {
+ LOG(DEBUG) << "Detected label change from " << before << " to " << after << " at "
+ << path << "; running recursive restorecon";
+ }
+ if (selinux_android_restorecon_pkgdir(path.c_str(), seInfo.c_str(), uid,
+ SELINUX_ANDROID_RESTORECON_RECURSE) < 0) {
+ PLOG(ERROR) << "Failed recursive restorecon for " << path;
+ goto fail;
+ }
+ }
+
+ goto done;
+fail:
+ res = -1;
+done:
+ free(before);
+ free(after);
+ return res;
+}
+
+static int restorecon_app_data_lazy(const std::string& parent, const char* name,
+ const std::string& seInfo, uid_t uid, bool existing) {
+ return restorecon_app_data_lazy(StringPrintf("%s/%s", parent.c_str(), name), seInfo, uid,
+ existing);
+}
+
+static int prepare_app_dir(const std::string& path, mode_t target_mode, uid_t uid) {
+ if (fs_prepare_dir_strict(path.c_str(), target_mode, uid, uid) != 0) {
+ PLOG(ERROR) << "Failed to prepare " << path;
+ return -1;
+ }
+ return 0;
+}
+
+/**
+ * Ensure that we have a hard-limit quota to protect against abusive apps;
+ * they should never use more than 90% of blocks or 50% of inodes.
+ */
+static int prepare_app_quota(const std::unique_ptr<std::string>& uuid, const std::string& device,
+ uid_t uid) {
+ if (device.empty()) return 0;
+
+ struct dqblk dq;
+ if (quotactl(QCMD(Q_GETQUOTA, USRQUOTA), device.c_str(), uid,
+ reinterpret_cast<char*>(&dq)) != 0) {
+ PLOG(WARNING) << "Failed to find quota for " << uid;
+ return -1;
+ }
+
+ if ((dq.dqb_bhardlimit == 0) || (dq.dqb_ihardlimit == 0)) {
+ auto path = create_data_path(uuid ? uuid->c_str() : nullptr);
+ struct statvfs stat;
+ if (statvfs(path.c_str(), &stat) != 0) {
+ PLOG(WARNING) << "Failed to statvfs " << path;
+ return -1;
+ }
+
+ dq.dqb_valid = QIF_LIMITS;
+ dq.dqb_bhardlimit = (((stat.f_blocks * stat.f_frsize) / 10) * 9) / QIF_DQBLKSIZE;
+ dq.dqb_ihardlimit = (stat.f_files / 2);
+ if (quotactl(QCMD(Q_SETQUOTA, USRQUOTA), device.c_str(), uid,
+ reinterpret_cast<char*>(&dq)) != 0) {
+ PLOG(WARNING) << "Failed to set hard quota for " << uid;
+ return -1;
+ } else {
+ LOG(DEBUG) << "Applied hard quotas for " << uid;
+ return 0;
+ }
+ } else {
+ // Hard quota already set; assume it's reasonable
+ return 0;
+ }
+}
+
+binder::Status InstalldNativeService::createAppData(const std::unique_ptr<std::string>& uuid,
+ const std::string& packageName, int32_t userId, int32_t flags, int32_t appId,
+ const std::string& seInfo, int32_t targetSdkVersion, int64_t* _aidl_return) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_UUID(uuid);
+ CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ const char* uuid_ = uuid ? uuid->c_str() : nullptr;
+ const char* pkgname = packageName.c_str();
+
+ // Assume invalid inode unless filled in below
+ if (_aidl_return != nullptr) *_aidl_return = -1;
+
+ int32_t uid = multiuser_get_uid(userId, appId);
+ int32_t cacheGid = multiuser_get_cache_gid(userId, appId);
+ mode_t targetMode = targetSdkVersion >= MIN_RESTRICTED_HOME_SDK_VERSION ? 0700 : 0751;
+
+ // If UID doesn't have a specific cache GID, use UID value
+ if (cacheGid == -1) {
+ cacheGid = uid;
+ }
+
+ if (flags & FLAG_STORAGE_CE) {
+ auto path = create_data_user_ce_package_path(uuid_, userId, pkgname);
+ bool existing = (access(path.c_str(), F_OK) == 0);
+
+ if (prepare_app_dir(path, targetMode, uid) ||
+ prepare_app_cache_dir(path, "cache", 02771, uid, cacheGid) ||
+ prepare_app_cache_dir(path, "code_cache", 02771, uid, cacheGid)) {
+ return error("Failed to prepare " + path);
+ }
+
+ // Consider restorecon over contents if label changed
+ if (restorecon_app_data_lazy(path, seInfo, uid, existing) ||
+ restorecon_app_data_lazy(path, "cache", seInfo, uid, existing) ||
+ restorecon_app_data_lazy(path, "code_cache", seInfo, uid, existing)) {
+ return error("Failed to restorecon " + path);
+ }
+
+ // Remember inode numbers of cache directories so that we can clear
+ // contents while CE storage is locked
+ if (write_path_inode(path, "cache", kXattrInodeCache) ||
+ write_path_inode(path, "code_cache", kXattrInodeCodeCache)) {
+ return error("Failed to write_path_inode for " + path);
+ }
+
+ // And return the CE inode of the top-level data directory so we can
+ // clear contents while CE storage is locked
+ if ((_aidl_return != nullptr)
+ && get_path_inode(path, reinterpret_cast<ino_t*>(_aidl_return)) != 0) {
+ return error("Failed to get_path_inode for " + path);
+ }
+ }
+ if (flags & FLAG_STORAGE_DE) {
+ auto path = create_data_user_de_package_path(uuid_, userId, pkgname);
+ bool existing = (access(path.c_str(), F_OK) == 0);
+
+ if (prepare_app_dir(path, targetMode, uid) ||
+ prepare_app_cache_dir(path, "cache", 02771, uid, cacheGid) ||
+ prepare_app_cache_dir(path, "code_cache", 02771, uid, cacheGid)) {
+ return error("Failed to prepare " + path);
+ }
+
+ // Consider restorecon over contents if label changed
+ if (restorecon_app_data_lazy(path, seInfo, uid, existing) ||
+ restorecon_app_data_lazy(path, "cache", seInfo, uid, existing) ||
+ restorecon_app_data_lazy(path, "code_cache", seInfo, uid, existing)) {
+ return error("Failed to restorecon " + path);
+ }
+
+ if (prepare_app_quota(uuid, findQuotaDeviceForUuid(uuid), uid)) {
+ return error("Failed to set hard quota " + path);
+ }
+
+ if (property_get_bool("dalvik.vm.usejitprofiles", false)) {
+ const std::string profile_dir =
+ create_primary_current_profile_package_dir_path(userId, pkgname);
+ // read-write-execute only for the app user.
+ if (fs_prepare_dir_strict(profile_dir.c_str(), 0700, uid, uid) != 0) {
+ return error("Failed to prepare " + profile_dir);
+ }
+ const std::string profile_file = create_current_profile_path(userId, pkgname,
+ /*is_secondary_dex*/false);
+ // read-write only for the app user.
+ if (fs_prepare_file_strict(profile_file.c_str(), 0600, uid, uid) != 0) {
+ return error("Failed to prepare " + profile_file);
+ }
+ const std::string ref_profile_path =
+ create_primary_reference_profile_package_dir_path(pkgname);
+ // dex2oat/profman runs under the shared app gid and it needs to read/write reference
+ // profiles.
+ int shared_app_gid = multiuser_get_shared_gid(0, appId);
+ if ((shared_app_gid != -1) && fs_prepare_dir_strict(
+ ref_profile_path.c_str(), 0700, shared_app_gid, shared_app_gid) != 0) {
+ return error("Failed to prepare " + ref_profile_path);
+ }
+ }
+ }
+ return ok();
+}
+
+binder::Status InstalldNativeService::migrateAppData(const std::unique_ptr<std::string>& uuid,
+ const std::string& packageName, int32_t userId, int32_t flags) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_UUID(uuid);
+ CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ const char* uuid_ = uuid ? uuid->c_str() : nullptr;
+ const char* pkgname = packageName.c_str();
+
+ // This method only exists to upgrade system apps that have requested
+ // forceDeviceEncrypted, so their default storage always lives in a
+ // consistent location. This only works on non-FBE devices, since we
+ // never want to risk exposing data on a device with real CE/DE storage.
+
+ auto ce_path = create_data_user_ce_package_path(uuid_, userId, pkgname);
+ auto de_path = create_data_user_de_package_path(uuid_, userId, pkgname);
+
+ // If neither directory is marked as default, assume CE is default
+ if (getxattr(ce_path.c_str(), kXattrDefault, nullptr, 0) == -1
+ && getxattr(de_path.c_str(), kXattrDefault, nullptr, 0) == -1) {
+ if (setxattr(ce_path.c_str(), kXattrDefault, nullptr, 0, 0) != 0) {
+ return error("Failed to mark default storage " + ce_path);
+ }
+ }
+
+ // Migrate default data location if needed
+ auto target = (flags & FLAG_STORAGE_DE) ? de_path : ce_path;
+ auto source = (flags & FLAG_STORAGE_DE) ? ce_path : de_path;
+
+ if (getxattr(target.c_str(), kXattrDefault, nullptr, 0) == -1) {
+ LOG(WARNING) << "Requested default storage " << target
+ << " is not active; migrating from " << source;
+ if (delete_dir_contents_and_dir(target) != 0) {
+ return error("Failed to delete " + target);
+ }
+ if (rename(source.c_str(), target.c_str()) != 0) {
+ return error("Failed to rename " + source + " to " + target);
+ }
+ }
+
+ return ok();
+}
+
+
+binder::Status InstalldNativeService::clearAppProfiles(const std::string& packageName) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ binder::Status res = ok();
+ if (!clear_primary_reference_profile(packageName)) {
+ res = error("Failed to clear reference profile for " + packageName);
+ }
+ if (!clear_primary_current_profiles(packageName)) {
+ res = error("Failed to clear current profiles for " + packageName);
+ }
+ return res;
+}
+
+binder::Status InstalldNativeService::clearAppData(const std::unique_ptr<std::string>& uuid,
+ const std::string& packageName, int32_t userId, int32_t flags, int64_t ceDataInode) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_UUID(uuid);
+ CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ const char* uuid_ = uuid ? uuid->c_str() : nullptr;
+ const char* pkgname = packageName.c_str();
+
+ binder::Status res = ok();
+ if (flags & FLAG_STORAGE_CE) {
+ auto path = create_data_user_ce_package_path(uuid_, userId, pkgname, ceDataInode);
+ if (flags & FLAG_CLEAR_CACHE_ONLY) {
+ path = read_path_inode(path, "cache", kXattrInodeCache);
+ } else if (flags & FLAG_CLEAR_CODE_CACHE_ONLY) {
+ path = read_path_inode(path, "code_cache", kXattrInodeCodeCache);
+ }
+ if (access(path.c_str(), F_OK) == 0) {
+ if (delete_dir_contents(path) != 0) {
+ res = error("Failed to delete contents of " + path);
+ }
+ }
+ }
+ if (flags & FLAG_STORAGE_DE) {
+ std::string suffix = "";
+ bool only_cache = false;
+ if (flags & FLAG_CLEAR_CACHE_ONLY) {
+ suffix = CACHE_DIR_POSTFIX;
+ only_cache = true;
+ } else if (flags & FLAG_CLEAR_CODE_CACHE_ONLY) {
+ suffix = CODE_CACHE_DIR_POSTFIX;
+ only_cache = true;
+ }
+
+ auto path = create_data_user_de_package_path(uuid_, userId, pkgname) + suffix;
+ if (access(path.c_str(), F_OK) == 0) {
+ if (delete_dir_contents(path) != 0) {
+ res = error("Failed to delete contents of " + path);
+ }
+ }
+ if (!only_cache) {
+ if (!clear_primary_current_profile(packageName, userId)) {
+ res = error("Failed to clear current profile for " + packageName);
+ }
+ }
+ }
+ return res;
+}
+
+static int destroy_app_reference_profile(const std::string& pkgname) {
+ return delete_dir_contents_and_dir(
+ create_primary_reference_profile_package_dir_path(pkgname),
+ /*ignore_if_missing*/ true);
+}
+
+static int destroy_app_current_profiles(const std::string& pkgname, userid_t userid) {
+ return delete_dir_contents_and_dir(
+ create_primary_current_profile_package_dir_path(userid, pkgname),
+ /*ignore_if_missing*/ true);
+}
+
+binder::Status InstalldNativeService::destroyAppProfiles(const std::string& packageName) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ binder::Status res = ok();
+ std::vector<userid_t> users = get_known_users(/*volume_uuid*/ nullptr);
+ for (auto user : users) {
+ if (destroy_app_current_profiles(packageName, user) != 0) {
+ res = error("Failed to destroy current profiles for " + packageName);
+ }
+ }
+ if (destroy_app_reference_profile(packageName) != 0) {
+ res = error("Failed to destroy reference profile for " + packageName);
+ }
+ return res;
+}
+
+binder::Status InstalldNativeService::destroyAppData(const std::unique_ptr<std::string>& uuid,
+ const std::string& packageName, int32_t userId, int32_t flags, int64_t ceDataInode) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_UUID(uuid);
+ CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ const char* uuid_ = uuid ? uuid->c_str() : nullptr;
+ const char* pkgname = packageName.c_str();
+
+ binder::Status res = ok();
+ if (flags & FLAG_STORAGE_CE) {
+ auto path = create_data_user_ce_package_path(uuid_, userId, pkgname, ceDataInode);
+ if (delete_dir_contents_and_dir(path) != 0) {
+ res = error("Failed to delete " + path);
+ }
+ }
+ if (flags & FLAG_STORAGE_DE) {
+ auto path = create_data_user_de_package_path(uuid_, userId, pkgname);
+ if (delete_dir_contents_and_dir(path) != 0) {
+ res = error("Failed to delete " + path);
+ }
+ destroy_app_current_profiles(packageName, userId);
+ // TODO(calin): If the package is still installed by other users it's probably
+ // beneficial to keep the reference profile around.
+ // Verify if it's ok to do that.
+ destroy_app_reference_profile(packageName);
+ }
+ return res;
+}
+
+static gid_t get_cache_gid(uid_t uid) {
+ int32_t gid = multiuser_get_cache_gid(multiuser_get_user_id(uid), multiuser_get_app_id(uid));
+ return (gid != -1) ? gid : uid;
+}
+
+binder::Status InstalldNativeService::fixupAppData(const std::unique_ptr<std::string>& uuid,
+ int32_t flags) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_UUID(uuid);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ const char* uuid_ = uuid ? uuid->c_str() : nullptr;
+ for (auto user : get_known_users(uuid_)) {
+ ATRACE_BEGIN("fixup user");
+ FTS* fts;
+ FTSENT* p;
+ auto ce_path = create_data_user_ce_path(uuid_, user);
+ auto de_path = create_data_user_de_path(uuid_, user);
+ char *argv[] = { (char*) ce_path.c_str(), (char*) de_path.c_str(), nullptr };
+ if (!(fts = fts_open(argv, FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV, NULL))) {
+ return error("Failed to fts_open");
+ }
+ while ((p = fts_read(fts)) != nullptr) {
+ if (p->fts_info == FTS_D && p->fts_level == 1) {
+ // Track down inodes of cache directories
+ uint64_t raw = 0;
+ ino_t inode_cache = 0;
+ ino_t inode_code_cache = 0;
+ if (getxattr(p->fts_path, kXattrInodeCache, &raw, sizeof(raw)) == sizeof(raw)) {
+ inode_cache = raw;
+ }
+ if (getxattr(p->fts_path, kXattrInodeCodeCache, &raw, sizeof(raw)) == sizeof(raw)) {
+ inode_code_cache = raw;
+ }
+
+ // Figure out expected GID of each child
+ FTSENT* child = fts_children(fts, 0);
+ while (child != nullptr) {
+ if ((child->fts_statp->st_ino == inode_cache)
+ || (child->fts_statp->st_ino == inode_code_cache)
+ || !strcmp(child->fts_name, "cache")
+ || !strcmp(child->fts_name, "code_cache")) {
+ child->fts_number = get_cache_gid(p->fts_statp->st_uid);
+ } else {
+ child->fts_number = p->fts_statp->st_uid;
+ }
+ child = child->fts_link;
+ }
+ } else if (p->fts_level >= 2) {
+ if (p->fts_level > 2) {
+ // Inherit GID from parent once we're deeper into tree
+ p->fts_number = p->fts_parent->fts_number;
+ }
+
+ uid_t uid = p->fts_parent->fts_statp->st_uid;
+ gid_t cache_gid = get_cache_gid(uid);
+ gid_t expected = p->fts_number;
+ gid_t actual = p->fts_statp->st_gid;
+ if (actual == expected) {
+#if FIXUP_DEBUG
+ LOG(DEBUG) << "Ignoring " << p->fts_path << " with expected GID " << expected;
+#endif
+ if (!(flags & FLAG_FORCE)) {
+ fts_set(fts, p, FTS_SKIP);
+ }
+ } else if ((actual == uid) || (actual == cache_gid)) {
+ // Only consider fixing up when current GID belongs to app
+ if (p->fts_info != FTS_D) {
+ LOG(INFO) << "Fixing " << p->fts_path << " with unexpected GID " << actual
+ << " instead of " << expected;
+ }
+ switch (p->fts_info) {
+ case FTS_DP:
+ // If we're moving towards cache GID, we need to set S_ISGID
+ if (expected == cache_gid) {
+ if (chmod(p->fts_path, 02771) != 0) {
+ PLOG(WARNING) << "Failed to chmod " << p->fts_path;
+ }
+ }
+ // Intentional fall through to also set GID
+ case FTS_F:
+ if (chown(p->fts_path, -1, expected) != 0) {
+ PLOG(WARNING) << "Failed to chown " << p->fts_path;
+ }
+ break;
+ case FTS_SL:
+ case FTS_SLNONE:
+ if (lchown(p->fts_path, -1, expected) != 0) {
+ PLOG(WARNING) << "Failed to chown " << p->fts_path;
+ }
+ break;
+ }
+ } else {
+ // Ignore all other GID transitions, since they're kinda shady
+ LOG(WARNING) << "Ignoring " << p->fts_path << " with unexpected GID " << actual
+ << " instead of " << expected;
+ }
+ }
+ }
+ fts_close(fts);
+ ATRACE_END();
+ }
+ return ok();
+}
+
+binder::Status InstalldNativeService::moveCompleteApp(const std::unique_ptr<std::string>& fromUuid,
+ const std::unique_ptr<std::string>& toUuid, const std::string& packageName,
+ const std::string& dataAppName, int32_t appId, const std::string& seInfo,
+ int32_t targetSdkVersion) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_UUID(fromUuid);
+ CHECK_ARGUMENT_UUID(toUuid);
+ CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ const char* from_uuid = fromUuid ? fromUuid->c_str() : nullptr;
+ const char* to_uuid = toUuid ? toUuid->c_str() : nullptr;
+ const char* package_name = packageName.c_str();
+ const char* data_app_name = dataAppName.c_str();
+
+ binder::Status res = ok();
+ std::vector<userid_t> users = get_known_users(from_uuid);
+
+ // Copy app
+ {
+ auto from = create_data_app_package_path(from_uuid, data_app_name);
+ auto to = create_data_app_package_path(to_uuid, data_app_name);
+ auto to_parent = create_data_app_path(to_uuid);
+
+ char *argv[] = {
+ (char*) kCpPath,
+ (char*) "-F", /* delete any existing destination file first (--remove-destination) */
+ (char*) "-p", /* preserve timestamps, ownership, and permissions */
+ (char*) "-R", /* recurse into subdirectories (DEST must be a directory) */
+ (char*) "-P", /* Do not follow symlinks [default] */
+ (char*) "-d", /* don't dereference symlinks */
+ (char*) from.c_str(),
+ (char*) to_parent.c_str()
+ };
+
+ LOG(DEBUG) << "Copying " << from << " to " << to;
+ int rc = android_fork_execvp(ARRAY_SIZE(argv), argv, NULL, false, true);
+ if (rc != 0) {
+ res = error(rc, "Failed copying " + from + " to " + to);
+ goto fail;
+ }
+
+ if (selinux_android_restorecon(to.c_str(), SELINUX_ANDROID_RESTORECON_RECURSE) != 0) {
+ res = error("Failed to restorecon " + to);
+ goto fail;
+ }
+ }
+
+ // Copy private data for all known users
+ for (auto user : users) {
+
+ // Data source may not exist for all users; that's okay
+ auto from_ce = create_data_user_ce_package_path(from_uuid, user, package_name);
+ if (access(from_ce.c_str(), F_OK) != 0) {
+ LOG(INFO) << "Missing source " << from_ce;
+ continue;
+ }
+
+ if (!createAppData(toUuid, packageName, user, FLAG_STORAGE_CE | FLAG_STORAGE_DE, appId,
+ seInfo, targetSdkVersion, nullptr).isOk()) {
+ res = error("Failed to create package target");
+ goto fail;
+ }
+
+ char *argv[] = {
+ (char*) kCpPath,
+ (char*) "-F", /* delete any existing destination file first (--remove-destination) */
+ (char*) "-p", /* preserve timestamps, ownership, and permissions */
+ (char*) "-R", /* recurse into subdirectories (DEST must be a directory) */
+ (char*) "-P", /* Do not follow symlinks [default] */
+ (char*) "-d", /* don't dereference symlinks */
+ nullptr,
+ nullptr
+ };
+
+ {
+ auto from = create_data_user_de_package_path(from_uuid, user, package_name);
+ auto to = create_data_user_de_path(to_uuid, user);
+ argv[6] = (char*) from.c_str();
+ argv[7] = (char*) to.c_str();
+
+ LOG(DEBUG) << "Copying " << from << " to " << to;
+ int rc = android_fork_execvp(ARRAY_SIZE(argv), argv, NULL, false, true);
+ if (rc != 0) {
+ res = error(rc, "Failed copying " + from + " to " + to);
+ goto fail;
+ }
+ }
+ {
+ auto from = create_data_user_ce_package_path(from_uuid, user, package_name);
+ auto to = create_data_user_ce_path(to_uuid, user);
+ argv[6] = (char*) from.c_str();
+ argv[7] = (char*) to.c_str();
+
+ LOG(DEBUG) << "Copying " << from << " to " << to;
+ int rc = android_fork_execvp(ARRAY_SIZE(argv), argv, NULL, false, true);
+ if (rc != 0) {
+ res = error(rc, "Failed copying " + from + " to " + to);
+ goto fail;
+ }
+ }
+
+ if (!restoreconAppData(toUuid, packageName, user, FLAG_STORAGE_CE | FLAG_STORAGE_DE,
+ appId, seInfo).isOk()) {
+ res = error("Failed to restorecon");
+ goto fail;
+ }
+ }
+
+ // We let the framework scan the new location and persist that before
+ // deleting the data in the old location; this ordering ensures that
+ // we can recover from things like battery pulls.
+ return ok();
+
+fail:
+ // Nuke everything we might have already copied
+ {
+ auto to = create_data_app_package_path(to_uuid, data_app_name);
+ if (delete_dir_contents(to.c_str(), 1, NULL) != 0) {
+ LOG(WARNING) << "Failed to rollback " << to;
+ }
+ }
+ for (auto user : users) {
+ {
+ auto to = create_data_user_de_package_path(to_uuid, user, package_name);
+ if (delete_dir_contents(to.c_str(), 1, NULL) != 0) {
+ LOG(WARNING) << "Failed to rollback " << to;
+ }
+ }
+ {
+ auto to = create_data_user_ce_package_path(to_uuid, user, package_name);
+ if (delete_dir_contents(to.c_str(), 1, NULL) != 0) {
+ LOG(WARNING) << "Failed to rollback " << to;
+ }
+ }
+ }
+ return res;
+}
+
+binder::Status InstalldNativeService::createUserData(const std::unique_ptr<std::string>& uuid,
+ int32_t userId, int32_t userSerial ATTRIBUTE_UNUSED, int32_t flags) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_UUID(uuid);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ const char* uuid_ = uuid ? uuid->c_str() : nullptr;
+ if (flags & FLAG_STORAGE_DE) {
+ if (uuid_ == nullptr) {
+ if (ensure_config_user_dirs(userId) != 0) {
+ return error(StringPrintf("Failed to ensure dirs for %d", userId));
+ }
+ }
+ }
+
+ // Data under /data/media doesn't have an app, but we still want
+ // to limit it to prevent abuse.
+ if (prepare_app_quota(uuid, findQuotaDeviceForUuid(uuid),
+ multiuser_get_uid(userId, AID_MEDIA_RW))) {
+ return error("Failed to set hard quota for media_rw");
+ }
+
+ return ok();
+}
+
+binder::Status InstalldNativeService::destroyUserData(const std::unique_ptr<std::string>& uuid,
+ int32_t userId, int32_t flags) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_UUID(uuid);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ const char* uuid_ = uuid ? uuid->c_str() : nullptr;
+ binder::Status res = ok();
+ if (flags & FLAG_STORAGE_DE) {
+ auto path = create_data_user_de_path(uuid_, userId);
+ if (delete_dir_contents_and_dir(path, true) != 0) {
+ res = error("Failed to delete " + path);
+ }
+ if (uuid_ == nullptr) {
+ path = create_data_misc_legacy_path(userId);
+ if (delete_dir_contents_and_dir(path, true) != 0) {
+ res = error("Failed to delete " + path);
+ }
+ path = create_primary_cur_profile_dir_path(userId);
+ if (delete_dir_contents_and_dir(path, true) != 0) {
+ res = error("Failed to delete " + path);
+ }
+ }
+ }
+ if (flags & FLAG_STORAGE_CE) {
+ auto path = create_data_user_ce_path(uuid_, userId);
+ if (delete_dir_contents_and_dir(path, true) != 0) {
+ res = error("Failed to delete " + path);
+ }
+ path = findDataMediaPath(uuid, userId);
+ if (delete_dir_contents_and_dir(path, true) != 0) {
+ res = error("Failed to delete " + path);
+ }
+ }
+ return res;
+}
+
+binder::Status InstalldNativeService::freeCache(const std::unique_ptr<std::string>& uuid,
+ int64_t targetFreeBytes, int64_t cacheReservedBytes, int32_t flags) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_UUID(uuid);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ const char* uuid_ = uuid ? uuid->c_str() : nullptr;
+ auto data_path = create_data_path(uuid_);
+ auto device = findQuotaDeviceForUuid(uuid);
+ auto noop = (flags & FLAG_FREE_CACHE_NOOP);
+
+ int64_t free = data_disk_free(data_path);
+ if (free < 0) {
+ return error("Failed to determine free space for " + data_path);
+ }
+
+ int64_t cleared = 0;
+ int64_t needed = targetFreeBytes - free;
+ LOG(DEBUG) << "Device " << data_path << " has " << free << " free; requested "
+ << targetFreeBytes << "; needed " << needed;
+
+ if (free >= targetFreeBytes) {
+ return ok();
+ }
+
+ if (flags & FLAG_FREE_CACHE_V2) {
+ // This new cache strategy fairly removes files from UIDs by deleting
+ // files from the UIDs which are most over their allocated quota
+
+ // 1. Create trackers for every known UID
+ ATRACE_BEGIN("create");
+ std::unordered_map<uid_t, std::shared_ptr<CacheTracker>> trackers;
+ for (auto user : get_known_users(uuid_)) {
+ FTS *fts;
+ FTSENT *p;
+ auto ce_path = create_data_user_ce_path(uuid_, user);
+ auto de_path = create_data_user_de_path(uuid_, user);
+ auto media_path = findDataMediaPath(uuid, user) + "/Android/data/";
+ char *argv[] = { (char*) ce_path.c_str(), (char*) de_path.c_str(),
+ (char*) media_path.c_str(), nullptr };
+ if (!(fts = fts_open(argv, FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV, NULL))) {
+ return error("Failed to fts_open");
+ }
+ while ((p = fts_read(fts)) != NULL) {
+ if (p->fts_info == FTS_D && p->fts_level == 1) {
+ uid_t uid = p->fts_statp->st_uid;
+ if (multiuser_get_app_id(uid) == AID_MEDIA_RW) {
+ uid = (multiuser_get_app_id(p->fts_statp->st_gid) - AID_EXT_GID_START)
+ + AID_APP_START;
+ }
+ auto search = trackers.find(uid);
+ if (search != trackers.end()) {
+ search->second->addDataPath(p->fts_path);
+ } else {
+ auto tracker = std::shared_ptr<CacheTracker>(new CacheTracker(
+ multiuser_get_user_id(uid), multiuser_get_app_id(uid), device));
+ tracker->addDataPath(p->fts_path);
+ {
+ std::lock_guard<std::recursive_mutex> lock(mQuotasLock);
+ tracker->cacheQuota = mCacheQuotas[uid];
+ }
+ if (tracker->cacheQuota == 0) {
+#if MEASURE_DEBUG
+ LOG(WARNING) << "UID " << uid << " has no cache quota; assuming 64MB";
+#endif
+ tracker->cacheQuota = 67108864;
+ }
+ trackers[uid] = tracker;
+ }
+ fts_set(fts, p, FTS_SKIP);
+ }
+ }
+ fts_close(fts);
+ }
+ ATRACE_END();
+
+ // 2. Populate tracker stats and insert into priority queue
+ ATRACE_BEGIN("populate");
+ int64_t cacheTotal = 0;
+ auto cmp = [](std::shared_ptr<CacheTracker> left, std::shared_ptr<CacheTracker> right) {
+ return (left->getCacheRatio() < right->getCacheRatio());
+ };
+ std::priority_queue<std::shared_ptr<CacheTracker>,
+ std::vector<std::shared_ptr<CacheTracker>>, decltype(cmp)> queue(cmp);
+ for (const auto& it : trackers) {
+ it.second->loadStats();
+ queue.push(it.second);
+ cacheTotal += it.second->cacheUsed;
+ }
+ ATRACE_END();
+
+ // 3. Bounce across the queue, freeing items from whichever tracker is
+ // the most over their assigned quota
+ ATRACE_BEGIN("bounce");
+ std::shared_ptr<CacheTracker> active;
+ while (active || !queue.empty()) {
+ // Only look at apps under quota when explicitly requested
+ if (active && (active->getCacheRatio() < 10000)
+ && !(flags & FLAG_FREE_CACHE_V2_DEFY_QUOTA)) {
+ LOG(DEBUG) << "Active ratio " << active->getCacheRatio()
+ << " isn't over quota, and defy not requested";
+ break;
+ }
+
+ // Only keep clearing when we haven't pushed into reserved area
+ if (cacheReservedBytes > 0 && cleared >= (cacheTotal - cacheReservedBytes)) {
+ LOG(DEBUG) << "Refusing to clear cached data in reserved space";
+ break;
+ }
+
+ // Find the best tracker to work with; this might involve swapping
+ // if the active tracker is no longer the most over quota
+ bool nextBetter = active && !queue.empty()
+ && active->getCacheRatio() < queue.top()->getCacheRatio();
+ if (!active || nextBetter) {
+ if (active) {
+ // Current tracker still has items, so we'll consider it
+ // again later once it bubbles up to surface
+ queue.push(active);
+ }
+ active = queue.top(); queue.pop();
+ active->ensureItems();
+ continue;
+ }
+
+ // If no items remain, go find another tracker
+ if (active->items.empty()) {
+ active = nullptr;
+ continue;
+ } else {
+ auto item = active->items.back();
+ active->items.pop_back();
+
+ LOG(DEBUG) << "Purging " << item->toString() << " from " << active->toString();
+ if (!noop) {
+ item->purge();
+ }
+ active->cacheUsed -= item->size;
+ needed -= item->size;
+ cleared += item->size;
+ }
+
+ // Verify that we're actually done before bailing, since sneaky
+ // apps might be using hardlinks
+ if (needed <= 0) {
+ free = data_disk_free(data_path);
+ needed = targetFreeBytes - free;
+ if (needed <= 0) {
+ break;
+ } else {
+ LOG(WARNING) << "Expected to be done but still need " << needed;
+ }
+ }
+ }
+ ATRACE_END();
+
+ } else {
+ return error("Legacy cache logic no longer supported");
+ }
+
+ free = data_disk_free(data_path);
+ if (free >= targetFreeBytes) {
+ return ok();
+ } else {
+ return error(StringPrintf("Failed to free up %" PRId64 " on %s; final free space %" PRId64,
+ targetFreeBytes, data_path.c_str(), free));
+ }
+}
+
+binder::Status InstalldNativeService::rmdex(const std::string& codePath,
+ const std::string& instructionSet) {
+ ENFORCE_UID(AID_SYSTEM);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ char dex_path[PKG_PATH_MAX];
+
+ const char* path = codePath.c_str();
+ const char* instruction_set = instructionSet.c_str();
+
+ if (validate_apk_path(path) && validate_system_app_path(path)) {
+ return error("Invalid path " + codePath);
+ }
+
+ if (!create_cache_path(dex_path, path, instruction_set)) {
+ return error("Failed to create cache path for " + codePath);
+ }
+
+ ALOGV("unlink %s\n", dex_path);
+ if (unlink(dex_path) < 0) {
+ // It's ok if we don't have a dalvik cache path. Report error only when the path exists
+ // but could not be unlinked.
+ if (errno != ENOENT) {
+ return error(StringPrintf("Failed to unlink %s", dex_path));
+ }
+ }
+ return ok();
+}
+
+struct stats {
+ int64_t codeSize;
+ int64_t dataSize;
+ int64_t cacheSize;
+};
+
+#if MEASURE_DEBUG
+static std::string toString(std::vector<int64_t> values) {
+ std::stringstream res;
+ res << "[";
+ for (size_t i = 0; i < values.size(); i++) {
+ res << values[i];
+ if (i < values.size() - 1) {
+ res << ",";
+ }
+ }
+ res << "]";
+ return res.str();
+}
+#endif
+
+static void collectQuotaStats(const std::string& device, int32_t userId,
+ int32_t appId, struct stats* stats, struct stats* extStats) {
+ if (device.empty()) return;
+
+ struct dqblk dq;
+
+ if (stats != nullptr) {
+ uid_t uid = multiuser_get_uid(userId, appId);
+ if (quotactl(QCMD(Q_GETQUOTA, USRQUOTA), device.c_str(), uid,
+ reinterpret_cast<char*>(&dq)) != 0) {
+ if (errno != ESRCH) {
+ PLOG(ERROR) << "Failed to quotactl " << device << " for UID " << uid;
+ }
+ } else {
+#if MEASURE_DEBUG
+ LOG(DEBUG) << "quotactl() for UID " << uid << " " << dq.dqb_curspace;
+#endif
+ stats->dataSize += dq.dqb_curspace;
+ }
+
+ int cacheGid = multiuser_get_cache_gid(userId, appId);
+ if (cacheGid != -1) {
+ if (quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), device.c_str(), cacheGid,
+ reinterpret_cast<char*>(&dq)) != 0) {
+ if (errno != ESRCH) {
+ PLOG(ERROR) << "Failed to quotactl " << device << " for GID " << cacheGid;
+ }
+ } else {
+#if MEASURE_DEBUG
+ LOG(DEBUG) << "quotactl() for GID " << cacheGid << " " << dq.dqb_curspace;
+#endif
+ stats->cacheSize += dq.dqb_curspace;
+ }
+ }
+
+ int sharedGid = multiuser_get_shared_gid(0, appId);
+ if (sharedGid != -1) {
+ if (quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), device.c_str(), sharedGid,
+ reinterpret_cast<char*>(&dq)) != 0) {
+ if (errno != ESRCH) {
+ PLOG(ERROR) << "Failed to quotactl " << device << " for GID " << sharedGid;
+ }
+ } else {
+#if MEASURE_DEBUG
+ LOG(DEBUG) << "quotactl() for GID " << sharedGid << " " << dq.dqb_curspace;
+#endif
+ stats->codeSize += dq.dqb_curspace;
+ }
+ }
+ }
+
+ if (extStats != nullptr) {
+ int extGid = multiuser_get_ext_gid(userId, appId);
+ if (extGid != -1) {
+ if (quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), device.c_str(), extGid,
+ reinterpret_cast<char*>(&dq)) != 0) {
+ if (errno != ESRCH) {
+ PLOG(ERROR) << "Failed to quotactl " << device << " for GID " << extGid;
+ }
+ } else {
+#if MEASURE_DEBUG
+ LOG(DEBUG) << "quotactl() for GID " << extGid << " " << dq.dqb_curspace;
+#endif
+ extStats->dataSize += dq.dqb_curspace;
+ }
+ }
+
+ int extCacheGid = multiuser_get_ext_cache_gid(userId, appId);
+ if (extCacheGid != -1) {
+ if (quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), device.c_str(), extCacheGid,
+ reinterpret_cast<char*>(&dq)) != 0) {
+ if (errno != ESRCH) {
+ PLOG(ERROR) << "Failed to quotactl " << device << " for GID " << extCacheGid;
+ }
+ } else {
+#if MEASURE_DEBUG
+ LOG(DEBUG) << "quotactl() for GID " << extCacheGid << " " << dq.dqb_curspace;
+#endif
+ extStats->dataSize += dq.dqb_curspace;
+ extStats->cacheSize += dq.dqb_curspace;
+ }
+ }
+ }
+}
+
+static void collectManualStats(const std::string& path, struct stats* stats) {
+ DIR *d;
+ int dfd;
+ struct dirent *de;
+ struct stat s;
+
+ d = opendir(path.c_str());
+ if (d == nullptr) {
+ if (errno != ENOENT) {
+ PLOG(WARNING) << "Failed to open " << path;
+ }
+ return;
+ }
+ dfd = dirfd(d);
+ while ((de = readdir(d))) {
+ const char *name = de->d_name;
+
+ int64_t size = 0;
+ if (fstatat(dfd, name, &s, AT_SYMLINK_NOFOLLOW) == 0) {
+ size = s.st_blocks * 512;
+ }
+
+ if (de->d_type == DT_DIR) {
+ if (!strcmp(name, ".")) {
+ // Don't recurse, but still count node size
+ } else if (!strcmp(name, "..")) {
+ // Don't recurse or count node size
+ continue;
+ } else {
+ // Measure all children nodes
+ size = 0;
+ calculate_tree_size(StringPrintf("%s/%s", path.c_str(), name), &size);
+ }
+
+ if (!strcmp(name, "cache") || !strcmp(name, "code_cache")) {
+ stats->cacheSize += size;
+ }
+ }
+
+ // Legacy symlink isn't owned by app
+ if (de->d_type == DT_LNK && !strcmp(name, "lib")) {
+ continue;
+ }
+
+ // Everything found inside is considered data
+ stats->dataSize += size;
+ }
+ closedir(d);
+}
+
+static void collectManualStatsForUser(const std::string& path, struct stats* stats,
+ bool exclude_apps = false) {
+ DIR *d;
+ int dfd;
+ struct dirent *de;
+ struct stat s;
+
+ d = opendir(path.c_str());
+ if (d == nullptr) {
+ if (errno != ENOENT) {
+ PLOG(WARNING) << "Failed to open " << path;
+ }
+ return;
+ }
+ dfd = dirfd(d);
+ while ((de = readdir(d))) {
+ if (de->d_type == DT_DIR) {
+ const char *name = de->d_name;
+ if (fstatat(dfd, name, &s, AT_SYMLINK_NOFOLLOW) != 0) {
+ continue;
+ }
+ int32_t user_uid = multiuser_get_app_id(s.st_uid);
+ if (!strcmp(name, ".") || !strcmp(name, "..")) {
+ continue;
+ } else if (exclude_apps && (user_uid >= AID_APP_START && user_uid <= AID_APP_END)) {
+ continue;
+ } else {
+ collectManualStats(StringPrintf("%s/%s", path.c_str(), name), stats);
+ }
+ }
+ }
+ closedir(d);
+}
+
+static void collectManualExternalStatsForUser(const std::string& path, struct stats* stats) {
+ FTS *fts;
+ FTSENT *p;
+ char *argv[] = { (char*) path.c_str(), nullptr };
+ if (!(fts = fts_open(argv, FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV, NULL))) {
+ PLOG(ERROR) << "Failed to fts_open " << path;
+ return;
+ }
+ while ((p = fts_read(fts)) != NULL) {
+ p->fts_number = p->fts_parent->fts_number;
+ switch (p->fts_info) {
+ case FTS_D:
+ if (p->fts_level == 4
+ && !strcmp(p->fts_name, "cache")
+ && !strcmp(p->fts_parent->fts_parent->fts_name, "data")
+ && !strcmp(p->fts_parent->fts_parent->fts_parent->fts_name, "Android")) {
+ p->fts_number = 1;
+ }
+ // Fall through to count the directory
+ case FTS_DEFAULT:
+ case FTS_F:
+ case FTS_SL:
+ case FTS_SLNONE:
+ int64_t size = (p->fts_statp->st_blocks * 512);
+ if (p->fts_number == 1) {
+ stats->cacheSize += size;
+ }
+ stats->dataSize += size;
+ break;
+ }
+ }
+ fts_close(fts);
+}
+
+binder::Status InstalldNativeService::getAppSize(const std::unique_ptr<std::string>& uuid,
+ const std::vector<std::string>& packageNames, int32_t userId, int32_t flags,
+ int32_t appId, const std::vector<int64_t>& ceDataInodes,
+ const std::vector<std::string>& codePaths, std::vector<int64_t>* _aidl_return) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_UUID(uuid);
+ for (auto packageName : packageNames) {
+ CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+ }
+ // NOTE: Locking is relaxed on this method, since it's limited to
+ // read-only measurements without mutation.
+
+ // When modifying this logic, always verify using tests:
+ // runtest -x frameworks/base/services/tests/servicestests/src/com/android/server/pm/InstallerTest.java -m testGetAppSize
+
+#if MEASURE_DEBUG
+ LOG(INFO) << "Measuring user " << userId << " app " << appId;
+#endif
+
+ // Here's a summary of the common storage locations across the platform,
+ // and how they're each tagged:
+ //
+ // /data/app/com.example UID system
+ // /data/app/com.example/oat UID system
+ // /data/user/0/com.example UID u0_a10 GID u0_a10
+ // /data/user/0/com.example/cache UID u0_a10 GID u0_a10_cache
+ // /data/media/0/foo.txt UID u0_media_rw
+ // /data/media/0/bar.jpg UID u0_media_rw GID u0_media_image
+ // /data/media/0/Android/data/com.example UID u0_media_rw GID u0_a10_ext
+ // /data/media/0/Android/data/com.example/cache UID u0_media_rw GID u0_a10_ext_cache
+ // /data/media/obb/com.example UID system
+
+ struct stats stats;
+ struct stats extStats;
+ memset(&stats, 0, sizeof(stats));
+ memset(&extStats, 0, sizeof(extStats));
+
+ const char* uuid_ = uuid ? uuid->c_str() : nullptr;
+
+ auto device = findQuotaDeviceForUuid(uuid);
+ if (device.empty()) {
+ flags &= ~FLAG_USE_QUOTA;
+ }
+
+ ATRACE_BEGIN("obb");
+ for (auto packageName : packageNames) {
+ auto obbCodePath = create_data_media_obb_path(uuid_, packageName.c_str());
+ calculate_tree_size(obbCodePath, &extStats.codeSize);
+ }
+ ATRACE_END();
+
+ if (flags & FLAG_USE_QUOTA && appId >= AID_APP_START) {
+ ATRACE_BEGIN("code");
+ for (auto codePath : codePaths) {
+ calculate_tree_size(codePath, &stats.codeSize, -1,
+ multiuser_get_shared_gid(0, appId));
+ }
+ ATRACE_END();
+
+ ATRACE_BEGIN("quota");
+ collectQuotaStats(device, userId, appId, &stats, &extStats);
+ ATRACE_END();
+ } else {
+ ATRACE_BEGIN("code");
+ for (auto codePath : codePaths) {
+ calculate_tree_size(codePath, &stats.codeSize);
+ }
+ ATRACE_END();
+
+ for (size_t i = 0; i < packageNames.size(); i++) {
+ const char* pkgname = packageNames[i].c_str();
+
+ ATRACE_BEGIN("data");
+ auto cePath = create_data_user_ce_package_path(uuid_, userId, pkgname, ceDataInodes[i]);
+ collectManualStats(cePath, &stats);
+ auto dePath = create_data_user_de_package_path(uuid_, userId, pkgname);
+ collectManualStats(dePath, &stats);
+ ATRACE_END();
+
+ if (!uuid) {
+ ATRACE_BEGIN("profiles");
+ calculate_tree_size(
+ create_primary_current_profile_package_dir_path(userId, pkgname),
+ &stats.dataSize);
+ calculate_tree_size(
+ create_primary_reference_profile_package_dir_path(pkgname),
+ &stats.codeSize);
+ ATRACE_END();
+ }
+
+ ATRACE_BEGIN("external");
+ auto extPath = create_data_media_package_path(uuid_, userId, "data", pkgname);
+ collectManualStats(extPath, &extStats);
+ auto mediaPath = create_data_media_package_path(uuid_, userId, "media", pkgname);
+ calculate_tree_size(mediaPath, &extStats.dataSize);
+ ATRACE_END();
+ }
+
+ if (!uuid) {
+ ATRACE_BEGIN("dalvik");
+ int32_t sharedGid = multiuser_get_shared_gid(0, appId);
+ if (sharedGid != -1) {
+ calculate_tree_size(create_data_dalvik_cache_path(), &stats.codeSize,
+ sharedGid, -1);
+ }
+ ATRACE_END();
+ }
+ }
+
+ std::vector<int64_t> ret;
+ ret.push_back(stats.codeSize);
+ ret.push_back(stats.dataSize);
+ ret.push_back(stats.cacheSize);
+ ret.push_back(extStats.codeSize);
+ ret.push_back(extStats.dataSize);
+ ret.push_back(extStats.cacheSize);
+#if MEASURE_DEBUG
+ LOG(DEBUG) << "Final result " << toString(ret);
+#endif
+ *_aidl_return = ret;
+ return ok();
+}
+
+binder::Status InstalldNativeService::getUserSize(const std::unique_ptr<std::string>& uuid,
+ int32_t userId, int32_t flags, const std::vector<int32_t>& appIds,
+ std::vector<int64_t>* _aidl_return) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_UUID(uuid);
+ // NOTE: Locking is relaxed on this method, since it's limited to
+ // read-only measurements without mutation.
+
+ // When modifying this logic, always verify using tests:
+ // runtest -x frameworks/base/services/tests/servicestests/src/com/android/server/pm/InstallerTest.java -m testGetUserSize
+
+#if MEASURE_DEBUG
+ LOG(INFO) << "Measuring user " << userId;
+#endif
+
+ struct stats stats;
+ struct stats extStats;
+ memset(&stats, 0, sizeof(stats));
+ memset(&extStats, 0, sizeof(extStats));
+
+ const char* uuid_ = uuid ? uuid->c_str() : nullptr;
+
+ auto device = findQuotaDeviceForUuid(uuid);
+ if (device.empty()) {
+ flags &= ~FLAG_USE_QUOTA;
+ }
+
+ if (flags & FLAG_USE_QUOTA) {
+ struct dqblk dq;
+
+ ATRACE_BEGIN("obb");
+ if (quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), device.c_str(), AID_MEDIA_OBB,
+ reinterpret_cast<char*>(&dq)) != 0) {
+ if (errno != ESRCH) {
+ PLOG(ERROR) << "Failed to quotactl " << device << " for GID " << AID_MEDIA_OBB;
+ }
+ } else {
+#if MEASURE_DEBUG
+ LOG(DEBUG) << "quotactl() for GID " << AID_MEDIA_OBB << " " << dq.dqb_curspace;
+#endif
+ extStats.codeSize += dq.dqb_curspace;
+ }
+ ATRACE_END();
+
+ ATRACE_BEGIN("code");
+ calculate_tree_size(create_data_app_path(uuid_), &stats.codeSize, -1, -1, true);
+ ATRACE_END();
+
+ ATRACE_BEGIN("data");
+ auto cePath = create_data_user_ce_path(uuid_, userId);
+ collectManualStatsForUser(cePath, &stats, true);
+ auto dePath = create_data_user_de_path(uuid_, userId);
+ collectManualStatsForUser(dePath, &stats, true);
+ ATRACE_END();
+
+ if (!uuid) {
+ ATRACE_BEGIN("profile");
+ auto userProfilePath = create_primary_cur_profile_dir_path(userId);
+ calculate_tree_size(userProfilePath, &stats.dataSize, -1, -1, true);
+ auto refProfilePath = create_primary_ref_profile_dir_path();
+ calculate_tree_size(refProfilePath, &stats.codeSize, -1, -1, true);
+ ATRACE_END();
+ }
+
+ ATRACE_BEGIN("external");
+ uid_t uid = multiuser_get_uid(userId, AID_MEDIA_RW);
+ if (quotactl(QCMD(Q_GETQUOTA, USRQUOTA), device.c_str(), uid,
+ reinterpret_cast<char*>(&dq)) != 0) {
+ if (errno != ESRCH) {
+ PLOG(ERROR) << "Failed to quotactl " << device << " for UID " << uid;
+ }
+ } else {
+#if MEASURE_DEBUG
+ LOG(DEBUG) << "quotactl() for UID " << uid << " " << dq.dqb_curspace;
+#endif
+ extStats.dataSize += dq.dqb_curspace;
+ }
+ ATRACE_END();
+
+ if (!uuid) {
+ ATRACE_BEGIN("dalvik");
+ calculate_tree_size(create_data_dalvik_cache_path(), &stats.codeSize,
+ -1, -1, true);
+ calculate_tree_size(create_primary_cur_profile_dir_path(userId), &stats.dataSize,
+ -1, -1, true);
+ ATRACE_END();
+ }
+
+ ATRACE_BEGIN("quota");
+ int64_t dataSize = extStats.dataSize;
+ for (auto appId : appIds) {
+ if (appId >= AID_APP_START) {
+ collectQuotaStats(device, userId, appId, &stats, &extStats);
+
+#if MEASURE_DEBUG
+ // Sleep to make sure we don't lose logs
+ usleep(1);
+#endif
+ }
+ }
+ extStats.dataSize = dataSize;
+ ATRACE_END();
+ } else {
+ ATRACE_BEGIN("obb");
+ auto obbPath = create_data_path(uuid_) + "/media/obb";
+ calculate_tree_size(obbPath, &extStats.codeSize);
+ ATRACE_END();
+
+ ATRACE_BEGIN("code");
+ calculate_tree_size(create_data_app_path(uuid_), &stats.codeSize);
+ ATRACE_END();
+
+ ATRACE_BEGIN("data");
+ auto cePath = create_data_user_ce_path(uuid_, userId);
+ collectManualStatsForUser(cePath, &stats);
+ auto dePath = create_data_user_de_path(uuid_, userId);
+ collectManualStatsForUser(dePath, &stats);
+ ATRACE_END();
+
+ if (!uuid) {
+ ATRACE_BEGIN("profile");
+ auto userProfilePath = create_primary_cur_profile_dir_path(userId);
+ calculate_tree_size(userProfilePath, &stats.dataSize);
+ auto refProfilePath = create_primary_ref_profile_dir_path();
+ calculate_tree_size(refProfilePath, &stats.codeSize);
+ ATRACE_END();
+ }
+
+ ATRACE_BEGIN("external");
+ auto dataMediaPath = create_data_media_path(uuid_, userId);
+ collectManualExternalStatsForUser(dataMediaPath, &extStats);
+#if MEASURE_DEBUG
+ LOG(DEBUG) << "Measured external data " << extStats.dataSize << " cache "
+ << extStats.cacheSize;
+#endif
+ ATRACE_END();
+
+ if (!uuid) {
+ ATRACE_BEGIN("dalvik");
+ calculate_tree_size(create_data_dalvik_cache_path(), &stats.codeSize);
+ calculate_tree_size(create_primary_cur_profile_dir_path(userId), &stats.dataSize);
+ ATRACE_END();
+ }
+ }
+
+ std::vector<int64_t> ret;
+ ret.push_back(stats.codeSize);
+ ret.push_back(stats.dataSize);
+ ret.push_back(stats.cacheSize);
+ ret.push_back(extStats.codeSize);
+ ret.push_back(extStats.dataSize);
+ ret.push_back(extStats.cacheSize);
+#if MEASURE_DEBUG
+ LOG(DEBUG) << "Final result " << toString(ret);
+#endif
+ *_aidl_return = ret;
+ return ok();
+}
+
+binder::Status InstalldNativeService::getExternalSize(const std::unique_ptr<std::string>& uuid,
+ int32_t userId, int32_t flags, const std::vector<int32_t>& appIds,
+ std::vector<int64_t>* _aidl_return) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_UUID(uuid);
+ // NOTE: Locking is relaxed on this method, since it's limited to
+ // read-only measurements without mutation.
+
+ // When modifying this logic, always verify using tests:
+ // runtest -x frameworks/base/services/tests/servicestests/src/com/android/server/pm/InstallerTest.java -m testGetExternalSize
+
+#if MEASURE_DEBUG
+ LOG(INFO) << "Measuring external " << userId;
+#endif
+
+ const char* uuid_ = uuid ? uuid->c_str() : nullptr;
+
+ int64_t totalSize = 0;
+ int64_t audioSize = 0;
+ int64_t videoSize = 0;
+ int64_t imageSize = 0;
+ int64_t appSize = 0;
+
+ auto device = findQuotaDeviceForUuid(uuid);
+ if (device.empty()) {
+ flags &= ~FLAG_USE_QUOTA;
+ }
+
+ if (flags & FLAG_USE_QUOTA) {
+ struct dqblk dq;
+
+ ATRACE_BEGIN("quota");
+ uid_t uid = multiuser_get_uid(userId, AID_MEDIA_RW);
+ if (quotactl(QCMD(Q_GETQUOTA, USRQUOTA), device.c_str(), uid,
+ reinterpret_cast<char*>(&dq)) != 0) {
+ if (errno != ESRCH) {
+ PLOG(ERROR) << "Failed to quotactl " << device << " for UID " << uid;
+ }
+ } else {
+#if MEASURE_DEBUG
+ LOG(DEBUG) << "quotactl() for UID " << uid << " " << dq.dqb_curspace;
+#endif
+ totalSize = dq.dqb_curspace;
+ }
+
+ gid_t audioGid = multiuser_get_uid(userId, AID_MEDIA_AUDIO);
+ if (quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), device.c_str(), audioGid,
+ reinterpret_cast<char*>(&dq)) == 0) {
+#if MEASURE_DEBUG
+ LOG(DEBUG) << "quotactl() for GID " << audioGid << " " << dq.dqb_curspace;
+#endif
+ audioSize = dq.dqb_curspace;
+ }
+ gid_t videoGid = multiuser_get_uid(userId, AID_MEDIA_VIDEO);
+ if (quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), device.c_str(), videoGid,
+ reinterpret_cast<char*>(&dq)) == 0) {
+#if MEASURE_DEBUG
+ LOG(DEBUG) << "quotactl() for GID " << videoGid << " " << dq.dqb_curspace;
+#endif
+ videoSize = dq.dqb_curspace;
+ }
+ gid_t imageGid = multiuser_get_uid(userId, AID_MEDIA_IMAGE);
+ if (quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), device.c_str(), imageGid,
+ reinterpret_cast<char*>(&dq)) == 0) {
+#if MEASURE_DEBUG
+ LOG(DEBUG) << "quotactl() for GID " << imageGid << " " << dq.dqb_curspace;
+#endif
+ imageSize = dq.dqb_curspace;
+ }
+ ATRACE_END();
+
+ ATRACE_BEGIN("apps");
+ struct stats extStats;
+ memset(&extStats, 0, sizeof(extStats));
+ for (auto appId : appIds) {
+ if (appId >= AID_APP_START) {
+ collectQuotaStats(device, userId, appId, nullptr, &extStats);
+ }
+ }
+ appSize = extStats.dataSize;
+ ATRACE_END();
+ } else {
+ ATRACE_BEGIN("manual");
+ FTS *fts;
+ FTSENT *p;
+ auto path = create_data_media_path(uuid_, userId);
+ char *argv[] = { (char*) path.c_str(), nullptr };
+ if (!(fts = fts_open(argv, FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV, NULL))) {
+ return error("Failed to fts_open " + path);
+ }
+ while ((p = fts_read(fts)) != NULL) {
+ char* ext;
+ int64_t size = (p->fts_statp->st_blocks * 512);
+ switch (p->fts_info) {
+ case FTS_F:
+ // Only categorize files not belonging to apps
+ if (p->fts_parent->fts_number == 0) {
+ ext = strrchr(p->fts_name, '.');
+ if (ext != nullptr) {
+ switch (MatchExtension(++ext)) {
+ case AID_MEDIA_AUDIO: audioSize += size; break;
+ case AID_MEDIA_VIDEO: videoSize += size; break;
+ case AID_MEDIA_IMAGE: imageSize += size; break;
+ }
+ }
+ }
+ // Fall through to always count against total
+ case FTS_D:
+ // Ignore data belonging to specific apps
+ p->fts_number = p->fts_parent->fts_number;
+ if (p->fts_level == 1 && !strcmp(p->fts_name, "Android")) {
+ p->fts_number = 1;
+ }
+ case FTS_DEFAULT:
+ case FTS_SL:
+ case FTS_SLNONE:
+ if (p->fts_parent->fts_number == 1) {
+ appSize += size;
+ }
+ totalSize += size;
+ break;
+ }
+ }
+ fts_close(fts);
+ ATRACE_END();
+ }
+
+ std::vector<int64_t> ret;
+ ret.push_back(totalSize);
+ ret.push_back(audioSize);
+ ret.push_back(videoSize);
+ ret.push_back(imageSize);
+ ret.push_back(appSize);
+#if MEASURE_DEBUG
+ LOG(DEBUG) << "Final result " << toString(ret);
+#endif
+ *_aidl_return = ret;
+ return ok();
+}
+
+binder::Status InstalldNativeService::setAppQuota(const std::unique_ptr<std::string>& uuid,
+ int32_t userId, int32_t appId, int64_t cacheQuota) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_UUID(uuid);
+ std::lock_guard<std::recursive_mutex> lock(mQuotasLock);
+
+ int32_t uid = multiuser_get_uid(userId, appId);
+ mCacheQuotas[uid] = cacheQuota;
+
+ return ok();
+}
+
+// Dumps the contents of a profile file, using pkgname's dex files for pretty
+// printing the result.
+binder::Status InstalldNativeService::dumpProfiles(int32_t uid, const std::string& packageName,
+ const std::string& codePaths, bool* _aidl_return) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ const char* pkgname = packageName.c_str();
+ const char* code_paths = codePaths.c_str();
+
+ *_aidl_return = dump_profiles(uid, pkgname, code_paths);
+ return ok();
+}
+
+// TODO: Consider returning error codes.
+binder::Status InstalldNativeService::mergeProfiles(int32_t uid, const std::string& packageName,
+ bool* _aidl_return) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ *_aidl_return = analyze_primary_profiles(uid, packageName);
+ return ok();
+}
+
+binder::Status InstalldNativeService::dexopt(const std::string& apkPath, int32_t uid,
+ const std::unique_ptr<std::string>& packageName, const std::string& instructionSet,
+ int32_t dexoptNeeded, const std::unique_ptr<std::string>& outputPath, int32_t dexFlags,
+ const std::string& compilerFilter, const std::unique_ptr<std::string>& uuid,
+ const std::unique_ptr<std::string>& sharedLibraries,
+ const std::unique_ptr<std::string>& seInfo) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_UUID(uuid);
+ if (packageName && *packageName != "*") {
+ CHECK_ARGUMENT_PACKAGE_NAME(*packageName);
+ }
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ const char* apk_path = apkPath.c_str();
+ const char* pkgname = packageName ? packageName->c_str() : "*";
+ const char* instruction_set = instructionSet.c_str();
+ const char* oat_dir = outputPath ? outputPath->c_str() : nullptr;
+ const char* compiler_filter = compilerFilter.c_str();
+ const char* volume_uuid = uuid ? uuid->c_str() : nullptr;
+ const char* shared_libraries = sharedLibraries ? sharedLibraries->c_str() : nullptr;
+ const char* se_info = seInfo ? seInfo->c_str() : nullptr;
+ int res = android::installd::dexopt(apk_path, uid, pkgname, instruction_set, dexoptNeeded,
+ oat_dir, dexFlags, compiler_filter, volume_uuid, shared_libraries, se_info);
+ return res ? error(res, "Failed to dexopt") : ok();
+}
+
+binder::Status InstalldNativeService::markBootComplete(const std::string& instructionSet) {
+ ENFORCE_UID(AID_SYSTEM);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ const char* instruction_set = instructionSet.c_str();
+
+ char boot_marker_path[PKG_PATH_MAX];
+ sprintf(boot_marker_path,
+ "%s/%s/%s/.booting",
+ android_data_dir.path,
+ DALVIK_CACHE,
+ instruction_set);
+
+ ALOGV("mark_boot_complete : %s", boot_marker_path);
+ if (unlink(boot_marker_path) != 0) {
+ return error(StringPrintf("Failed to unlink %s", boot_marker_path));
+ }
+ return ok();
+}
+
+void mkinnerdirs(char* path, int basepos, mode_t mode, int uid, int gid,
+ struct stat* statbuf)
+{
+ while (path[basepos] != 0) {
+ if (path[basepos] == '/') {
+ path[basepos] = 0;
+ if (lstat(path, statbuf) < 0) {
+ ALOGV("Making directory: %s\n", path);
+ if (mkdir(path, mode) == 0) {
+ chown(path, uid, gid);
+ } else {
+ ALOGW("Unable to make directory %s: %s\n", path, strerror(errno));
+ }
+ }
+ path[basepos] = '/';
+ basepos++;
+ }
+ basepos++;
+ }
+}
+
+binder::Status InstalldNativeService::linkNativeLibraryDirectory(
+ const std::unique_ptr<std::string>& uuid, const std::string& packageName,
+ const std::string& nativeLibPath32, int32_t userId) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_UUID(uuid);
+ CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ const char* uuid_ = uuid ? uuid->c_str() : nullptr;
+ const char* pkgname = packageName.c_str();
+ const char* asecLibDir = nativeLibPath32.c_str();
+ struct stat s, libStat;
+ binder::Status res = ok();
+
+ auto _pkgdir = create_data_user_ce_package_path(uuid_, userId, pkgname);
+ auto _libsymlink = _pkgdir + PKG_LIB_POSTFIX;
+
+ const char* pkgdir = _pkgdir.c_str();
+ const char* libsymlink = _libsymlink.c_str();
+
+ if (stat(pkgdir, &s) < 0) {
+ return error("Failed to stat " + _pkgdir);
+ }
+
+ if (chown(pkgdir, AID_INSTALL, AID_INSTALL) < 0) {
+ return error("Failed to chown " + _pkgdir);
+ }
+
+ if (chmod(pkgdir, 0700) < 0) {
+ res = error("Failed to chmod " + _pkgdir);
+ goto out;
+ }
+
+ if (lstat(libsymlink, &libStat) < 0) {
+ if (errno != ENOENT) {
+ res = error("Failed to stat " + _libsymlink);
+ goto out;
+ }
+ } else {
+ if (S_ISDIR(libStat.st_mode)) {
+ if (delete_dir_contents(libsymlink, 1, NULL) < 0) {
+ res = error("Failed to delete " + _libsymlink);
+ goto out;
+ }
+ } else if (S_ISLNK(libStat.st_mode)) {
+ if (unlink(libsymlink) < 0) {
+ res = error("Failed to unlink " + _libsymlink);
+ goto out;
+ }
+ }
+ }
+
+ if (symlink(asecLibDir, libsymlink) < 0) {
+ res = error("Failed to symlink " + _libsymlink + " to " + nativeLibPath32);
+ goto out;
+ }
+
+out:
+ if (chmod(pkgdir, s.st_mode) < 0) {
+ auto msg = "Failed to cleanup chmod " + _pkgdir;
+ if (res.isOk()) {
+ res = error(msg);
+ } else {
+ PLOG(ERROR) << msg;
+ }
+ }
+
+ if (chown(pkgdir, s.st_uid, s.st_gid) < 0) {
+ auto msg = "Failed to cleanup chown " + _pkgdir;
+ if (res.isOk()) {
+ res = error(msg);
+ } else {
+ PLOG(ERROR) << msg;
+ }
+ }
+
+ return res;
+}
+
+static void run_idmap(const char *target_apk, const char *overlay_apk, int idmap_fd)
+{
+ execl(kIdMapPath, kIdMapPath, "--fd", target_apk, overlay_apk,
+ StringPrintf("%d", idmap_fd).c_str(), (char*)NULL);
+ PLOG(ERROR) << "execl (" << kIdMapPath << ") failed";
+}
+
+static void run_verify_idmap(const char *target_apk, const char *overlay_apk, int idmap_fd)
+{
+ execl(kIdMapPath, kIdMapPath, "--verify", target_apk, overlay_apk,
+ StringPrintf("%d", idmap_fd).c_str(), (char*)NULL);
+ PLOG(ERROR) << "execl (" << kIdMapPath << ") failed";
+}
+
+static bool delete_stale_idmap(const char* target_apk, const char* overlay_apk,
+ const char* idmap_path, int32_t uid) {
+ int idmap_fd = open(idmap_path, O_RDWR);
+ if (idmap_fd < 0) {
+ PLOG(ERROR) << "idmap open failed: " << idmap_path;
+ unlink(idmap_path);
+ return true;
+ }
+
+ pid_t pid;
+ pid = fork();
+ if (pid == 0) {
+ /* child -- drop privileges before continuing */
+ if (setgid(uid) != 0) {
+ LOG(ERROR) << "setgid(" << uid << ") failed during idmap";
+ exit(1);
+ }
+ if (setuid(uid) != 0) {
+ LOG(ERROR) << "setuid(" << uid << ") failed during idmap";
+ exit(1);
+ }
+ if (flock(idmap_fd, LOCK_EX | LOCK_NB) != 0) {
+ PLOG(ERROR) << "flock(" << idmap_path << ") failed during idmap";
+ exit(1);
+ }
+
+ run_verify_idmap(target_apk, overlay_apk, idmap_fd);
+ exit(1); /* only if exec call to deleting stale idmap failed */
+ } else {
+ int status = wait_child(pid);
+ close(idmap_fd);
+
+ if (status != 0) {
+ // Failed on verifying if idmap is made from target_apk and overlay_apk.
+ LOG(DEBUG) << "delete stale idmap: " << idmap_path;
+ unlink(idmap_path);
+ return true;
+ }
+ }
+ return false;
+}
+
+// Transform string /a/b/c.apk to (prefix)/a@b@c.apk@(suffix)
+// eg /a/b/c.apk to /data/resource-cache/a@b@c.apk@idmap
+static int flatten_path(const char *prefix, const char *suffix,
+ const char *overlay_path, char *idmap_path, size_t N)
+{
+ if (overlay_path == NULL || idmap_path == NULL) {
+ return -1;
+ }
+ const size_t len_overlay_path = strlen(overlay_path);
+ // will access overlay_path + 1 further below; requires absolute path
+ if (len_overlay_path < 2 || *overlay_path != '/') {
+ return -1;
+ }
+ const size_t len_idmap_root = strlen(prefix);
+ const size_t len_suffix = strlen(suffix);
+ if (SIZE_MAX - len_idmap_root < len_overlay_path ||
+ SIZE_MAX - (len_idmap_root + len_overlay_path) < len_suffix) {
+ // additions below would cause overflow
+ return -1;
+ }
+ if (N < len_idmap_root + len_overlay_path + len_suffix) {
+ return -1;
+ }
+ memset(idmap_path, 0, N);
+ snprintf(idmap_path, N, "%s%s%s", prefix, overlay_path + 1, suffix);
+ char *ch = idmap_path + len_idmap_root;
+ while (*ch != '\0') {
+ if (*ch == '/') {
+ *ch = '@';
+ }
+ ++ch;
+ }
+ return 0;
+}
+
+binder::Status InstalldNativeService::idmap(const std::string& targetApkPath,
+ const std::string& overlayApkPath, int32_t uid) {
+ ENFORCE_UID(AID_SYSTEM);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ const char* target_apk = targetApkPath.c_str();
+ const char* overlay_apk = overlayApkPath.c_str();
+ ALOGV("idmap target_apk=%s overlay_apk=%s uid=%d\n", target_apk, overlay_apk, uid);
+
+ int idmap_fd = -1;
+ char idmap_path[PATH_MAX];
+ struct stat idmap_stat;
+ bool outdated = false;
+
+ if (flatten_path(IDMAP_PREFIX, IDMAP_SUFFIX, overlay_apk,
+ idmap_path, sizeof(idmap_path)) == -1) {
+ ALOGE("idmap cannot generate idmap path for overlay %s\n", overlay_apk);
+ goto fail;
+ }
+
+ if (stat(idmap_path, &idmap_stat) < 0) {
+ outdated = true;
+ } else {
+ outdated = delete_stale_idmap(target_apk, overlay_apk, idmap_path, uid);
+ }
+
+ if (outdated) {
+ idmap_fd = open(idmap_path, O_RDWR | O_CREAT | O_EXCL, 0644);
+ } else {
+ idmap_fd = open(idmap_path, O_RDWR);
+ }
+
+ if (idmap_fd < 0) {
+ ALOGE("idmap cannot open '%s' for output: %s\n", idmap_path, strerror(errno));
+ goto fail;
+ }
+ if (fchown(idmap_fd, AID_SYSTEM, uid) < 0) {
+ ALOGE("idmap cannot chown '%s'\n", idmap_path);
+ goto fail;
+ }
+ if (fchmod(idmap_fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0) {
+ ALOGE("idmap cannot chmod '%s'\n", idmap_path);
+ goto fail;
+ }
+
+ if (!outdated) {
+ close(idmap_fd);
+ return ok();
+ }
+
+ pid_t pid;
+ pid = fork();
+ if (pid == 0) {
+ /* child -- drop privileges before continuing */
+ if (setgid(uid) != 0) {
+ ALOGE("setgid(%d) failed during idmap\n", uid);
+ exit(1);
+ }
+ if (setuid(uid) != 0) {
+ ALOGE("setuid(%d) failed during idmap\n", uid);
+ exit(1);
+ }
+ if (flock(idmap_fd, LOCK_EX | LOCK_NB) != 0) {
+ ALOGE("flock(%s) failed during idmap: %s\n", idmap_path, strerror(errno));
+ exit(1);
+ }
+
+ run_idmap(target_apk, overlay_apk, idmap_fd);
+ exit(1); /* only if exec call to idmap failed */
+ } else {
+ int status = wait_child(pid);
+ if (status != 0) {
+ ALOGE("idmap failed, status=0x%04x\n", status);
+ goto fail;
+ }
+ }
+
+ close(idmap_fd);
+ return ok();
+fail:
+ if (idmap_fd >= 0) {
+ close(idmap_fd);
+ unlink(idmap_path);
+ }
+ return error();
+}
+
+binder::Status InstalldNativeService::removeIdmap(const std::string& overlayApkPath) {
+ const char* overlay_apk = overlayApkPath.c_str();
+ char idmap_path[PATH_MAX];
+
+ if (flatten_path(IDMAP_PREFIX, IDMAP_SUFFIX, overlay_apk,
+ idmap_path, sizeof(idmap_path)) == -1) {
+ ALOGE("idmap cannot generate idmap path for overlay %s\n", overlay_apk);
+ return error();
+ }
+ if (unlink(idmap_path) < 0) {
+ ALOGE("couldn't unlink idmap file %s\n", idmap_path);
+ return error();
+ }
+ return ok();
+}
+
+binder::Status InstalldNativeService::restoreconAppData(const std::unique_ptr<std::string>& uuid,
+ const std::string& packageName, int32_t userId, int32_t flags, int32_t appId,
+ const std::string& seInfo) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_UUID(uuid);
+ CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ binder::Status res = ok();
+
+ // SELINUX_ANDROID_RESTORECON_DATADATA flag is set by libselinux. Not needed here.
+ unsigned int seflags = SELINUX_ANDROID_RESTORECON_RECURSE;
+ const char* uuid_ = uuid ? uuid->c_str() : nullptr;
+ const char* pkgName = packageName.c_str();
+ const char* seinfo = seInfo.c_str();
+
+ uid_t uid = multiuser_get_uid(userId, appId);
+ if (flags & FLAG_STORAGE_CE) {
+ auto path = create_data_user_ce_package_path(uuid_, userId, pkgName);
+ if (selinux_android_restorecon_pkgdir(path.c_str(), seinfo, uid, seflags) < 0) {
+ res = error("restorecon failed for " + path);
+ }
+ }
+ if (flags & FLAG_STORAGE_DE) {
+ auto path = create_data_user_de_package_path(uuid_, userId, pkgName);
+ if (selinux_android_restorecon_pkgdir(path.c_str(), seinfo, uid, seflags) < 0) {
+ res = error("restorecon failed for " + path);
+ }
+ }
+ return res;
+}
+
+binder::Status InstalldNativeService::createOatDir(const std::string& oatDir,
+ const std::string& instructionSet) {
+ ENFORCE_UID(AID_SYSTEM);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ const char* oat_dir = oatDir.c_str();
+ const char* instruction_set = instructionSet.c_str();
+ char oat_instr_dir[PKG_PATH_MAX];
+
+ if (validate_apk_path(oat_dir)) {
+ return error("Invalid path " + oatDir);
+ }
+ if (fs_prepare_dir(oat_dir, S_IRWXU | S_IRWXG | S_IXOTH, AID_SYSTEM, AID_INSTALL)) {
+ return error("Failed to prepare " + oatDir);
+ }
+ if (selinux_android_restorecon(oat_dir, 0)) {
+ return error("Failed to restorecon " + oatDir);
+ }
+ snprintf(oat_instr_dir, PKG_PATH_MAX, "%s/%s", oat_dir, instruction_set);
+ if (fs_prepare_dir(oat_instr_dir, S_IRWXU | S_IRWXG | S_IXOTH, AID_SYSTEM, AID_INSTALL)) {
+ return error(StringPrintf("Failed to prepare %s", oat_instr_dir));
+ }
+ return ok();
+}
+
+binder::Status InstalldNativeService::rmPackageDir(const std::string& packageDir) {
+ ENFORCE_UID(AID_SYSTEM);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ if (validate_apk_path(packageDir.c_str())) {
+ return error("Invalid path " + packageDir);
+ }
+ if (delete_dir_contents_and_dir(packageDir) != 0) {
+ return error("Failed to delete " + packageDir);
+ }
+ return ok();
+}
+
+binder::Status InstalldNativeService::linkFile(const std::string& relativePath,
+ const std::string& fromBase, const std::string& toBase) {
+ ENFORCE_UID(AID_SYSTEM);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ const char* relative_path = relativePath.c_str();
+ const char* from_base = fromBase.c_str();
+ const char* to_base = toBase.c_str();
+ char from_path[PKG_PATH_MAX];
+ char to_path[PKG_PATH_MAX];
+ snprintf(from_path, PKG_PATH_MAX, "%s/%s", from_base, relative_path);
+ snprintf(to_path, PKG_PATH_MAX, "%s/%s", to_base, relative_path);
+
+ if (validate_apk_path_subdirs(from_path)) {
+ return error(StringPrintf("Invalid from path %s", from_path));
+ }
+
+ if (validate_apk_path_subdirs(to_path)) {
+ return error(StringPrintf("Invalid to path %s", to_path));
+ }
+
+ if (link(from_path, to_path) < 0) {
+ return error(StringPrintf("Failed to link from %s to %s", from_path, to_path));
+ }
+
+ return ok();
+}
+
+binder::Status InstalldNativeService::moveAb(const std::string& apkPath,
+ const std::string& instructionSet, const std::string& outputPath) {
+ ENFORCE_UID(AID_SYSTEM);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ const char* apk_path = apkPath.c_str();
+ const char* instruction_set = instructionSet.c_str();
+ const char* oat_dir = outputPath.c_str();
+
+ bool success = move_ab(apk_path, instruction_set, oat_dir);
+ return success ? ok() : error();
+}
+
+binder::Status InstalldNativeService::deleteOdex(const std::string& apkPath,
+ const std::string& instructionSet, const std::unique_ptr<std::string>& outputPath) {
+ ENFORCE_UID(AID_SYSTEM);
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+
+ const char* apk_path = apkPath.c_str();
+ const char* instruction_set = instructionSet.c_str();
+ const char* oat_dir = outputPath ? outputPath->c_str() : nullptr;
+
+ bool res = delete_odex(apk_path, instruction_set, oat_dir);
+ return res ? ok() : error();
+}
+
+binder::Status InstalldNativeService::reconcileSecondaryDexFile(
+ const std::string& dexPath, const std::string& packageName, int32_t uid,
+ const std::vector<std::string>& isas, const std::unique_ptr<std::string>& volumeUuid,
+ int32_t storage_flag, bool* _aidl_return) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_UUID(volumeUuid);
+ CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+
+ std::lock_guard<std::recursive_mutex> lock(mLock);
+ bool result = android::installd::reconcile_secondary_dex_file(
+ dexPath, packageName, uid, isas, volumeUuid, storage_flag, _aidl_return);
+ return result ? ok() : error();
+}
+
+binder::Status InstalldNativeService::invalidateMounts() {
+ ENFORCE_UID(AID_SYSTEM);
+ std::lock_guard<std::recursive_mutex> lock(mMountsLock);
+
+ mStorageMounts.clear();
+ mQuotaReverseMounts.clear();
+
+ std::ifstream in("/proc/mounts");
+ if (!in.is_open()) {
+ return error("Failed to read mounts");
+ }
+
+ std::string source;
+ std::string target;
+ std::string ignored;
+ while (!in.eof()) {
+ std::getline(in, source, ' ');
+ std::getline(in, target, ' ');
+ std::getline(in, ignored);
+
+#if !BYPASS_SDCARDFS
+ if (target.compare(0, 21, "/mnt/runtime/default/") == 0) {
+ LOG(DEBUG) << "Found storage mount " << source << " at " << target;
+ mStorageMounts[source] = target;
+ }
+#endif
+
+#if !BYPASS_QUOTA
+ if (source.compare(0, 11, "/dev/block/") == 0) {
+ struct dqblk dq;
+ if (quotactl(QCMD(Q_GETQUOTA, USRQUOTA), source.c_str(), 0,
+ reinterpret_cast<char*>(&dq)) == 0) {
+ LOG(DEBUG) << "Found quota mount " << source << " at " << target;
+ mQuotaReverseMounts[target] = source;
+
+ // ext4 only enables DQUOT_USAGE_ENABLED by default, so we
+ // need to kick it again to enable DQUOT_LIMITS_ENABLED.
+ if (quotactl(QCMD(Q_QUOTAON, USRQUOTA), source.c_str(), QFMT_VFS_V1, nullptr) != 0
+ && errno != EBUSY) {
+ PLOG(ERROR) << "Failed to enable USRQUOTA on " << source;
+ }
+ if (quotactl(QCMD(Q_QUOTAON, GRPQUOTA), source.c_str(), QFMT_VFS_V1, nullptr) != 0
+ && errno != EBUSY) {
+ PLOG(ERROR) << "Failed to enable GRPQUOTA on " << source;
+ }
+ }
+ }
+#endif
+ }
+ return ok();
+}
+
+std::string InstalldNativeService::findDataMediaPath(
+ const std::unique_ptr<std::string>& uuid, userid_t userid) {
+ std::lock_guard<std::recursive_mutex> lock(mMountsLock);
+ const char* uuid_ = uuid ? uuid->c_str() : nullptr;
+ auto path = StringPrintf("%s/media", create_data_path(uuid_).c_str());
+ auto resolved = mStorageMounts[path];
+ if (resolved.empty()) {
+ LOG(WARNING) << "Failed to find storage mount for " << path;
+ resolved = path;
+ }
+ return StringPrintf("%s/%u", resolved.c_str(), userid);
+}
+
+std::string InstalldNativeService::findQuotaDeviceForUuid(
+ const std::unique_ptr<std::string>& uuid) {
+ std::lock_guard<std::recursive_mutex> lock(mMountsLock);
+ auto path = create_data_path(uuid ? uuid->c_str() : nullptr);
+ return mQuotaReverseMounts[path];
+}
+
+binder::Status InstalldNativeService::isQuotaSupported(
+ const std::unique_ptr<std::string>& volumeUuid, bool* _aidl_return) {
+ *_aidl_return = !findQuotaDeviceForUuid(volumeUuid).empty();
+ return ok();
+}
+
+} // namespace installd
+} // namespace android
diff --git a/cmds/installd/InstalldNativeService.h b/cmds/installd/InstalldNativeService.h
new file mode 100644
index 0000000..4011315
--- /dev/null
+++ b/cmds/installd/InstalldNativeService.h
@@ -0,0 +1,142 @@
+/*
+**
+** Copyright 2008, 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.
+*/
+
+#ifndef COMMANDS_H_
+#define COMMANDS_H_
+
+#include <inttypes.h>
+#include <unistd.h>
+
+#include <vector>
+#include <unordered_map>
+
+#include <android-base/macros.h>
+#include <binder/BinderService.h>
+#include <cutils/multiuser.h>
+
+#include "android/os/BnInstalld.h"
+#include "installd_constants.h"
+
+namespace android {
+namespace installd {
+
+class InstalldNativeService : public BinderService<InstalldNativeService>, public os::BnInstalld {
+public:
+ static status_t start();
+ static char const* getServiceName() { return "installd"; }
+ virtual status_t dump(int fd, const Vector<String16> &args) override;
+
+ binder::Status createUserData(const std::unique_ptr<std::string>& uuid, int32_t userId,
+ int32_t userSerial, int32_t flags);
+ binder::Status destroyUserData(const std::unique_ptr<std::string>& uuid, int32_t userId,
+ int32_t flags);
+
+ binder::Status createAppData(const std::unique_ptr<std::string>& uuid,
+ const std::string& packageName, int32_t userId, int32_t flags, int32_t appId,
+ const std::string& seInfo, int32_t targetSdkVersion, int64_t* _aidl_return);
+ binder::Status restoreconAppData(const std::unique_ptr<std::string>& uuid,
+ const std::string& packageName, int32_t userId, int32_t flags, int32_t appId,
+ const std::string& seInfo);
+ binder::Status migrateAppData(const std::unique_ptr<std::string>& uuid,
+ const std::string& packageName, int32_t userId, int32_t flags);
+ binder::Status clearAppData(const std::unique_ptr<std::string>& uuid,
+ const std::string& packageName, int32_t userId, int32_t flags, int64_t ceDataInode);
+ binder::Status destroyAppData(const std::unique_ptr<std::string>& uuid,
+ const std::string& packageName, int32_t userId, int32_t flags, int64_t ceDataInode);
+
+ binder::Status fixupAppData(const std::unique_ptr<std::string>& uuid, int32_t flags);
+
+ binder::Status getAppSize(const std::unique_ptr<std::string>& uuid,
+ const std::vector<std::string>& packageNames, int32_t userId, int32_t flags,
+ int32_t appId, const std::vector<int64_t>& ceDataInodes,
+ const std::vector<std::string>& codePaths, std::vector<int64_t>* _aidl_return);
+ binder::Status getUserSize(const std::unique_ptr<std::string>& uuid,
+ int32_t userId, int32_t flags, const std::vector<int32_t>& appIds,
+ std::vector<int64_t>* _aidl_return);
+ binder::Status getExternalSize(const std::unique_ptr<std::string>& uuid,
+ int32_t userId, int32_t flags, const std::vector<int32_t>& appIds,
+ std::vector<int64_t>* _aidl_return);
+
+ binder::Status setAppQuota(const std::unique_ptr<std::string>& uuid,
+ int32_t userId, int32_t appId, int64_t cacheQuota);
+
+ binder::Status moveCompleteApp(const std::unique_ptr<std::string>& fromUuid,
+ const std::unique_ptr<std::string>& toUuid, const std::string& packageName,
+ const std::string& dataAppName, int32_t appId, const std::string& seInfo,
+ int32_t targetSdkVersion);
+
+ binder::Status dexopt(const std::string& apkPath, int32_t uid,
+ const std::unique_ptr<std::string>& packageName, const std::string& instructionSet,
+ int32_t dexoptNeeded, const std::unique_ptr<std::string>& outputPath, int32_t dexFlags,
+ const std::string& compilerFilter, const std::unique_ptr<std::string>& uuid,
+ const std::unique_ptr<std::string>& sharedLibraries,
+ const std::unique_ptr<std::string>& seInfo);
+
+ binder::Status rmdex(const std::string& codePath, const std::string& instructionSet);
+
+ binder::Status mergeProfiles(int32_t uid, const std::string& packageName, bool* _aidl_return);
+ binder::Status dumpProfiles(int32_t uid, const std::string& packageName,
+ const std::string& codePaths, bool* _aidl_return);
+ binder::Status clearAppProfiles(const std::string& packageName);
+ binder::Status destroyAppProfiles(const std::string& packageName);
+
+ binder::Status idmap(const std::string& targetApkPath, const std::string& overlayApkPath,
+ int32_t uid);
+ binder::Status removeIdmap(const std::string& overlayApkPath);
+ binder::Status rmPackageDir(const std::string& packageDir);
+ binder::Status markBootComplete(const std::string& instructionSet);
+ binder::Status freeCache(const std::unique_ptr<std::string>& uuid, int64_t targetFreeBytes,
+ int64_t cacheReservedBytes, int32_t flags);
+ binder::Status linkNativeLibraryDirectory(const std::unique_ptr<std::string>& uuid,
+ const std::string& packageName, const std::string& nativeLibPath32, int32_t userId);
+ binder::Status createOatDir(const std::string& oatDir, const std::string& instructionSet);
+ binder::Status linkFile(const std::string& relativePath, const std::string& fromBase,
+ const std::string& toBase);
+ binder::Status moveAb(const std::string& apkPath, const std::string& instructionSet,
+ const std::string& outputPath);
+ binder::Status deleteOdex(const std::string& apkPath, const std::string& instructionSet,
+ const std::unique_ptr<std::string>& outputPath);
+ binder::Status reconcileSecondaryDexFile(const std::string& dexPath,
+ const std::string& packageName, int32_t uid, const std::vector<std::string>& isa,
+ const std::unique_ptr<std::string>& volumeUuid, int32_t storage_flag, bool* _aidl_return);
+
+ binder::Status invalidateMounts();
+ binder::Status isQuotaSupported(const std::unique_ptr<std::string>& volumeUuid,
+ bool* _aidl_return);
+
+private:
+ std::recursive_mutex mLock;
+
+ std::recursive_mutex mMountsLock;
+ std::recursive_mutex mQuotasLock;
+
+ /* Map of all storage mounts from source to target */
+ std::unordered_map<std::string, std::string> mStorageMounts;
+ /* Map of all quota mounts from target to source */
+ std::unordered_map<std::string, std::string> mQuotaReverseMounts;
+
+ /* Map from UID to cache quota size */
+ std::unordered_map<uid_t, int64_t> mCacheQuotas;
+
+ std::string findDataMediaPath(const std::unique_ptr<std::string>& uuid, userid_t userid);
+ std::string findQuotaDeviceForUuid(const std::unique_ptr<std::string>& uuid);
+};
+
+} // namespace installd
+} // namespace android
+
+#endif // COMMANDS_H_
diff --git a/cmds/installd/MatchExtensionGen.h b/cmds/installd/MatchExtensionGen.h
new file mode 100644
index 0000000..fded6b7
--- /dev/null
+++ b/cmds/installd/MatchExtensionGen.h
@@ -0,0 +1,628 @@
+/*
+ * 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.
+ */
+
+/******************************************************************
+ * THIS CODE WAS GENERATED BY matchgen.py, DO NOT MODIFY DIRECTLY *
+ ******************************************************************/
+
+#include <private/android_filesystem_config.h>
+
+int MatchExtension(const char* ext) {
+
+ switch (ext[0]) {
+ case '3':
+ switch (ext[1]) {
+ case 'g': case 'G':
+ switch (ext[2]) {
+ case '2':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ case 'p': case 'P':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ case 'p': case 'P':
+ switch (ext[4]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ case '2':
+ switch (ext[5]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ }
+ }
+ }
+ }
+ case 'a': case 'A':
+ switch (ext[1]) {
+ case 'a': case 'A':
+ switch (ext[2]) {
+ case 'c': case 'C':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ }
+ case 'i': case 'I':
+ switch (ext[2]) {
+ case 'f': case 'F':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ case 'c': case 'C':
+ switch (ext[4]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ case 'f': case 'F':
+ switch (ext[4]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ }
+ }
+ case 'm': case 'M':
+ switch (ext[2]) {
+ case 'r': case 'R':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ }
+ case 'r': case 'R':
+ switch (ext[2]) {
+ case 't': case 'T':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ case 'w': case 'W':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ case 's': case 'S':
+ switch (ext[2]) {
+ case 'f': case 'F':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ case 'x': case 'X':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ }
+ case 'v': case 'V':
+ switch (ext[2]) {
+ case 'i': case 'I':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ }
+ case 'w': case 'W':
+ switch (ext[2]) {
+ case 'b': case 'B':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ }
+ }
+ case 'b': case 'B':
+ switch (ext[1]) {
+ case 'm': case 'M':
+ switch (ext[2]) {
+ case 'p': case 'P':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ }
+ case 'c': case 'C':
+ switch (ext[1]) {
+ case 'r': case 'R':
+ switch (ext[2]) {
+ case '2':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ }
+ case 'd': case 'D':
+ switch (ext[1]) {
+ case 'i': case 'I':
+ switch (ext[2]) {
+ case 'f': case 'F':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ }
+ case 'l': case 'L':
+ switch (ext[2]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ case 'n': case 'N':
+ switch (ext[2]) {
+ case 'g': case 'G':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ case 'v': case 'V':
+ switch (ext[2]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ }
+ case 'f': case 'F':
+ switch (ext[1]) {
+ case 'l': case 'L':
+ switch (ext[2]) {
+ case 'a': case 'A':
+ switch (ext[3]) {
+ case 'c': case 'C':
+ switch (ext[4]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ }
+ case 'i': case 'I':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ }
+ }
+ case 'g': case 'G':
+ switch (ext[1]) {
+ case 'i': case 'I':
+ switch (ext[2]) {
+ case 'f': case 'F':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ case 's': case 'S':
+ switch (ext[2]) {
+ case 'm': case 'M':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ }
+ }
+ case 'j': case 'J':
+ switch (ext[1]) {
+ case 'n': case 'N':
+ switch (ext[2]) {
+ case 'g': case 'G':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ case 'p': case 'P':
+ switch (ext[2]) {
+ case 'e': case 'E':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ case 'g': case 'G':
+ switch (ext[4]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ case 'g': case 'G':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ }
+ case 'l': case 'L':
+ switch (ext[1]) {
+ case 's': case 'S':
+ switch (ext[2]) {
+ case 'f': case 'F':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ case 'x': case 'X':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ }
+ }
+ case 'm': case 'M':
+ switch (ext[1]) {
+ case '3':
+ switch (ext[2]) {
+ case 'u': case 'U':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ }
+ case '4':
+ switch (ext[2]) {
+ case 'a': case 'A':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ case 'v': case 'V':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ }
+ case 'k': case 'K':
+ switch (ext[2]) {
+ case 'a': case 'A':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ case 'v': case 'V':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ }
+ case 'n': case 'N':
+ switch (ext[2]) {
+ case 'g': case 'G':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ }
+ case 'o': case 'O':
+ switch (ext[2]) {
+ case 'v': case 'V':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ case 'i': case 'I':
+ switch (ext[4]) {
+ case 'e': case 'E':
+ switch (ext[5]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ }
+ }
+ }
+ case 'p': case 'P':
+ switch (ext[2]) {
+ case '2':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ case '3':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ case '4':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ case 'e': case 'E':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ case 'g': case 'G':
+ switch (ext[4]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ case 'a': case 'A':
+ switch (ext[5]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ }
+ }
+ case 'g': case 'G':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ case 'a': case 'A':
+ switch (ext[4]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ }
+ }
+ case 'x': case 'X':
+ switch (ext[2]) {
+ case 'u': case 'U':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ }
+ }
+ case 'n': case 'N':
+ switch (ext[1]) {
+ case 'e': case 'E':
+ switch (ext[2]) {
+ case 'f': case 'F':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ case 'r': case 'R':
+ switch (ext[2]) {
+ case 'w': case 'W':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ }
+ case 'o': case 'O':
+ switch (ext[1]) {
+ case 'g': case 'G':
+ switch (ext[2]) {
+ case 'a': case 'A':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ case 'g': case 'G':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ }
+ case 'r': case 'R':
+ switch (ext[2]) {
+ case 'f': case 'F':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ }
+ case 'p': case 'P':
+ switch (ext[1]) {
+ case 'b': case 'B':
+ switch (ext[2]) {
+ case 'm': case 'M':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ case 'c': case 'C':
+ switch (ext[2]) {
+ case 'x': case 'X':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ case 'e': case 'E':
+ switch (ext[2]) {
+ case 'f': case 'F':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ case 'g': case 'G':
+ switch (ext[2]) {
+ case 'm': case 'M':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ case 'l': case 'L':
+ switch (ext[2]) {
+ case 's': case 'S':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ }
+ case 'n': case 'N':
+ switch (ext[2]) {
+ case 'g': case 'G':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ case 'm': case 'M':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ case 'p': case 'P':
+ switch (ext[2]) {
+ case 'm': case 'M':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ case 's': case 'S':
+ switch (ext[2]) {
+ case 'd': case 'D':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ }
+ case 'q': case 'Q':
+ switch (ext[1]) {
+ case 't': case 'T':
+ switch (ext[2]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ }
+ case 'r': case 'R':
+ switch (ext[1]) {
+ case 'a': case 'A':
+ switch (ext[2]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ case 'm': case 'M':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ case 's': case 'S':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ case 'g': case 'G':
+ switch (ext[2]) {
+ case 'b': case 'B':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ case 'm': case 'M':
+ switch (ext[2]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ case 'w': case 'W':
+ switch (ext[2]) {
+ case '2':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ }
+ case 's': case 'S':
+ switch (ext[1]) {
+ case 'd': case 'D':
+ switch (ext[2]) {
+ case '2':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ }
+ case 'n': case 'N':
+ switch (ext[2]) {
+ case 'd': case 'D':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ }
+ case 'r': case 'R':
+ switch (ext[2]) {
+ case 'w': case 'W':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ case 'v': case 'V':
+ switch (ext[2]) {
+ case 'g': case 'G':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ case 'z': case 'Z':
+ switch (ext[4]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ }
+ }
+ case 't': case 'T':
+ switch (ext[1]) {
+ case 'i': case 'I':
+ switch (ext[2]) {
+ case 'f': case 'F':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ case 'f': case 'F':
+ switch (ext[4]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ }
+ case 's': case 'S':
+ switch (ext[2]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ }
+ case 'v': case 'V':
+ switch (ext[1]) {
+ case 'o': case 'O':
+ switch (ext[2]) {
+ case 'b': case 'B':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ }
+ }
+ case 'w': case 'W':
+ switch (ext[1]) {
+ case 'a': case 'A':
+ switch (ext[2]) {
+ case 'v': case 'V':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ case 'x': case 'X':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ }
+ case 'b': case 'B':
+ switch (ext[2]) {
+ case 'm': case 'M':
+ switch (ext[3]) {
+ case 'p': case 'P':
+ switch (ext[4]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ }
+ case 'e': case 'E':
+ switch (ext[2]) {
+ case 'b': case 'B':
+ switch (ext[3]) {
+ case 'm': case 'M':
+ switch (ext[4]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ case 'p': case 'P':
+ switch (ext[4]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ }
+ case 'm': case 'M':
+ switch (ext[2]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ case 'a': case 'A':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_AUDIO;
+ }
+ case 'v': case 'V':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ case 'x': case 'X':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ }
+ case 'r': case 'R':
+ switch (ext[2]) {
+ case 'f': case 'F':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ }
+ case 'v': case 'V':
+ switch (ext[2]) {
+ case 'x': case 'X':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_VIDEO;
+ }
+ }
+ }
+ case 'x': case 'X':
+ switch (ext[1]) {
+ case 'b': case 'B':
+ switch (ext[2]) {
+ case 'm': case 'M':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ case 'p': case 'P':
+ switch (ext[2]) {
+ case 'm': case 'M':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ case 'w': case 'W':
+ switch (ext[2]) {
+ case 'd': case 'D':
+ switch (ext[3]) {
+ case '\0': return AID_MEDIA_IMAGE;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/cmds/installd/binder/android/os/IInstalld.aidl b/cmds/installd/binder/android/os/IInstalld.aidl
new file mode 100644
index 0000000..f09a397
--- /dev/null
+++ b/cmds/installd/binder/android/os/IInstalld.aidl
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package android.os;
+
+/** {@hide} */
+interface IInstalld {
+ void createUserData(@nullable @utf8InCpp String uuid, int userId, int userSerial, int flags);
+ void destroyUserData(@nullable @utf8InCpp String uuid, int userId, int flags);
+
+ long createAppData(@nullable @utf8InCpp String uuid, in @utf8InCpp String packageName,
+ int userId, int flags, int appId, in @utf8InCpp String seInfo, int targetSdkVersion);
+ void restoreconAppData(@nullable @utf8InCpp String uuid, @utf8InCpp String packageName,
+ int userId, int flags, int appId, @utf8InCpp String seInfo);
+ void migrateAppData(@nullable @utf8InCpp String uuid, @utf8InCpp String packageName,
+ int userId, int flags);
+ void clearAppData(@nullable @utf8InCpp String uuid, @utf8InCpp String packageName,
+ int userId, int flags, long ceDataInode);
+ void destroyAppData(@nullable @utf8InCpp String uuid, @utf8InCpp String packageName,
+ int userId, int flags, long ceDataInode);
+
+ void fixupAppData(@nullable @utf8InCpp String uuid, int flags);
+
+ long[] getAppSize(@nullable @utf8InCpp String uuid, in @utf8InCpp String[] packageNames,
+ int userId, int flags, int appId, in long[] ceDataInodes,
+ in @utf8InCpp String[] codePaths);
+ long[] getUserSize(@nullable @utf8InCpp String uuid, int userId, int flags, in int[] appIds);
+ long[] getExternalSize(@nullable @utf8InCpp String uuid, int userId, int flags, in int[] appIds);
+
+ void setAppQuota(@nullable @utf8InCpp String uuid, int userId, int appId, long cacheQuota);
+
+ void moveCompleteApp(@nullable @utf8InCpp String fromUuid, @nullable @utf8InCpp String toUuid,
+ @utf8InCpp String packageName, @utf8InCpp String dataAppName, int appId,
+ @utf8InCpp String seInfo, int targetSdkVersion);
+
+ void dexopt(@utf8InCpp String apkPath, int uid, @nullable @utf8InCpp String packageName,
+ @utf8InCpp String instructionSet, int dexoptNeeded,
+ @nullable @utf8InCpp String outputPath, int dexFlags,
+ @utf8InCpp String compilerFilter, @nullable @utf8InCpp String uuid,
+ @nullable @utf8InCpp String sharedLibraries,
+ @nullable @utf8InCpp String seInfo);
+
+ void rmdex(@utf8InCpp String codePath, @utf8InCpp String instructionSet);
+
+ boolean mergeProfiles(int uid, @utf8InCpp String packageName);
+ boolean dumpProfiles(int uid, @utf8InCpp String packageName, @utf8InCpp String codePaths);
+ void clearAppProfiles(@utf8InCpp String packageName);
+ void destroyAppProfiles(@utf8InCpp String packageName);
+
+ void idmap(@utf8InCpp String targetApkPath, @utf8InCpp String overlayApkPath, int uid);
+ void removeIdmap(@utf8InCpp String overlayApkPath);
+ void rmPackageDir(@utf8InCpp String packageDir);
+ void markBootComplete(@utf8InCpp String instructionSet);
+ void freeCache(@nullable @utf8InCpp String uuid, long targetFreeBytes,
+ long cacheReservedBytes, int flags);
+ void linkNativeLibraryDirectory(@nullable @utf8InCpp String uuid,
+ @utf8InCpp String packageName, @utf8InCpp String nativeLibPath32, int userId);
+ void createOatDir(@utf8InCpp String oatDir, @utf8InCpp String instructionSet);
+ void linkFile(@utf8InCpp String relativePath, @utf8InCpp String fromBase,
+ @utf8InCpp String toBase);
+ void moveAb(@utf8InCpp String apkPath, @utf8InCpp String instructionSet,
+ @utf8InCpp String outputPath);
+ void deleteOdex(@utf8InCpp String apkPath, @utf8InCpp String instructionSet,
+ @nullable @utf8InCpp String outputPath);
+
+ boolean reconcileSecondaryDexFile(@utf8InCpp String dexPath, @utf8InCpp String pkgName,
+ int uid, in @utf8InCpp String[] isas, @nullable @utf8InCpp String volume_uuid,
+ int storage_flag);
+
+ void invalidateMounts();
+ boolean isQuotaSupported(@nullable @utf8InCpp String uuid);
+}
diff --git a/cmds/installd/commands.cpp b/cmds/installd/commands.cpp
deleted file mode 100644
index 271c75b..0000000
--- a/cmds/installd/commands.cpp
+++ /dev/null
@@ -1,2294 +0,0 @@
-/*
-** Copyright 2008, 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.
-*/
-
-#include "commands.h"
-
-#include <errno.h>
-#include <inttypes.h>
-#include <regex>
-#include <stdlib.h>
-#include <sys/capability.h>
-#include <sys/file.h>
-#include <sys/resource.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <sys/xattr.h>
-#include <unistd.h>
-
-#include <android-base/logging.h>
-#include <android-base/stringprintf.h>
-#include <android-base/strings.h>
-#include <android-base/unique_fd.h>
-#include <cutils/fs.h>
-#include <cutils/log.h> // TODO: Move everything to base/logging.
-#include <cutils/sched_policy.h>
-#include <diskusage/dirsize.h>
-#include <logwrap/logwrap.h>
-#include <private/android_filesystem_config.h>
-#include <selinux/android.h>
-#include <system/thread_defs.h>
-
-#include <globals.h>
-#include <installd_deps.h>
-#include <otapreopt_utils.h>
-#include <utils.h>
-
-#ifndef LOG_TAG
-#define LOG_TAG "installd"
-#endif
-
-using android::base::EndsWith;
-using android::base::StringPrintf;
-
-namespace android {
-namespace installd {
-
-static constexpr const char* kCpPath = "/system/bin/cp";
-static constexpr const char* kXattrDefault = "user.default";
-
-static constexpr const char* PKG_LIB_POSTFIX = "/lib";
-static constexpr const char* CACHE_DIR_POSTFIX = "/cache";
-static constexpr const char* CODE_CACHE_DIR_POSTFIX = "/code_cache";
-
-static constexpr const char* IDMAP_PREFIX = "/data/resource-cache/";
-static constexpr const char* IDMAP_SUFFIX = "@idmap";
-
-// NOTE: keep in sync with StorageManager
-static constexpr int FLAG_STORAGE_DE = 1 << 0;
-static constexpr int FLAG_STORAGE_CE = 1 << 1;
-
-// NOTE: keep in sync with Installer
-static constexpr int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
-static constexpr int FLAG_CLEAR_CODE_CACHE_ONLY = 1 << 9;
-
-/* dexopt needed flags matching those in dalvik.system.DexFile */
-static constexpr int DEXOPT_DEX2OAT_NEEDED = 1;
-static constexpr int DEXOPT_PATCHOAT_NEEDED = 2;
-static constexpr int DEXOPT_SELF_PATCHOAT_NEEDED = 3;
-
-#define MIN_RESTRICTED_HOME_SDK_VERSION 24 // > M
-
-typedef int fd_t;
-
-static bool property_get_bool(const char* property_name, bool default_value = false) {
- char tmp_property_value[kPropertyValueMax];
- bool have_property = get_property(property_name, tmp_property_value, nullptr) > 0;
- if (!have_property) {
- return default_value;
- }
- return strcmp(tmp_property_value, "true") == 0;
-}
-
-// Keep profile paths in sync with ActivityThread.
-constexpr const char* PRIMARY_PROFILE_NAME = "primary.prof";
-static std::string create_primary_profile(const std::string& profile_dir) {
- return StringPrintf("%s/%s", profile_dir.c_str(), PRIMARY_PROFILE_NAME);
-}
-
-/**
- * Perform restorecon of the given path, but only perform recursive restorecon
- * if the label of that top-level file actually changed. This can save us
- * significant time by avoiding no-op traversals of large filesystem trees.
- */
-static int restorecon_app_data_lazy(const std::string& path, const char* seinfo, uid_t uid) {
- int res = 0;
- char* before = nullptr;
- char* after = nullptr;
-
- // Note that SELINUX_ANDROID_RESTORECON_DATADATA flag is set by
- // libselinux. Not needed here.
-
- if (lgetfilecon(path.c_str(), &before) < 0) {
- PLOG(ERROR) << "Failed before getfilecon for " << path;
- goto fail;
- }
- if (selinux_android_restorecon_pkgdir(path.c_str(), seinfo, uid, 0) < 0) {
- PLOG(ERROR) << "Failed top-level restorecon for " << path;
- goto fail;
- }
- if (lgetfilecon(path.c_str(), &after) < 0) {
- PLOG(ERROR) << "Failed after getfilecon for " << path;
- goto fail;
- }
-
- // If the initial top-level restorecon above changed the label, then go
- // back and restorecon everything recursively
- if (strcmp(before, after)) {
- LOG(DEBUG) << "Detected label change from " << before << " to " << after << " at " << path
- << "; running recursive restorecon";
- if (selinux_android_restorecon_pkgdir(path.c_str(), seinfo, uid,
- SELINUX_ANDROID_RESTORECON_RECURSE) < 0) {
- PLOG(ERROR) << "Failed recursive restorecon for " << path;
- goto fail;
- }
- }
-
- goto done;
-fail:
- res = -1;
-done:
- free(before);
- free(after);
- return res;
-}
-
-static int restorecon_app_data_lazy(const std::string& parent, const char* name, const char* seinfo,
- uid_t uid) {
- return restorecon_app_data_lazy(StringPrintf("%s/%s", parent.c_str(), name), seinfo, uid);
-}
-
-static int prepare_app_dir(const std::string& path, mode_t target_mode, uid_t uid) {
- if (fs_prepare_dir_strict(path.c_str(), target_mode, uid, uid) != 0) {
- PLOG(ERROR) << "Failed to prepare " << path;
- return -1;
- }
- return 0;
-}
-
-static int prepare_app_dir(const std::string& parent, const char* name, mode_t target_mode,
- uid_t uid) {
- return prepare_app_dir(StringPrintf("%s/%s", parent.c_str(), name), target_mode, uid);
-}
-
-int create_app_data(const char *uuid, const char *pkgname, userid_t userid, int flags,
- appid_t appid, const char* seinfo, int target_sdk_version) {
- uid_t uid = multiuser_get_uid(userid, appid);
- mode_t target_mode = target_sdk_version >= MIN_RESTRICTED_HOME_SDK_VERSION ? 0700 : 0751;
- if (flags & FLAG_STORAGE_CE) {
- auto path = create_data_user_ce_package_path(uuid, userid, pkgname);
- if (prepare_app_dir(path, target_mode, uid) ||
- prepare_app_dir(path, "cache", 0771, uid) ||
- prepare_app_dir(path, "code_cache", 0771, uid)) {
- return -1;
- }
-
- // Consider restorecon over contents if label changed
- if (restorecon_app_data_lazy(path, seinfo, uid) ||
- restorecon_app_data_lazy(path, "cache", seinfo, uid) ||
- restorecon_app_data_lazy(path, "code_cache", seinfo, uid)) {
- return -1;
- }
-
- // Remember inode numbers of cache directories so that we can clear
- // contents while CE storage is locked
- if (write_path_inode(path, "cache", kXattrInodeCache) ||
- write_path_inode(path, "code_cache", kXattrInodeCodeCache)) {
- return -1;
- }
- }
- if (flags & FLAG_STORAGE_DE) {
- auto path = create_data_user_de_package_path(uuid, userid, pkgname);
- if (prepare_app_dir(path, target_mode, uid)) {
- // TODO: include result once 25796509 is fixed
- return 0;
- }
-
- // Consider restorecon over contents if label changed
- if (restorecon_app_data_lazy(path, seinfo, uid)) {
- return -1;
- }
-
- if (property_get_bool("dalvik.vm.usejitprofiles")) {
- const std::string profile_path = create_data_user_profile_package_path(userid, pkgname);
- // read-write-execute only for the app user.
- if (fs_prepare_dir_strict(profile_path.c_str(), 0700, uid, uid) != 0) {
- PLOG(ERROR) << "Failed to prepare " << profile_path;
- return -1;
- }
- std::string profile_file = create_primary_profile(profile_path);
- // read-write only for the app user.
- if (fs_prepare_file_strict(profile_file.c_str(), 0600, uid, uid) != 0) {
- PLOG(ERROR) << "Failed to prepare " << profile_path;
- return -1;
- }
- const std::string ref_profile_path = create_data_ref_profile_package_path(pkgname);
- // dex2oat/profman runs under the shared app gid and it needs to read/write reference
- // profiles.
- appid_t shared_app_gid = multiuser_get_shared_app_gid(uid);
- if (fs_prepare_dir_strict(
- ref_profile_path.c_str(), 0700, shared_app_gid, shared_app_gid) != 0) {
- PLOG(ERROR) << "Failed to prepare " << ref_profile_path;
- return -1;
- }
- }
- }
- return 0;
-}
-
-int migrate_app_data(const char *uuid, const char *pkgname, userid_t userid, int flags) {
- // This method only exists to upgrade system apps that have requested
- // forceDeviceEncrypted, so their default storage always lives in a
- // consistent location. This only works on non-FBE devices, since we
- // never want to risk exposing data on a device with real CE/DE storage.
-
- auto ce_path = create_data_user_ce_package_path(uuid, userid, pkgname);
- auto de_path = create_data_user_de_package_path(uuid, userid, pkgname);
-
- // If neither directory is marked as default, assume CE is default
- if (getxattr(ce_path.c_str(), kXattrDefault, nullptr, 0) == -1
- && getxattr(de_path.c_str(), kXattrDefault, nullptr, 0) == -1) {
- if (setxattr(ce_path.c_str(), kXattrDefault, nullptr, 0, 0) != 0) {
- PLOG(ERROR) << "Failed to mark default storage " << ce_path;
- return -1;
- }
- }
-
- // Migrate default data location if needed
- auto target = (flags & FLAG_STORAGE_DE) ? de_path : ce_path;
- auto source = (flags & FLAG_STORAGE_DE) ? ce_path : de_path;
-
- if (getxattr(target.c_str(), kXattrDefault, nullptr, 0) == -1) {
- LOG(WARNING) << "Requested default storage " << target
- << " is not active; migrating from " << source;
- if (delete_dir_contents_and_dir(target) != 0) {
- PLOG(ERROR) << "Failed to delete";
- return -1;
- }
- if (rename(source.c_str(), target.c_str()) != 0) {
- PLOG(ERROR) << "Failed to rename";
- return -1;
- }
- }
-
- return 0;
-}
-
-static bool clear_profile(const std::string& profile) {
- base::unique_fd ufd(open(profile.c_str(), O_WRONLY | O_NOFOLLOW | O_CLOEXEC));
- if (ufd.get() < 0) {
- if (errno != ENOENT) {
- PLOG(WARNING) << "Could not open profile " << profile;
- return false;
- } else {
- // Nothing to clear. That's ok.
- return true;
- }
- }
-
- if (flock(ufd.get(), LOCK_EX | LOCK_NB) != 0) {
- if (errno != EWOULDBLOCK) {
- PLOG(WARNING) << "Error locking profile " << profile;
- }
- // This implies that the app owning this profile is running
- // (and has acquired the lock).
- //
- // If we can't acquire the lock bail out since clearing is useless anyway
- // (the app will write again to the profile).
- //
- // Note:
- // This does not impact the this is not an issue for the profiling correctness.
- // In case this is needed because of an app upgrade, profiles will still be
- // eventually cleared by the app itself due to checksum mismatch.
- // If this is needed because profman advised, then keeping the data around
- // until the next run is again not an issue.
- //
- // If the app attempts to acquire a lock while we've held one here,
- // it will simply skip the current write cycle.
- return false;
- }
-
- bool truncated = ftruncate(ufd.get(), 0) == 0;
- if (!truncated) {
- PLOG(WARNING) << "Could not truncate " << profile;
- }
- if (flock(ufd.get(), LOCK_UN) != 0) {
- PLOG(WARNING) << "Error unlocking profile " << profile;
- }
- return truncated;
-}
-
-static bool clear_reference_profile(const char* pkgname) {
- std::string reference_profile_dir = create_data_ref_profile_package_path(pkgname);
- std::string reference_profile = create_primary_profile(reference_profile_dir);
- return clear_profile(reference_profile);
-}
-
-static bool clear_current_profile(const char* pkgname, userid_t user) {
- std::string profile_dir = create_data_user_profile_package_path(user, pkgname);
- std::string profile = create_primary_profile(profile_dir);
- return clear_profile(profile);
-}
-
-static bool clear_current_profiles(const char* pkgname) {
- bool success = true;
- std::vector<userid_t> users = get_known_users(/*volume_uuid*/ nullptr);
- for (auto user : users) {
- success &= clear_current_profile(pkgname, user);
- }
- return success;
-}
-
-int clear_app_profiles(const char* pkgname) {
- bool success = true;
- success &= clear_reference_profile(pkgname);
- success &= clear_current_profiles(pkgname);
- return success ? 0 : -1;
-}
-
-int clear_app_data(const char *uuid, const char *pkgname, userid_t userid, int flags,
- ino_t ce_data_inode) {
- int res = 0;
- if (flags & FLAG_STORAGE_CE) {
- auto path = create_data_user_ce_package_path(uuid, userid, pkgname, ce_data_inode);
- if (flags & FLAG_CLEAR_CACHE_ONLY) {
- path = read_path_inode(path, "cache", kXattrInodeCache);
- } else if (flags & FLAG_CLEAR_CODE_CACHE_ONLY) {
- path = read_path_inode(path, "code_cache", kXattrInodeCodeCache);
- }
- if (access(path.c_str(), F_OK) == 0) {
- res |= delete_dir_contents(path);
- }
- }
- if (flags & FLAG_STORAGE_DE) {
- std::string suffix = "";
- bool only_cache = false;
- if (flags & FLAG_CLEAR_CACHE_ONLY) {
- suffix = CACHE_DIR_POSTFIX;
- only_cache = true;
- } else if (flags & FLAG_CLEAR_CODE_CACHE_ONLY) {
- suffix = CODE_CACHE_DIR_POSTFIX;
- only_cache = true;
- }
-
- auto path = create_data_user_de_package_path(uuid, userid, pkgname) + suffix;
- if (access(path.c_str(), F_OK) == 0) {
- // TODO: include result once 25796509 is fixed
- delete_dir_contents(path);
- }
- if (!only_cache) {
- if (!clear_current_profile(pkgname, userid)) {
- res |= -1;
- }
- }
- }
- return res;
-}
-
-static int destroy_app_reference_profile(const char *pkgname) {
- return delete_dir_contents_and_dir(
- create_data_ref_profile_package_path(pkgname),
- /*ignore_if_missing*/ true);
-}
-
-static int destroy_app_current_profiles(const char *pkgname, userid_t userid) {
- return delete_dir_contents_and_dir(
- create_data_user_profile_package_path(userid, pkgname),
- /*ignore_if_missing*/ true);
-}
-
-int destroy_app_profiles(const char *pkgname) {
- int result = 0;
- std::vector<userid_t> users = get_known_users(/*volume_uuid*/ nullptr);
- for (auto user : users) {
- result |= destroy_app_current_profiles(pkgname, user);
- }
- result |= destroy_app_reference_profile(pkgname);
- return result;
-}
-
-int destroy_app_data(const char *uuid, const char *pkgname, userid_t userid, int flags,
- ino_t ce_data_inode) {
- int res = 0;
- if (flags & FLAG_STORAGE_CE) {
- res |= delete_dir_contents_and_dir(
- create_data_user_ce_package_path(uuid, userid, pkgname, ce_data_inode));
- }
- if (flags & FLAG_STORAGE_DE) {
- res |= delete_dir_contents_and_dir(
- create_data_user_de_package_path(uuid, userid, pkgname));
- destroy_app_current_profiles(pkgname, userid);
- // TODO(calin): If the package is still installed by other users it's probably
- // beneficial to keep the reference profile around.
- // Verify if it's ok to do that.
- destroy_app_reference_profile(pkgname);
- }
- return res;
-}
-
-int move_complete_app(const char *from_uuid, const char *to_uuid, const char *package_name,
- const char *data_app_name, appid_t appid, const char* seinfo, int target_sdk_version) {
- std::vector<userid_t> users = get_known_users(from_uuid);
-
- // Copy app
- {
- auto from = create_data_app_package_path(from_uuid, data_app_name);
- auto to = create_data_app_package_path(to_uuid, data_app_name);
- auto to_parent = create_data_app_path(to_uuid);
-
- char *argv[] = {
- (char*) kCpPath,
- (char*) "-F", /* delete any existing destination file first (--remove-destination) */
- (char*) "-p", /* preserve timestamps, ownership, and permissions */
- (char*) "-R", /* recurse into subdirectories (DEST must be a directory) */
- (char*) "-P", /* Do not follow symlinks [default] */
- (char*) "-d", /* don't dereference symlinks */
- (char*) from.c_str(),
- (char*) to_parent.c_str()
- };
-
- LOG(DEBUG) << "Copying " << from << " to " << to;
- int rc = android_fork_execvp(ARRAY_SIZE(argv), argv, NULL, false, true);
-
- if (rc != 0) {
- LOG(ERROR) << "Failed copying " << from << " to " << to
- << ": status " << rc;
- goto fail;
- }
-
- if (selinux_android_restorecon(to.c_str(), SELINUX_ANDROID_RESTORECON_RECURSE) != 0) {
- LOG(ERROR) << "Failed to restorecon " << to;
- goto fail;
- }
- }
-
- // Copy private data for all known users
- for (auto user : users) {
-
- // Data source may not exist for all users; that's okay
- auto from_ce = create_data_user_ce_package_path(from_uuid, user, package_name);
- if (access(from_ce.c_str(), F_OK) != 0) {
- LOG(INFO) << "Missing source " << from_ce;
- continue;
- }
-
- if (create_app_data(to_uuid, package_name, user, FLAG_STORAGE_CE | FLAG_STORAGE_DE,
- appid, seinfo, target_sdk_version) != 0) {
- LOG(ERROR) << "Failed to create package target on " << to_uuid;
- goto fail;
- }
-
- char *argv[] = {
- (char*) kCpPath,
- (char*) "-F", /* delete any existing destination file first (--remove-destination) */
- (char*) "-p", /* preserve timestamps, ownership, and permissions */
- (char*) "-R", /* recurse into subdirectories (DEST must be a directory) */
- (char*) "-P", /* Do not follow symlinks [default] */
- (char*) "-d", /* don't dereference symlinks */
- nullptr,
- nullptr
- };
-
- {
- auto from = create_data_user_de_package_path(from_uuid, user, package_name);
- auto to = create_data_user_de_path(to_uuid, user);
- argv[6] = (char*) from.c_str();
- argv[7] = (char*) to.c_str();
-
- LOG(DEBUG) << "Copying " << from << " to " << to;
- int rc = android_fork_execvp(ARRAY_SIZE(argv), argv, NULL, false, true);
- if (rc != 0) {
- LOG(ERROR) << "Failed copying " << from << " to " << to << " with status " << rc;
- goto fail;
- }
- }
- {
- auto from = create_data_user_ce_package_path(from_uuid, user, package_name);
- auto to = create_data_user_ce_path(to_uuid, user);
- argv[6] = (char*) from.c_str();
- argv[7] = (char*) to.c_str();
-
- LOG(DEBUG) << "Copying " << from << " to " << to;
- int rc = android_fork_execvp(ARRAY_SIZE(argv), argv, NULL, false, true);
- if (rc != 0) {
- LOG(ERROR) << "Failed copying " << from << " to " << to << " with status " << rc;
- goto fail;
- }
- }
-
- if (restorecon_app_data(to_uuid, package_name, user, FLAG_STORAGE_CE | FLAG_STORAGE_DE,
- appid, seinfo) != 0) {
- LOG(ERROR) << "Failed to restorecon";
- goto fail;
- }
- }
-
- // We let the framework scan the new location and persist that before
- // deleting the data in the old location; this ordering ensures that
- // we can recover from things like battery pulls.
- return 0;
-
-fail:
- // Nuke everything we might have already copied
- {
- auto to = create_data_app_package_path(to_uuid, data_app_name);
- if (delete_dir_contents(to.c_str(), 1, NULL) != 0) {
- LOG(WARNING) << "Failed to rollback " << to;
- }
- }
- for (auto user : users) {
- {
- auto to = create_data_user_de_package_path(to_uuid, user, package_name);
- if (delete_dir_contents(to.c_str(), 1, NULL) != 0) {
- LOG(WARNING) << "Failed to rollback " << to;
- }
- }
- {
- auto to = create_data_user_ce_package_path(to_uuid, user, package_name);
- if (delete_dir_contents(to.c_str(), 1, NULL) != 0) {
- LOG(WARNING) << "Failed to rollback " << to;
- }
- }
- }
- return -1;
-}
-
-int create_user_data(const char *uuid, userid_t userid, int user_serial ATTRIBUTE_UNUSED,
- int flags) {
- if (flags & FLAG_STORAGE_DE) {
- if (uuid == nullptr) {
- return ensure_config_user_dirs(userid);
- }
- }
- return 0;
-}
-
-int destroy_user_data(const char *uuid, userid_t userid, int flags) {
- int res = 0;
- if (flags & FLAG_STORAGE_DE) {
- res |= delete_dir_contents_and_dir(create_data_user_de_path(uuid, userid), true);
- if (uuid == nullptr) {
- res |= delete_dir_contents_and_dir(create_data_misc_legacy_path(userid), true);
- res |= delete_dir_contents_and_dir(create_data_user_profiles_path(userid), true);
- }
- }
- if (flags & FLAG_STORAGE_CE) {
- res |= delete_dir_contents_and_dir(create_data_user_ce_path(uuid, userid), true);
- res |= delete_dir_contents_and_dir(create_data_media_path(uuid, userid), true);
- }
- return res;
-}
-
-/* Try to ensure free_size bytes of storage are available.
- * Returns 0 on success.
- * This is rather simple-minded because doing a full LRU would
- * be potentially memory-intensive, and without atime it would
- * also require that apps constantly modify file metadata even
- * when just reading from the cache, which is pretty awful.
- */
-int free_cache(const char *uuid, int64_t free_size) {
- cache_t* cache;
- int64_t avail;
-
- auto data_path = create_data_path(uuid);
-
- avail = data_disk_free(data_path);
- if (avail < 0) return -1;
-
- ALOGI("free_cache(%" PRId64 ") avail %" PRId64 "\n", free_size, avail);
- if (avail >= free_size) return 0;
-
- cache = start_cache_collection();
-
- auto users = get_known_users(uuid);
- for (auto user : users) {
- add_cache_files(cache, create_data_user_ce_path(uuid, user));
- add_cache_files(cache, create_data_user_de_path(uuid, user));
- add_cache_files(cache,
- StringPrintf("%s/Android/data", create_data_media_path(uuid, user).c_str()));
- }
-
- clear_cache_files(data_path, cache, free_size);
- finish_cache_collection(cache);
-
- return data_disk_free(data_path) >= free_size ? 0 : -1;
-}
-
-int rm_dex(const char *path, const char *instruction_set)
-{
- char dex_path[PKG_PATH_MAX];
-
- if (validate_apk_path(path) && validate_system_app_path(path)) {
- ALOGE("invalid apk path '%s' (bad prefix)\n", path);
- return -1;
- }
-
- if (!create_cache_path(dex_path, path, instruction_set)) return -1;
-
- ALOGV("unlink %s\n", dex_path);
- if (unlink(dex_path) < 0) {
- if (errno != ENOENT) {
- ALOGE("Couldn't unlink %s: %s\n", dex_path, strerror(errno));
- }
- return -1;
- } else {
- return 0;
- }
-}
-
-static void add_app_data_size(std::string& path, int64_t *codesize, int64_t *datasize,
- int64_t *cachesize) {
- DIR *d;
- int dfd;
- struct dirent *de;
- struct stat s;
-
- d = opendir(path.c_str());
- if (d == nullptr) {
- PLOG(WARNING) << "Failed to open " << path;
- return;
- }
- dfd = dirfd(d);
- while ((de = readdir(d))) {
- const char *name = de->d_name;
-
- int64_t statsize = 0;
- if (fstatat(dfd, name, &s, AT_SYMLINK_NOFOLLOW) == 0) {
- statsize = stat_size(&s);
- }
-
- if (de->d_type == DT_DIR) {
- int subfd;
- int64_t dirsize = 0;
- /* always skip "." and ".." */
- if (name[0] == '.') {
- if (name[1] == 0) continue;
- if ((name[1] == '.') && (name[2] == 0)) continue;
- }
- subfd = openat(dfd, name, O_RDONLY | O_DIRECTORY);
- if (subfd >= 0) {
- dirsize = calculate_dir_size(subfd);
- close(subfd);
- }
- // TODO: check xattrs!
- if (!strcmp(name, "cache") || !strcmp(name, "code_cache")) {
- *datasize += statsize;
- *cachesize += dirsize;
- } else {
- *datasize += dirsize + statsize;
- }
- } else if (de->d_type == DT_LNK && !strcmp(name, "lib")) {
- *codesize += statsize;
- } else {
- *datasize += statsize;
- }
- }
- closedir(d);
-}
-
-int get_app_size(const char *uuid, const char *pkgname, int userid, int flags, ino_t ce_data_inode,
- const char *code_path, int64_t *codesize, int64_t *datasize, int64_t *cachesize,
- int64_t* asecsize) {
- DIR *d;
- int dfd;
-
- d = opendir(code_path);
- if (d != nullptr) {
- dfd = dirfd(d);
- *codesize += calculate_dir_size(dfd);
- closedir(d);
- }
-
- if (flags & FLAG_STORAGE_CE) {
- auto path = create_data_user_ce_package_path(uuid, userid, pkgname, ce_data_inode);
- add_app_data_size(path, codesize, datasize, cachesize);
- }
- if (flags & FLAG_STORAGE_DE) {
- auto path = create_data_user_de_package_path(uuid, userid, pkgname);
- add_app_data_size(path, codesize, datasize, cachesize);
- }
-
- *asecsize = 0;
-
- return 0;
-}
-
-int get_app_data_inode(const char *uuid, const char *pkgname, int userid, int flags, ino_t *inode) {
- if (flags & FLAG_STORAGE_CE) {
- auto path = create_data_user_ce_package_path(uuid, userid, pkgname);
- return get_path_inode(path, inode);
- }
- return -1;
-}
-
-static int split_count(const char *str)
-{
- char *ctx;
- int count = 0;
- char buf[kPropertyValueMax];
-
- strncpy(buf, str, sizeof(buf));
- char *pBuf = buf;
-
- while(strtok_r(pBuf, " ", &ctx) != NULL) {
- count++;
- pBuf = NULL;
- }
-
- return count;
-}
-
-static int split(char *buf, const char **argv)
-{
- char *ctx;
- int count = 0;
- char *tok;
- char *pBuf = buf;
-
- while((tok = strtok_r(pBuf, " ", &ctx)) != NULL) {
- argv[count++] = tok;
- pBuf = NULL;
- }
-
- return count;
-}
-
-static void run_patchoat(int input_fd, int oat_fd, const char* input_file_name,
- const char* output_file_name, const char *pkgname ATTRIBUTE_UNUSED, const char *instruction_set)
-{
- static const int MAX_INT_LEN = 12; // '-'+10dig+'\0' -OR- 0x+8dig
- static const unsigned int MAX_INSTRUCTION_SET_LEN = 7;
-
- static const char* PATCHOAT_BIN = "/system/bin/patchoat";
- if (strlen(instruction_set) >= MAX_INSTRUCTION_SET_LEN) {
- ALOGE("Instruction set %s longer than max length of %d",
- instruction_set, MAX_INSTRUCTION_SET_LEN);
- return;
- }
-
- /* input_file_name/input_fd should be the .odex/.oat file that is precompiled. I think*/
- char instruction_set_arg[strlen("--instruction-set=") + MAX_INSTRUCTION_SET_LEN];
- char output_oat_fd_arg[strlen("--output-oat-fd=") + MAX_INT_LEN];
- char input_oat_fd_arg[strlen("--input-oat-fd=") + MAX_INT_LEN];
- const char* patched_image_location_arg = "--patched-image-location=/system/framework/boot.art";
- // The caller has already gotten all the locks we need.
- const char* no_lock_arg = "--no-lock-output";
- sprintf(instruction_set_arg, "--instruction-set=%s", instruction_set);
- sprintf(output_oat_fd_arg, "--output-oat-fd=%d", oat_fd);
- sprintf(input_oat_fd_arg, "--input-oat-fd=%d", input_fd);
- ALOGV("Running %s isa=%s in-fd=%d (%s) out-fd=%d (%s)\n",
- PATCHOAT_BIN, instruction_set, input_fd, input_file_name, oat_fd, output_file_name);
-
- /* patchoat, patched-image-location, no-lock, isa, input-fd, output-fd */
- char* argv[7];
- argv[0] = (char*) PATCHOAT_BIN;
- argv[1] = (char*) patched_image_location_arg;
- argv[2] = (char*) no_lock_arg;
- argv[3] = instruction_set_arg;
- argv[4] = output_oat_fd_arg;
- argv[5] = input_oat_fd_arg;
- argv[6] = NULL;
-
- execv(PATCHOAT_BIN, (char* const *)argv);
- ALOGE("execv(%s) failed: %s\n", PATCHOAT_BIN, strerror(errno));
-}
-
-static void run_dex2oat(int zip_fd, int oat_fd, int image_fd, const char* input_file_name,
- const char* output_file_name, int swap_fd, const char *instruction_set,
- const char* compiler_filter, bool vm_safe_mode, bool debuggable, bool post_bootcomplete,
- int profile_fd, const char* shared_libraries) {
- static const unsigned int MAX_INSTRUCTION_SET_LEN = 7;
-
- if (strlen(instruction_set) >= MAX_INSTRUCTION_SET_LEN) {
- ALOGE("Instruction set %s longer than max length of %d",
- instruction_set, MAX_INSTRUCTION_SET_LEN);
- return;
- }
-
- char dex2oat_Xms_flag[kPropertyValueMax];
- bool have_dex2oat_Xms_flag = get_property("dalvik.vm.dex2oat-Xms", dex2oat_Xms_flag, NULL) > 0;
-
- char dex2oat_Xmx_flag[kPropertyValueMax];
- bool have_dex2oat_Xmx_flag = get_property("dalvik.vm.dex2oat-Xmx", dex2oat_Xmx_flag, NULL) > 0;
-
- char dex2oat_threads_buf[kPropertyValueMax];
- bool have_dex2oat_threads_flag = get_property(post_bootcomplete
- ? "dalvik.vm.dex2oat-threads"
- : "dalvik.vm.boot-dex2oat-threads",
- dex2oat_threads_buf,
- NULL) > 0;
- char dex2oat_threads_arg[kPropertyValueMax + 2];
- if (have_dex2oat_threads_flag) {
- sprintf(dex2oat_threads_arg, "-j%s", dex2oat_threads_buf);
- }
-
- char dex2oat_isa_features_key[kPropertyKeyMax];
- sprintf(dex2oat_isa_features_key, "dalvik.vm.isa.%s.features", instruction_set);
- char dex2oat_isa_features[kPropertyValueMax];
- bool have_dex2oat_isa_features = get_property(dex2oat_isa_features_key,
- dex2oat_isa_features, NULL) > 0;
-
- char dex2oat_isa_variant_key[kPropertyKeyMax];
- sprintf(dex2oat_isa_variant_key, "dalvik.vm.isa.%s.variant", instruction_set);
- char dex2oat_isa_variant[kPropertyValueMax];
- bool have_dex2oat_isa_variant = get_property(dex2oat_isa_variant_key,
- dex2oat_isa_variant, NULL) > 0;
-
- const char *dex2oat_norelocation = "-Xnorelocate";
- bool have_dex2oat_relocation_skip_flag = false;
-
- char dex2oat_flags[kPropertyValueMax];
- int dex2oat_flags_count = get_property("dalvik.vm.dex2oat-flags",
- dex2oat_flags, NULL) <= 0 ? 0 : split_count(dex2oat_flags);
- ALOGV("dalvik.vm.dex2oat-flags=%s\n", dex2oat_flags);
-
- // If we booting without the real /data, don't spend time compiling.
- char vold_decrypt[kPropertyValueMax];
- bool have_vold_decrypt = get_property("vold.decrypt", vold_decrypt, "") > 0;
- bool skip_compilation = (have_vold_decrypt &&
- (strcmp(vold_decrypt, "trigger_restart_min_framework") == 0 ||
- (strcmp(vold_decrypt, "1") == 0)));
-
- bool generate_debug_info = property_get_bool("debug.generate-debug-info");
-
- char app_image_format[kPropertyValueMax];
- char image_format_arg[strlen("--image-format=") + kPropertyValueMax];
- bool have_app_image_format =
- image_fd >= 0 && get_property("dalvik.vm.appimageformat", app_image_format, NULL) > 0;
- if (have_app_image_format) {
- sprintf(image_format_arg, "--image-format=%s", app_image_format);
- }
-
- char dex2oat_large_app_threshold[kPropertyValueMax];
- bool have_dex2oat_large_app_threshold =
- get_property("dalvik.vm.dex2oat-very-large", dex2oat_large_app_threshold, NULL) > 0;
- char dex2oat_large_app_threshold_arg[strlen("--very-large-app-threshold=") + kPropertyValueMax];
- if (have_dex2oat_large_app_threshold) {
- sprintf(dex2oat_large_app_threshold_arg,
- "--very-large-app-threshold=%s",
- dex2oat_large_app_threshold);
- }
-
- static const char* DEX2OAT_BIN = "/system/bin/dex2oat";
-
- static const char* RUNTIME_ARG = "--runtime-arg";
-
- static const int MAX_INT_LEN = 12; // '-'+10dig+'\0' -OR- 0x+8dig
-
- char zip_fd_arg[strlen("--zip-fd=") + MAX_INT_LEN];
- char zip_location_arg[strlen("--zip-location=") + PKG_PATH_MAX];
- char oat_fd_arg[strlen("--oat-fd=") + MAX_INT_LEN];
- char oat_location_arg[strlen("--oat-location=") + PKG_PATH_MAX];
- char instruction_set_arg[strlen("--instruction-set=") + MAX_INSTRUCTION_SET_LEN];
- char instruction_set_variant_arg[strlen("--instruction-set-variant=") + kPropertyValueMax];
- char instruction_set_features_arg[strlen("--instruction-set-features=") + kPropertyValueMax];
- char dex2oat_Xms_arg[strlen("-Xms") + kPropertyValueMax];
- char dex2oat_Xmx_arg[strlen("-Xmx") + kPropertyValueMax];
- char dex2oat_compiler_filter_arg[strlen("--compiler-filter=") + kPropertyValueMax];
- bool have_dex2oat_swap_fd = false;
- char dex2oat_swap_fd[strlen("--swap-fd=") + MAX_INT_LEN];
- bool have_dex2oat_image_fd = false;
- char dex2oat_image_fd[strlen("--app-image-fd=") + MAX_INT_LEN];
-
- sprintf(zip_fd_arg, "--zip-fd=%d", zip_fd);
- sprintf(zip_location_arg, "--zip-location=%s", input_file_name);
- sprintf(oat_fd_arg, "--oat-fd=%d", oat_fd);
- sprintf(oat_location_arg, "--oat-location=%s", output_file_name);
- sprintf(instruction_set_arg, "--instruction-set=%s", instruction_set);
- sprintf(instruction_set_variant_arg, "--instruction-set-variant=%s", dex2oat_isa_variant);
- sprintf(instruction_set_features_arg, "--instruction-set-features=%s", dex2oat_isa_features);
- if (swap_fd >= 0) {
- have_dex2oat_swap_fd = true;
- sprintf(dex2oat_swap_fd, "--swap-fd=%d", swap_fd);
- }
- if (image_fd >= 0) {
- have_dex2oat_image_fd = true;
- sprintf(dex2oat_image_fd, "--app-image-fd=%d", image_fd);
- }
-
- if (have_dex2oat_Xms_flag) {
- sprintf(dex2oat_Xms_arg, "-Xms%s", dex2oat_Xms_flag);
- }
- if (have_dex2oat_Xmx_flag) {
- sprintf(dex2oat_Xmx_arg, "-Xmx%s", dex2oat_Xmx_flag);
- }
-
- // Compute compiler filter.
-
- bool have_dex2oat_compiler_filter_flag;
- if (skip_compilation) {
- strcpy(dex2oat_compiler_filter_arg, "--compiler-filter=verify-none");
- have_dex2oat_compiler_filter_flag = true;
- have_dex2oat_relocation_skip_flag = true;
- } else if (vm_safe_mode) {
- strcpy(dex2oat_compiler_filter_arg, "--compiler-filter=interpret-only");
- have_dex2oat_compiler_filter_flag = true;
- } else if (compiler_filter != nullptr &&
- strlen(compiler_filter) + strlen("--compiler-filter=") <
- arraysize(dex2oat_compiler_filter_arg)) {
- sprintf(dex2oat_compiler_filter_arg, "--compiler-filter=%s", compiler_filter);
- have_dex2oat_compiler_filter_flag = true;
- } else {
- char dex2oat_compiler_filter_flag[kPropertyValueMax];
- have_dex2oat_compiler_filter_flag = get_property("dalvik.vm.dex2oat-filter",
- dex2oat_compiler_filter_flag, NULL) > 0;
- if (have_dex2oat_compiler_filter_flag) {
- sprintf(dex2oat_compiler_filter_arg,
- "--compiler-filter=%s",
- dex2oat_compiler_filter_flag);
- }
- }
-
- // Check whether all apps should be compiled debuggable.
- if (!debuggable) {
- char prop_buf[kPropertyValueMax];
- debuggable =
- (get_property("dalvik.vm.always_debuggable", prop_buf, "0") > 0) &&
- (prop_buf[0] == '1');
- }
- char profile_arg[strlen("--profile-file-fd=") + MAX_INT_LEN];
- if (profile_fd != -1) {
- sprintf(profile_arg, "--profile-file-fd=%d", profile_fd);
- }
-
-
- ALOGV("Running %s in=%s out=%s\n", DEX2OAT_BIN, input_file_name, output_file_name);
-
- const char* argv[7 // program name, mandatory arguments and the final NULL
- + (have_dex2oat_isa_variant ? 1 : 0)
- + (have_dex2oat_isa_features ? 1 : 0)
- + (have_dex2oat_Xms_flag ? 2 : 0)
- + (have_dex2oat_Xmx_flag ? 2 : 0)
- + (have_dex2oat_compiler_filter_flag ? 1 : 0)
- + (have_dex2oat_threads_flag ? 1 : 0)
- + (have_dex2oat_swap_fd ? 1 : 0)
- + (have_dex2oat_image_fd ? 1 : 0)
- + (have_dex2oat_relocation_skip_flag ? 2 : 0)
- + (generate_debug_info ? 1 : 0)
- + (debuggable ? 1 : 0)
- + (have_app_image_format ? 1 : 0)
- + dex2oat_flags_count
- + (profile_fd == -1 ? 0 : 1)
- + (shared_libraries != nullptr ? 4 : 0)
- + (have_dex2oat_large_app_threshold ? 1 : 0)];
- int i = 0;
- argv[i++] = DEX2OAT_BIN;
- argv[i++] = zip_fd_arg;
- argv[i++] = zip_location_arg;
- argv[i++] = oat_fd_arg;
- argv[i++] = oat_location_arg;
- argv[i++] = instruction_set_arg;
- if (have_dex2oat_isa_variant) {
- argv[i++] = instruction_set_variant_arg;
- }
- if (have_dex2oat_isa_features) {
- argv[i++] = instruction_set_features_arg;
- }
- if (have_dex2oat_Xms_flag) {
- argv[i++] = RUNTIME_ARG;
- argv[i++] = dex2oat_Xms_arg;
- }
- if (have_dex2oat_Xmx_flag) {
- argv[i++] = RUNTIME_ARG;
- argv[i++] = dex2oat_Xmx_arg;
- }
- if (have_dex2oat_compiler_filter_flag) {
- argv[i++] = dex2oat_compiler_filter_arg;
- }
- if (have_dex2oat_threads_flag) {
- argv[i++] = dex2oat_threads_arg;
- }
- if (have_dex2oat_swap_fd) {
- argv[i++] = dex2oat_swap_fd;
- }
- if (have_dex2oat_image_fd) {
- argv[i++] = dex2oat_image_fd;
- }
- if (generate_debug_info) {
- argv[i++] = "--generate-debug-info";
- }
- if (debuggable) {
- argv[i++] = "--debuggable";
- }
- if (have_app_image_format) {
- argv[i++] = image_format_arg;
- }
- if (have_dex2oat_large_app_threshold) {
- argv[i++] = dex2oat_large_app_threshold_arg;
- }
- if (dex2oat_flags_count) {
- i += split(dex2oat_flags, argv + i);
- }
- if (have_dex2oat_relocation_skip_flag) {
- argv[i++] = RUNTIME_ARG;
- argv[i++] = dex2oat_norelocation;
- }
- if (profile_fd != -1) {
- argv[i++] = profile_arg;
- }
- if (shared_libraries != nullptr) {
- argv[i++] = RUNTIME_ARG;
- argv[i++] = "-classpath";
- argv[i++] = RUNTIME_ARG;
- argv[i++] = shared_libraries;
- }
- // Do not add after dex2oat_flags, they should override others for debugging.
- argv[i] = NULL;
-
- execv(DEX2OAT_BIN, (char * const *)argv);
- ALOGE("execv(%s) failed: %s\n", DEX2OAT_BIN, strerror(errno));
-}
-
-/*
- * Whether dexopt should use a swap file when compiling an APK.
- *
- * If kAlwaysProvideSwapFile, do this on all devices (dex2oat will make a more informed decision
- * itself, anyways).
- *
- * Otherwise, read "dalvik.vm.dex2oat-swap". If the property exists, return whether it is "true".
- *
- * Otherwise, return true if this is a low-mem device.
- *
- * Otherwise, return default value.
- */
-static bool kAlwaysProvideSwapFile = false;
-static bool kDefaultProvideSwapFile = true;
-
-static bool ShouldUseSwapFileForDexopt() {
- if (kAlwaysProvideSwapFile) {
- return true;
- }
-
- // Check the "override" property. If it exists, return value == "true".
- char dex2oat_prop_buf[kPropertyValueMax];
- if (get_property("dalvik.vm.dex2oat-swap", dex2oat_prop_buf, "") > 0) {
- if (strcmp(dex2oat_prop_buf, "true") == 0) {
- return true;
- } else {
- return false;
- }
- }
-
- // Shortcut for default value. This is an implementation optimization for the process sketched
- // above. If the default value is true, we can avoid to check whether this is a low-mem device,
- // as low-mem is never returning false. The compiler will optimize this away if it can.
- if (kDefaultProvideSwapFile) {
- return true;
- }
-
- bool is_low_mem = property_get_bool("ro.config.low_ram");
- if (is_low_mem) {
- return true;
- }
-
- // Default value must be false here.
- return kDefaultProvideSwapFile;
-}
-
-static void SetDex2OatAndPatchOatScheduling(bool set_to_bg) {
- if (set_to_bg) {
- if (set_sched_policy(0, SP_BACKGROUND) < 0) {
- ALOGE("set_sched_policy failed: %s\n", strerror(errno));
- exit(70);
- }
- if (setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_BACKGROUND) < 0) {
- ALOGE("setpriority failed: %s\n", strerror(errno));
- exit(71);
- }
- }
-}
-
-static void close_all_fds(const std::vector<fd_t>& fds, const char* description) {
- for (size_t i = 0; i < fds.size(); i++) {
- if (close(fds[i]) != 0) {
- PLOG(WARNING) << "Failed to close fd for " << description << " at index " << i;
- }
- }
-}
-
-static fd_t open_profile_dir(const std::string& profile_dir) {
- fd_t profile_dir_fd = TEMP_FAILURE_RETRY(open(profile_dir.c_str(),
- O_PATH | O_CLOEXEC | O_DIRECTORY | O_NOFOLLOW));
- if (profile_dir_fd < 0) {
- // In a multi-user environment, these directories can be created at
- // different points and it's possible we'll attempt to open a profile
- // dir before it exists.
- if (errno != ENOENT) {
- PLOG(ERROR) << "Failed to open profile_dir: " << profile_dir;
- }
- }
- return profile_dir_fd;
-}
-
-static fd_t open_primary_profile_file_from_dir(const std::string& profile_dir, mode_t open_mode) {
- fd_t profile_dir_fd = open_profile_dir(profile_dir);
- if (profile_dir_fd < 0) {
- return -1;
- }
-
- fd_t profile_fd = -1;
- std::string profile_file = create_primary_profile(profile_dir);
-
- profile_fd = TEMP_FAILURE_RETRY(open(profile_file.c_str(), open_mode | O_NOFOLLOW));
- if (profile_fd == -1) {
- // It's not an error if the profile file does not exist.
- if (errno != ENOENT) {
- PLOG(ERROR) << "Failed to lstat profile_dir: " << profile_dir;
- }
- }
- // TODO(calin): use AutoCloseFD instead of closing the fd manually.
- if (close(profile_dir_fd) != 0) {
- PLOG(WARNING) << "Could not close profile dir " << profile_dir;
- }
- return profile_fd;
-}
-
-static fd_t open_primary_profile_file(userid_t user, const char* pkgname) {
- std::string profile_dir = create_data_user_profile_package_path(user, pkgname);
- return open_primary_profile_file_from_dir(profile_dir, O_RDONLY);
-}
-
-static fd_t open_reference_profile(uid_t uid, const char* pkgname, bool read_write) {
- std::string reference_profile_dir = create_data_ref_profile_package_path(pkgname);
- int flags = read_write ? O_RDWR | O_CREAT : O_RDONLY;
- fd_t fd = open_primary_profile_file_from_dir(reference_profile_dir, flags);
- if (fd < 0) {
- return -1;
- }
- if (read_write) {
- // Fix the owner.
- if (fchown(fd, uid, uid) < 0) {
- close(fd);
- return -1;
- }
- }
- return fd;
-}
-
-static void open_profile_files(uid_t uid, const char* pkgname,
- /*out*/ std::vector<fd_t>* profiles_fd, /*out*/ fd_t* reference_profile_fd) {
- // Open the reference profile in read-write mode as profman might need to save the merge.
- *reference_profile_fd = open_reference_profile(uid, pkgname, /*read_write*/ true);
- if (*reference_profile_fd < 0) {
- // We can't access the reference profile file.
- return;
- }
-
- std::vector<userid_t> users = get_known_users(/*volume_uuid*/ nullptr);
- for (auto user : users) {
- fd_t profile_fd = open_primary_profile_file(user, pkgname);
- // Add to the lists only if both fds are valid.
- if (profile_fd >= 0) {
- profiles_fd->push_back(profile_fd);
- }
- }
-}
-
-static void drop_capabilities(uid_t uid) {
- if (setgid(uid) != 0) {
- ALOGE("setgid(%d) failed in installd during dexopt\n", uid);
- exit(64);
- }
- if (setuid(uid) != 0) {
- ALOGE("setuid(%d) failed in installd during dexopt\n", uid);
- exit(65);
- }
- // drop capabilities
- struct __user_cap_header_struct capheader;
- struct __user_cap_data_struct capdata[2];
- memset(&capheader, 0, sizeof(capheader));
- memset(&capdata, 0, sizeof(capdata));
- capheader.version = _LINUX_CAPABILITY_VERSION_3;
- if (capset(&capheader, &capdata[0]) < 0) {
- ALOGE("capset failed: %s\n", strerror(errno));
- exit(66);
- }
-}
-
-static constexpr int PROFMAN_BIN_RETURN_CODE_COMPILE = 0;
-static constexpr int PROFMAN_BIN_RETURN_CODE_SKIP_COMPILATION = 1;
-static constexpr int PROFMAN_BIN_RETURN_CODE_BAD_PROFILES = 2;
-static constexpr int PROFMAN_BIN_RETURN_CODE_ERROR_IO = 3;
-static constexpr int PROFMAN_BIN_RETURN_CODE_ERROR_LOCKING = 4;
-
-static void run_profman_merge(const std::vector<fd_t>& profiles_fd, fd_t reference_profile_fd) {
- static const size_t MAX_INT_LEN = 32;
- static const char* PROFMAN_BIN = "/system/bin/profman";
-
- std::vector<std::string> profile_args(profiles_fd.size());
- char profile_buf[strlen("--profile-file-fd=") + MAX_INT_LEN];
- for (size_t k = 0; k < profiles_fd.size(); k++) {
- sprintf(profile_buf, "--profile-file-fd=%d", profiles_fd[k]);
- profile_args[k].assign(profile_buf);
- }
- char reference_profile_arg[strlen("--reference-profile-file-fd=") + MAX_INT_LEN];
- sprintf(reference_profile_arg, "--reference-profile-file-fd=%d", reference_profile_fd);
-
- // program name, reference profile fd, the final NULL and the profile fds
- const char* argv[3 + profiles_fd.size()];
- int i = 0;
- argv[i++] = PROFMAN_BIN;
- argv[i++] = reference_profile_arg;
- for (size_t k = 0; k < profile_args.size(); k++) {
- argv[i++] = profile_args[k].c_str();
- }
- // Do not add after dex2oat_flags, they should override others for debugging.
- argv[i] = NULL;
-
- execv(PROFMAN_BIN, (char * const *)argv);
- ALOGE("execv(%s) failed: %s\n", PROFMAN_BIN, strerror(errno));
- exit(68); /* only get here on exec failure */
-}
-
-// Decides if profile guided compilation is needed or not based on existing profiles.
-// Returns true if there is enough information in the current profiles that worth
-// a re-compilation of the package.
-// If the return value is true all the current profiles would have been merged into
-// the reference profiles accessible with open_reference_profile().
-static bool analyse_profiles(uid_t uid, const char* pkgname) {
- std::vector<fd_t> profiles_fd;
- fd_t reference_profile_fd = -1;
- open_profile_files(uid, pkgname, &profiles_fd, &reference_profile_fd);
- if (profiles_fd.empty() || (reference_profile_fd == -1)) {
- // Skip profile guided compilation because no profiles were found.
- // Or if the reference profile info couldn't be opened.
- close_all_fds(profiles_fd, "profiles_fd");
- if ((reference_profile_fd != - 1) && (close(reference_profile_fd) != 0)) {
- PLOG(WARNING) << "Failed to close fd for reference profile";
- }
- return false;
- }
-
- ALOGV("PROFMAN (MERGE): --- BEGIN '%s' ---\n", pkgname);
-
- pid_t pid = fork();
- if (pid == 0) {
- /* child -- drop privileges before continuing */
- drop_capabilities(uid);
- run_profman_merge(profiles_fd, reference_profile_fd);
- exit(68); /* only get here on exec failure */
- }
- /* parent */
- int return_code = wait_child(pid);
- bool need_to_compile = false;
- bool should_clear_current_profiles = false;
- bool should_clear_reference_profile = false;
- if (!WIFEXITED(return_code)) {
- LOG(WARNING) << "profman failed for package " << pkgname << ": " << return_code;
- } else {
- return_code = WEXITSTATUS(return_code);
- switch (return_code) {
- case PROFMAN_BIN_RETURN_CODE_COMPILE:
- need_to_compile = true;
- should_clear_current_profiles = true;
- should_clear_reference_profile = false;
- break;
- case PROFMAN_BIN_RETURN_CODE_SKIP_COMPILATION:
- need_to_compile = false;
- should_clear_current_profiles = false;
- should_clear_reference_profile = false;
- break;
- case PROFMAN_BIN_RETURN_CODE_BAD_PROFILES:
- LOG(WARNING) << "Bad profiles for package " << pkgname;
- need_to_compile = false;
- should_clear_current_profiles = true;
- should_clear_reference_profile = true;
- break;
- case PROFMAN_BIN_RETURN_CODE_ERROR_IO: // fall-through
- case PROFMAN_BIN_RETURN_CODE_ERROR_LOCKING:
- // Temporary IO problem (e.g. locking). Ignore but log a warning.
- LOG(WARNING) << "IO error while reading profiles for package " << pkgname;
- need_to_compile = false;
- should_clear_current_profiles = false;
- should_clear_reference_profile = false;
- break;
- default:
- // Unknown return code or error. Unlink profiles.
- LOG(WARNING) << "Unknown error code while processing profiles for package " << pkgname
- << ": " << return_code;
- need_to_compile = false;
- should_clear_current_profiles = true;
- should_clear_reference_profile = true;
- break;
- }
- }
- close_all_fds(profiles_fd, "profiles_fd");
- if (close(reference_profile_fd) != 0) {
- PLOG(WARNING) << "F